Can i change a single child component in a loop without re-rendering the whole list (react hooks)

Viewed 1175

I have a parent component where i am looping over a products array and rendering a child component within. Child component contains a button which should only update some value of that component only without re-rendering the whole list.

import React, { useState } from "react";
import { connect } from "react-redux";
import ListComponent from "./ListComponent";
import Search from "./search";
import Loader from "./../Loader";

// import { getProductList } from "../../redux/actions/index";

const mapStateToProps = state => {
    return { list: state.product.productList, loading: state.product.loader };
};

const App = ({ list, loading }) => {
    const [products, setProducts] = useState(list);
    const handleButtonAction = data => {
        let newProductList = list.map((v, i) => {
            if (v.id === data.id) {
                v.isInCatalogue = true;
            }
            return v;
        });
        setProducts(newProductList);
        list = products;
    };

    return (
        <div className="container-fluid">
            {loading ? <Loader /> : ""}
            <div className="row d-flex mt-5">
                <div className="col-md-12 col-sm-12">
                    <Search />
                </div>
                <div className="col-md-12 col-sm-12">
                    <div className="row ">
                        {list.map((v, i) => {
                            if (v.isInCatalogue) {
                                v.iconClass = "fa fa-check text-success";
                            } else {
                                v.iconClass = "fa fa-plus";
                            }
                            return (
                                <div key={i} className="col-sm-6 col-md-4 mb-4 ">
                                    <ListComponent
                                        data={v}
                                        handleButtonAction={handleButtonAction}
                                    />

                                </div>
                            );
                        })}
                    </div>
                </div>
            </div>
        </div>
    );
};

export default connect(mapStateToProps)(App);
3 Answers
function ListComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(ListComponent, areEqual);

As you are doing immutable change of your list (correctly), the rerendering of the whole list is unavoidable.

Functional stateless component rerender also when its props did not changed (unless you are using React.memo as @AspirinWang suggested), see this answer:

Will a stateless component re-render if its props have not changed?

The purpose of keys in react are exactly designed for that situation. If your not-updated (and also updated) items has the same key before and after the change, it is ok and do not bother of rerender (unless your list item carries a huge JSX tree).

It is good to know that callig render for any component is not a problem and making a component purposely to not rerender should not be your target (especially functional stateless components), because:

It is important to remember that the reconciliation algorithm is an implementation detail. React could rerender the whole app on every action; the end result would be the same.

React will not rerender the component if the output is not different, moreover in this case in listComponent, save the data prop as state, and use useEffect, with data as dependency.

Something like below

const ListComponent = ({data})=>{
  const [compData, setCompData] = useState(data);

  useEffect(()=>{
    setCompData(data)
  },[data])

  return (
    <h1>{compData}</h1>
  )
}
Related