Accessing state from callback

﹥>﹥吖頭↗ 提交于 2021-02-10 17:53:06

问题


I am having trouble accessing the component state from a callback.

The value of the state num changes correctly, but such changes are not visible to the callback function defined at load time.

import React, {useState} from "react";

class MyObject {
  callback: (() => void);

  constructor(callback: () => void) {
    this.callback = callback;
  }
};

export const TestPage = () => {
  const [num, setNum] = useState<number>(0);

  // will print the changing values
  console.log(`num: ${num}`);

  const counter = useState<MyObject>(() => new MyObject(() => {
    // will always print 0
    console.log(`from callback. num: ${num}`);
  }))[0];

  const btnClick = () => {
    setNum((new Date()).getTime());
    counter.callback();
  };

  return (
      <div>Test, num: {num}
        <div>
          <button onClick={btnClick}>click to update state</button>
        </div>
      </div>
  );
}

the above doesn't happen with the plain non-React code as follows

  let myVar = 0;
  const obj = new MyObject(() => console.log(`myVar: ${myVar}`));
  obj.callback(); // prints 0
  myVar = 1;
  obj.callback(); // prints 1

edit: I've tried to reproduce the problem with closures, but I cannot

 const makeFun = (callback: (() => void)) => {
    return () => {
      callback();
    }
  }
  let myVar = 0;
  const f1 = makeFun(() => console.log(`myVar: ${myVar}`));
  myVar = 1;
  const f2 = makeFun(() => console.log(`myVar: ${myVar}`));
  f1(); // prints 1
  f2(); // prints 1

so I suspect is something specific to React and useState.

Any ideas?

edit#2: solution. this is how to make it visible in non-React code.

let callbackPersistent: (() => void);
const createCallback = (callback: (() => void)) => {
  if (!callbackPersistent) {
    callbackPersistent = callback;
  }
  return callbackPersistent;
}

const myFunction = (num:number) => {
  const callback = createCallback(() => console.log(`callback num: ${num}`));
  callback();
};

and then call it as

  myFunction(0); // prints 0
  myFunction(1); // prints 0

there's only one instance of the callback and it points to the value it saw once first created.

edit#3:

A reasonable solution can be found using useRef and useEffect, as follows:

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

class MyObject {
  callback: (() => void);

  constructor(callback: () => void) {
    this.callback = callback;
  }
};

export const TestPage = () => {
  const [num, setNum] = useState<number>(0);
  const numRef = useRef(num);

  // will print the changing values
  console.log(`num: ${num}`);

  const counter = useState<MyObject>(() => new MyObject(() => {
    // will always print the latest value
    console.log(`from callback. num: ${numRef.current}`);
  }))[0];

  useEffect(()=> {numRef.current = num}, [num]);

  const btnClick = () => {
    setNum(new Date().getTime());
    counter.callback();
  };

  return (
      <div>Test, num: {num}
        <div>
          <button onClick={btnClick}>click to update state</button>
        </div>
      </div>
  );
}

useRef guarantees that numRef is always the same object over multiple invocations. When the value of num changes the onEffect call will update the numRef.current value, which is then accesses by the the callback. Only one instance of the callback and of numRef exist, making sure the changes are correctly picked up


回答1:


Callback function has a closure over the num state and its value is zero because when callback was defined, num had a value of zero.

To fix the problem, you can use useEffect hook that creates a new MyObject with a new callback function that has a closure over the latest value of num.

useEffect(() => {
   // update counter state
}, [num]);

Edit

Reason why non-react code works as you expect is because functions close over the variables, not their values. So in the second code example, callback function passed to MyObject's constructor has a closure over the myVar variable. This means that if you change the value of myVar, callback function, when called, will log the latest value of myVar.

Now you might ask, if closures are over the variables and not their values, then shouldn't the callback function in React code see the latest value of num?

Reason why callback function always logs zero is because in React, callback functions see the value of state or prop that was available in the component at the time when that callback function was defined.


In my opinion, you shouldn't be using MyObject class at all. If you have some complex state, then i would suggest you to consider using useReducer hook for managing the state.



来源:https://stackoverflow.com/questions/64010671/accessing-state-from-callback

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!