React - HeaderComponent não muda após o login?

Sep 01 2020

Eu tenho um TodoComponentque foi escrito para mostrar certos links na barra de navegação no HeaderComponent, dependendo do estado atual do usuário - conectado ou desconectado.

class TodoApp extends Component {
    render = () => {
        return (
            <div className="TodoApp">
                <Router>
                        <HeaderComponent />
                        <Switch>
                            <Route path="/" exact component={LoginComponent} />
                            <Route path="/login" exact component={LoginComponent} />
                            <AuthenticatedRoute path="/welcome/:name" exact component={WelcomeComponent} />
                            <AuthenticatedRoute path="/todos/" exact component={ListTodosComponent} />
                            <AuthenticatedRoute path="/logout" exact component={LogoutComponent} />
                            <Route component={ErrorFuncComponent} />
                        </Switch>
                        <FooterComponent />
                </Router>
            </div>
        )
    }
}

Sinto que o HeaderComponentnão renderiza novamente depois que o usuário está logado.

class HeaderComponent extends Component {

    render() {
        
        const isUserLoggedIn = AuthenticationService.isUserLoggedIn()
        console.log(isUserLoggedIn)

        return (
            <header>
                <nav className="navbar navbar-expand-md navbar-dark bg-dark">
                    <div><a href="http://localhost:3000" className="navbar-brand">Todo Management</a></div>
                    <ul className="navbar-nav">
                        {isUserLoggedIn && <li><Link className="nav-link" to="/welcome/in28minutes">Home</Link></li>}
                        {isUserLoggedIn && <li><Link className="nav-link" to="/todos">Todos</Link></li>}
                    </ul>
                    <ul className="navbar-nav navbar-collapse justify-content-end">
                        {!isUserLoggedIn && <li><Link className="nav-link" to="/login">Login</Link></li>}
                        {isUserLoggedIn && <li><Link className="nav-link" onClick={AuthenticationService.logoutAuthenticatedUser} to="/logout">Logout</Link></li>}
                    </ul>
                </nav>
            </header>
        )
    }
}

porque o componente LogIn não força? (Devo forçar outros componentes? Não tenho certeza se esta é a maneira correta de fazer as coisas)

Snippet de LoginComponent.jsx

handleLoginButtonClick = () => {

    if (this.state.username === 'zaid' && this.state.password === 'khan') {
        AuthenticationService.registerAuthenticatedUser(this.state.username, this.state.password)
        // window.location.reload(false);
        this.props.history.push(`/welcome/${this.state.username}`) //String templating //this.props.history.push("/welcome/"+this.state.username)
        this.setState(
            { isLoginSuccess: true, isLoginFailed: false }, function () {
                console.log(this.state);
            }
        )
    }
    else {
        this.setState(
            { isLoginSuccess: false, isLoginFailed: true }, function () {
                console.log(this.state);
            }
        )
    }
    // console.log(this.state)
}

Como faço a atualização do HeaderComponent (basicamente, chamo o rendermétodo novamente após o login do usuário?!?!).

PS: Acho que pode haver outra maneira de fazer isso, que envolve a execução automática de alguma ação com base em um evento. Só não sei como isso se encaixaria no que fiz até agora. Sou novo no React.

Esse cara enfrentou o mesmo problema (mas explicou mal o IMO): habilitar a autenticação de usuário baseada em link de menu não está funcionando no reactjs

Respostas

1 AliAbuHijleh Sep 01 2020 at 08:01

Os componentes são renderizados novamente se detectar qualquer mudança no estado ou nas propriedades, isso é o que a comparação superficial faz, então, para renderizar novamente o componente, ele deve depender de um deles, então o que você pode fazer é usar isLoginSuccess e passá-lo de volta para TodoComponent e, em seguida, envie-o como um adereço para HeaderComponent como este:

class TodoApp extends Component {
    state = {isLoginSuccess:false}
    render = () => {
        return (
            <div className="TodoApp">
                <Router>
                        <HeaderComponent isLoginSuccess={this.state.isLoginSuccess}/>
                        
                        <Switch>
                            <Route path="/" exact component={() => <LoginComponent isAuthed={true} onLogin={(status)=> this.setState({isLoginSuccess:status})}/>} />
                            ...
                </Router>
            </div>
        )
    }
}

Em LoginComponent

handleLoginButtonClick = () => {
    if (this.state.username === 'zaid' && this.state.password === 'khan') {
        ...
        this.props.onLogin(true);
        ...
    }
    else {
        this.props.onLogin(false);
        ...
    }
}

Em HeaderComponent, substitua isUserLoggedIn por isLoginSuccess:

class HeaderComponent extends Component {

    render() {
    let { isLoginSuccess } = this.props;
        ...
        return (
            <header>
                <nav className="navbar navbar-expand-md navbar-dark bg-dark">
                    <div><a href="http://localhost:3000" className="navbar-brand">Todo Management</a></div>
                    <ul className="navbar-nav">
                        {isLoginSuccess && <li><Link className="nav-link" to="/welcome/in28minutes">Home</Link></li>}
                        ...
                </nav>
            </header>
        )
    }
}

Nota: Usar Redux ou Contexto em vez de passar adereços entre componentes seria mais estruturado e fácil de manusear

1 Nalhin Sep 01 2020 at 07:43

Você não deve manter seu status de autenticação em uma classe externa. A detecção de mudança de reação dispara apenas se o estado ou props forem diferentes (comparação superficial). Ele não pode detectar mudanças em uma classe externa. Tal estado pode, por exemplo, ser gerenciado com React Context.

const AuthContext = React.createContext()

function AuthProvider(props) {
  const login = () => {}
  const register = () => {}
  const logout = () => {} 
  const isLoggedIn = ()=>{}
  return (
    <AuthContext.Provider value={{data, login, logout, register, isLoggedIn }} {...props} />
  )
}

export function withAuthContext(Component) {
    return function WrapperComponent(props) {
        return (
            <AuthContext.Consumerr>
                {state => <Component {...props} context={state} />}
            </AuthContext.Consumer>
        );
    };
}

export {AuthProvider, withAuthContext}
class HeaderComponent extends Component {

    render() {
        
        const isUserLoggedIn = props.context.isLoggedIn()

        return (
            <header>
                <nav className="navbar navbar-expand-md navbar-dark bg-dark">
                    <div><a href="http://localhost:3000" className="navbar-brand">Todo Management</a></div>
                    <ul className="navbar-nav">
                        {isUserLoggedIn && <li><Link className="nav-link" to="/welcome/in28minutes">Home</Link></li>}
                        {isUserLoggedIn && <li><Link className="nav-link" to="/todos">Todos</Link></li>}
                    </ul>
                    <ul className="navbar-nav navbar-collapse justify-content-end">
                        {!isUserLoggedIn && <li><Link className="nav-link" to="/login">Login</Link></li>}
                        {isUserLoggedIn && <li><Link className="nav-link" onClick={props.context.logout} to="/logout">Logout</Link></li>}
                    </ul>
                </nav>
            </header>
        )
    }
}

export default withAuthContext(HeaderComponent)

Lembre-se de embrulhar toda a sua árvore React com AuthProvider.