反応フックで `setState`コールバックを使用する方法


133

useStateコンポーネントの状態を設定するためのReactフックが導入されました。しかし、以下のコードのように、フックを使用してコールバックを置き換えるにはどうすればよいですか?

setState(
  { name: "Michael" },
  () => console.log(this.state)
);

状態が更新された後、何かしたいです。

useEffectは余分なことをするために使用できることを知っていますが、ビットコードを必要とする前の状態の値をチェックする必要があります。useStateフックで使える簡単な解決策を探しています。


1
クラスコンポーネントでは、asyncを使用して、setStateにコールバックを追加した場合と同じ結果が得られるのを待ちます。残念ながら、それはフックで動作していません。asyncを追加してawaitを追加しても、reactは状態が更新されるのを待ちません。たぶんuseEffectがそれを行う唯一の方法です。
MINGWU19年

2
@趙、あなたはまだ正解をマークしていません。数秒お時間をいただけますか
ZohaibIjaz20年

回答:


146

useEffectこれを実現するには、フックを使用する必要があります。

const [counter, setCounter] = useState(0);

const doSomething = () => {
  setCounter(123);
}

useEffect(() => {
   console.log('Do something after counter has changed', counter);
}, [counter]);

55
これconsole.logにより、最初のレンダリングと時間のcounter変更時にが起動します。状態が更新された後でのみ何かを実行したいが、初期値が設定されているため初期レンダリングでは実行したくない場合はどうなりますか?値をチェックインして、useEffect何かをしたいかどうかを決めることができると思います。それはベストプラクティスと見なされますか?
ダリルヤング

2
useEffect初期レンダリングでの実行を回避するために、初期レンダリングで実行されないカスタムuseEffectフックを作成できます。:そのようなフックを作成するには、この質問をチェックアウトすることができますstackoverflow.com/questions/53253940/...

14
そして、同じ状態値を変更する異なるsetState呼び出しで異なるコールバックを呼び出したい場合はどうですか?あなたの答えは間違っています、そして初心者を混乱させないために正しいとマークされるべきではありません。本当のことは、setStateは、明確な解決方法が1つもないクラスからフックを移行するときに、最も難しい問題の1つをコールバックするということです。場合によっては、実際に値に依存する効果が得られることもあれば、Refsにある種のフラグを保存するなどのハッキーな方法が必要になることもあります。
ドミトリーロボフ

どうやら、今日、私はsetCounter(value、callback)を呼び出して、状態が更新された後にいくつかのタスクが実行され、useEffectを回避できるようになりました。これが好みなのか特定のメリットなのか完全にはわかりません。
ケンイングラム

2
@KenIngram私はちょうどことを試してみました。この非常に特定のエラーを得た:Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
グレッグSadetsky

25

以前の状態を更新したい場合は、フックで次のようにすることができます。

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


setCount(previousCount => previousCount + 1);

2
私はsetCounterあなたが意味すると思いますsetCount
MehdiDehghani20年

@BimalGrgこれは本当に機能しますか?これを複製することはできません。このエラーでコンパイルに失敗します:expected as assignment or function call and instead saw an expression
tonitone1 2020年

@ tonitone120setCount内に矢印関数があります。
BimalGrg20年

@BimalGrg質問は状態が更新された直後コードを実行する機能を求めています。このコードは本当にそのタスクに役立ちますか?
tonitone1 2020

はい、クラスコンポーネントのthis.setStateのように機能します
Aashiq ahmed 2010

19

useEffect状態の更新時にのみ発生します

const [state, setState] = useState({ name: "Michael" })
const isFirstRender = useRef(true)

useEffect(() => {
  if (!isFirstRender.current) {
    console.log(state) // do something after state has updated
  }
}, [state])

useEffect(() => { 
  isFirstRender.current = false // toggle flag after first render/mounting
}, [])

上記はsetStateコールバックを最もよく模倣し、初期状態では起動しません。あなたはそれからカスタムフックを抽出することができます:

function useEffectUpdate(effect, deps) {
  const isFirstRender = useRef(true) 

  useEffect(() => {
    if (!isFirstRender.current) {
      effect()
    }  
  }, deps) // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
    isFirstRender.current = false;
  }, []);
}

