Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React hooks value is not accessible in event listener function

I decided to use React hooks for my component using windom width and resize event listener. The problem is that I can't access current value that I need. I get nested value that was set while adding event listener.

Passing function to value setter function is not a solution for me because it's forcing render and breaking my other functionalities.

I am attaching minimalistic example to present core problem:

import React, { Component, useState, useEffect } from 'react';
import { render } from 'react-dom';

const App = () => {
  const [width, setWidth] = useState(0);

  const resize = () => {
    console.log("width:", width); // it's always 0 even after many updates
    setWidth(window.innerWidth);
  };

  useEffect(() => {
    resize();
    window.addEventListener("resize", resize);
    return () => window.removeEventListener("resize", resize);
  }, []);

  return <div>{width}</div>;
}

render(<App />, document.getElementById('root'));

LIVE DEMO IS HERE

Please for help.

like image 790
Artimal Avatar asked Mar 24 '19 17:03

Artimal


People also ask

Do React Hooks return a value?

React hooks are not required to return anything.

Can I use a React hook inside a function?

Don't call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders.

Are React Hooks memoized?

The React useMemo Hook returns a memoized value. Think of memoization as caching a value so that it does not need to be recalculated. The useMemo Hook only runs when one of its dependencies update. This can improve performance.


1 Answers

Every render you get a new copy of resize function. Each copy captures the current value of width. Your listener has a copy which was created on the first render with width = 0.

To fix this issue you have several options:

  1. Update listeners when width changes

    useEffect(() => {
      resize();
      window.addEventListener("resize", resize);
      return () => window.removeEventListener("resize", resize);
    }, [width]);
    
  2. Use functional updates to get the current width inside listener

    const resize = () => {
      setWidth(oldWidth => {
        console.log("width:", oldWidth);
        return window.innerWidth
      });
    };
    
  3. Store the width in a mutable reference

    const widthRef = useRef(width);
    
    const resize = () => {
      console.log("width:", widthRef.current);
      widthRef.current = window.innerWidth;
      setWidth(window.innerWidth);
    };
    
like image 57
UjinT34 Avatar answered Sep 29 '22 15:09

UjinT34