🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Next.js

Next.jsは、Reactを用いたサーバサイドレンダリングなどを行う軽量なフレームワークです。Zeit社が開発しており、nextコマンドでプロジェクトを作成することにより、開発環境整備が整った環境が即時に作成できます。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

3436閲覧

ReactでuseStateがうまく効かない

nobodytolove123

総合スコア61

Next.js

Next.jsは、Reactを用いたサーバサイドレンダリングなどを行う軽量なフレームワークです。Zeit社が開発しており、nextコマンドでプロジェクトを作成することにより、開発環境整備が整った環境が即時に作成できます。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2019/10/20 09:11

編集2019/10/20 10:21

概要

お世話になります。

現在、Next.jsでアプリケーションを開発していて、その中で要素ダブルクリックした場合にdiv要素、input要素としてトグルさせるコンポーネントを作成しています。

フレームワークにNext.jsを利用し、React.jsTypeScript形式で記述しています。

トグルさせる手法としてReact Hookを利用していて、コンポーネントトグル状態、およびエレメントuseStateを利用して管理しています。

状態を管理するところまではうまく行ったのですが、トグルさせる処理がうまく行かないので、useStateの挙動等についてご教授頂きたいです。

問題点

まずはソースコードをご覧いただければと思います。

tsx

1import React from 'react' 2import styled, { StyledComponent } from 'styled-components' 3 4export default () => { 5 return ( 6 <Cell 7 text='Hello World!!!' 8 /> 9 ) 10} 11 12const CellStyle: StyledComponent<'div', {}> = styled.div`` 13 14const InputStyle: StyledComponent<'input', {}> = styled.input`` 15 16interface CellInterface { 17 key?: number 18 text: string 19} 20 21const Cell: React.FC<CellInterface> = ({ key = 0, text }) => { 22 const onDoubleClick = () => { 23 if (elementState.selected) { 24 setElementState({ 25 selected: false, 26 element: <CellStyle 27 key={key} 28 onDoubleClick={onDoubleClick} 29 > 30 {text} 31 </CellStyle> 32 }) 33 } else { 34 setElementState({ 35 selected: true, 36 element: <InputStyle 37 key={key} 38 onDoubleClick={onDoubleClick} 39 defaultValue={text} 40 /> 41 }) 42 } 43 } 44 45 type ElementState = { 46 selected: boolean 47 element: JSX.Element 48 } 49 50 const [elementState, setElementState] : [ 51 ElementState, 52 React.Dispatch<React.SetStateAction<ElementState>> 53 ] = React.useState<ElementState>({ 54 selected: false, 55 element: <CellStyle 56 key={key} 57 onDoubleClick={onDoubleClick} 58 > 59 {text} 60 </CellStyle> 61 }) 62 63 return elementState.element 64}

簡単に説明すると、Cellコンポーネント内でelementStateオブジェクトを管理していて、elementStateはコンポーネントが編集対象として選択されているかを管理するselected、表示要素であるelementを保持しています。

CellStyleInputStyleコンポーネントのonDoubleClickイベントハンドラに関数を登録し、ダブルクリックされた場合、要素の切り替えを行えるようにしています。

上記で問題の箇所はonDoubleClickです。

僕の期待では、下記の手順を踏むことで処理が行われることを期待しています。

  1. まずCellコンポーネント内部でuseStateメソッドが呼ばれ、elementState.elementCellStyleに設定される。
  2. Cellコンポーネントをダブルクリックした場合、elementState.selectedfalseなので、elementState.selectedtrueに設定され、elementState.elementInputStyleに設定される。
  3. 再度Cellコンポーネントをダブルクリックした場合、elementState.selectedtrueなので、elementState.selectedfalseに設定され、elementState.elementCellStyleに設定される。

しかし僕の手元では上記2の部分がうまく行っていなく、CellコンポーネントをダブルクリックしてもelementState.selectedtrueに設定されません。

console.log等でデバックしていると、上記2の部分でelementState.elementは期待通りInputStyleに設定されていますが、elementState.selectedが上手くいきません。

どなたかuseStateの挙動についてご教授頂ければと思います。

上記の説明を文字で見てもわかりづらいと思いますので、下記に再現可能なように環境情報を掲載させて頂きます。

環境情報

項目
OSWindows 10
Nodev10.16.2
  • 構成

sh

1$ find -type f 2./package.json 3./pages/index.tsx
  • package.json

json

1{ 2 "name": "tmp", 3 "version": "1.0.0", 4 "description": "", 5 "scripts": { 6 "dev": "next" 7 }, 8 "keywords": [], 9 "author": "", 10 "license": "ISC", 11 "dependencies": { 12 "next": "^9.0.4", 13 "react": "^16.8.6", 14 "react-dom": "^16.8.6", 15 "styled-components": "^4.3.2", 16 "styled-reset": "^3.0.0", 17 "typescript": "^3.5.3" 18 }, 19 "devDependencies": { 20 "@types/next": "^8.0.6", 21 "@types/react": "^16.8.23", 22 "@types/react-dom": "^16.8.5", 23 "@types/styled-components": "^4.1.18", 24 "@zeit/next-typescript": "^1.1.1", 25 "babel-plugin-styled-components": "^1.6.0", 26 "ts-node": "^8.3.0" 27 } 28}
  • pages/index.tsx
