Как использовать обратный вызов `setState` для реагирующих хуков

Хуки React вводят useState для установки состояния компонента. Но как я могу использовать хуки для замены обратного вызова, как показано ниже:

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

Я хочу что-то сделать после обновления состояния.

Я знаю, что могу использовать useEffect для выполнения дополнительных действий, но мне нужно проверить предыдущее значение состояния, для чего требуется битовый код. Я ищу простое решение, которое можно использовать с крючком useState.

в компоненте класса я использовал async и await для достижения того же результата, что и вы, чтобы добавить обратный вызов в setState. К сожалению, он не работает в хуке. Даже если я добавлю асинхронность и ожидание, реакция не будет ждать обновления состояния. Возможно, useEffect — единственный способ сделать это.

MING WU 22.05.2019 02:58

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

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

Ответы 20

Ответ принят как подходящий

Для этого вам нужно использовать крючок useEffect.

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

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

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

Это вызовет console.info при первом рендеринге, а также при каждом изменении counter. Что, если вы хотите сделать что-то только после того, как состояние было обновлено, но не при начальном рендеринге, поскольку установлено начальное значение? Я думаю, вы могли бы проверить значение в useEffect и решить, хотите ли вы что-то сделать. Будет ли это считаться лучшей практикой?

Darryl Young 06.06.2019 11:20

Чтобы избежать запуска useEffect при начальном рендеринге, вы можете создать нестандартный useEffect крючок, который не запускается при начальном рендеринге. Чтобы создать такой хук, вы можете проверить этот вопрос: stackoverflow.com/questions/53253940/…

user12200634 26.03.2020 17:24

А как насчет случая, когда я хочу вызывать разные обратные вызовы в разных вызовах setState, которые изменят одно и то же значение состояния? Ваш ответ неверен, и его не следует помечать как правильный, чтобы не сбивать с толку новичков. Правда в том, что setState вызывает одну из самых сложных проблем при миграции на хуки из классов, у которых нет одного четкого метода решения. Иногда вам действительно будет достаточно некоторого эффекта, зависящего от значения, а иногда потребуются некоторые хакерские методы, такие как сохранение каких-то флагов в ссылках.

Dmitry Lobov 03.06.2020 10:51

Судя по всему, сегодня я мог бы вызвать setCounter(value, callback), чтобы какая-то задача выполнялась после обновления состояния, избегая useEffect. Я не совсем уверен, является ли это предпочтением или особым преимуществом.

Ken Ingram 15.09.2020 17:40

@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().

Greg Sadetsky 30.09.2020 06:10

Ах. Хорошо. Это казалось простым.

Ken Ingram 30.09.2020 07:53
useEffect нельзя использовать во всех сценариях. иногда состояние обновляется из нескольких мест, но вы хотите использовать вызов только из одного из этих мест. Как легко отличить? Обратный вызов идеален в таких ситуациях. Взломом будет использование другого уродливого useState только для того, чтобы можно было обнаружить конкретное изменение. очень страшный...
vsync 06.12.2020 23:10

Согласитесь с @vsync , второй обратный вызов должен быть повторно введен обратно в useState хук. Это похоже на шаг назад от this.setState от компонента класса. :/

Kong 18.05.2021 15:15

Это не лучшая альтернатива тому, что может быть достигнуто с помощью обратного вызова, нам нужен только обратный вызов для определенного вызова setState, а не для каждого изменения состояния, и нет никакого способа определить это с помощью this.

Abhishek Choudhary 18.10.2021 06:22

Если вы хотите обновить предыдущее состояние, вы можете сделать это в хуках:

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


setCount(previousCount => previousCount + 1);

@BimalGrg Это действительно работает? Я не могу повторить это. Не компилируется с этой ошибкой: expected as assignment or function call and instead saw an expression

tonitone120 30.11.2020 20:30

@tonitone120 tonitone120 внутри setCount есть функция стрелки.

Bimal Grg 03.12.2020 04:09

@BimalGrg Вопрос заключается в возможности выполнения кода сразу после обновления состояния после. Действительно ли этот код помогает с этой задачей?

