admin管理员组

文章数量:1400572

In this simple example, I am memoizing Child ponent using useMemo and passing callback back to parent function to add data to react hook array, which is initially set as empty.

My question is as follows: Why hook data never changes and keeps it's initial state in callback function that es from Child ponent which is memoized. However, when checking data hook in useEffect method or using that hook in rendering - it always has the newest state.

It is more of a general question what happens behind the hood and what is the best way, for example, to check against duplicate values in callback functions from memoized child ponents if data always has initial state.

I am aware of useReducer and that I can manipulate data in hooks setter method before adding it, but maybe there are other methods?

Parent ponent:

import React, {useEffect, useState} from 'react';
import Child from "./Child";

function App() {

const [data, setData] = useState([]);

useEffect(()=>{
    console.log("UseEffect", data)
},[data]);

return (
<div>
  <Child callback={(val)=>{
      console.log("Always returns initial state", data);  // <----------Why?
      setData(old=>{
          console.log("Return previous state", old);
            return [...old, val]
      })
  }} />

   Length: {data.length /*Always gets updated*/}
</div>
);
}

export default App;

Child ponent: In real scenario it is a map, that I want to render only once, without causing re-renders.

import React, {useMemo} from "react"

export default function Child({callback}) {
return useMemo(()=>{
    return <button onClick={()=>callback(1)}>
        Press!
    </button>
},[])
}

In this simple example, I am memoizing Child ponent using useMemo and passing callback back to parent function to add data to react hook array, which is initially set as empty.

My question is as follows: Why hook data never changes and keeps it's initial state in callback function that es from Child ponent which is memoized. However, when checking data hook in useEffect method or using that hook in rendering - it always has the newest state.

It is more of a general question what happens behind the hood and what is the best way, for example, to check against duplicate values in callback functions from memoized child ponents if data always has initial state.

I am aware of useReducer and that I can manipulate data in hooks setter method before adding it, but maybe there are other methods?

Parent ponent:

import React, {useEffect, useState} from 'react';
import Child from "./Child";

function App() {

const [data, setData] = useState([]);

useEffect(()=>{
    console.log("UseEffect", data)
},[data]);

return (
<div>
  <Child callback={(val)=>{
      console.log("Always returns initial state", data);  // <----------Why?
      setData(old=>{
          console.log("Return previous state", old);
            return [...old, val]
      })
  }} />

   Length: {data.length /*Always gets updated*/}
</div>
);
}

export default App;

Child ponent: In real scenario it is a map, that I want to render only once, without causing re-renders.

import React, {useMemo} from "react"

export default function Child({callback}) {
return useMemo(()=>{
    return <button onClick={()=>callback(1)}>
        Press!
    </button>
},[])
}
Share Improve this question edited May 18, 2020 at 20:42 Benas.M asked May 18, 2020 at 20:10 Benas.MBenas.M 3405 silver badges14 bronze badges 4
  • Why do you expect updated value before setData? – Bhojendra Rauniyar Commented May 18, 2020 at 20:18
  • I don't expect updated value before setData, but it never changes even after multiple callbacks received, which means that setData was already called before, but hook didn't change – Benas.M Commented May 18, 2020 at 20:22
  • 2 If I understand it correctly, you need to pass data as props and use it in your second argument of useMemo. – Bhojendra Rauniyar Commented May 18, 2020 at 20:26
  • 1 setData doesn't mutate original data array, it return a new array reference. you are not passing down new data reference, given you use memo. once you use memo, you have the first data reference, and it keeps that way forever. – buzatto Commented May 18, 2020 at 20:31
Add a ment  | 

2 Answers 2

Reset to default 4

Is there a way to return new reference of callback, without adding dependencies in useMemo method

Like this

// returns *(always the same)* function that will forward the call to the latest passed `callback`
function useFunction(callback) {
  const ref = React.useRef();
  ref.current = callback;

  return React.useCallback(function() {
    const callback = ref.current;
    if (typeof callback === "function") {
      return callback.apply(this, arguments);
    }
  }, []);
}

and then:

export function Child({ callback }) {
  const handler = useFunction(callback);

  return useMemo(() => {
    return <button onClick={() => handler(1)}>Press!</button>;
  }, []);
}

or

function App() {
  const [data, setData] = useState([]);

  const callback = useFunction((val) => {
    console.log("Always returns the latest state", data);
    setData([...data, val]);
  });

  return (
    <div>
      <Child callback={callback} />

     Length: {data.length /*Always gets updated*/}
    </div>
  );
}

function Child({ callback }) {
  return useMemo(() => {
    return <button onClick={() => callback(1)}>
      Press!
      </button>
  }, [])
}

As you said, your Child ponent is memoized, which means it will only update if one of its dependency changes. So at the first rendering, you create your Child ponent with the function you pass to the callback prop, and at this time, data is []. If you click on the button, data is updated correctly and so is the function passed to callback prop to Child, but since you did not set any dependency to useMemo, your Child ponent will not update and will still return its memoized version, with first callback prop it received, which indeed always log the initial value of data: [].

So all you need is to add callback to the list of dependencies of useMemo:

export function Child({ callback }) {
  return useMemo(() => {
    return <button onClick={() => callback(1)}>Press!</button>;
  }, [callback]);
}

This way, when the callback prop changes, your Child ponent will also update its onClick event handler.

Also, I remend using eslint-plugin-react npm package, which will instantly warn you about missing dependencies in React hooks, and more generally about bad practices in your code.

本文标签: javascriptHook39s useState initial value never changes in callback that is memoizedStack Overflow