简体   繁体   中英

How to prevent react child components from re-render?

I've a problem with rerendering children components.

I have a checkbox in the child ListItem component which adds/removes an id to/from an array of ids when clicked. I also need to access this array via useSelector from Redux but each time I click the checkbox the array changes and my component re-renders which child components do too.

How can I prevent child components from re-render?

I've done a codesandbox

https://codesandbox.io/s/determined-danilo-4im9x?file=/src/pages/UsersPage/index.js:718-821



const UsersPage = () => {
    const dispatch = useDispatch();

    const usersLoadingStatus = useSelector(state => state.users.usersLoadingStatus);
    const usersList = useSelector(state => state.users.usersList);

    //this makes re-rendering
    const usersToDelete = useSelector((state) => state.users.usersToDelete);

    console.log(usersToDelete);

    

    const [currentPage, setPage] = useState(1);


    useEffect(() => {
        getUsers(currentPage);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])


    
    function getUsers(page) {
        dispatch(fetchUsers(page));
    }

    function handleUserDelete(listId) {
        dispatch(deleteUser(listId));
    };


    function handleUserToDeleteList(method, id) {
        if (method === 'add') {
            dispatch(addUserToDeleteList(id));
        } 
        if (method === 'remove') {
            dispatch(removeUserFromDeleteList(id));
        }
    }


    function checkListPosition(e) {
        const target = e.target;
        const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
        if (usersLoadingStatus === 'idle' && scrollBottom < target.scrollHeight / 3) {
            getUsers(currentPage);
            setPage(prevState => prevState + 1);
        }
    }

    

    return (
        <main className="users">
            <PageTitle title={'Пользователи'}/>
            <div className="container">
                <div className="row">
                    <Btn content={'Удалить'} isDisabled />
                </div>
                <div className="container__content-wrapper">
                    <div className="container-content">
                        {/* here the ListItem with component is */}
                        <UserList 
                            data={usersList} 
                            handleScroll={checkListPosition}
                            handleUserDelete={handleUserDelete}
                            handleUserToDeleteList={handleUserToDeleteList}/>
                    </div>
                    <div className="container-aside">
                        <Filter type="users"/>
                    </div>
                </div>
            </div>
        </main>
    );
};





export default UsersPage;

ListItem below

const ListItem = ({
    type = 'userList', 
    namespace = false, 
    data = {}, 
    listId = null,
    handleUserDelete = null,
    handleDeleteList = null
}) => {
    const inputId = nanoid(3);
    const [isChecked, setChecked] = useState(false);


    const namespaceData = {
        userName: 'Имя',
        phone: 'Номер телефона',
        email: 'E-mail',
        date: 'Дата регистрации',
        time: '',
        refs: 'Реф ссылка'
    }

    const dateOptions = {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        timezone: 'UTC',
        hour: 'numeric',
        minute: 'numeric'
    };
    
    function formatDataObj(obj) {
        const newObj = {};
        for (let key in obj) {
            if (obj[key] === null || obj[key] === undefined) {
                newObj[key] = 'noData';
            } else if (key === 'confirmed_at') {
                const {date, time} = formatDate(obj[key], dateOptions);
                newObj['date'] = date;
                newObj['time'] = time;
            }  else {
                newObj[key] = obj[key];
            }
        }
        return newObj;
    }

    function formatDate(unformatted, options) {
        const dateSample = new Date(unformatted);
        const formatedDate = dateSample.toLocaleDateString('ru', options);
        return {
            date: formatedDate.slice(0, formatedDate.indexOf('г')),
            time: formatedDate.slice(-5)
        }
    }

    function handleCheckboxClick() {
        setChecked(!isChecked);
        if (isChecked) {
            handleDeleteList('remove', listId);
        } else {
            handleDeleteList('add', listId);
        }
    }

    const {
        name: userName,
        email,
        phone_number: phone,
        date,
        time,
        referal_counter: refs
    } = formatDataObj(data);

    

    return (
        <li className={`${type}-item ${namespace ? 'namespace' : ''}`} >
            <div className={`${type}-item__column`}>
                <div className={`${type}-item__column-checkbox`}>
                    <label className="checkbox">
                        <input 
                            onChange={() => handleCheckboxClick()}
                            checked={isChecked} 
                            type="checkbox" 
                            name="toDelete" 
                            id={inputId}/>
                        <span></span>
                    </label>
                </div>
            </div>
            <div className={`${type}-item__column`}>
                <span className={`${type}-item__column-text`}>
                    {namespace ? namespaceData.userName : userName}
                </span>
            </div>
            <div className={`${type}-item__column`}>
                <span className={`${type}-item__column-text`}>
                    {namespace ? namespaceData.phone : phone}
                </span>
            </div>
            <div className={`${type}-item__column`}>
                <span className={`${type}-item__column-text`}>
                    {namespace ? namespaceData.email : email}
                </span>
            </div>
            <div className={`${type}-item__column`}>
                <span className={`${type}-item__column-text`}>
                    {namespace ? `${namespaceData.date} ${namespaceData.time}` : `${date} ${time}`}
                </span>
            </div>
            <div className={`${type}-item__column`}>
                <span className={`${type}-item__column-text`}>
                    {
                        namespace ? `${namespaceData.refs}` : `Пользователей: ${refs}`
                    }
                </span>
            </div>
            <div className={`${type}-item__column`}>
                <span 
                    className={`${type}-item__btn-delete`}
                    onClick={() => handleUserDelete(listId)}
                    >Удалить</span>
            </div>
        </li>
    );
};


ListItem.propTypes = {
    data: PropTypes.shape({
    userName: PropTypes.string,
    phone: PropTypes.string,
    email: PropTypes.string,
    date: PropTypes.string || PropTypes.object,
    time: PropTypes.string || PropTypes.number,
    ref: PropTypes.number
  }),
  namespace: PropTypes.bool,
  type: PropTypes.string
}




export default ListItem;

The problem was in re-rendering of the child components. I found the answer in memoization. Code below.

const memoizedList = useMemo(() => data.map(item => {
        const key = nanoid(3);
        const {id, attributes} = item;
        return <ListItem
                key={key}
                type={'userList'} 
                listId={id}
                data={attributes}
                handleUserDelete={handleUserDelete}
                handleUsersDeleteList={handleUsersDeleteList}/>
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }), [data]);

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