import React from 'react' import styled, { StyledComponent } from 'styled-components' export default () => { return ( <Cell text='Hello World!!!' /> ) } const CellStyle: StyledComponent<'div', {}> = styled.div`` const InputStyle: StyledComponent<'input', {}> = styled.input`` interface CellInterface { key?: number text: string } const Cell: React.FC<CellInterface> = ({ key = 0, text }) => { const onDoubleClick = () => { if (elementState.selected) { setElementState({ selected: false, element: <CellStyle key={key} onDoubleClick={onDoubleClick} > {text} </CellStyle> }) } else { setElementState({ selected: true, element: <InputStyle key={key} onDoubleClick={onDoubleClick} defaultValue={text} /> }) } } type ElementState = { selected: boolean element: JSX.Element } const [elementState, setElementState] : [ ElementState, React.Dispatch<React.SetStateAction<ElementState>> ] = React.useState<ElementState>({ selected: false, element: <CellStyle key={key} onDoubleClick={onDoubleClick} > {text} </CellStyle> }) return elementState.element }
  • 起動コマンド

sh

1$ npm run dev

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

自己解決

調べてみたところ、useStateにオブジェクトを格納するのは推奨されないパターンらしいです。

リンク: 関数型の更新useStateの設定メソッドがすぐに変更を反映しないReact の Hooks で state に Object を使うReact Hooks useState with Object

また今回の場合、ステートがオブジェクトとプリミティブで混合しているので、オブジェクトの深さが違うことから、上で挙げられたような挙動になったのかと思います。

リンクにあるようにuseReducerを利用することで、オブジェクトの変更を安定して行えるようになりました。

tsx

1import React from 'react' 2import styled, { StyledComponent } from 'styled-components' 3 4export default () => { 5 return ( 6 <Cell 7 text='Hello World!!!' 8 /> 9 ) 10} 11 12const CellStyle: StyledComponent<'div', {}> = styled.div`` 13 14const InputStyle: StyledComponent<'input', {}> = styled.input`` 15 16interface CellInterface { 17 key?: number 18 text: string 19} 20 21const Cell: React.FC<CellInterface> = ({ key = 0, text }) => { 22 interface State { 23 element: JSX.Element 24 selected: boolean 25 } 26 27 const reducer = (state: State, selected: boolean): State => { 28 if (selected) { 29 return { 30 ...state, 31 selected: false, 32 element: <CellStyle 33 key={key} 34 onDoubleClick={() => dispatch(false)} 35 > 36 {text} 37 </CellStyle> 38 } 39 } else { 40 return { 41 ...state, 42 selected: true, 43 element: <InputStyle 44 key={key} 45 onDoubleClick={() => dispatch(true)} 46 defaultValue={text} 47 /> 48 } 49 } 50 } 51 52 const [state, dispatch] : [ 53 State, 54 React.Dispatch<boolean> 55 ] = React.useReducer(reducer, { 56 selected: false, 57 element: <CellStyle 58 key={key} 59 onDoubleClick={() => dispatch(state.selected)} 60 > 61 {text} 62 </CellStyle> 63 }) 64 65 return state.element 66}

また変更対象オブジェクトのプロパティselectedと変更検知に利用されるselectedが被っているのはなんだかスッキリしません。

何を言っているのかというとuseReducerの第一引数のstate.selectedと第二引数selectedが被っているという点です。

今回はオブジェクトの更新というよりかは、要素の入れ替えなので、下記の様に書くととてもスッキリします。

useStateでプリミティブ値を扱うという本来の目的通りの使い方かと思います。

tsx

1import React from 'react' 2import styled, { StyledComponent } from 'styled-components' 3 4export default () => { 5 return ( 6 <Cell 7 text='Hello World!!!' 8 /> 9 ) 10} 11 12const CellStyle: StyledComponent<'div', {}> = styled.div`` 13 14const InputStyle: StyledComponent<'input', {}> = styled.input`` 15 16interface CellInterface { 17 key?: number 18 text: string 19} 20 21const Cell: React.FC<CellInterface> = ({ key = 0, text }) => { 22 const [selected, setSelected] : [ 23 boolean, 24 React.Dispatch<React.SetStateAction<boolean>> 25 ] = React.useState<boolean>(false) 26 27 const element = seleted ? 28 <InputStyle 29 key={key} 30 onDoubleClick={() => setSelected(!selected)} 31 defaultValue={text} 32 /> : 33 <CellStyle 34 key={key} 35 onDoubleClick={() => setSelected(!selected)} 36 > 37 {text} 38 </CellStyle> 39 40 return element 41}

投稿2019/10/20 13:42

編集2019/10/21 01:25
nobodytolove123

総合スコア61

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問