tonitone120 03.12.2020 14:20

Да, это будет работать, как в this.setState в компонентах класса,

Aashiq 12.12.2020 06:11

@tonitone120 tonitone120, если вы хотите выполнить код сразу после обновления состояния, вам нужно использовать хук useEffect. В useEffect вы можете проверить, обновлено ли состояние или нет, и выполнить какое-либо действие соответственно.

Bimal Grg 16.12.2020 04:12

Почему я нигде не могу найти документацию по этому шаблону?

Drazen Bjelovuk 17.02.2022 23:38

UseEffect является основным решением. Но, как упомянул Дэррил, использование useEffect и передача состояния в качестве второго параметра имеет один недостаток: компонент будет работать в процессе инициализации. Если вы просто хотите, чтобы функция обратного вызова выполнялась с использованием обновленного значения состояния, вы можете установить локальную константу и использовать ее как в 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.info(updatedNumber);
}

setCounter является асинхронным, вы не знаете, будет ли вызываться console.info после setCounter или раньше.

Mohit Singh 30.03.2020 09:53

Ваш вопрос очень актуален. Позвольте мне сказать вам, что useEffect запускается один раз по умолчанию и после каждого изменения массива зависимостей.

проверьте пример ниже::

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.info('effect called without change - by default');
    }
    else{
      console.info('effect called with change ');
    }
  }, [ageFlag,age]);

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

export default App;

Если вы хотите, чтобы обратный вызов setState выполнялся с хуками, используйте переменную флага и задайте блок IF ELSE OR IF внутри useEffect, чтобы при выполнении этих условий выполнялся только этот блок кода. В любом случае эффект запускается при изменении массива зависимостей, но этот внутренний код IF будет выполняться только при этих конкретных условиях.

Это не сработает. Вы не знаете, в каком порядке будут работать три оператора внутри updateAge. Все три асинхронны. Единственное, что гарантируется, это то, что первая строка выполняется до 3-й (поскольку они работают в одном и том же состоянии). Вы ничего не знаете о 2-й линии. Этот пример слишком прост, чтобы увидеть это.

Mohit Singh 30.03.2020 09:51

Мой друг Мохит. Я применил эту технику в большом сложном проекте реагирования, когда переходил от классов реагирования к хукам, и он отлично работает. Просто попробуйте ту же логику где угодно в хуках для замены обратного вызова setState, и вы все поймете.

arjun sah 19.04.2020 22:25

«работает в моем проекте — это не объяснение», читайте документы. Они вообще не синхронны. Нельзя сказать наверняка, что три строки в updateAge будут работать в таком порядке. Если это было синхронизировано, то зачем нужен флаг, напрямую вызывайте строку console.info() после setAge.

Mohit Singh 20.04.2020 06:56

useRef — гораздо лучшее решение для «ageFlag».

Dr.Flink 26.05.2020 08:58

Имитация обратного вызова setState с помощью useEffect, срабатывает только в состоянии обновления (не начальное состояние):

const [state, setState] = useState({ name: "Michael" })
const isFirstRender = useRef(true)
useEffect(() => {
  if (isFirstRender.current) {
    isFirstRender.current = false // toggle flag after first render/mounting
    return;
  }
  console.info(state) // do something after state has updated
}, [state])

Пользовательский крючок useEffectUpdate

function useEffectUpdate(callback) {
  const isFirstRender = useRef(true);
  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false; // toggle flag after first render/mounting
      return;
    }
    callback(); // performing action after state has updated
  }, [callback]);
}

// client usage, given some state dep
const cb = useCallback(() => { console.info(state) }, [state]); // memoize callback
useEffectUpdate(cb);

Альтернатива: useStateможет быть реализован для получения обратного вызова типа setState в классах.

ford04 11.09.2020 09:55

Я думаю, что использование useEffect не является интуитивным способом.

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

Я только что создал версию Typescript. Поэтому, если вам нужно использовать это в Javascript, просто удалите некоторые обозначения типов из кода.

Применение

