Skip to content
Advertisement

React Redux – Mapping over array while rerendering only components holding changed object (in array)

Explanation Hi, I’m pretty new in ‘advanced’ React/Redux field. The problem that I have is: I didn’t use actions so the problem and code can be simplified as much as possible. MyParent component:

const MyParent = () => {
  const ref = useRef(0)
  const myArray = useSelector(state => state.someReducer.myArray)

  const changeOneItem = () => {
     dispatch({ type: CHANGE_ONE_ITEM })
  }

  return (
    <div>
        {ref.current ++ }
        <button onClick={() => changeOneItem()}>Add</button>
        {
            myArray.map((item) => 
                <MyChild
                    key={item.id}
                    name={item.text}
                    name2={item.text2}
                ></MyChild>
            )
        }
    </div>

Now here is my child component:

const MyChild = ({name, name2}) => {
const ref = useRef(0)
return (
    <div>
        <hr/>
        <p>{ref.current ++}</p>
        <p>{name}</p>
        <p>{name2}</p>
    </div>
)}

And the reducer:

const initialState = {
myArray: [
    {
        id: "1",
        text: "first",
        text2: "001"
    },
    {
        id: "2",
        text: "second",
        text2: "002"
    }
]}

case CHANGE_ONE_ITEM:
        return {
            ...state,
            myArray: state.myArray.map(t => t.id == "1" ? {...t, text: "new text"} : t)
        }

Question Let’s imagine there is ~1,000 objects inside the array. Everytime I change one of the object inside array, parent component rerenders (because of the selector), which also triggers all child components to rerender. I’m kind of confused when it comes to immutable changes with Redux, when does immutable change helps if this one is not the case? Every child component has their own key, but still, whole list will get rerender, is there something I’m missing? Is there a way to trigger render on only one child which corresponding object did change?

Example in main project Subtitle translator. You will have table, each row will have own textarea where you can write your subtitle for specific timestamp (start of subtitle – end of subtitle). After leaving the textarea, changes should be saved, that save causes lag because each “child” component (in this case each row) rerenders.

Thanks! Good luck 🙂

Advertisement

Answer

You can make MyChild a pure component with React.memo, your reducer already doesn’t change all the other elements of the array t.id == "1" ? {...t, text: "new text"} : t and each MyChild item has a unuque key so none should re render when you only chanage one item but you have to use React.memo because functional components will always re render. That is they will re create jsx but React may not render Dom when current generated jsx is the same as last time.

const { memo, useRef, useCallback, useState } = React;
//using React.memo to make MyChild a pure component
const MyChild = memo(function MyChild({
  id,
  text,
  change,
}) {
  const ref = useRef(0);
  return (
    <div>
      <p>Rendered: {++ref.current} times</p>
      <input
        type="text"
        value={text}
        onChange={(e) => change(id, e.target.value)}
      />
    </div>
  );
});

const App = () => {
  const [data, setData] = useState(() =>
    [...new Array(10)].map((_, i) => ({
      id: i + 1,
      text: `text ${i+1}`,
    }))
  );
  //use callback so change is only created on mount
  const change = useCallback(
    (id, text) =>
      //set data item where id is the id passed to change
      setData((data) =>
        data.map((d) => (d.id === id ? { ...d, text } : d))
      ),
    []//deps array is empty so only created on mount
  );
  return (
    <div>
      {data.map((item) => (
        <MyChild
          key={item.id}
          id={item.id}
          text={item.text}
          change={change}
        />
      ))}
    </div>
  );
};

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>
User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement