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.