最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Hooks useCallback keeps using old values when refreshing list - Stack Overflow

programmeradmin3浏览0评论

What is wrong with the useCallback below that I do not get the values below every time the function onRefresh is called ? How can I make it return the expected values using Hooks?

Example when I call onRefresh 2x

values expected:

true
0
20
false

true
0
20
false

values: received

false
0
0
false

false
20
20
false

initialization of the state variables

const [refreshing, setRefreshing] = useState(false)
const [offsetQuestions, setOffsetQuestions] = useState(0)

Function call with useCallback:

const fetchSomeData = async () => {
  await new Promise(resolve => setTimeout(resolve, 3000)) // 3 sec
}

const onRefresh = useCallback( async () => {
  setRefreshing(true)
  setOffsetQuestions(0)
  console.log(refreshing)
  console.log(offsetQuestions)

  await fetchSomeData()

  setOffsetQuestions(20)
  setRefreshing(false)
  console.log(offsetQuestions)
  console.log(refreshing)
}, [refreshing, offsetQuestions])

Where the function is called:

<FlatList
   data={questionsLocal}
   refreshing={refreshing}
   onRefresh={onRefresh}
   ...
/>

What is wrong with the useCallback below that I do not get the values below every time the function onRefresh is called ? How can I make it return the expected values using Hooks?

Example when I call onRefresh 2x

values expected:

true
0
20
false

true
0
20
false

values: received

false
0
0
false

false
20
20
false

initialization of the state variables

const [refreshing, setRefreshing] = useState(false)
const [offsetQuestions, setOffsetQuestions] = useState(0)

Function call with useCallback:

const fetchSomeData = async () => {
  await new Promise(resolve => setTimeout(resolve, 3000)) // 3 sec
}

const onRefresh = useCallback( async () => {
  setRefreshing(true)
  setOffsetQuestions(0)
  console.log(refreshing)
  console.log(offsetQuestions)

  await fetchSomeData()

  setOffsetQuestions(20)
  setRefreshing(false)
  console.log(offsetQuestions)
  console.log(refreshing)
}, [refreshing, offsetQuestions])

Where the function is called:

<FlatList
   data={questionsLocal}
   refreshing={refreshing}
   onRefresh={onRefresh}
   ...
/>
Share Improve this question edited Feb 20, 2020 at 9:04 skyboyer 23.7k7 gold badges61 silver badges71 bronze badges asked Feb 20, 2020 at 3:14 Roni CastroRoni Castro 2,13424 silver badges41 bronze badges
Add a comment  | 

3 Answers 3

Reset to default 11

What you are getting is the expected behaviour in hooks. It's all about closures. Each render in react has its own props, state, functions and event handlers and they forever stay the same for that particular render. So whats happening here is that the useCallback is closing over the state variables for that particular render, So the console.log even after a setState will always give you the value of the state for that particular render because of closure.

This situation confuses many developers who start using hooks. You would expect that React will be reactive and will change the value of the state after you called the setX, but if you think about it, you can not expect React to stop the execution flow change the state variable and continue execution.

So, what you are getting is the state variable from the scope of the creation of the useCallback (Read it again and again).

Let's break it down to two parts, the scope, and useCallback, as these are two different things you must understand to be able to use useState and useCallback.

Scope:

Lets say that you have a state called age and a function that write/reads this state (For now, without using useCallback)

const [age, setAge] = useState(42);

const increaseAge = () => {
  setAge(age + 1);   // this "age" is the same "age" from above 
  console.log(age);  // also this one
}

From this you can see that every render a new age variable is defined (The value if given by React), also a new increaseAge function is defined with access to the scope that holds the age variable.

This is how the scope works, this is why you don't get a new value of age just after calling setAge. To be able to get a new value on age React will have to re-render the component, so a new age variable will be defined on a new scope. And this is exactly what happens after calling setAge, React will trigger a new render.

useCallback

Now, let's say that you don't want to define the increaseAge function again and again, you want to keep the same reference (Can help preventing useless renders). This is where you'd want to use useCallback.

When using useCallback, React provides the same function reference each render, unless one of the dependencies changes.

Without giving the useCallback the dependencies, React will not recreate the function and the function will access to the scope that it was created on (First render), when giving the useCallback the correct dependencies, it will be recreated, and now have access to the scope with the latest state variable.

This should explain why you must provide dependencies, and how state variables are accessed (scope).

You can use UseRef hook to use the current value of state instead of lexical value.

const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });

This how you can set value in a reference and use it wherever you want.

Below is the link where you can understand this problem in detail and with possible solutions https://dmitripavlutin.com/react-hooks-stale-closures/

Thanks

发布评论

评论列表(0)

  1. 暂无评论