Как использовать обратный вызов с хуком useState в реакции

Я использую функциональный компонент с хуками. Мне нужно обновить состояние родителя от ребенка. Я использую функцию поддержки в Parent. Все работает нормально, за исключением того, что моя функция поддержки получает предыдущее состояние, а не текущее состояние. Моя функция поддержки выполняется до того, как хук использование состояния устанавливает текущее состояние. Как я могу дождаться выполнения моей функции обратного вызова после вызова useState. Я ищу что-то вроде setState (состояние, обратный вызов) из компонентов на основе классов.

Вот фрагмент кода:

function Parent() {
  const [Name, setName] = useState("");
  getChildChange = getChildChange.bind(this);
  function getChildChange(value) {
    setName(value);
  }

  return <div> {Name} :
    <Child getChildChange = {getChildChange} ></Child>
  </div>
}

function Child(props) {
  const [Name, setName] = useState("");
  handleChangeEvent = handleChangeEvent.bind(this);

  function handleChangeEvent(ele) {
    setName(ele.target.value);
    props.getChildChange(collectState());
  }

  function collectState() {
    return Name;
  }

  return (<div>
    <input onChange = {handleChangeEvent} value = {Name}></input>
  </div>);
} 

почему бы вам просто не передать setName и не назвать это от ребенка?

dan-klasson 02.03.2019 04:41

Я надеюсь, что мы получим интересные комментарии в этой теме github.com/facebook/реагировать/вопросы/17969

RajaSekhar K 19.04.2020 05:40

Есть простой способ сделать это без useEffect stackoverflow.com/a/70405577/5823517

Tunn 18.12.2021 18:37
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
160
3
256 645
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

function Parent() {
  const [Name, setName] = useState("");
  getChildChange = getChildChange.bind(this);
  function getChildChange(value) {
    setName(value);
  }

  return <div> {Name} :
    <Child getChildChange = {getChildChange} ></Child>
  </div>
}

function Child(props) {
  const [Name, setName] = useState("");
  handleChangeEvent = handleChangeEvent.bind(this);
  collectState = collectState.bind(this);
  
  function handleChangeEvent(ele) {
    setName(ele.target.value);
  }

  function collectState() {
    return Name;
  }
  
   useEffect(() => {
    props.getChildChange(collectState());
   });

  return (<div>
    <input onChange = {handleChangeEvent} value = {Name}></input>
  </div>);
} 

useEffect действовать как componentDidMount, componentDidUpdate, поэтому после обновления состояния он будет работать

На самом деле вам следует избегать использования this при использовании реагирующих хуков. Он вызывает побочные эффекты. Вот почему команда реагирования создает react hooks.

Если вы удалите коды, которые пытаются связать this, вы можете просто передать setName из Parent в Child и вызвать его handleChangeEvent. Более чистый код!

function Parent() {
  const [Name, setName] = useState("");

  return <div> {Name} :
    <Child setName = {setName} ></Child>
  </div>
}

function Child(props) {
  const [Name, setName] = useState("");

  function handleChangeEvent(ele) {
    setName(ele.target.value);
    props.setName(ele.target.value);
  }

  return (<div>
    <input onChange = {handleChangeEvent} value = {Name}></input>
  </div>);
} 

Более того, вам не нужно создавать две копии Name (одну в Parent, а другую в Child). Придерживайтесь принципа «Единого источника правды», Child не должен владеть государством Name, но должен получать его от Parent. Узел чище!

function Parent() {
  const [Name, setName] = useState("");

  return <div> {Name} :
    <Child setName = {setName} Name = {Name}></Child>
  </div>
}

function Child(props) {    
  function handleChangeEvent(ele) {
    props.setName(ele.target.value);
  }

  return (<div>
    <input onChange = {handleChangeEvent} value = {props.Name}></input>
  </div>);
} 
Ответ принят как подходящий

Для этого вы можете использовать useEffect/useLayoutEffect:

