Lodash delay function doesn't let me to see state inside called function

Viewed 96

I am using lodash's delay function to call my function 3 times after certain time period and for some reason I can't see an updated React state in that function. What can be a reason for that?

import React, { useEffect, useState } from 'react';
import Delays from './helper';
import { delay } from 'lodash';

const App = () => {
  const [progress, setProgress] = useState(0);

  console.log(progress); // Here I can see the updated state

  const testFunction = () => {
    console.log('inside function', progress); // here I can't see the updated state
  };

  useEffect(() => {
    const seconds = 40;
    const attempts = 3;
    const milliseconds = 4000;

    setInterval(() => {
      setProgress((prevProgress) =>
        prevProgress >= 100 ? 100 : prevProgress + 1
      );
    }, seconds * attempts * 10); // 1200

    for (let i = 0; i < attempts; i++) {
      delay(testFunction, milliseconds * (i + 1));
    }
  }, []);
  return <div>Hello World</div>;
};

export default App;

The weird thing for me is that if I use a class based approach then I don't have any issues and the value of state will be present in my function.

import React, { Component } from 'react';
import Delays from './helper';
import { delay } from 'lodash';

class App extends Component {
  state = {
    progress: 0,
  };

  udpateProgress = (value) => {
    this.setState({ progress: value });
  };

  testFunction = () => {
    console.log('inside function', this.state.progress); // here I can see the Updated state
  };

  componentDidMount = () => {
    const seconds = 40;
    const milliseconds = 4000;
    const attempts = 3;

    setInterval(() => {
      this.udpateProgress(
        this.state.progress >= 100 ? 100 : this.state.progress + 1
      );
    }, seconds * attempts * 10); // 1200

    for (let i = 0; i < attempts; i++) {
      delay(this.testFunction.bind(this), milliseconds * (i + 1));
    }
  };

  render() {
    console.log('inside render', this.state.progress);
    return <div>Hello World</div>;
  }
}

export default App;
1 Answers

Issue

The issue here is that you've closed over the initial progress state value in testFunction that you are looping over from the useEffect that runs once on component mount.

A Solution

A solution could be to use a React ref to hold the current state value that you can then reference in testFunction.

function App() {
  const [progress, setProgress] = useState(0);
  const progressRef = useRef(); // <-- ref to hold state value

  const testFunction = () => {
    console.log('inside function', progressRef.current); // <-- reference state value
  };
  
  useEffect(() => {
    console.log(progress); // <-- log per update
    progressRef.current = progress; // <-- cache state value
  }, [progress])

  useEffect(() => {
    const seconds = 40;
    const attempts = 3;
    const milliseconds = 4000;

    const timer = setInterval(() => { // <-- don't forget to save ref to interval timer
      setProgress((prevProgress) =>
        prevProgress >= 100 ? 100 : prevProgress + 1
      );
    }, seconds * attempts * 10); // 1200

    for (let i = 0; i < attempts; i++) {
      delay(testFunction, milliseconds * (i + 1));
    }

    return () => clearInterval(timer); // <-- to clear when component unmounts!!!
  }, []);
  return <div>Hello World</div>;
}

Edit lodash-delay-function-doesnt-let-me-to-see-state-inside-called-function

Related