Дочерний компонент не перерисовывается при изменении состояния родительского компонента

У меня следующая проблема: у меня есть компонент, который отображает в нем другие компоненты. Один из этих компонентов получает переменные состояния моего родительского компонента в качестве параметра и активно их использует, но они не перерисовываются при изменении состояния родительского компонента. Другая проблема, с которой я столкнулся, заключается в том, что у меня есть дополнительный элемент в моем списке, который активируется, когда у пользователя есть специальный идентификатор роли. Изменение состояния работает полностью нормально, но в этой ситуации дополнительный элемент становится видимым только после того, как я изменил параметр пути своего URL-адреса.

родительский компонент:

import React, { useEffect, useState } from 'react';
import {Row, Col} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import '../../App.css';
import ProfileSettings from './profileSettings';
import SettingsChooser from './settingsChooser';
// import SettingRoutings from '../settingRoutings';
import {BrowserRouter as Router, useHistory, useLocation, useParams} from 'react-router-dom';
// import Routings from '../Routings.js';
import UserRequests from './userRequests';
import useAuth from '../../API/useAuthentification';
import { CONTROLLERS, useBackend } from '../../hooks/useBackend';

function UserSettings({user}) {

    const {title: path} = useParams();
    const [acst, setAcst] = useState(localStorage.accessToken);
    const [rft, setRft] = useState(localStorage.refreshToken);
    const history = useHistory();
    const [items, setItems] = useState(['Profile', 'Requests','Log Out', 'Delete Account']);
    const [authError, setAuthError] = useState(false);
    const [userValues, authentificate] = useBackend(authError, setAuthError, user);
    const [component, setComponent] = useState(<></>);
    const [defaultItem, setDefaultItem] = useState(0);
    

    useEffect(() => {
        console.log('render');
        authentificate(CONTROLLERS.USERS.getUserByAccessToken());
    }, [acst, rft]);

    window.addEventListener('storage', () => localStorage.accessToken !== acst ? setAcst(localStorage.accessToken) : '');
    window.addEventListener('storage', () => localStorage.refreshToken !== rft ? setRft(localStorage.refreshToken) : '');

    useEffect(() => {
        if(userValues?.roleID === 1) {
            items.splice(0, 0, 'Admin Panel');
            setItems(items);
        }
        console.log(items);
    }, [userValues]);

    useEffect(() => {
        // if(path==='logout') setDefaultItem(2);
        // else if(path==='deleteAccount') setDefaultItem(3);
        // else if(path==='requests') setDefaultItem(1);
    }, [])

    const clearTokens = () => {
        localStorage.accessToken = undefined;
        localStorage.refreshToken = undefined;
    }
    useEffect(() => {
        console.log(path);
        if(path ==='logout' && !authError) {
            setDefaultItem(2);
            clearTokens();
        }
        else if(path === 'deleteaccount') {
            setDefaultItem(3);
            if(userValues?.userID && !authError) {
             authentificate(CONTROLLERS.USERS.delete(userValues.userID));
             }
            clearTokens();
            history.push('/movies/pages/1');
        }
        else if(path==='requests') {
            setDefaultItem(1);
            setComponent(<UserRequests user={userValues} setAuthError={setAuthError} authError={authError}/>);
        } else {
            setComponent(<ProfileSettings user={userValues} setAuthError={setAuthError} authError={authError}/>);
        }
    }, [path]);

    useEffect(() => {
        console.log(defaultItem);
    }, [defaultItem])
    

 

    return (
            <div >
            <Row className="">
            <Col className="formsettings2"   md={ {span: 3, offset: 1}}>
                <SettingsChooser  items={items} headline={'Your Details'} defaultpath='userSettings' defaultactive={defaultItem} />
            </Col>
           <Col  className="ml-5 formsettings2"md={ {span: 6}}>
                     {authError ? <p>No Access, please Login first</p> : component}
            </Col>
            </Row>
            </div>
    );
}

export default UserSettings;

Дочерний компонент (settingsChooser):

import React, {useEffect, useState} from 'react';
import {Card, Form, Button, Nav, Col} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import { LinkContainer } from 'react-router-bootstrap';
import '../../App.css'

function SettingsChooser({items, headline, defaultpath, defaultactive}) {

    const [selected, setSelected] = useState(defaultactive);
   

    const handleClick = (e, key) => {
        setSelected(key);
    }

    useEffect(() => console.log("rerender"), [items, defaultactive]);

    useEffect(() => {
        setSelected(defaultactive);
    }, [])

    return(
        <>
        <Card className="shadow-sm">
            <Card.Header className="bg-white h6 ">{headline}</Card.Header>
        {items.map((item, idx) =>{
            return(
              <LinkContainer to={`/${defaultpath}/${(item.replace(/\s/g,'').toLowerCase())}`}><Nav.Link onClick={(e) => handleClick(this, idx)}  className={'text-decoration-none text-secondary item-text ' + (selected === idx? 'active-item' : 'item')}>{item}</Nav.Link></LinkContainer>
            );           
        })}
        </Card>
        </>
    );
}

export default SettingsChooser;

person Niklas    schedule 14.04.2021    source источник
comment
Не могли бы вы попробовать setItems ([... items]) вместо setItems (items)   -  person Viktor W    schedule 15.04.2021
comment
Это прекрасно работает, спасибо. Но можете ли вы объяснить мне, почему мой settingsChooser не выполняет повторную визуализацию при изменении defaultItem с помощью метода setDefaultItem?   -  person Niklas    schedule 15.04.2021


Ответы (1)


Во-первых, в родительском компоненте, когда вы делаете

setItems(items)

вы фактически не изменяете состояние, поскольку items уже хранится в состоянии. React проверит переданное вами значение и не вызовет повторную визуализацию, если значение уже сохранено в состоянии. Когда вы изменяете свой массив с помощью splice, это все тот же массив, только другое содержимое.

Один из способов обойти это - сделать setItems([...items]), который вызовет setItems с массивом new, содержащим те же элементы.

Во-вторых, в вашем дочернем классе в настоящее время не действует следующее:

useEffect(() => {
    setSelected(defaultactive);
}, [])

Поскольку массив зависимостей пуст, он будет вызываться только при первом рендеринге. Если вы хотите, чтобы он вызывал каждый раз при изменении, активном по умолчанию, вам нужно сделать это вместо этого:

useEffect(() => {
    setSelected(defaultactive);
}, [defaultactive])
person Viktor W    schedule 16.04.2021