const SomeComponent = () => {
  const [count, setCount] = React.useState(0)

  React.useEffect(() => {
    if (count > 1) {
      document.title = 'Threshold of over 1 reached.';
    } else {
      document.title = 'No threshold reached.';
    }
  }, [count]);

  return (
    <div>
      <p>{count}</p>

      <button type = "button" onClick = {() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
};

Подробнее об этом в здесь.

Если вы ищете готовое решение, обратите внимание на этот пользовательский крючок, который работает как useState, но принимает в качестве второго параметра функцию обратного вызова:

// npm install use-state-with-callback

import useStateWithCallback from 'use-state-with-callback';

const SomeOtherComponent = () => {
  const [count, setCount] = useStateWithCallback(0, count => {
    if (count > 1) {
      document.title = 'Threshold of over 1 reached.';
    } else {
      document.title = 'No threshold reached.';
    }
  });

  return (
    <div>
      <p>{count}</p>

      <button type = "button" onClick = {() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
};

Я пытаюсь установить использование состояния с обратным вызовом, но это не работает. Это дает ошибку. Что я могу сделать?

Efe FRK 29.04.2021 19:13

действительно хороший подход. Я нашел это чрезвычайно полезным

Rafael 19.06.2021 22:09

В React16.x и более поздних версиях, если вы хотите вызвать функцию обратного вызова при изменении состояния с помощью хука useState, вы можете использовать хук useEffect, прикрепленный к изменению состояния.

import React, { useEffect } from "react";

useEffect(() => {
  props.getChildChange(name); // using camelCase for variable name is recommended.
}, [name]); // this will call getChildChange when ever name changes.

Что делать, если функций больше одной и только одна из них должна работать в повторе?

Gucal 19.01.2021 11:29

@Gucal, вы можете использовать useEffect несколько раз, например useEffect(() => loadFunctionAOnce()). useEffect(() => loadFunctionBIfNameChange(), [name])

DAMIEN JIANG 03.05.2021 23:21

Хм супер. Спасибо @DAMIENJIANG :)

Gucal 04.05.2021 11:45

Это также запустит props.getChildChange при начальном рендеринге.

Coderboi 15.09.2021 09:45

мы можем написать функцию настройки, которая будет вызывать функцию callBack, если какие-либо изменения в состоянии

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

const useStateCallbackWrapper = (initilValue, callBack) => {
  const [state, setState] = useState(initilValue);
  useEffect(() => callBack(state), [state]);
  return [state, setState];
};

const callBack = state => {
  console.info("---------------", state);
};
function App() {
  const [count, setCount] = useStateCallbackWrapper(0, callBack);
  return (
    <div className = "App">
      <h1>{count}</h1>
      <button onClick = {() => setCount(count + 1)}>+</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

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

`

Это решение не работает при производственной сборке с React Hook useEffect has a missing dependency: 'callBack'. Either include it or remove the dependency array. If 'callBack' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps

Pablo Albaladejo 07.02.2020 17:30

попробуйте сохранить строку useEffect, например useEffect(() => callBack?callBack(state):null, [state, callBack]);

RajaSekhar K 18.04.2020 21:11

Другой способ добиться этого:

const [Name, setName] = useState({val:"", callback: null});
React.useEffect(()=>{
  console.info(Name)
  const {callback} = Name;
  callback && callback();
}, [Name]);
setName({val:'foo', callback: ()=>setName({val: 'then bar'})})

это как-то аккуратно. Таким образом, последовательность выполнения будет зависеть от того, как вы установите значения ключей? сначала val, затем обратный вызов?

Nick Chan Abdullah 22.02.2021 09:56

вы можете использовать хук использоватьОбратный звонок для этого.

function Parent() {
  const [name, setName] = useState("");
  const getChildChange = useCallback( (updatedName) => {
    setName(updatedName);
  }, []);

  return <div> {name} :
    <Child getChildChange = {getChildChange} ></Child>
  </div>
}

function Child(props) {
  const [name, setName] = useState("");

  function handleChangeEvent(ele) {
    setName(ele.target.value);
    props.getChildChange(ele.target.value);
  }

  function collectState() {
    return name;
  }

  return (<div>
    <input onChange = {handleChangeEvent} value = {name}></input>
  </div>);
}

Это был ответ, который я искал, спасибо!

RowanX 15.08.2020 01:24

Установка состояния в двух компонентах для одной и той же переменной не кажется мне хорошей идеей.

Isaac Pak 28.08.2020 22:44
useState Hook doesn't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect(). Это то, что я получаю после ввода обратного вызова
Hidayt Rahman 07.06.2021 07:36

Привет, @dishwasherWithProgrammingSkill, для чего используется этот код? что отличается от встроенного setState следующим образом: <Child getChildChange = {(value) => setValue(value)} ></Child>

tmohammad78 26.11.2021 20:28

setState(updater, callback) за useState

Следующая реализация очень близка к исходному обратному вызову классов setState.

Улучшения, внесенные в принятый ответ:

  1. Выполнение обратного вызова опущено при начальном рендеринге — мы хотим вызывать его только в состоянии обновления.
  2. Обратный вызов может быть динамическим для каждого вызова setState, как с классами.

Применение

const App = () => {
  const [state, setState] = useStateCallback(0); // same API as useState

  const handleClick = () => {
    setState(
      prev => prev + 1,
      // second argument is callback, `s` being the *updated* state
      s => console.info("I am called after setState, state:", s)
    );
  };

  return <button onClick = {handleClick}>Increment</button>;
}

useStateCallback

function useStateCallback(initialState) {
  const [state, setState] = useState(initialState);
  const cbRef = useRef(null); // init mutable ref container for callbacks

  const setStateCallback = useCallback((state, cb) => {
    cbRef.current = cb; // store current, passed callback in ref
    setState(state);
  }, []); // keep object reference stable, exactly like `useState`

  useEffect(() => {
    // cb.current is `null` on initial render, 
    // so we only invoke callback on state *updates*
    if (cbRef.current) {
      cbRef.current(state);
      cbRef.current = null; // reset callback after execution
    }
  }, [state]);

  return [state, setStateCallback];
}

Дополнительная информация: React Hooks FAQ: есть ли что-то вроде переменных экземпляра?

Рабочий пример

const App = () => {
  const [state, setState] = useStateCallback(0);

  const handleClick = () =>
    setState(
      prev => prev + 1,
      // important: use `s`, not the stale/old closure value `state`
      s => console.info("I am called after setState, state:", s)
    );

  return (
    <div>
      <p>Hello Comp. State: {state} </p>
      <button onClick = {handleClick}>Click me</button>
    </div>
  );
}

function useStateCallback(initialState) {
  const [state, setState] = useState(initialState);
  const cbRef = useRef(null);

  const setStateCallback = useCallback((state, cb) => {
    cbRef.current = cb; 
    setState(state);
  }, []);

  useEffect(() => {
    if (cbRef.current) {
      cbRef.current(state);
      cbRef.current = null;
    }
  }, [state]);

  return [state, setStateCallback];
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity = "sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4 = " crossorigin = "anonymous"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity = "sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w = " crossorigin = "anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id = "root"></div>

что делает cbRef.current(state); в этом коде внутри условного оператора в useEffect?

bot19 17.06.2021 03:50

@ bot19 это фактический вызов обратного вызова, который ранее был установлен через setState(..,cb). cbRef.current хранит функцию. Затем эта функция вызывается — (state) — с текущим обновленным состоянием.

ford04 17.06.2021 06:26

Проблема с этим фрагментом заключается в том, что он срабатывает не будет, если состояние идентично. Это никогда не проблема с компонентами класса, потому что вы всегда создаете новый объект состояния. codeandbox.io/s/thirsty-tamas-7cqbp?file=/src/App.js

dwjohnston 06.09.2021 05:01

Альтернативное решение: codeandbox.io/s/дерзкий-мендель-tqh58?file=/src/App.js

dwjohnston 06.09.2021 05:07

@dwjohnston спасение от обновлений состояния в случае идентичных значений — это новое значение React по умолчанию для хуков, поэтому в большинстве случаев я бы не стал менять это поведение. Если вам действительно нужно использовать старое сравнение на основе классов по устаревшим причинам (которое ведет себя таким образом из-за слияния объектов), подход codeandbox выглядит разумным! Вместо использования Symbol вы могли бы каждый раз помещать значение состояния в новый контейнер объекта.

ford04 08.09.2021 14:00

@ford04 Это хорошее решение, но это не 100% замена оригинала this.setState. 1) вы вызываете обратный вызов только тогда, когда состояние действительно меняется, исходный вызывал его всегда this.setState(prevState => prevState, () => console.info('Changed')); 2) Вы поддерживаете только 1 обратный вызов в то время, последний. Должен быть массив обратных вызовов. Вы можете вызвать свой setState дважды с разными обратными вызовами. Самое простое решение — использовать массив обратных вызовов и, возможно, использовать только этот массив в качестве зависимостей в useEffect — useEffect(..., [cbRefs.current]

Petr Újezdský 13.09.2021 19:33
codeandbox.io/s/gifted-dhawan-hedsp?file=/src/App.tsx — я использовал массив и добавил параметр текущего состояния в обратный вызов. Также есть пример с двойным обновлением. Надеюсь кому-нибудь поможет :)
Petr Újezdský 13.09.2021 20:43

@PetrÚjezdský спасибо за ваши идеи! К 1: Думаю, этот комментарий подходит. Re 2: Если вы вызываете setState дважды во время цикла рендеринга такой же и одного и того же экземпляра хука, последнее значение выигрывает в React. Поэтому я ожидаю такого же поведения при установке обратного вызова и скорее запутаюсь, если будут вызваны оба старых а также нового обратного вызова. И то, и другое в любом случае кажется скорее крайним случаем - скорее всего, у вас будет обработчик событий, в котором состояние настройки выполняется в разных рендерах.

ford04 18.09.2021 13:27

Подумайте о том, чтобы сделать это пакетом npm!

Ott 11.11.2021 18:42

Другие вопросы по теме