Enzyme: `setState` not updating state when using `shallow`

Created on 25 Dec 2019  路  3Comments  路  Source: enzymejs/enzyme

Current behavior

Consider this simplified component:

import React, {useState} from 'react';
import {Input} from '@material-ui/core';

const Foo = ({initialValue = 0}) => {
  const [inputValue, setInputValue] = useState(initialValue);

  const handleInputChange = ({target: {value}}) => {
    const number = Number(value);
    setInputValue(value !== '' ? number : '');
    console.log(value, number, value !== '' ? number : '', inputValue);
  };

  return ( 
    <Input  value={inputValue} onChange={handleInputChange}  />
  );
};

and the unit test:

  it('is able to change inputValue', () => {
    const initialValue = 0;
    const props = {initialValue};
    const wrapper = shallow(<Foo {...props} />);
    const input = wrapper.find(Input);

    input.simulate('change', {target: {value: 33}}); 
  });

when running the test the console logs

    33 33 33 0

While if i run it on the browser i get:

    33 33 33 33

Expected behavior

setInputValue should set inputValue to 33.

Your environment

Distributor ID: Ubuntu
Description:    Ubuntu 19.10
Release:    19.10
Codename:   eoan

API

  • [x] shallow
  • [ ] mount
  • [ ] render

Version

| library | version
| ------------------- | -------
| enzyme | 3.11.0
| react | 16.12.0
| react-dom | 16.12.0
| react-test-renderer | 16.12.0
| adapter (below) |

Adapter

  • [x] enzyme-adapter-react-16
  • [ ] enzyme-adapter-react-16.3
  • [ ] enzyme-adapter-react-16.2
  • [ ] enzyme-adapter-react-16.1
  • [ ] enzyme-adapter-react-15
  • [ ] enzyme-adapter-react-15.4
  • [ ] enzyme-adapter-react-14
  • [ ] enzyme-adapter-react-13
  • [ ] enzyme-adapter-react-helper
  • [ ] others ( )

Most helpful comment

I finally found the solution in one of your old comments @ljharb .

You can't reuse the find method, with every call to setState a new re-render will happen which appears to trash the old reference.

  it('is able to change inputValue', () => {
    const initialValue = 0;
    const props = {initialValue};
    const wrapper = shallow(<Foo {...props} />);
    // const input = wrapper.find(Input); <--- don't do this

    // `onChange` will trigger a re-render, the second find will return a different
    // reference from the first find.
    wrapper.find(Input).prop('onChange')({target: {value: 22}});
    wrapper.find(Input).prop('onBlur')();
  });

I wish this was a bit clearer in the documentations, but other than that, there isn't really an issue with enzyme, so I will close the ticket.

Thank you for your help!

All 3 comments

useState is a hook - it's not actually state, it's just confusingly named that; it's more of a side channel for an element.

Separately, I'd discourage you from using simulate at all; it doesn't faithfully simulate anything. Instead, try input.prop('onClick')({target: {value: 33}}), and then wrapper.update(), and see what wrapper.debug() prints out before and after?

Hello @ljharb,

Thank you for your suggestions. Unfortunately using simulate or invoking the prop directly as you propose produces the same outcome at the console.log. However, wrapper.debug correctly shows that inputValue passed to the Input value prop after a wrapper.update() with the correct value.

The problem here is that in the real code, i'm trying to use the value of InputValue and I can't see it updated.

Something like:

const Foo = ({initialValue = 0}) => {
  const [inputValue, setInputValue] = useState(initialValue);

  const handleInputChange = ({target: {value}}) => {
    const number = Number(value);
    setInputValue(value !== '' ? number : '');
  };

 const handleBlur = () => console.log(inputValue);

  return ( 
    <Input  value={inputValue} onChange={handleInputChange}  onBlur={handleBlur} />
  );
};
    console.log(wrapper.debug());
    input.prop('onChange')({target: {value: 22}});
    wrapper.update();
    input.prop('onBlur')();
    console.log(wrapper.debug());

I can see that the wrapper has the inputValue updated but the onBlur call will still have the old inputValue

I've also tried modifying the onBlurcall like this, but it also doesnt work:

const Foo = ({initialValue = 0}) => {
  const [inputValue, setInputValue] = useState(initialValue);

  const handleInputChange = ({target: {value}}) => {
    const number = Number(value);
    setInputValue(value !== '' ? number : '');
  };

 const handleBlur = (inputValue) => console.log(inputValue);

  return ( 
    <Input  value={inputValue} onChange={handleInputChange}  onBlur={() =>handleBlur(inputValue)} />
  );
};

I finally found the solution in one of your old comments @ljharb .

You can't reuse the find method, with every call to setState a new re-render will happen which appears to trash the old reference.

  it('is able to change inputValue', () => {
    const initialValue = 0;
    const props = {initialValue};
    const wrapper = shallow(<Foo {...props} />);
    // const input = wrapper.find(Input); <--- don't do this

    // `onChange` will trigger a re-render, the second find will return a different
    // reference from the first find.
    wrapper.find(Input).prop('onChange')({target: {value: 22}});
    wrapper.find(Input).prop('onBlur')();
  });

I wish this was a bit clearer in the documentations, but other than that, there isn't really an issue with enzyme, so I will close the ticket.

Thank you for your help!

Was this page helpful?
0 / 5 - 0 ratings