Skip to content

Instantly share code, notes, and snippets.

@gaearon
Created November 1, 2018 10:05
Show Gist options
  • Star 94 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save gaearon/cb5add26336003ed8c0004c4ba820eae to your computer and use it in GitHub Desktop.
Save gaearon/cb5add26336003ed8c0004c4ba820eae to your computer and use it in GitHub Desktop.
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
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
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
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
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
Copy link

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
Copy link

bakytn commented Nov 7, 2018

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

@chriswilty
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.

@earthtosid
Copy link

How would one go about testing these event listeners?

Copy link

ghost commented Dec 6, 2019

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?

I had same thought, I wonder why Dan uses his original example though (useEffect without []) - it can be slightly confusing. @gaearon

@cs-joy
Copy link

cs-joy commented Sep 26, 2021

i am one of the newbie in react learning.
thank you for explanation.

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