tag:crieit.net,2005:https://crieit.net/tags/ReactHooks/feed 「ReactHooks」の記事 - Crieit Crieitでタグ「ReactHooks」に投稿された最近の記事 2021-03-09T01:32:10+09:00 https://crieit.net/tags/ReactHooks/feed tag:crieit.net,2005:PublicArticle/16725 2021-03-09T01:32:10+09:00 2021-03-09T01:32:10+09:00 https://crieit.net/posts/React-Hook-Form-Getform-io React Hook FormとGetform.ioを使って、お問い合わせフォームを作ろう! <p><a target="_blank" rel="nofollow noopener" href="https://react-hook-form.com/jp/">React Hook Form</a>が便利らしいと聞いたので使ってみることにしました。</p> <h2 id="React Hook Form"><a href="#React+Hook+Form">React Hook Form</a></h2> <p>皆さん、<a target="_blank" rel="nofollow noopener" href="https://react-hook-form.com/jp/">React Hook Form</a>を知ってますか?</p> <p>最近トレンドに乗っかってきた、<strong>Form</strong>を<strong>React Hooks</strong>で簡単に作ることのできる代物です。</p> <p><img src="https://i.imgur.com/2dqEW7L.png" alt="img" /></p> <p>特徴として、Hooksを使って簡単にFormが作れる、そして再レンダリングが最小限に抑えられているのでパフォーマンスも高い、らしいです。</p> <h2 id="Getform.io"><a href="#Getform.io">Getform.io</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://getform.io/">Getform.io</a>はフォームのバックエンドを提供するすばらしいサービスです。</p> <p>詳しくは<a target="_blank" rel="nofollow noopener" href="https://blog.tubone-project24.xyz/2021/02/13/netlify-github-action#getformio">こちらの過去記事</a>をご確認いただければと思います。</p> <h2 id="React Hook Form + Getform.io"><a href="#React+Hook+Form+%2B+Getform.io">React Hook Form + Getform.io</a></h2> <p><strong>合体!</strong></p> <p>だめ~となるかと思いましたがうまいことできました。</p> <p><img src="https://i.imgur.com/yYJBK98.jpg" alt="img" /></p> <p>今回はこちらの2技術を使って、お問い合わせフォームを作っていきます。</p> <h2 id="実コード"><a href="#%E5%AE%9F%E3%82%B3%E3%83%BC%E3%83%89">実コード</a></h2> <p>こんな感じのコンポーネントができました。</p> <pre><code class="typescript">import React, {useState} from "react"; import { useForm } from "react-hook-form"; import Button from "./button"; type Inputs = { name: string, email: string, subject: string, message: string, }; const ContactForm = (): JSX.Element => { const [serverState, setServerState] = useState({ submitting: false, status: {ok: false, msg: ""} }); const { register, handleSubmit, errors } = useForm<Inputs>(); const handleServerResponse = (ok: boolean, msg: string) => { setServerState({ submitting: true, status: { ok, msg } }); }; const onSubmit = (data: Inputs, e: any) => { const formData = new FormData(); formData.append("name", data.name) formData.append("email", data.email) formData.append("subject", data.subject) formData.append("message", data.message) fetch('https://getform.io/f/8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', { method: 'POST', body: formData }) .then(() => { e.target.reset(); handleServerResponse(true, "Submitted!"); }) .catch((error) => { alert(error) console.error(error) handleServerResponse(false, error.toString()); }); } return ( <form onSubmit={handleSubmit(onSubmit)}> <p> <label>Your Name<br/> <input name="name" placeholder="Enter your name" type="text" ref={register({ required: true })} /> {errors.name && <span>This field is required</span>} </label> </p> <p> <label> Your email<br/> <input name="email" type="email" placeholder="Enter your email" ref={register({ pattern: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/i, required: true })} /> {errors.email && <span>This field is required and only email format</span>} </label> </p> <p> <label> Subject<br/> <input name="subject" type="text" maxLength={30} placeholder="Subject here..." ref={register({required: true })} /> {errors.subject && <span>This field is required</span>} </label> </p> <p> <label> Message<br /> <textarea name="message" placeholder="Something writing..." rows={6} cols={25} ref={register({required: true })}/> {errors.message && <span>This field is required</span>} </label> </p> <Button dark={serverState.submitting && serverState.status.ok} disabled={serverState.submitting && serverState.status.ok}> { serverState.submitting && serverState.status.ok ? serverState.status.msg: 'Submit'} </Button> </form> ); } export default ContactForm </code></pre> <h2 id="解説"><a href="#%E8%A7%A3%E8%AA%AC">解説</a></h2> <h3 id="準備"><a href="#%E6%BA%96%E5%82%99">準備</a></h3> <p>まず、フォームの項目に該当するTypeを作ります。</p> <pre><code class="typescript">type Inputs = { name: string, email: string, subject: string, message: string, }; </code></pre> <p>今回は名前、email、題名、メッセージを設定します。</p> <p>次に<strong>React Hook Form</strong>のuseFormを使って<strong>register</strong>などを作っていきます。正直これができれば基本的な機能は8割くらい完成です。</p> <pre><code class="typescript">const { register, handleSubmit, errors } = useForm<Inputs>(); </code></pre> <p>とりあえず用意するのは、formのrefに設定する<strong>register</strong>、onSubmitをコントロールできる<strong>handleSubmit</strong>、requireを検査できる<strong>errors</strong>です。</p> <p>ほかにも、form全体の項目検査のformState.isValidなども使うことができます。</p> <h3 id="Submit"><a href="#Submit">Submit</a></h3> <p>そして肝心な送信(Submit)部分ですがこちらは<a target="_blank" rel="nofollow noopener" href="https://blog.tubone-project24.xyz/2021/02/13/netlify-github-action#getformio">前記事</a>とほぼ同じように<strong>onSubmit</strong>に合わせて処理する関数を用意して、formの<strong>onsubmit属性</strong>に渡してあげればいいだけです。</p> <p>........いいだけですが一つ注意として、渡す際に<strong>handleSubmit</strong>で関数をラップしないと、form情報がうまく取れない、ということです。忘れずに設定してくださいませ。</p> <pre><code class="typescript"> <form onSubmit={handleSubmit(onSubmit)}> </form> </code></pre> <p>またGetform.ioへのPOSTはJSONではなく<strong>mulitpart/form-data</strong>で渡さないと行けないので、FormDataにappendする形でFormのデータを差し込みます。</p> <pre><code class="typescript"> const onSubmit = (data: Inputs, e: any) => { const formData = new FormData(); formData.append("name", data.name) formData.append("email", data.email) formData.append("subject", data.subject) formData.append("message", data.message) fetch('https://getform.io/f/8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', { method: 'POST', body: formData }) .then(() => { e.target.reset(); handleServerResponse(true, "Submitted!"); }) .catch((error) => { alert(error) console.error(error) handleServerResponse(false, error.toString()); }); } </code></pre> <p>また、第二引数として、formのeventが取得できます。おそらくEventは<strong>React.BaseSyntheticEvent</strong>だとは思うのですがうまく型が通せなくて悩みだしてしまいましたのでとりあえずanyにしてしまいました。</p> <p>一応、<a target="_blank" rel="nofollow noopener" href="https://github.com/react-hook-form/react-hook-form/discussions/4376">こちら</a>で質問は投げてますが英語がへたくそで誰も答えてくれそうにありませんね。</p> <p>特にeventでデータを取る必要はなさそうですが、たとえば送信時にFormの内容をリセットするなどの処理を書きたいときは</p> <pre><code class="typescript"> e.target.reset(); handleServerResponse(true, "Submitted!"); </code></pre> <p>とやってあげればOKです。</p> <h3 id="Formを書く"><a href="#Form%E3%82%92%E6%9B%B8%E3%81%8F">Formを書く</a></h3> <p>さて、あとは普通のFormを作るようにJSXを書いていきます。</p> <p>唯一違うところはinputやtextareaのref属性にregisterをつけなければいけないですが、それだけで大丈夫です。</p> <pre><code class="typescript"> <textarea name="message" placeholder="Something writing..." rows={6} cols={25} ref={register({required: true })}/> </code></pre> <p>ちなみに、registerのパラメーターで、必須項目やパターンの検査もできます。</p> <pre><code class="typescript"> <input name="email" type="email" placeholder="Enter your email" ref={register({ pattern: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/i, required: true })} /> </code></pre> <p>また、検査が通っていないときに警告メッセージを出すのはerrorsをつかうことで実現できます。</p> <pre><code class="typescript"> {errors.subject && <span>This field is required</span>} </code></pre> <p>Form部分をすべて実装するとこんな感じです。</p> <pre><code class="typescript"> <form onSubmit={handleSubmit(onSubmit)}> <p> <label>Your Name<br/> <input name="name" placeholder="Enter your name" type="text" ref={register({ required: true })} /> {errors.name && <span>This field is required</span>} </label> </p> <p> <label> Your email<br/> <input name="email" type="email" placeholder="Enter your email" ref={register({ pattern: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/i, required: true })} /> {errors.email && <span>This field is required and only email format</span>} </label> </p> <p> <label> Subject<br/> <input name="subject" type="text" maxLength={30} placeholder="Subject here..." ref={register({required: true })} /> {errors.subject && <span>This field is required</span>} </label> </p> <p> <label> Message<br /> <textarea name="message" placeholder="Something writing..." rows={6} cols={25} ref={register({required: true })}/> {errors.message && <span>This field is required</span>} </label> </p> <Button dark={serverState.submitting && serverState.status.ok} disabled={serverState.submitting && serverState.status.ok}> { serverState.submitting && serverState.status.ok ? serverState.status.msg: 'Submit'} </Button> </form> </code></pre> <p>もう完成です。実に簡単ですね。</p> <p>React Hook Formを使わないと、<a target="_blank" rel="nofollow noopener" href="https://blog.tubone-project24.xyz/2021/02/13/netlify-github-action#getformio">前記事</a>のように、formのonChangeのたびに、setStateしなきゃいけないのですが、すっきり実装できました。</p> <p>React Hook Formを使わないと</p> <pre><code class="typescript"> handleChange(e) { this.setState({ [e.target.name]: e.target.value }); } (中略) <input type="text" name="name" className="form-control" maxLength="30" minLength="2" required placeholder="Enter your name" onChange={this.handleChange} /> </code></pre> <p>となります。</p> <p>出来上がりはただのFormですのでかっこいいCSSを当ててくださいね。</p> <p><img src="https://i.imgur.com/DsrFLOE.png" alt="img" /></p> <h2 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h2> <p>楽に実装できたので余った時間は担当<strong>ウマ娘</strong>に捧げます。</p> tubone24 tag:crieit.net,2005:PublicArticle/15863 2020-04-23T08:51:55+09:00 2020-12-15T18:14:14+09:00 https://crieit.net/posts/Next-js-Dynamic-Routes-Link-SPA Next.jsのDynamic RoutesでLinkを使うとSPAにならない件について <p>追記)Next.js10からはこの <code>as</code> の件は必要なくなり、勝手に認識してくれるようになりました。</p> <p>Next.jsにはDynamic Routesという機能があり、 <code>posts/[id].js</code> のようなファイル名でページを作ると <code>posts/12</code> のように動的なURLでアクセスできるページにすることができる。</p> <p>しかし、このページに対してNext.jsの Link を使って <code><Link href="/posts/12"></code> のようにして遷移してみると、どうもSPAとしてアクセスできるのではなく、通常のリンクとしてのアクセスになってしまい、画面全体が読み込まれてSPAにならない(Next.js9.3現在)。</p> <p>ではどうするのかというと下記のように <code>as</code> を利用する。</p> <pre><code class="html"><Link href="/posts/[id]" as="/posts/12"> </code></pre> <p>つまり、実在する <code>[id]</code> ページにアクセスするが、見た目上はasのURLにアクセスさせる、という意味。ドキュメントにも一応書かれてはいた。</p> <pre><code class="text">as - The path that will be rendered in the browser URL bar. Used for dynamic routes </code></pre> <p>なんとなく、サーバーサイド、クライアント側のWebフレームワーク、例えばNuxt.jsにしろLaravelなどにしろ、作ったURLにそのままアクセスすればなんとなくいい感じにルーティングしてくれるのでその感覚でいると悩むことになる。Next.jsはちゃんと実際のURLと見た目上のURLを両方指定しなければいけない。恐らくNext.js的にも「そんなURLないけど?」という感じで単なるリンクにしてしまうのだろう。</p> <h2 id="パラメータとstateを利用する場合の問題"><a href="#%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%81%A8state%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E5%95%8F%E9%A1%8C">パラメータとstateを利用する場合の問題</a></h2> <p>これはこれで良いとして、useStateを使っている場合は別の問題が発生する。例えば、動的URLのパラメータを取得するのには下記のようにrouterを利用する。</p> <pre><code class="javascript"> const router = useRouter() return <div>{router.query.id}</div> </code></pre> <p>これは別に良いのだが、違うパラメータの同じページ、例えば <code>posts/11</code> から <code>posts/12</code> にアクセスする場合、この間にuseStateが混ざっているとうまくいかなくなる。</p> <pre><code class="javascript"> const router = useRouter() const [post, setPost] = useState(getPost(router.query.id)) return <div>{post.name}</div> </code></pre> <p>画面遷移後にrouterの値自体はもちろんちゃんと変わるのだが、useStateから取得している値は変わらない。もちろんこれはsetしていないから。元々は完全なページ切り替わりによって全てがリセットされていたのでちゃんと全てが新しいデータに切り替わっていたが、ちゃんとSPAできるようになると今度はルーティングのパラメータ以外は何も変わらなくなり、両方の値を使って表示をしているとなんかぐちゃぐちゃになる。</p> <p>ということで、ルーティングのパラメータが変わった場合は、また改めて手動で値をセットし直さなければならない。具体的にはuseEffectを使って下記のようにする。</p> <pre><code class="javascript"> const router = useRouter() const [post, setPost] = useState(getPost(router.query.id)) useEffect(() => { setPost(getPost(router.query.id)) }, [router.query.id]) return <div>{post.name}</div> </code></pre> <p>このようにして、idが変わったらデータを取り直す、という処理を入れる。もしpostに紐づくcommentsも取得している、ということであればそちらなども全部取得してsetし直す必要がある。コンポーネントの最初で色々連動させてuseStateしているからといって勝手に連動してくれたりはしない。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/15581 2019-12-05T00:04:33+09:00 2019-12-05T00:04:33+09:00 https://crieit.net/posts/React-Hooks-useEffect-ref React HooksのuseEffect経由でrefを使う場合 <p>React HooksのuseEffect内経由でrefを使うとundefinedになっていて動作しなかった。具体的にはこんな感じでキーボードイベントを使いたかったパターン。</p> <pre><code class="typescript">const ref = createRef<HTMLDivElement>() useEffect(() => { document.addEventListener('keydown', onKeyDown) return () => document.removeEventListener('keydown', onKeyDown) }) function onKeyDown(e: KeyboardEvent) { someFunc() } function someFunc() { ref.current } </code></pre> <p>これでrefを指定する。</p> <pre><code class="jsx">return ( <div ref={ref}> </div> ) </code></pre> <p>すると、キー入力をしてuseEffect経由で呼び出されたsomeFunc内のref.currentはundefinedになっている。よく分からない。</p> <p>とりあえず、createRefではなく、useRefとuseCallbackを組み合わせるとちゃんといけるっぽい。</p> <pre><code class="typescript">const ref = useRef<HTMLDivElement>() const setRef = useCallback((node: HTMLDivElement) => { ref.current = node }, []) </code></pre> <p>refの指定は下記。</p> <pre><code class="jsx">return ( <div ref={setRef}> </div> ) </code></pre> <p>複雑…。</p> <p><a target="_blank" rel="nofollow noopener" href="https://medium.com/@teh_builder/ref-objects-inside-useeffect-hooks-eb7c15198780">Ref objects inside useEffect Hooks - Daniel Schmidt - Medium</a></p> だら@Crieit開発者