question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

React hooks + old way components = cached state value (😱😱😱)

See original GitHub issue

Please see this demo. Edit React Hooks Problem As you can see, I have an old way component looks like this

import React, { Component } from "react";

class OldLibrary extends Component {
  componentDidMount() {
    setInterval(this.props.onProgress, 50);
  }
  render() {
    return null;
  }
}
export default OldLibrary;

A simple component setting interval when mount.

Now I have a component use React Hooks

function App() {
  const [value, setValue] = useState(0);
  const onProgress = () => {
    console.log(value);
    // this will always console logging 0 no matter what
  };
  const onClick = () => {
    setValue(value + 1);
    // Click me and state changes, but onProgress won't notice
  };
  return (
    <>
      <OldLibrary onProgress={onProgress} />
      <button onClick={onClick}>Click</button>
    </>
  );
}

This code above, no matter how I click the button, onProgress will always console logging initial value(which is 0), it’s just like the state is being cached or something I don’t know.

useRef won’t help too.

function App() {
  const [value, setValue] = useState(0);
  const onProgress = useRef(() => {
    console.log(value);
  });
  const onClick = () => {
    setValue(value + 1);
  };
  return (
    <>
      <OldLibrary onProgress={onProgress.current} />
      <button onClick={onClick}>Click</button>
    </>
  );
}

But if we use the old way, it will work just fine.

class App extends Component {
  state = {
    value: 0
  };
  onClick = () => {
    this.setState({ value: this.state.value + 1 });
  };
  onProgress = () => {
    console.log(this.state.value);
    // I will console logging different value when state changes
  };
  render() {
    return (
      <>
        <OldLibrary onProgress={this.onProgress} />
        <button onClick={this.onClick}>Click</button>
      </>
    );
  }
}

Thank you for listening my question. Please help, I can’t live without hooks 😭😭😭

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:11 (1 by maintainers)

github_iconTop GitHub Comments

18reactions
sophiebitscommented, Oct 30, 2018

@wojtekmaj’s explanation is correct – the onProgress prop gets updated to a new function but it is the old function that was passed to setInterval. In your class component example, onProgress always reads the latest this.state which is a mutable field.

If you aren’t able to change OldLibrary, your best approach is to store the value in a ref as described in our FAQ: https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback.

function App() {
  const [value, setValue] = useState(0);
  const onClick = () => {
    setValue(value + 1);
  };

  const valueRef = useRef();
  useEffect(() => {
    valueRef.current = value;
  });
  const onProgress = () => {
    console.log(valueRef.current);
  };

  return (
    <>
      <OldLibrary onProgress={onProgress} />
      <button onClick={onClick}>Click</button>
    </>
  );
}

Note that this is one of the “sharp edges” of Hooks right now and we might introduce a better way to deal with this before the final release.

2reactions
wojtekmajcommented, Oct 30, 2018

With the first render of App, you created onProgress function that returns value (0). You are then invoking onProgress function and it returns 0.

You then increment the value of value, so that it’s now 1. Within App, now there is value with value of 1, and a new onProgress function which would return 1 if called.

You continue to call the FIRST onProgress function in setInterval, but you ignore the fact that now OldLibrary have a SECOND onProgress function passed by props, which returns incremented value of value.

What you can do is wrap your setInterval so it would not call the first given this.props.onProgress directly, but an internal method which gets the newest onProgress function from props and calls it.

import React, { Component } from "react";

class OldLibrary extends Component {
  onProgress = () => {
    this.props.onProgress();
  }

  componentDidMount() {
    setInterval(this.onProgress, 500);
  }
  render() {
    return null;
  }
}
export default OldLibrary;

Read more comments on GitHub >

github_iconTop Results From Across the Web

Accessing previous props or state with React Hooks
Leverage the useRef, useState, usePrevious, and useEffect React Hooks to access previous props and states from within functional components.
Read more >
Guide to access previous props or state in React hooks
With class components, we can use the componentDidUpdate method which is called with oldProps and oldState as input.
Read more >
Why custom react hooks could destroy your app performance
every state change in a hook will cause its “host” component to re-render, regardless of whether this state is returned in the hook...
Read more >
Global Cached State in React using Hooks, Context, and ...
Context Using Hooks​​ We're going to set up a simple context class to store user info (we'll get to local storage later on)....
Read more >
How to Add to an Array in React State using Hooks
The previous code example did not work because .push() returns the length of the array after modification, not the array itself. The React...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found