// ... Usage inside component
useEffectUpdate(() => { console.log(state) }, [state])

代替:クラスのようにコールバックを受信するようにuseState 実装できますsetState
ford 0420

17

使用するのuseEffectは直感的な方法ではないと思います。

このためのラッパーを作成しました。このカスタムフックでは、コールバックをsetStateパラメーターではなくパラメーターに送信できuseStateます。

Typescriptバージョンを作成しました。したがって、これをJavascriptで使用する必要がある場合は、コードから型表記を削除するだけです。

使用法

const [state, setState] = useStateCallback(1);
setState(2, (n) => {
  console.log(n) // 2
});

宣言

import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react';

type Callback<T> = (value?: T) => void;
type DispatchWithCallback<T> = (value: T, callback?: Callback<T>) => void;

function useStateCallback<T>(initialState: T | (() => T)): [T, DispatchWithCallback<SetStateAction<T>>] {
  const [state, _setState] = useState(initialState);

  const callbackRef = useRef<Callback<T>>();
  const isFirstCallbackCall = useRef<boolean>(true);

  const setState = useCallback((setStateAction: SetStateAction<T>, callback?: Callback<T>): void => {
    callbackRef.current = callback;
    _setState(setStateAction);
  }, []);

  useEffect(() => {
    if (isFirstCallbackCall.current) {
      isFirstCallbackCall.current = false;
      return;
    }
    callbackRef.current?.(state);
  }, [state]);

  return [state, setState];
}

export default useStateCallback;

欠点

渡された矢印関数が変数の外部関数を参照している場合、状態が更新された後の値ではなく、現在の値をキャプチャします。上記の使用例では、console.log(state)は2ではなく1を出力します。


1
ありがとう!クールなアプローチ。サンプル作成codesandbox
ValYouW

@ValYouWサンプルを作成していただきありがとうございます。唯一の問題は、渡された矢印関数が変数の外部関数を参照している場合、状態が更新された後の値ではなく、現在の値をキャプチャすることです。
MJStudio20年

はい、それは機能性成分...とキャッチーな部分です
ValYouW

ここで以前の状態値を取得するにはどうすればよいですか?
Ankan-Zerob

@ Ankan-Zerob使用法の部分ではstate、コールバックが呼び出されたときに、まだ前のものを参照していると思います。そうですね。
MJStudio20年

2

setState() コンポーネントの状態への変更をキューに入れ、このコンポーネントとその子を更新された状態で再レンダリングする必要があることをReactに通知します。

setStateメソッドは非同期であり、実際のところ、promiseは返されません。したがって、関数を更新または呼び出したい場合は、関数を2番目の引数としてsetState関数のコールバックと呼ぶことができます。たとえば、上記のケースでは、関数をsetStateコールバックとして呼び出しています。

setState(
  { name: "Michael" },
  () => console.log(this.state)
);

上記のコードはクラスコンポーネントでは正常に機能しますが、機能コンポーネントの場合、setStateメソッドを使用することはできません。これにより、useeffectフックを利用して同じ結果を得ることができます。

頭に浮かぶ明らかな方法は、ypuがuseEffectで使用できる方法は次のとおりです。

const [state, setState] = useState({ name: "Michael" })

useEffect(() => {
  console.log(state) // do something after state has updated
}, [state])

ただし、これは最初のレンダリングでも発生するため、次のようにコードを変更して、最初のレンダリングイベントを確認し、状態のレンダリングを回避できます。したがって、実装は次の方法で実行できます。

ここでユーザーフックを使用して、最初のレンダリングを識別できます。

useRefフックを使用すると、関数型コンポーネントで可変変数を作成できます。これは、DOMノード/ React要素にアクセスし、再レンダリングをトリガーせずに可変変数を格納するのに役立ちます。

const [state, setState] = useState({ name: "Michael" });
const firstTimeRender = useRef(true);

useEffect(() => {
 if (!firstTimeRender.current) {
    console.log(state);
  }
}, [state])

useEffect(() => { 
  firstTimeRender.current = false 
}, [])

1

同じ問題が発生しました。セットアップでuseEffectを使用してもうまくいきませんでした(配列の複数の子コンポーネントから親の状態を更新しているので、どのコンポーネントがデータを更新したかを知る必要があります)。