const [state, setState] = useStateCallback(1);
setState(2, (n) => {
  console.info(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.info(состояние) напечатает 1, а не 2.

Спасибо! классный подход. Создан образец кодыпесочница

ValYouW 30.08.2020 15:05

@ValYouW Спасибо за создание образца. Дело только в том, что если переданная стрелочная функция ссылается на переменную внешнюю функцию, то она будет захватывать текущие значения, а не значения после обновления состояния.

MJ Studio 30.08.2020 18:10

Да, это броская часть с функциональными компонентами...

ValYouW 31.08.2020 00:45

Как здесь получить предыдущее значение состояния?

Ankan-Zerob 27.09.2020 14:15

@ Ankan-Zerob Я думаю, что в части «Использование» state ссылается на предыдущий, когда вызывается обратный вызов. Не так ли?

MJ Studio 29.09.2020 14:54

@MJStudio Привет, MJ, спасибо за ваш ответ, в основном он правильный, но я не могу указать конкретный ключ в вашем примере. Просто попробуйте setState({name: 'Bob'}, (n) => {console.info(n.name)}). Если быть более точным, я получу здесь ошибку n.name, как это можно исправить?

Andrew 23.12.2020 13:55

@Andrew Это ошибка TypeScript? В моем случае const [s, setS] = useStateCallback({ name: 'mj' }); useEffect(() => { setS({ name: 'Bob' }, (n) => { console.info(n.name); }); }, [setS]); хорошо работает как печать Bob не mj но TypeScript вызывает ошибку, которая может быть n undefined

MJ Studio 26.12.2020 08:57
const [state, setState] = useStateCallback(1);setState(2, (n) => {console.info(n, state) });почему в приведенном выше примере: n равно 2, но состояние по-прежнему равно 1, и в любом случае это состояние также может печатать 2?
Jochen Shi 21.12.2021 04:27

setState() ставит в очередь изменения состояния компонента и сообщает React, что этот компонент и его дочерние элементы необходимо повторно отобразить с обновленным состоянием.

Метод setState является асинхронным и фактически не возвращает промис. Итак, в случаях, когда мы хотим обновить или вызвать функцию, функция может быть вызвана обратным вызовом в функции setState в качестве второго аргумента. Например, в приведенном выше случае вы вызвали функцию как обратный вызов setState.

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

Приведенный выше код отлично работает для компонента класса, но в случае функционального компонента мы не можем использовать метод setState, и мы можем использовать хук эффекта использования для достижения того же результата.

Очевидный метод, который приходит на ум, заключается в том, что ypu может использовать с useEffect, как показано ниже:

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

useEffect(() => {
  console.info(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.info(state);
  }
}, [state])

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

это мне очень помогает, спасибо. Я хотел бы добавить кое-что, потому что поначалу это давало мне сбои, порядок функций useEffect очень важен. обратите внимание, что вы должны сначала написать useEffect с зависимостями, а затем «componentDidMount» без зависимостей. то есть как в примере. Спасибо еще раз.

Carlos Martínez 27.01.2021 05:54

У меня был вариант использования, когда я хотел создать вызов 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];
};

разве он не выдает ошибку, когда не предоставляется обратный вызов?

adir abargil 07.03.2021 16:11

Я столкнулся с той же проблемой, использование useEffect в моей настройке не помогло (я обновляю родительское состояние из массива нескольких дочерних компонентов, и мне нужно знать, какой компонент обновил данные).

Обертывание setState в промис позволяет запускать произвольное действие после завершения:

import React, {useState} from 'react'

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

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


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

export default App;

Следующий вопрос поставил меня в правильном направлении: Функционирует ли пакетное обновление React при использовании хуков?

setCount асинхронный, верно? Если это так, возникнет состояние гонки, и console.info может напечатать старое значение.
Aaron 07.04.2021 21:11

Как насчет этого:

const [Name, setName] = useState("");
...
onClick = {()=>{
setName("Michael")
setName(prevName=>{...}) //prevName is Michael?
}}

prevName не будет "Майкл", поскольку useState является асинхронным, как и setState в компонентах класса. Вы не можете обновить состояние в одной строке и предположить, что оно уже изменилось в следующей. Скорее всего, вы будете использовать неизменное состояние.
Zsolt Meszaros 14.01.2021 10:31

Хорошо, спасибо, я только что проверил этот код, вы правы,

james h 14.01.2021 10:35

Это странно, prevName — это Michael, но если вы вызываете другую функцию в обратном вызове, которая использует имя, оно все равно не обновляется.

james h 14.01.2021 10:45

вы можете использовать следующие способы, которые я знал, чтобы получить последнее состояние после обновления:

  1. использоватьЭффект
    https://reactjs.org/docs/hooks-reference.html#useeffect
    const [state, setState] = useState({name: "Michael"});
    
    const handleChangeEventName = () => {
      setState({name: "Jack"});
    }
    
    useEffect(() => {
      console.info(state.name); //"Jack"

      //do something here
    }, [state]);
  1. функциональное обновление
    https://reactjs.org/docs/hooks-reference.html#functional-updates
    «Если новое состояние вычисляется с использованием предыдущего состояния, вы можете передать функцию в setState. Функция получит предыдущее значение и вернет обновленное значение».
    const [state, setState] = useState({name: "Michael"});

    const handleChangeEventName = () => {
      setState({name: "Jack"})
      setState(prevState => {
        console.info(prevState.name);//"Jack"

        //do something here

        // return updated state
        return prevState;
      });
    }
  1. использоватьRef
    https://reactjs.org/docs/hooks-reference.html#useref
    «Возвращенный объект ref будет сохраняться в течение всего срока службы компонента».
    const [state, setState] = useState({name: "Michael"});

    const stateRef = useRef(state);
    stateRef.current  = state;
    const handleClick = () => {
      setState({name: "Jack"});

      setTimeout(() => {
        //it refers to old state object
        console.info(state.name);// "Michael";

        //out of syntheticEvent and after batch update
        console.info(stateRef.current.name);//"Jack"

        //do something here
      }, 0);
    }

В обработчике react syntheticEvent setState — это процесс пакетного обновления, поэтому каждое изменение состояния будет ожидаться и возвращать новое состояние. «setState() не всегда сразу обновляет компонент. Он может пакетно обновлять или откладывать обновление на потом»,
https://reactjs.org/docs/react-component.html#setstate

Вот полезная ссылка
Сохраняет ли React порядок обновления состояния?

Мы можем написать хук с именем useScheduleNextRenderCallback, который возвращает функцию «расписание». После вызова setState мы можем вызвать функцию «расписание», передав обратный вызов, который мы хотим запустить при следующем рендеринге.

import { useCallback, useEffect, useRef } from "react";

type ScheduledCallback = () => void;
export const useScheduleNextRenderCallback = () => {
  const ref = useRef<ScheduledCallback>();

  useEffect(() => {
    if (ref.current !== undefined) {
      ref.current();
      ref.current = undefined;
    }
  });

  const schedule = useCallback((fn: ScheduledCallback) => {
    ref.current = fn;
  }, []);

  return schedule;
};

Пример использования:

const App = () => {
  const scheduleNextRenderCallback = useScheduleNextRenderCallback();

  const [state, setState] = useState(0);

  const onClick = useCallback(() => {
    setState(state => state + 1);
    scheduleNextRenderCallback(() => {
      console.info("next render");
    });
  }, []);

  return <button onClick = {onClick}>click me to update state</button>;
};

Сокращенный тестовый пример: https://stackblitz.com/edit/react-ts-rjd9jk

Я написал кастомный хук с машинописным текстом, если он еще кому-то нужен.

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

export const useStateWithCallback = <T>(initialState: T): [state: T, setState: (updatedState: React.SetStateAction<T>, callback?: (updatedState: T) => void) => void] => {
    const [state, setState] = useState<T>(initialState);
    const callbackRef = useRef<(updated: T) => void>();

    const handleSetState = (updatedState: React.SetStateAction<T>, callback?: (updatedState: T) => void) => {
        callbackRef.current = callback;
        setState(updatedState);
    };

    useEffect(() => {
        if (typeof callbackRef.current === "function") {
            callbackRef.current(state);
            callbackRef.current = undefined;
        }
    }, [state]);

    return [state, handleSetState];
}

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

Robert Rendell 19.09.2021 03:35

С помощью всех вас я смог создать этот пользовательский хук:

Очень похоже на класс this.setState(state, callback)

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

  const setStateCB = (newState, callback) => {
    callbackRef.current = callback;
    setState(newState);
  };

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

  return [state, setStateCB];
};

