Search code examples
reactjsreact-hooksuse-statereact-functional-componentreact-class-based-component

Not getting the expected output when using react hooks


I was converting react class components to functional components in one of my projects.

This is the class component

export default class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      user: null,
    }
  }

  componentDidMount() {
    this.func1().then((res) => {
      this.func2();
    })

  }
  async func1() {
    this.setState({
      user: "ishan"
    })
    const res = await fetch('https://jsonplaceholder.typicode.com/todos/1')
    const data = await res.json();
    return data;
  }

  func2() {
    console.log("user state: ",this.state.user);
  }
  render() {
    return (
    <div className='App'>
      <h1>Hello World</h1>
    </div>
    );
  }
}

This gives the following output in the console

user state: ishan

This is the corresponding functional component

export const App = () => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    func1().then((res) => {
      func2();
    });
  }, []);

  const func1 = async () => {
    setUser("ishan");
    const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
    const data = await res.json();
    return data;
  };

  const func2 = () => {
    console.log("user state: ",user);
  };

  return (
    <div className="App">
      <h1>Hello World</h1>
    </div>
  );
};

This gives the following output in the console:

user state: null

I'm not sure why the user state is null in case of functional component, the flow of logic is similar in both the cases. Please explain the difference in the output.


Solution

  • The problem seems to be in closures.

    Everything inside the useEffects callback will only ever run on mount, hence, user will always be null there because closures "remembered" only its initial state.

    One way to workaround this issue would be to split your useEffect into 2:

    useEffect(() => {
      func1();
    }, []);
    
    useEffect(() => {
      func2();
    }, [user]);
    

    In this case, func2 will run each time user changes.