setStateをpromiseでラップすると、完了後に任意のアクションをトリガーできます。

import React, {useState} from 'react'

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

  function handleClick(){
    Promise.resolve()
      .then(() => { setCount(count => count+1)})
      .then(() => console.log(count))
  }


  return (
    <button onClick= {handleClick}> Increase counter </button>
  )
}

export default App;

次の質問は私を正しい方向に導きましたフックを使用するときにReactバッチ状態更新機能はありますか?


0

あなたの質問は非常に有効です.useEffectはデフォルトで1回実行され、依存関係の配列が変更されるたびに実行されることをお伝えします。

以下の例を確認してください::

import React,{ useEffect, useState } from "react";

const App = () => {
  const [age, setAge] = useState(0);
  const [ageFlag, setAgeFlag] = useState(false);

  const updateAge = ()=>{
    setAgeFlag(false);
    setAge(age+1);
    setAgeFlag(true);
  };

  useEffect(() => {
    if(!ageFlag){
      console.log('effect called without change - by default');
    }
    else{
      console.log('effect called with change ');
    }
  }, [ageFlag,age]);

  return (
    <form>
      <h2>hooks demo effect.....</h2>
      {age}
      <button onClick={updateAge}>Text</button>
    </form>
  );
}

export default App;

setStateコールバックをフックで実行する場合は、フラグ変数を使用し、useEffect内にIF ELSEまたはIFブロックを指定して、その条件が満たされたときにそのコードブロックのみが実行されるようにします。ただし、依存関係の配列が変更されるとエフェクトが実行されますが、エフェクト内のIFコードはその特定の条件でのみ実行されます。


4
これは機能しません。updateAge内の3つのステートメントが実際にどの順序で機能するかはわかりません。3つすべてが非同期です。保証される唯一のことは、最初の行が3番目の前に実行されることです(同じ状態で動作するため)。2行目については何も知りません。この例は単純すぎてこれを見ることができません。
MohitSingh20年

私の友人はmohit。この手法は、reactクラスからフックに移行するときに、大規模で複雑なreactプロジェクトに実装しましたが、完全に機能します。setStateコールバックを置き換えるために、フックのどこかで同じロジックを試すだけで、わかります。
arjunsah20年

4
「私のプロジェクトでの作業は説明ではありません」、ドキュメントを読んでください。それらはまったく同期していません。updateAgeの3行がこの順序で機能するかどうかは定かではありません。同期していた場合は、フラグが必要です。setAgeの後にconsole.log()行を直接呼び出します。
MohitSingh20年

useRefは、「ageFlag」のはるかに優れたソリューションです。
Dr.Flink

0

状態が設定された後、いくつかのパラメーターを使用してAPI呼び出しを行いたいというユースケースがありました。これらのパラメータを状態として設定したくなかったので、カスタムフックを作成しました。これが私の解決策です。

import { useState, useCallback, useRef, useEffect } from 'react';
import _isFunction from 'lodash/isFunction';
import _noop from 'lodash/noop';

export const useStateWithCallback = initialState => {
  const [state, setState] = useState(initialState);
  const callbackRef = useRef(_noop);

  const handleStateChange = useCallback((updatedState, callback) => {
    setState(updatedState);
    if (_isFunction(callback)) callbackRef.current = callback;
  }, []);

  useEffect(() => {
    callbackRef.current();
    callbackRef.current = _noop; // to clear the callback after it is executed
  }, [state]);

  return [state, handleStateChange];
};

-3

UseEffectが主要なソリューションです。しかし、Darrylが述べたように、useEffectを使用し、2番目のパラメーターとして状態を渡すことには1つの欠陥があるため、コンポーネントは初期化プロセスで実行されます。更新された状態の値を使用してコールバック関数を実行するだけの場合は、ローカル定数を設定し、それをsetStateとコールバックの両方で使用できます。

const [counter, setCounter] = useState(0);

const doSomething = () => {
  const updatedNumber = 123;
  setCounter(updatedNumber);

  // now you can "do something" with updatedNumber and don't have to worry about the async nature of setState!
  console.log(updatedNumber);
}

5
setCounterは非同期であり、console.logがsetCounterの後または前に呼び出されるかどうかはわかりません。
MohitSingh20年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.