Таким образом, мы можем использовать его как..

const [isVisible, setIsVisible] = useStateWithCallback(false);

...

setIsVisible(true, () => console.info('callback called now!! =)');

Сохраняйте спокойствие и счастливого кодирования!

Это не работает, если вам нужно использовать новое значение состояния в обратном вызове. Например, если вы измените свой обратный вызов на () => console.info('the new value of isVisible = ' + isVisible), он отобразит старое значение.

Johnny Tisdale 28.06.2021 21:10

Вы уверены? Потому что обратный вызов вызывается только тогда, когда состояние действительно изменилось.

sugaith 29.06.2021 22:42

Да по крайней мере в моем сценарии. Я должен упомянуть, что я использую React в рамках Ionic, поэтому, возможно, из-за этого поведение немного отличается.

Johnny Tisdale 01.07.2021 18:55

Имеет ли приведенный выше ответ @Geoman такой же эффект в вашей ионной среде? Должен отображать тот же эффект, что и этот ответ здесь. Почти то же самое, если вы удалите машинописную часть и другие незначительные отличия.

sugaith 02.07.2021 00:16

Между прочим: это было протестировано как в React Native, так и в стандартном React.js.

sugaith 02.07.2021 00:52

Я не думаю, что различать смонтированное или нет с помощью useRef - это хороший способ, не лучше ли определить значение, сгенерированное useState() в useEffect(), является ли оно начальным значением?

const [val, setVal] = useState(null)

useEffect(() => {
  if (val === null) return
  console.info('not mounted, val updated', val)
}, [val])

Пока у нас нет встроенной поддержки обратного вызова setState, мы можем сделать простой способ javascript... вызвать функцию и напрямую передать ей новые переменные.

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

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

  function doSomethingWCounter(newCounter) {
    console.info(newCounter); // 123
  }

Не гарантируется, что doSomethingWCounter будет вызван после завершения setState.

Design by Adrian 27.01.2022 17:25

Простое решение, просто установите

npm i use-state-with-callback

import React from 'react';
import { useStateWithCallbackLazy } from "use-state-with-callback";

const initialFilters = {
  smart_filter: "",
};

const MyCallBackComp = () => {
  const [filters, setFilters] = useStateWithCallbackLazy(initialFilters);

  const filterSearchHandle = (e) => {
    setFilters(
      {
        ...filters,
        smart_filter: e,
      },
      (value) => console.info("smartFilters:>", value)
    );
  };

  return (
    <Input
      type = "text"
      onChange = {(e) => filterSearchHandle(e.target.value)}
      name = "filter"
      placeholder = "Search any thing..."
    />
  );
};

зачислено: РЕАКТИВНЫЙ ОБРАТНЫЙ ВЫЗОВ USESTATE

Если вам не нужно обновлять состояние асинхронно, вы можете использовать ссылку для сохранения значения вместо useState.

const name = useRef("John");
name.current = "Michael";
console.info(name.current); // will print "Michael" since updating the ref is not async

Я думаю, вам нужны useState и useCallback:

Пример кода

import React, { useCallback, useState } from 'react';

const Test = () => {
  const [name, setName] = useState("");
  const testCallback = useCallback(() => console.info(name), [name]);

  return (
    <button onClick = {() => {
      setName("Michael")
      testCallback();
    }}>Name</button>
  )
};

export default Test;

Я изучил npm-библиотеку use-state-with-callback и другие подобные пользовательские хуки, но в конце концов понял, что могу просто сделать что-то вроде этого:

const [user, setUser] = React.useState(
  {firstName: 'joe', lastName: 'schmo'}
)

const handleFirstNameChange=(val)=> {
  const updatedUser = {
     ...user,
     firstName: val
  }
  setUser(updatedUser)
  updateDatabase(updatedUser)
}

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