简体   繁体   中英

React.memo() resets state

I have some inputs which I render from an array of objects.

When I type in one input, I map over the array, find the corresponding object and set the input value.

The problem: Type some characters in one input, change input, type characters. Repeat this and observe the previous inputs are cleared and sometimes some random character appears in some inputs.

What is causing this behavior?

If I remove the React.memo() from SearchBar, the problem does not appear anymore.

What do I need to do to make it work with React.memo().

const shouldSkipComponentUpdate = (prevProps, props) =>
prevProps.value === props.value;

const SearchBar = React.memo((props) => {
  const { label, width, hasSearch, handleSearchChange, value } = props;
    return (
      <div width={width} style = { { margin: "15px", display: "inline" } }>
        {label}
        {hasSearch && (
          <SearchInput
            onChange={handleSearchChange}
            value={value}
            label={label}
          />
        )}
      </div>
    );
  }, (prevProps, props) => shouldSkipComponentUpdate(prevProps, props));


function DevicesContainer() {
  const [searchBoxes, setSearchBoxes] = React.useState(() => {
    return [
      {
        id: 1,
        label: '',
        width: '5%',
        hasSearch: false,
        value: ''
      },
      {
        id: 2,
        label: 'ID',
        width: '5%',
        value: '',
        hasSearch: false
      },
      {
        id: 3,
        width: '10%',
        label: 'Name',
        value: '',
        hasSearch: true
      },
      {
        id: 4,
        width: '10%',
        label: 'Owner',
        value: '',
        hasSearch: true
      },
      {
        id: 5,
        width: '7%',
        label: 'Availability',
        value: '',
        hasSearch: false
      },
      {
        id: 6,
        width: '10%',
        label: 'Location',
        value: '',
        hasSearch: true
      },
      {
        id: 7,
        width: '20%',
        label: 'Environment',
        value: '',
        hasSearch: true
      },
      {
        id: 8,
        width: '10%',
        label: 'Firmware',
        value: '',
        hasSearch: true
      },
    ];
  });
  function handleSearchChange(event, label) {
    const {
      target: { value }
    } = event;
    const updated = searchBoxes.map(elem => {
      if (elem.label === label) {
        return { ...elem, value };
      }
      return elem;
    });
    setSearchBoxes(updated);
  }
  return (
    <main>
      <SearchBars
        searchBars={searchBoxes}
        handleSearchChange={handleSearchChange}
        />
    </main>
  );
}

function SearchBars(props) {
  const { searchBars, handleSearchChange } = props;
  return (
    <div style={ { margin: '20px' } }>
      {searchBars.map(elem => (
        <SearchBar
          key={elem.id}
          label={elem.label}
          width={elem.width}
          hasSearch={elem.hasSearch}
          value={elem.value}
          handleSearchChange={handleSearchChange}
          />
      ))}
    </div>
  );
}

function SearchInput(props) {
  const { onChange, value, label } = props;
  return (
    <input
      type="search"
      value={value}
      placeholder="Search"
      onChange={event => onChange(event, label)}
      />
  );
}

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


codepen link https://codepen.io/Apoptotic/pen/ZdPpQG

You can change the handleSearchChange() function. See the docs on the setState behaviour. You might also note, that this function runs asynchronously. I just moved the mapping function into the setSearchBox update handle to apply the data to the previous state.

  function handleSearchChange(event, label) {
    const {
      target: { value }
    } = event;


    setSearchBoxes((prevSearchBoxes) => {
      let s = [...prevSearchBoxes];
      let updated = s.map(elem => {
        if (elem.label === label) {
          return { ...elem, value };
        }
        return elem;
      });
      return updated;
    });
  }

Additionally see the following note from the React Docs.

Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

Another option is useReducer, which is more suited for managing state objects that contain multiple sub-values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM