実現したいこと
Reactのコンポーネントの不必要な再レンダリングを防止したいです。
発生している問題
name, password どちらか1文字でも値を変更するとApp自体が再レンダリングされてしまいます。
Reactのコンポーネントの再レンダリングが発生しているかは React Developer Tools を利用して視覚的に確認しています。
該当のソースコード
jsx
1import { useState } from 'react' 2 3export default function App() { 4 const [name, setName] = useState('') 5 const [password, setPassword] = useState('') 6 7 function handleSubmit(e: React.SubmitEvent<HTMLFormElement>) { 8 e.preventDefault() 9 10 console.log(`name is ${name}`) 11 console.log(`password is ${password}`) 12 } 13 14 return ( 15 <form onSubmit={handleSubmit}> 16 <div> 17 <input value={name} onChange={(e) => { setName(e.target.value) }} /> 18 </div> 19 20 <div> 21 <input value={password} onChange={(e) => { setPassword(e.target.value) }} /> 22 </div> 23 24 <input type="submit" /> 25 </form> 26 ) 27}
試したこと1
以下のように NameInput と PasswordInput に別けました。
コンポーネントごとの最小限の再レンダリングなので期待する結果になりました。
しかし、 handleSubmit で name と password が取得できなくなりました。
tsx
1import { useState } from 'react' 2 3function NameInput() { 4 const [name, setName] = useState('') 5 6 return ( 7 <div> 8 <input name="name" value={name} onChange={(e) => setName(e.target.value)} /> 9 </div> 10 ) 11} 12 13function PasswordInput() { 14 const [password, setPassword] = useState('') 15 16 return ( 17 <div> 18 <input name="password" value={password} onChange={(e) => setPassword(e.target.value)} /> 19 </div> 20 ) 21} 22 23export default function App() { 24 function handleSubmit(e: React.SubmitEvent<HTMLFormElement>) { 25 e.preventDefault() 26 27 console.log(`name is ${name}`) 28 console.log(`password is ${password}`) 29 } 30 31 return ( 32 <form onSubmit={handleSubmit}> 33 <NameInput /> 34 <PasswordInput /> 35 <input type="submit" /> 36 </form> 37 ) 38}
試したこと2
useState が原因で再レンダリングされているので、そもそも useState を利用しないようにしました。
handleSubmit で name, password の値が取得できていますが React として適切な書き方なのかが疑問です。
どちらかというと今までの古き良きHTML + JavaScriptのような書き方を彷彿とさせます。
この書き方で問題ないならそもそも useState の存在理由も疑問に思えてきます。
また、useEffect であればエディタの名前変更機能を利用すれば自動的に全ての変数名が変更されますが、FormDataの場合は必ず手動でname属性と formData.get を修正する必要があります。
フォームが複雑になると人為的ミスをする可能性も出てくるので避けたいです。
tsx
1export default function App() { 2 function handleSubmit(e: React.SubmitEvent<HTMLFormElement>) { 3 e.preventDefault() 4 5 const formData = new FormData(e.currentTarget) 6 7 const name = formData.get('name') 8 const password = formData.get('password') 9 10 console.log(`name is ${name}`) 11 console.log(`password is ${password}`) 12 } 13 14 return ( 15 <form onSubmit={handleSubmit}> 16 <div> 17 <input name="name" /> 18 </div> 19 <div> 20 <input name="password" /> 21 </div> 22 <input type="submit" /> 23 </form> 24 ) 25}
試したこと3
memo を利用してメモ化しましたが name, password どちらか1方を変更すると、どちらも再レンダリングされてしまいます。
tsx
1import { memo, useState } from 'react' 2 3interface Props { 4 value: string 5 setValue: (value: string) => void 6} 7 8function NameInput(props: Props) { 9 return ( 10 <div> 11 <input name="name" value={props.value} onChange={(e) => props.setValue(e.target.value)} /> 12 </div> 13 ) 14} 15 16function PasswordInput(props: Props) { 17 return ( 18 <div> 19 <input name="password" value={props.value} onChange={(e) => props.setValue(e.target.value)} /> 20 </div> 21 ) 22} 23 24const MemoNameInput = memo(NameInput) 25const MemoPasswordInput = memo(PasswordInput) 26 27export default function App() { 28 const [name, setName] = useState('') 29 const [password, setPassword] = useState('') 30 31 function handleSubmit(e: React.SubmitEvent<HTMLFormElement>) { 32 e.preventDefault() 33 34 console.log(`name is ${name}`) 35 console.log(`password is ${password}`) 36 } 37 38 return ( 39 <form onSubmit={handleSubmit}> 40 <MemoNameInput value={name} setValue={(value) => { setName(value) }} /> 41 <MemoPasswordInput value={password} setValue={(value) => { setPassword(value) }} /> 42 43 <input type="submit" /> 44 </form> 45 ) 46}
試したこと4
useCallback を利用して「試したこと3」を改良しました。
値を変更すると「AppとNameInput」または「AppとPasswordInput」のように個別にレンダリングできるようになりました。
Appが再レンダリングされる事は App 内に useState が存在するので仕方ないようです。
しかし、たった2つのinputですが、これだけ大量のコードを書かなくてはいけないのでしょうか?
とても大変です。なにか良い方法はないですか?
tsx
1import { memo, useCallback, useState } from 'react' 2 3interface Props { 4 value: string 5 setValue: (value: string) => void 6} 7 8function NameInput(props: Props) { 9 return ( 10 <div> 11 <input name="name" value={props.value} onChange={(e) => props.setValue(e.target.value)} /> 12 </div> 13 ) 14} 15 16function PasswordInput(props: Props) { 17 return ( 18 <div> 19 <input name="password" value={props.value} onChange={(e) => props.setValue(e.target.value)} /> 20 </div> 21 ) 22} 23 24const MemoNameInput = memo(NameInput) 25const MemoPasswordInput = memo(PasswordInput) 26 27export default function App() { 28 const [name, setName] = useState('') 29 const [password, setPassword] = useState('') 30 31 function handleSubmit(e: React.SubmitEvent<HTMLFormElement>) { 32 e.preventDefault() 33 34 console.log(`name is ${name}`) 35 console.log(`password is ${password}`) 36 } 37 38 const handleSetName = useCallback((value: string) => { 39 setName(value) 40 }, []) 41 42 const handleSetPassword = useCallback((value: string) => { 43 setPassword(value) 44 }, []) 45 46 return ( 47 <form onSubmit={handleSubmit}> 48 <MemoNameInput value={name} setValue={handleSetName} /> 49 <MemoPasswordInput value={password} setValue={handleSetPassword} /> 50 51 <input type="submit" /> 52 </form> 53 ) 54}
試したこと5
setValue に直接 setter を指定して「試したこと3」を改良しました。
関数オブジェクトが再生成されないようになりました。
tsx
1import { memo, useState } from 'react' 2 3interface Props { 4 value: string 5 setValue: (value: string) => void 6} 7 8function Input(props: Props) { 9 return ( 10 <div> 11 <input value={props.value} onChange={(e) => props.setValue(e.target.value)} /> 12 </div> 13 ) 14} 15 16const MemoInput = memo(Input) 17 18export default function App() { 19 const [name, setName] = useState('') 20 const [password, setPassword] = useState('') 21 22 function handleSubmit(e: React.SubmitEvent<HTMLFormElement>) { 23 e.preventDefault() 24 25 console.log(`name is ${name}`) 26 console.log(`password is ${password}`) 27 } 28 29 return ( 30 <form onSubmit={handleSubmit}> 31 <MemoInput value={name} setValue={setName} /> 32 <MemoInput value={password} setValue={setPassword} /> 33 34 <input type="submit" /> 35 </form> 36 ) 37}
試したこと6
「試したこと5」の memo を利用する必要がないことに気付いたので修正しました。
tsx
1import { useState } from 'react' 2 3export default function App() { 4 const [name, setName] = useState('') 5 const [password, setPassword] = useState('') 6 7 function handleSubmit(e: React.SubmitEvent<HTMLFormElement>) { 8 e.preventDefault() 9 10 console.log(`name is ${name}`) 11 console.log(`password is ${password}`) 12 } 13 14 function handleChangeName(e: React.ChangeEvent<HTMLInputElement>) { 15 setName(e.target.value) 16 } 17 18 function handleChangePassword(e: React.ChangeEvent<HTMLInputElement>) { 19 setPassword(e.target.value) 20 } 21 22 return ( 23 <form onSubmit={handleSubmit}> 24 <div> 25 <input name="name" value={name} onChange={handleChangeName} /> 26 </div> 27 <div> 28 <input name="password" value={password} onChange={handleChangePassword} /> 29 </div> 30 31 <input type="submit" /> 32 </form> 33 ) 34}
試したこと7
handleChangeName と handleChangePassword を共通化しました。
name の指定が少し長くなっていますが型安全の為です。仕方ないです。
tsx
1import { useState } from 'react' 2 3export default function App() { 4 const [form, setForm] = useState({ 5 name: '', 6 password: '' 7 }) 8 9 type FormKeys = keyof typeof form 10 11 function handleSubmit(e: React.SubmitEvent<HTMLFormElement>) { 12 e.preventDefault() 13 14 console.log(`name is ${form.name}`) 15 console.log(`password is ${form.password}`) 16 } 17 18 function handleChange(e: React.ChangeEvent<HTMLInputElement>) { 19 setForm({ ...form, [e.target.name]: e.target.value }) 20 } 21 22 return ( 23 <form onSubmit={handleSubmit}> 24 <div> 25 <input name={"name" satisfies FormKeys} value={form.name} onChange={handleChange} /> 26 </div> 27 <div> 28 <input name={"password" satisfies FormKeys} value={form.password} onChange={handleChange} /> 29 </div> 30 31 <input type="submit" /> 32 </form> 33 ) 34}
補足情報(FW/ツールのバージョンなど)
- React 19.2.4
回答3件
あなたの回答
tips
プレビュー
