Gatsby: Using the useState setter doesn't rerender component from template

Created on 25 Feb 2019  ·  11Comments  ·  Source: gatsbyjs/gatsby

Description

useState Hooks do not work when they are part of a template. I tried multiple solutions, eg adding pureSFC. It just doesn't work.

Steps to reproduce

  1. Clone this example and install dependencies
  2. Run yarn start
  3. go to http://localhost:8000/product/henning-ss-crew/ and try changing the variants.

Expected result

The component should rerender and give the Variation a white BG.

Actual result

The state of the variant doesn't change, because the component doesn't rerender.

The component is in this repo and in this component

Environment

System:
OS: macOS 10.14.3
CPU: (8) x64 Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
Shell: 5.3 - /bin/zsh
Binaries:
Node: 10.15.1 - /usr/local/bin/node
Yarn: 1.13.0 - /usr/local/bin/yarn
npm: 6.4.1 - /usr/local/bin/npm
Languages:
Python: 2.7.10 - /usr/bin/python
Browsers:
Chrome: 72.0.3626.119
Safari: 12.0.3
npmPackages:
gatsby: ^2.1.17 => 2.1.18
gatsby-image: ^2.0.29 => 2.0.30
gatsby-plugin-compile-es6-packages: ^1.0.6 => 1.0.6
gatsby-plugin-manifest: ^2.0.19 => 2.0.19
gatsby-plugin-typescript: ^2.0.9 => 2.0.9
gatsby-theme-shopify-poulo: ^0.1.4 => 0.1.4

Most helpful comment

@Saifadin I guess I wasn't very clear with my previous comment. Your variants is a nested object and that signature does not change and won't trigger re-render, I guess I wasn't clear enough when I was talking about your initial state value pointing to the first object in array, that's my bad. But thank you for explaining to me how useState works.

Aside from that, if you were to change the handleClick to be like the following, you will see that state is properly updated and re-render will trigger as the object compare will see the change.

const handleClick = (optionIndex, value) => {
  let currentOptions = variant.selectedOptions;
  currentOptions[optionIndex] = {
    ...currentOptions[optionIndex],
    value,
  };

  setVariant({ ...variant, selectedOptions: currentOptions });
};

I've updated the code and tested on your environment that you've shared, it is properly working for any type of variant change. _(color & size)_

cc — @DSchau; I don't think this issue is a Gatsby bug unless you guys are also responsible for the theme implementations, that I am not sure as this is the first issue I've spent time for Gatsby.

All 11 comments

PS: If I use a class component it works just fine.

Thank you for the report! That indeed sounds like a bug. Not sure what's the issue here. Wanna investigate it further and make a PR? Pinging our resident hook expert @sidharthachatterjee :)

Just to be clear here - this is a template component in a theme, correct?

I can create a reproduction in a bit--but does this issue also impact non-theme template components?

@DSchau yes it’s impacting a component in a template. They work perfectly fine in other places, like the icons in the header.

I am not sure what is causing it, tried to investigate but I didn’t get that far.

As far as I can tell from just reading the components — ProductDetails component always uses the same variant value as a state variable, thus change not being reflected.

const [variant, setVariant] = useState(variants[0]); will get run every time there's a render, unlike in a class component variant value is probably set once and used directly from that same object on future renders _(i.e. this.state.variant)_. setVariant call will not change what variants[0] corresponding to on the next render.

That is not how hooks work 😅
The initializing happens only ones with useState. After that with each rerender it will not reset to that value.

The main problem is that the component does not rerender at all when I click on one of them, but the state changes🤔

@Saifadin I guess I wasn't very clear with my previous comment. Your variants is a nested object and that signature does not change and won't trigger re-render, I guess I wasn't clear enough when I was talking about your initial state value pointing to the first object in array, that's my bad. But thank you for explaining to me how useState works.

Aside from that, if you were to change the handleClick to be like the following, you will see that state is properly updated and re-render will trigger as the object compare will see the change.

const handleClick = (optionIndex, value) => {
  let currentOptions = variant.selectedOptions;
  currentOptions[optionIndex] = {
    ...currentOptions[optionIndex],
    value,
  };

  setVariant({ ...variant, selectedOptions: currentOptions });
};

I've updated the code and tested on your environment that you've shared, it is properly working for any type of variant change. _(color & size)_

cc — @DSchau; I don't think this issue is a Gatsby bug unless you guys are also responsible for the theme implementations, that I am not sure as this is the first issue I've spent time for Gatsby.

This is not a proper solution either. variant now has still the same id of the first variant. If i select another option it is supposed to Exchange the whole variant 🤔

I will have another look and will answer back 😊

  const [variant, setVariant] = useState({ ...variants[0] });

  const handleClick = (optionIndex, value) => {
    let currentOptions = variant.selectedOptions;
    currentOptions[optionIndex] = {
      ...currentOptions[optionIndex],
      value,
    };
    const selectedVariant = find(variants, ({ selectedOptions }) => currentOptions === selectedOptions);

    setVariant({ ...selectedVariant });
  };

You were right, this might be a bug with React. The above approach works fine and fulfils my needs.

I will try to reproduce it in a codesandbox and open an Issue there, but it could be intended behaviour.

I will close this issue, sorry for wasting your time and thanks to @goksu for the solution. 👏🏽

I tried to reproduce it in a clean environment, but it works 🤔.

https://codesandbox.io/s/zlj9vp66m

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jimfilippou picture jimfilippou  ·  3Comments

mikestopcontinues picture mikestopcontinues  ·  3Comments

dustinhorton picture dustinhorton  ·  3Comments

rossPatton picture rossPatton  ·  3Comments

magicly picture magicly  ·  3Comments