Reactのフックを使用してコンポーネントを強制的に再レン​​ダリングするにはどうすればよいですか?


116

以下のフックの例を検討する

   import { useState } from 'react';

   function Example() {
       const [count, setCount] = useState(0);

       return (
           <div>
               <p>You clicked {count} times</p>
               <button onClick={() => setCount(count + 1)}>
                  Click me
               </button>
          </div>
        );
     }

基本的に、this.forceUpdate()メソッドを使用して、以下の例のように、Reactクラスコンポーネントでコンポーネントをすぐに再レンダリングするように強制します。

    class Test extends Component{
        constructor(props){
             super(props);
             this.state = {
                 count:0,
                 count2: 100
             }
             this.setCount = this.setCount.bind(this);//how can I do this with hooks in functional component 
        }
        setCount(){
              let count = this.state.count;
                   count = count+1;
              let count2 = this.state.count2;
                   count2 = count2+1;
              this.setState({count});
              this.forceUpdate();
              //before below setState the component will re-render immediately when this.forceUpdate() is called
              this.setState({count2: count
        }

        render(){
              return (<div>
                   <span>Count: {this.state.count}></span>. 
                   <button onClick={this.setCount}></button>
                 </div>
        }
 }

しかし、私の質問は、上記の機能コンポーネントをフックですぐに再レンダリングするように強制するにはどうすればよいですか?


1
を使用する元のコンポーネントのバージョンを投稿できますthis.forceUpdate()か?たぶんそれなしで同じことを達成する方法があります。
ジェイコブ

setCountの最後の行は切り捨てられます。現在の状態でのsetCountの目的が何であるかは不明です。
Estus Flask 2018

これはthis.forceUpdate();の後の単なるアクションです。私の質問でthis.forceUpdate()について説明するためだけに追加しました
Hemadri Dasari 2018

価値があるのは:手動で再レンダリングする必要があると思ったので、これに取り組んでいましたが、最終的に、外部に保持されている変数を状態フックに移動し、設定機能を利用するだけでよいことに気付きました。再レンダリング。決して必要ないというわけではありませんが、特定のユースケースで実際に必要かどうかを確認するために、3回目と4回目の調査を行う価値があります。
AlexanderNied20年

回答:


77

内部で使用するため、これはuseStateまたはuseReducerで可能です。useStateuseReducer

const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);

forceUpdate通常の状況で使用することを意図したものではなく、テストまたはその他の未解決のケースでのみ使用することを目的としています。この状況は、より一般的な方法で対処できます。

setCount不適切に使用の一例であるforceUpdatesetStateパフォーマンス上の理由から、非同期であり、状態の更新が正しく行われなかったという理由だけで同期するよう強制されるべきではありません。状態が以前に設定された状態に依存している場合、これはアップデータ関数を使用して実行する必要があります

以前の状態に基づいて状態を設定する必要がある場合は、以下のアップデータ引数についてお読みください。

<...>

アップデータ関数によって受信された状態と小道具の両方が最新であることが保証されます。アップデータの出力は、状態と浅くマージされます。

setCount その目的が不明確であるため、説明的な例ではないかもしれませんが、これはアップデータ関数の場合です。

setCount(){
  this.setState(({count}) => ({ count: count + 1 }));
  this.setState(({count2}) => ({ count2: count + 1 }));
  this.setState(({count}) => ({ count2: count + 1 }));
}

これは1:1でフックに変換されますが、コールバックとして使用される関数はメモ化する必要があります。

   const [state, setState] = useState({ count: 0, count2: 100 });

   const setCount = useCallback(() => {
     setState(({count}) => ({ count: count + 1 }));
     setState(({count2}) => ({ count2: count + 1 }));
     setState(({count}) => ({ count2: count + 1 }));
   }, []);

どのように機能しconst forceUpdate = useCallback(() => updateState({}), []);ますか?更新を強制することさえありますか?
デヴィッド・モルナー

2
@DávidMolnárはuseCallbackメモforceUpdate化するため、コンポーネントの有効期間中は一定に保たれ、小道具として安全に渡すことができます。呼び出しupdateState({})ごとに新しいオブジェクトで状態を更新しforceUpdateます。これにより、再レンダリングが行われます。そうです、呼び出されたときに強制的に更新されます。
EstusFlask19年

3
したがって、そのuseCallback部分は実際には必要ありません。それがなくても問題なく動作するはずです。
デヴィッド・モルナー

また、プリミティブデータ型であるupdateState(0)ため0、機能しませんか?それはオブジェクトである必要がありますか、それともupdateState([])(つまり、配列での使用)も機能しますか?
Andru

1
@Andruはい、0 === 0であるため、状態は1回更新されます。はい、配列もオブジェクトであるため、機能します。、、updateState(Math.random())またはカウンターなど、等価性チェックに合格しないものはすべて使用できます。
EstusFlask20年

29

他の人が述べているように、useState動作します-これがmobx-react-liteが更新を実装する方法です-あなたは同様のことをすることができます。

新しいフックを定義します、useForceUpdate-

import { useState, useCallback } from 'react'

export function useForceUpdate() {
  const [, setTick] = useState(0);
  const update = useCallback(() => {
    setTick(tick => tick + 1);
  }, [])
  return update;
}

コンポーネントで使用します-

const forceUpdate = useForceUpdate();
if (...) {
  forceUpdate(); // force re-render
}

https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.tsおよびhttps://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserverを参照してください.ts


2
私がフックから理解していることから、これはuseForceUpdate機能しない可能性があり、関数が再レンダリングされるたびに新しい関数が返されます。でforceUpdate使用したときに機能するにはuseEffect、次のようになります。kentcdodds.com/ blog / usememouseCallback(update)
usecallbackを

おかげで、@ MartinRatinaud-はい、useCallback(?)なしでメモリリークを引き起こす可能性があります-修正されました。
ブライアンバーンズ

26

通常、更新をトリガーする任意の状態処理アプローチを使用できます。

TypeScriptを使用

codesandboxの例

useState

const forceUpdate: () => void = React.useState()[1].bind(null, {})  // see NOTE below

useReducer

const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void

カスタムフックとして

このように好きなアプローチをラップするだけです

function useForceUpdate(): () => void {
  return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}

これはどのように機能しますか?

更新をトリガーする」とは、ある値が変更され、コンポーネントを再レンダリングする必要があることをReactエンジンに通知することを意味します。

[, setState]fromにuseState()はパラメータが必要です。新しいオブジェクトをバインドすることでそれを取り除きます{}
() => ({})inuseReducerは、アクションがディスパッチされるたびに新しいオブジェクトを返すダミーレデューサーです。
{} (新しいオブジェクト)は、状態の参照を変更することによって更新をトリガーするために必要です。

PS:内部でuseStateラップするだけuseReducerです。ソース

注: useStateで.bindを使用すると、レンダリング間で関数参照が変更されます。ここですでに説明したように、useCallback内にラップすることは可能ですが、その場合、セクシーなone-liner™にはなりません。レデューサーバージョンは、レンダリング間の参照の同等性をすでに維持しています。これは、小道具でforceUpdate関数を渡したい場合に重要です。

プレーンJS

const forceUpdate = React.useState()[1].bind(null, {})  // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]

17

@MinhKhaの答えの代替:

それはよりきれいになることができますuseReducer

const [, forceUpdate] = useReducer(x => x + 1, 0);

使用法: forceUpdate()-パラメータなしのクリーナー


13

useStateは次のように簡単に定義できます。

const [, forceUpdate] = React.useState(0);

そして使用法: forceUpdate(n => !n)

この助けを願っています!


7
forceUpdateがレンダリングごとに偶数回呼び出されると、失敗します。
Izhaki

値を増やし続けてください。
ゲイリー

2
これはエラーが発生しやすいため、削除または編集する必要があります。
slikts

11

React Hooks FAQの公式ソリューションforceUpdate

const [_, forceUpdate] = useReducer((x) => x + 1, 0);
// usage
<button onClick={forceUpdate}>Force update</button>

実例


10

コンポーネントを状態と小道具にのみ依存させることが望ましいです。期待どおりに機能しますが、コンポーネントを強制的に再レン​​ダリングする関数が本当に必要な場合は、useStateフックを使用して、必要に応じて関数を呼び出すことができます。

const { useState, useEffect } = React;

function Foo() {
  const [, forceUpdate] = useState();

  useEffect(() => {
    setTimeout(forceUpdate, 2000);
  }, []);

  return <div>{Date.now()}</div>;
}

ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>


わかりましたが、Reactがthis.forceUpdate();を導入した理由。そもそも、コンポーネントが以前のバージョンでsetStateを使用して再レンダリングする場合はどうでしょうか。
Hemadri Dasari 2018

1
@ Think-個人的には2回使用したことがなく、今のところ良いユースケースは考えられませんが、これらの本当に特別なユースケースのエスケープハッチだと思います。「通常は、のすべての使用を避けるべきforceUpdate()とのみから読み取るthis.propsthis.staterender()。」
Tholle 2018年

1
同意しました。私は自分の経験でそれを使用したことはありませんが、それがどのように機能するかを知っているので、フックで同じことがどのように行われるかを理解しようとしています
HemadriDasari18年

1
@Tholle and I can't think of a good use case for it right now 1つありますが、状態がReactによって制御されていない場合はどうでしょうか。私はReduxを使用していませんが、何らかのforceupdateを実行する必要があると思います。私は個人的にプロキシを使用して状態を維持し、コンポーネントは小道具の変更をチェックしてから更新することができます。非常に効率的に動作するようです。たとえば、すべてのコンポーネントがプロキシによって制御され、このプロキシはSessionStorageによってサポートされているため、ユーザーがWebページを更新しても、ドロップダウンの状態などが維持されます。IOW:状態はまったく使用しません。すべてが小道具で制御されます。
キース

8

簡単なコード

const forceUpdate = React.useReducer(bool => !bool)[1];

使用する:

forceUpdate();

5

ReactがJSXコードでブール値を出力しないという事実を利用して、通常のフックを(乱用)使用して再レンダリングを強制することができます

// create a hook
const [forceRerender, setForceRerender] = React.useState(true);

// ...put this line where you want to force a rerender
setForceRerender(!forceRerender);

// ...make sure that {forceRerender} is "visible" in your js code
// ({forceRerender} will not actually be visible since booleans are
// not printed, but updating its value will nonetheless force a
// rerender)
return (
  <div>{forceRerender}</div>
)


1
この場合、setBooleanが2回変更されると、子React.useEffectが更新を認識しない可能性があります。
アルマ

私が理解している限り、Reactはすべてのブール値の更新を、ブール値がすぐに元に戻ったとしてもページを再レンダリングする理由として扱います。とは言うものの、Reactはもちろん標準ではなく、この場合の正確な動作は未定義であり、変更される可能性があります。
ファーギー

1
知りません。どういうわけか私はこのバージョンに惹かれています。それは私をくすぐります:-)。それに加えて、それは純粋に感じます。JSXが依存している何かが変わったので、再レンダリングします。不可視性はそのIMOを損なうものではありません。
エイドリアンバーソロミュー

4

潜在的なオプションは、を使用して特定のコンポーネントでのみ更新を強制することkeyです。キーを更新すると、コンポーネントのレンダリングがトリガーされます(以前は更新できませんでした)

例えば:

const [tableKey, setTableKey] = useState(1);
...

useEffect(() => {
    ...
    setTableKey(tableKey + 1);
}, [tableData]);

...
<DataTable
    key={tableKey}
    data={tableData}/>

1
状態値と再レンダリングの要件の間に1:1の関係がある場合、これは多くの場合最もクリーンな方法です。
jaimefps

1
これは簡単な解決策です
PriyankaV20年


3

ワンラインソリューション:

const useForceUpdate = () => useState()[1];

useStateは、現在の状態とそれを更新する関数(statesetter)の値のペアを返します。ここでは、強制的に再レン​​ダリングするためにsetterのみを使用しています。



2

これにより、依存するコンポーネントが3回レンダリングされます(要素が等しい配列は等しくありません)。

const [msg, setMsg] = useState([""])

setMsg(["test"])
setMsg(["test"])
setMsg(["test"])

1
配列にアイテムを入れる必要すらないと思います。空の配列は、オブジェクトのように、単に異なる参照によって別の空の配列と厳密に等しくなるわけではありません。
QWERTY配列の


2

react-tidyそれを行うためだけのカスタムフックがありますuseRefresh

import React from 'react'
import {useRefresh} from 'react-tidy'

function App() {
  const refresh = useRefresh()
  return (
    <p>
      The time is {new Date()} <button onClick={refresh}>Refresh</button>
    </p>
  )
}

このフックの詳細

免責事項私はこのライブラリの作成者です。


1

通常のReactクラスベースのコンポーネントについてはforceUpdateこのURLのAPIのReactDocsを参照してください。ドキュメントには次のように記載されています。

通常、forceUpdate()のすべての使用を避け、render()のthis.propsとthis.stateからのみ読み取るようにしてください。

ただし、ドキュメントには次のことも記載されています。

render()メソッドが他のデータに依存している場合は、forceUpdate()を呼び出すことで、コンポーネントを再レンダリングする必要があることをReactに伝えることができます。

そのため、使用するユースケースはforceUpdateまれであり、私はこれまで使用したことがありませんが、私が取り組んできたいくつかのレガシー企業プロジェクトで他の開発者によって使用されているのを見ました。

したがって、機能コンポーネントの同等の機能については、このURLにあるHOOKSのReactDocsを参照してください。上記のURLに従って、「useReducer」フックを使用しforceUpdateて機能コンポーネントの機能を提供できます。

実用的なコードサンプルthat does not use state or propsを以下に示します。これは、CodeSandboxのこのURLからも入手できます。

import React, { useReducer, useRef } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  // Use the useRef hook to store a mutable value inside a functional component for the counter
  let countref = useRef(0);

  const [, forceUpdate] = useReducer(x => x + 1, 0);

  function handleClick() {
    countref.current++;
    console.log("Count = ", countref.current);
    forceUpdate(); // If you comment this out, the date and count in the screen will not be updated
  }

  return (
    <div className="App">
      <h1> {new Date().toLocaleString()} </h1>
      <h2>You clicked {countref.current} times</h2>
      <button
        onClick={() => {
          handleClick();
        }}
      >
        ClickToUpdateDateAndCount
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

注:このURLでは、(useReducerの代わりに)useStateフックを使用する別のアプローチも利用できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.