Instantly share code, notes, and snippets.

Embed
What would you like to do?
Examples from "Making Sense of React Hooks"
function MyResponsiveComponent() {
const width = useWindowWidth(); // Our custom Hook
return (
<p>Window width is {width}</p>
);
}
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
});
return width;
}
@harkinj

This comment has been minimized.

Copy link

harkinj commented Nov 1, 2018

Thanks for this. This helped me twig that setting state in a hook causes a re-render of its using/parent component. I read a lot about hooks but missed that point somehow :(

@harkinj

This comment has been minimized.

Copy link

harkinj commented Nov 1, 2018

It almost feels like using a custom hook is like a #include (I'm showing my age now :)) into the calling function.

@guilherme6191

This comment has been minimized.

Copy link

guilherme6191 commented Nov 2, 2018

Fun fact; this works on codesandbox:

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  // const handleResize = () => setWidth(window.innerWidth);
  // window.addEventListener("resize", handleResize);
  useEffect(() => {
    setWidth(window.innerWidth);
    return () => {
      // window.removeEventListener("resize", handleResize);
    };
  });

  return width;
}

I'm guessing that is because of the window.innerWidth reference.
https://codesandbox.io/s/rm3v532lwm

@dbismut

This comment has been minimized.

Copy link

dbismut commented Nov 5, 2018

Hi @gaearon, I'm very new to hooks, but isn't this typically the case where you would opt out from cleaning at each render in useEffect and rather have:

useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // <-- empty array

As suggested in the note from the docs, the empty array makes it so that the effect is run and cleaned up when the component mounts / unmounts.

Otherwise you would add and remove a window listener at each render, right?

@Memfisrain

This comment has been minimized.

Copy link

Memfisrain commented Nov 7, 2018

Hi @gaearon, I'm very new to hooks, but isn't this typically the case where you would opt out from cleaning at each render in useEffect and rather have:

useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // <-- empty array

As suggested in the note from the docs, the empty array makes it so that the effect is run and cleaned up when the component mounts / unmounts.

Otherwise you would add and remove a window listener at each render, right?

Correct, you can avoid running effect after every render passing empty array for this case. However react team refer to the more complex situation when people introduce memory leaks or break application logic as they forget to run "effect" after component's props are updated.

@bakytn

This comment has been minimized.

Copy link

bakytn commented Nov 7, 2018

This cool example just demonstrated everything I wanted to know about Hooks. Thank you!!!!

@chriswilty

This comment has been minimized.

Copy link

chriswilty commented Dec 17, 2018

@guilherme6191

Fun fact; this works on codesandbox:

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  // const handleResize = () => setWidth(window.innerWidth);
  // window.addEventListener("resize", handleResize);
  useEffect(() => {
    setWidth(window.innerWidth);
    return () => {
      // window.removeEventListener("resize", handleResize);
    };
  });

  return width;
}

I'm guessing that is because of the window.innerWidth reference.
https://codesandbox.io/s/rm3v532lwm

It's because the effect runs on every render, and within the effect you are updating the local state, which causes a subsequent re-render. It's a side-effect that causes a render, kind of a self-invoking hook :)

In fact this is an important point, as it is easy to mistakenly abuse Effect hooks if you aren't clear on how they (currently) work. As mentioned in other comments here, sticking in an empty array as the Effect trigger will give us the expected behaviour.

useEffect(() => {
  const handleResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', handleResize);
  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []); <-- empty array: effect runs only on mount, cleans up on unmount

It is a somewhat contentious issue, that updating a local state value always triggers a re-render even if the value you are setting is identical to the previous value. The useState hook is mimicking the behaviour of a class Component in which shouldComponentUpdate returns true as its default implementation. We can extend PureComponent if we want a smarter updating class component, and it would be trivial to implement a usePureState hook to achieve the same for a functional component. Personally, I think it would have been nice to take advantage of having a brand-new API to make "pure" the default behaviour in hooks, but I fully understand the reasons why the React team have not done that; I had just hoped they would provide a usePureState hook out-of-the-box.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment