简体   繁体   English

当父组件的 state 更改时,子组件不会重新渲染

[英]Child Component doesn't rerender when state of parent component changes

I have the following issue: I have an Component that renders other components in it.我有以下问题:我有一个在其中呈现其他组件的组件。 One of this component gets state variables of my parent component as parameter and are using them actively, but they don't rerender when the state of the parent component changes.该组件之一将我的父组件的 state 变量作为参数并正在积极使用它们,但是当父组件的 state 更改时,它们不会重新呈现。 Another problem that I am facing is that I have an additional item in my list that navigates that is activated when the user has a special roleID.我面临的另一个问题是,我的列表中有一个额外的导航项目,当用户具有特殊的角色 ID 时,它会被激活。 The changing of the state works completely fine, but in this situation the additional item only gets visible after I changed the path param of my url. state 的更改工作完全正常,但在这种情况下,只有在我更改了 url 的路径参数后才能看到附加项目。

parent component:父组件:

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;

Child component (settingsChooser):子组件(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;

Firstly, in your parent component when you do首先,当你这样做时,在你的父组件中

setItems(items)

you are not actually modifying the state, since items already is stored in the state.您实际上并未修改 state,因为items已存储在 state 中。 React will check the value you pass, and not cause a re-render if the value is already stored in the state. React 将检查您传递的值,如果该值已存储在 state 中,则不会导致重新渲染。 When you modify your array with splice, it is still the "same" array, just different contents.当您使用拼接修改数组时,它仍然是“相同”的数组,只是内容不同。

One way around this is to do setItems([...items]) , which will call setItems with a new array, containing the same items.解决此问题的一种方法是执行setItems([...items]) ,它将使用包含相同项目的数组调用setItems

Secondly, in your child class, the following currently has no effect:其次,在您的孩子 class 中,以下当前无效:

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

Since the dependency array is empty, it will only be called on the first render.由于依赖数组是空的,它只会在第一次渲染时被调用。 If you want it to be called any time defaultactive changes, you need to do this instead:如果您希望在 defaultactive 更改时随时调用它,则需要这样做:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM