- 投稿日:2020-01-10T23:51:49+09:00
React Hooksのルールをよく理解しないとハマるエラー対処法
概要
React Hooksは関数型コンポーネント内でクラスコンポーネントと同じような処理を簡潔に書くことができますが、
ルールをよく理解しないで使うと予期せぬエラーに見舞われます。
自分もフック使い始めの頃はなぜエラーが出ているのかわからずに時間を費やしていました。
この記事では、自分がハマったエラーをどう解決したのかを紹介します。ルール① フックはトップレベルでしか呼び出せない
フックはメソッド内の一番外側でしか、呼び出すことができません。
下の例を見てもらうとuseStateをprintメソッド内部で呼び出すことはできません。
なぜなら、printメソッドを実行することで、使用されるフックの数が変わってしまうからです。
フックを使用する場合は、コンポーネントを読み込んだ時点でどのフックが使われるかを明示しなければいけないので、printメソッドを実行して途中でフックの数が変わるといった挙動をしてしまうとエラーになってしまいます。import React, { useState } from "react"; export default function App() { const print = () => { const [value, setValue] = useState(0) console.log('print') } return ( <div> <h1 onClick={() => print()}>Hello Qiita</h1> </div> ); }実際にHello Qiitaをクリックすると以下のようなエラーが発生します。
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.これを解決するには、Appコンポーネントのルートでフックを呼び出す必要があります。
解決方法import React, { useState } from "react"; export default function App() { const [value, setValue] = useState(0) const print = () => { setValue(100) console.log('print') } return ( <div> <h1 onClick={() => print()}>Hello Qiita</h1> </div> ); }このように書くと、Appコンポーネントが読み込まれた時に使用するフックの数が変わることなく呼び出されるので正常に動作します。
ルール② カスタムフックを作る時のルール
関数内でフックを定義して他の関数にそのフックを渡したい場合があります。
その時にもルールを守っていないとエラーになります。
フックを関数の内部で使用する場合は、メソッド名をuseから始めないといけません。
これは一目でフックを使っているメソッドかどうかを分かりやすくするためです。
例えば、getValueメソッド内部でフックを使用しています。import React, { useState } from "react"; const getValue = () => { const [value, setValue] = useState(100) return value } export default function App() { const value = getValue() return ( <div> <h1>Hello Qiita{value}</h1> </div> ); }Appコンポーネントを呼び出すと、以下のようなwarningが発生します。
React Hook "useState" is called in function "getValue" which is neither a React function component or a custom React Hook function. (react-hooks/rules-of-hooks)これは、
getValue内でフックを使っているのにメソッド名がuseで始まっていないことによるエラーです。
getValueをuseValueに変更することでwarningがなくなります。
フックを使うメソッド名は、useで始めるようにしましょう。解決方法import React, { useState } from "react"; const useValue = () => { const [value, setValue] = useState(100) return value } export default function App() { const value = useValue() return ( <div> <h1>Hello Qiita{value}</h1> </div> ); }ルール③ フックを使う時はループ処理に気をつける
フックを使う時はリープ処理に陥っていないか気をつけましょう。
以下のように書くと、
setValue(200) → render → setValue(200) → renderのループに陥ってしまいエラーが発生します。import React, { useState } from "react"; export default function App() { const [value, setValue] = useState(100) setValue(200) return ( <div> <h1>Hello Qiita{value}</h1> </div> ); }Too many re-renders. React limits the number of renders to prevent an infinite loop.この場合は、
setValue(200)が呼び出される回数を制限します。
例として、初期表示時しかsetValue(200)を呼び出さないようにするなどで対応します。解決方法import React, { useState, useEffect } from "react"; export default function App() { const [value, setValue] = useState(100) useEffect(() => { setValue(200) }, []) return ( <div> <h1>Hello Qiita{value}</h1> </div> ); }
- 投稿日:2020-01-10T14:37:31+09:00
React にて、publicフォルダ内の画像を参照する
tl;dr
process.env.PUBLIC_URLもしくはwindow.location.originからpublicフォルダのパスを取得できます。サンプル
?以下のサンプルは、
create-react-app直後のApp.jsを編集したものです。App.jsimport React from 'react'; function App() { return ( <div className="App"> <img src={`${process.env.PUBLIC_URL}/logo192.png`} /> <img src={`${window.location.origin}/logo192.png`} /> </div> ); } export default App;参考
- 投稿日:2020-01-10T09:42:39+09:00
Next.js+Typescript+Express環境をサクッと構築する
create-next-app のexampleは便利だけど、「Next.js+Typescript+Express」の環境を作るテンプレートがなく、結構調べました;;
でも、結果としてとても簡単に導入できることがわかったので、メモがてらqiitaに投稿。1. プロジェクト作成
下記コマンドでカスタムサーバを使ったtypescriptのプロジェクトを作成
$ create-next-app --example custom-server-typescript {ProjectName}2. expressパッケージと型定義追加
$ yarn add express $ yarn add --dev @types/express3.
server/index.tsxを修正下記に書き換える
import next from 'next' const express = require('express') const port = parseInt(process.env.PORT || '3000', 10) const dev = process.env.NODE_ENV !== 'production' const app = next({ dev }) const handle = app.getRequestHandler() app.prepare().then(() => { const server = express() server.get('*', (req: any, res: any) => { return handle(req, res) }) server.listen(port, (err: any) => { if (err) throw err console.log(`> Ready on ${process.env.CLIENT_URL || `http://localhost:${port}`}`) }) })4. 起動
$ yarn dev
- 投稿日:2020-01-10T01:30:11+09:00
デザイナがReactを始める前に押さえておきたいJavaScriptの基本②(オブジェクト編)
はじめに
WebデザイナーがReactを始める前に最低限押さえておくべきJavaScriptの基本(個人的見解)について、前回のJavaScriptの基本①(配列編)に続き、JavaScriptの基本②(オブジェクト編)について書きます。
- JavaScriptの基本①(配列編)
読んでね
- JavaScriptの基本②(オブジェクト編)
今ここ
- JavaScriptの基本③(クラス編)
なぜこの順番なのか?
オブジェクトを理解するためには配列を理解していることが前提 => クラスを理解するためにはオブジェクトを理解していることが前提 => Reactを理解するためにはクラスを理解していることが大前提となるからです。(JavaScriptの基本的知識がしっかりしていないままReactの勉強に進んだことで、何度もつまづき、心が折れそうになった自身の経験から書きました。)
オブジェクトいろいろ
配列は連番が付けられた値(変数)をまとめたものですが、オブジェクトは値に名前を付けて管理出来ます。
const points = { x: 100, y: 180 }
x: 100,y: 180のように、オブジェクトを構成するこれらのひとつひとつの要素をプロパティといい、左側は名前、またキー、そして右側は値と言います。
プロパティーには関数なども格納することが出来、関数が格納されたプロパティのことをメソッドといいます。プロパティーの値にアクセスする方法。
console.log(points.x) //100 console.log(points['y']) //180
オブジェクト名.キーの名前(ドット表記法)、またはオブジェクト名[](ブラケット表記法) の中にキーを文字列の形で渡す方法があります。代入、追加、削除
オブジェクト
pointsのキーxの値を変更。points.x = 170 //またはpoint['x'] console.log(points) //{x: 170, y: 180}新しいキー
z値200のプロパティーを追加。
キーyのプロパティーを削除。points.z = 200 delete points.y console.log(points) //{x: 170, z: 200}スプレット構文
スプレット構文でオブジェクトを展開することが出来ます。
const otherPoints = {age: 30, hobby: 'music'} const newPoints = {x: 100, y: 180, ...otherPoints} console.log(newPoints) //{x: 100, y: 180, age: 30, hobby: "music"}分割代入とレスト構文
オブジェクトのキーと同じ定数名を使うと、そのキーの値が代入されます。(分割代入)
残りのプロパティはオブジェクトのまま取り出せます。(レスト構文)const {age, hobby, ...newPointsOthers} = newPoint console.log(age) //30 console.log(hobby) //music console.log(newPointsOthers) //{x: 100, y: 180}キーや値を取得
キーを配列で取得。
const newPoints = {x: 100, y: 180, age: 30, hobby: "music"} const keys = Object.keys(newPoints) console.log(keys) // ["x", "y", "age", "hobby"]値を配列で取得。
const values = Object.values(newPoints) console.log(values) // [100, 180, 30, "music"]forEach()でキーと値をひとつずつ取得
keys.forEach(key => console.log(`key: ${key} value: ${newPoints[key]}`)) //key: x, value: 100 //key: y, value: 180 //key: age, value: 30 //key: hobby, value: music*keyは文字列(string)で取得されていることに注意
map()を使うと、新しい配列で返してくれます。
const map = keys.map(key => `keyは${key}, valueは${newPoints[key]}です。`) console.log(map) //["keyはx, valueは100です。", "keyはy, valueは180です。", "keyはage, valueは30です。", "keyはhobby, valueはmusicです。"]複数オブジェクトの配列
const posts = [ { content: 'JavaScriptの楽しい!', likes: 2, }, { content: 'プログラミング楽しい!', likes: 3, }, { content: 'React楽しい!', likes: 1, } ]for()を使って、postsの値をひとつひとつ取得。
for(let i = 0; i < posts.length; i++ ){ console.log(`${posts[i].content} ${posts[i].likes}いいね`) } // JavaScriptの楽しい! 2いいね // プログラミング楽しい! 3いいね // React楽しい! 1いいねforEach()を使って、postsの値をひとつひとつ取得。
posts.forEach(post => console.log(`${post.content} ${post.likes}いいね`)) // JavaScriptの楽しい! 2いいね // プログラミング楽しい! 3いいね // React楽しい! 1いいねmap()を使って、postsのひとつひとつを取りだして新しい配列で返します。
const objMap = posts.map(post => `${post.content} ${post.likes}いいね`) console.log(objMap) // ["JavaScriptの楽しい! 2いいね", "プログラミング楽しい! 3いいね", "React楽しい! 1いいね"]メソッドを使う
postをひとつひとつ取り出す。
const posts = [ { content: 'JavaScriptの楽しい!', likes: 2, }, { content: 'プログラミング楽しい!', likes: 3, }, { content: 'React楽しい!', likes: 1, } ] const getPost = post => console.log(`${post.content} ${post.likes}いいね`) getPost(posts[0]) // JavaScriptの楽しい! 2いいね getPost(posts[1]) // プログラミング楽しい! 3いいね getPost(posts[2]) // React楽しい! 1いいねgetPost()をオブジェクトの中に入れて、プロパティの値として、関数を持たせます。
関数をプロパティの値にした時、その関数をメソッドと言います。
(同じオブジェクト内のプロパティへthisというキーワードでアクセス出来ます。)const posts = [ { content: 'JavaScript楽しい!', likes: 2, getPost: function(){console.log(`${this.content} ${this.likes}いいね`)} }, { content: 'プログラミング楽しい!', likes: 3, // getPost: function(){console.log(`${this.content} ${this.likes}いいね`)} // functionを省略したメソッドの書き方↓↓↓こちらが一般的 getPost(){console.log(`${this.content} ${this.likes}いいね`)} }, { content: 'React楽しい!', likes: 1, getPost(){console.log(`${this.content} ${this.likes}いいね`)} } ] posts[0].getPost() // JavaScriptの楽しい! 2いいね posts[1].getPost() // プログラミング楽しい! 3いいね posts[2].getPost() // React楽しい! 1いいねうまくメソッドを呼び出せましたが、オブジェクトの全てにメソッドを付けるのは同じ記述の繰り返しになってしまいます。
このようなオブジェクトをテンプレート化できるのがクラスです次回はクラスについて書きたいと思います。
最後に
道半ばですが、分からないことがあっても諦めずに少しずつすすめると、小さな成長を感じる瞬間があります。
そんな素敵な瞬間を楽しみながら頑張ろうと思っております
フロントエンドスキルを武器にしたいWebデザイナーのみなさん、一緒にがんばりましょう!参考にさせていただいたサイト
- 投稿日:2020-01-10T00:05:34+09:00
ウェブ技術とobnizで会社の受付タブレットアプリを作った話
こんにちは。トップゲートという会社に勤めております、古都ことです。
このたび弊社の大阪事業所が移転しまして、心機一転、新事業所での勤務が始まりました。そして場所が変わればやるべきことも変わり、新たな課題も出てきます。そんな中で、新たな事業所の改善について少し携われる機会をいただけたので、ここで軽く紹介したいと思います。
「受付タブレット」プロジェクト
社会人の方ならよくご存知だと思いますが、会社さんって受付に内線が置いてあって、訪問時にはそれで担当の方を呼び出してもらう、というフローがたぶん一般的になります。また小さなところであれば、ベルが置いてあってそれを鳴らすというスタイルもありますね。
弊社の移転前の大阪事業所では小さな部屋でしたので、来社時にはドアをノックしていただいていました。しかし新事業所ではそういうわけにもいきません。事業所の大きさは約3倍〜4倍になり、ドアをノックしても聞こえません。
ここで内線を置くという手もありますが、弊社はIT企業。自分たちの技術でなんとかしたい。そう思いません?そして生まれたのが「受付タブレット」プロジェクトです。このプロジェクトは社内のエンジニアが自由に受付アプリを作っても良く、そこに私も(勝手に)参加させていただきました。
普通の仕事の他に、こういった自由気ままに参加できるプロジェクトもあるので仕事の息抜きには最適です。みんなで寄ってたかって実用的な面白いものを作るというのは、実に楽しいです。
実機動画
実際にiPadで動作させている動画が以下になります:
大阪事業所の受付タブレットアプリ、こんな感じです。 pic.twitter.com/BVIjSiIov1
— 古都こと (@kfurumiya) January 9, 2020(※実際に設置しているのはiPadですが、ここではiPadProを使用して動画撮影しています)
アプリの機能
このアプリの主な機能は以下の2つです:
- Slackへの通知
- obnizと連携しブザーを鳴らす
Slackへの通知がメインの機能となります。来社された方がいたらSlackで担当者にメンションを飛ばし通知します。以下のようなイメージです。
このSlack連携部分に関しては別のエンジニアの方が主導となり作成したもので、私はAPIを叩いているだけです。
もうひとつの機能がobnizを用いたブザー機能です。obnizはクラウド経由で双方向通信できるIoTデバイスで、いろいろなセンサを簡単に動かすことができます。Slackでのメンションのみではなかなか気付きにくいということもあり、obnizを用いて室内でブザーを鳴らすことにしました。単純な音を鳴らしているだけですが、通知の見落としを防ぐことができています。
技術要素
このアプリでは以下の技術を採用しています:
- React
- TypeScript
- <canvas>
- Web Speech API
見ての通り、ウェブ技術で作られています。残念ながらPWAではありませんが、ネイティブに近い体験を提供できているのではないかなと思っています。PWAを不採用とした理由はiPadOSのPWA対応が十分に成熟していないためです。
フレームワークはReactです。Reactのシンプルさと強力さがこの規模のアプリに非常にマッチしており、簡単に作ることができました。
言語はTypeScriptです。現代において生のJavaScriptという選択肢はなかなか厳しいところがあるので、TSを採用しています。
背景はcanvasで生成しています。速度を考えるとWebGLの方が良いかもしれませんが、実装の簡単さを考慮して普通の2DContextを使いました。動き回る点と点が一定距離内に入ると線で繋がるという、よくあるアニメーションです。
音声はWeb Speech APIのSpeechSynthesis(音声合成)を用いています。これによりテキストデータのみで発話することができ、修正が容易になります。
気をつけた点
このアプリを作成するときに一番考えたのは、「迷わせない」ということでした。会社を訪れるお客さんというのは、なにもIT関係の人ばかりではありません。エンジニア基準でUIを作ってしまうと、操作に戸惑ってしまう可能性が出てきます。
そこで多くの人が触れたことがあるはずのATMや券売機といった機器のUIを参考にしました。大きなボタンがいくつか並び、それをタッチして選ぶ、という形式です。
また、当初はいきなり用件の選択肢を表示していたのですが、最初に一度ボタンをタップする画面遷移に変更しました。これにより「この白い四角はボタンである」「触れると反応する」ということを理解してから用件の選択に入れるようになっています。
ボタンにはアイコンも付けました。視覚情報が入ることにより、脳が処理する時間をできるだけ小さくできることを期待しています。アイコンは自作なので少々不格好ですが、なんとなくで雰囲気はわかると思います。
とにかく「初見でも理解できるもの」を目指して作りました。会社の受付というのはそう何度も利用するものではありませんし、1度目からスムーズに利用できるのが理想です。
1週間運用してみて
このアプリを実際に1週間設置してみましたが、ちゃんと機能し、きちんと受付の役割を果たせています!初見の方でも迷いなく使用していただけているようです。会社の受付という、場合によっては非常に重要となる要素を担当させていただいて、ドキドキしつつも一定の役割は果たせたようで安心しています。
ユーザにとっての使い心地というのも大切ですが、「事務所内の人がきちんと来客に気づくことができるか」というのも重要なポイントでした。どんなによくできた受付アプリでも、社内の人間が気付けなければ意味がありません。obnizでブザーを鳴らすという選択肢を取ったのは良かったと思います。
いろんな方に使用していただいて、良かった点や今後の課題などがいくつか見えてきたので、これからどんどん改善していきたいと思います。
さいごに
会社の受付のような、重要であり難しくある部分に自由に携われるというのは、非常に光栄です。息抜きにもなりますし、会社を訪れてくれる極めて大切な方を適切におもてなしする責務があると考えると、緊張感とやりがいがグッと上がってきます。
今回は根本となるAPIは別のエンジニアさんに作ってもらったので、私はただ画面を作っただけになってしまうのですが、貴重な経験ができたと思います。
もし弊社の大阪事業所を訪れることがあれば、「これが例の受付アプリか」と触ってみてください。ご来訪、お待ちしております。


