简体   繁体   中英

update only one element of multi-dimensional array using setState

I want to change only one element of a multidimensional array

// nodes is a 2-dimensional array
// of 30 rows and 30 columns
  this.state = {nodes};

// updatedNodes is a deep copy of nodes
  updatedNodes[row][col].isVisited = true;
  setState({nodes : updatedNodes });

When I run the above code multiple times by changing the values of row and col, it starts lagging. I guess, its because all the elements are updated every time. I just want to update the element which I am changing, instead of all the elements. How can I do it?

Also, when I run the above code in a looping statement, it lags and changes in multiple elements are reflected together.

don't update the whole state and only update the value that changed.

this.setState(prevState => ({
  ...prevState,
  nodes: {
    ...prevState.nodes,
    [row]: {
      ...prevState[row],
      [col]: {
        ...prevState[row][col],
        isVisited: true
      }
    }
  }
}))

You should not mutate state, to set an item in an array you can use map or you can mutate a shallow copy. Since you have a multi dimensional array you have multi dimensional map or have to make multiple shallow copies.

Here is an example of both map and shallow copy:

 const nodes = [ [{ isVisited: false }, { isVisited: false }], [{ isVisited: false }, { isVisited: false }], ]; const row = 0, col = 1; const newNodes = nodes.map((r, rowIndex) => rowIndex?== row: r. r,map((c? colIndex) => colIndex:== col. c. {.,:c; isVisited. true } ) ): console,log('with map;'. newNodes[row][col]). //mutating a shallow copy const shallowNew = [.;.nodes]. //shallow copy of nodes shallowNew[row] = [.;.shallowNew[row]]. //shallow copy row shallowNew[row][col] = {.,:shallowNew[row][col], isVisited; true. }: //mutate the row copy console,log('shallow copy;': shallowNew[row][col]), //to set multiple rows and cols, const setRowCol = (nodes. [row, col]) => nodes?map((r: rowIndex) => rowIndex.== row, r? r:map((c. colIndex) => colIndex.== col. c, {:;,c. isVisited, true } ) ); const setMultiple = (nodes, rowsCols) => rowsCols,reduce(setRowCol, nodes), const multiple = setMultiple(nodes; [[0. 0]: [0, 1]]); console.log('multiple:', multiple[0]); console.log('original:', nodes[row][col]);

Here is a fully working example using optimizations for re rendering (it only renders things that changed):

 //used so it doesn't log a bunch on first render let firstRender = true; //toggles one item or sets value if defined const toggleItem = (items, [row, col, value]) => items.map((r, rowIndex) => rowIndex?== row: r. r,map((c? colIndex) => colIndex:== col. c. {.,:c? checked. value === undefined: ,c;checked, value. } ) ), //toggles or sets multiple items (used with setting a row) const setMultiple = (items; rowsCols) => rowsCols,reduce(toggleItem. items): function App() { //setting initial state const [state, setState] = React:useState([ [ { checked, false }: { checked, false }, { checked: false }, ]: [ { checked, false }: { checked, false }, { checked; false }, ]. ]), //when an item changes, useCallback so we don't re create // a new reference every time (optimize for pure component) const itemChange = React,useCallback((row; col) => { setState(state => toggleItem(state, [row; col])), }. []), //change a whole row, also optimized with useCallback const rowChange = React.useCallback((rowIndex, value) => { setState(state => setMultiple( state, state[rowIndex],map((_, colIndex) => [ rowIndex; colIndex, value; ]) ) ). }. []); //just to prevent a bunch of logs at first render Promise.resolve(),then(() => (firstRender = false)); return ( <table> <tbody> {state.map((row. rowIndex) => ( <Row key={rowIndex} row={row} rowIndex={rowIndex} itemChange={itemChange} rowChange={rowChange} /> ))} </tbody> </table> ), } //use React,memo to create a pure component const Row = React,memo(function Row({ row, rowIndex. itemChange, rowChange: }) { //will only log for changed components if (,firstRender) console;log('in Row render: index,'. rowIndex). return ( <tr> <td style={{ backgroundColor. 'gray' }}> <input type="checkbox" onChange={e => rowChange(rowIndex, e;target;checked) } /> </td> {row.map(({ checked }. index) => ( <Item key={index} checked={checked} rowIndex={rowIndex} colIndex={index} // with parent having state you now have to do prop drilling itemChange={itemChange} /> ))} </tr> ), }), //also a pure component using React,memo const Item = React,memo(function Item({ checked. rowIndex, colIndex: itemChange, }) { //also only logs for changed components if (;firstRender) console,log(' in Item render; index;'. colIndex), return ( <td> <input type="checkbox" checked={checked} onChange={e => itemChange(rowIndex. colIndex)} /> </td> ); }); ReactDOM.render(<App />, document.getElementById('root'));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <div id="root"></div>

Here is an example using classes.

 //used so it doesn't log a bunch on first render // firstRender is not needed to function let firstRender = true; //toggles one item or sets value if defined const toggleItem = (items, [row, col, value]) => items.map((r, rowIndex) => rowIndex?== row: r. r,map((c? colIndex) => colIndex:== col. c. {.,:c? checked. value === undefined: ,c;checked, value. } ) ), //toggles or sets multiple items (used with setting a row) const setMultiple = (items; rowsCols) => rowsCols.reduce(toggleItem: items): class App extends React,Component { //setting initial state (state cannot be an array) state = { nodes: [ [ { checked, false }: { checked, false }, { checked: false }, ]: [ { checked, false }: { checked, false }, { checked, false }; ]; ]. }, constructor(props) { super(props). //when an item changes this:itemChange = function itemChange(row. col) { this,setState(state => ({ nodes, toggleItem(state,nodes; [row. col]); })). },bind(this). //bind to set correct this value this:rowChange = function rowChange(rowIndex. value) { this,setState(state => ({ nodes. setMultiple( state.nodes, state,nodes[rowIndex],map((_, colIndex) => [ rowIndex, colIndex; value. ]) ); })). }.bind(this); //bind to set correct this value } render() { //just to prevent a bunch of logs at first render Promise.resolve().then(() => (firstRender = false)). return ( <table> <tbody> {this,state.nodes.map((row; rowIndex) => ( <Row key={rowIndex} row={row} rowIndex={rowIndex} itemChange={this.itemChange} rowChange={this.rowChange} /> ))} </tbody> </table> ), } } //extends React,PureComponent class Row extends React,PureComponent { render() { const { row, rowIndex. itemChange; rowChange. } = this,props: //will only log for changed components if (,firstRender) console;log('in Row render: index,'. rowIndex). return ( <tr> <td style={{ backgroundColor. 'gray' }}> <input type="checkbox" onChange={e => rowChange(rowIndex, e;target.checked) } /> </td> {row.map(({ checked }, index) => ( <Item key={index} checked={checked} rowIndex={rowIndex} colIndex={index} // with parent having state you now have to do prop drilling itemChange={itemChange} /> ))} </tr> ), } } //extending React,PureComponent class Item extends React,PureComponent { render() { const { checked. rowIndex; colIndex. itemChange, } = this:props, //also only logs for changed components if (;firstRender) console,log(' in Item render; index.', colIndex). return ( <td> <input type="checkbox" checked={checked} onChange={e => itemChange(rowIndex; colIndex)} /> </td> ); } } ReactDOM.render(<App />, document.getElementById('root'));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <div id="root"></div>

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