Aula 25 - Golang - Fiber - React - Logout
Código da Aula
Centralizando o Estado do Usuário no React para Melhoria da Gestão e Acessibilidade dos Dados
Para colocar o
botão de
logout no
nav, precisamos do
user no componente
nav.
Como vamos fazer isso?
Em aplicações React, manter o estado relacionado ao usuário em um local centralizado é uma prática recomendada, especialmente em aplicações onde múltiplos componentes precisam acessar ou modificar esses dados.
No nosso caso, tanto o componente de navegação (
Nav) quanto a página inicial (
Home) necessitam acessar as informações do usuário.
Ao fazer a chamada do
Axios para recuperar os dados do usuário no componente
App, que é o componente pai tanto de
Nav quanto de
Home, podemos facilmente passar esses dados como
props para ambos os componentes.
Isso não só simplifica o fluxo de dados dentro da aplicação, como também facilita a manutenção, pois qualquer mudança relacionada à obtenção ou atualização dos dados do usuário pode ser gerenciada em um único local.
Além disso, isso nos permite implementar funcionalidades adicionais, como no caso desse projeto, um botão de
logout no
Nav, de maneira mais eficiente.
O estado do usuário, estando disponível no
App, pode ser modificado diretamente de
Nav sem a necessidade de múltiplos handlers ou contextos complexos.
Isso garante que o estado do usuário seja consistentemente atualizado e refletido em toda a aplicação, melhorando a experiência do usuário e a confiabilidade da interface.
Essa abordagem de centralização do estado também prepara o terreno para uma eventual expansão do aplicativo, onde componentes adicionais poderão necessitar acessar ou modificar as informações do usuário sem necessitar de uma reestruturação significativa do gerenciamento de estado.
Portanto, ajustar nossa arquitetura para recuperar e gerenciar o estado do usuário no componente
App é uma decisão estratégica que melhora a eficiência do código e a escalabilidade da aplicação.
Vamos a prática!
Apague a parte em
laranja do
Home.tsx, porque estamos transferindo a chamada ao endpoint
User, para o componente
App.tsx, pai do
Home.
src/pages/Home.tsx
import React, { useEffect } from 'react';
import axios from 'axios';
const Home: React.FC = () => {
const [message, setMessage] = useState('');
useEffect(() => {
(async () => {
try {
const response = await axios.get('user');
const user = response.data;
setMessage(`Hi ${user.first_name} ${user.last_name}`);
} catch (e) {
setMessage('You are not logged in!');
}
})();
}, []);
return (
<div className="container">
<h1>{message}</h1>
</div>
);
}
export default Home;
A parte
laranja no código abaixo, ou seja, a parte das
mensagens, pertencem ao
Home, por isso, vamos voltar com elas para o
Home e deixar só a parte em
azul.
src/App.tsx
import React, { useState, useEffect } from "react";
import axios from "axios";
import "./App.css";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Login from "./pages/Login";
import Home from "./pages/Home";
import Register from "./pages/Register";
import Nav from "./components/Nav";
function App() {
useEffect(() => {
(async () => {
try {
const response = await axios.get('user');
const user = response.data;
setMessage(`Hi ${user.first_name} ${user.last_name}`);
} catch (e) {
setMessage('You are not logged in!');
}
})();
}, []);
return (
<div className="App">
<Router>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Routes>
</Router>
</div>
);
}
export default App;
Então o App.tsx agora tá assim:
src/App.tsx
import React, { useState, useEffect } from "react";
import axios from "axios";
import "./App.css";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Login from "./pages/Login";
import Home from "./pages/Home";
import Register from "./pages/Register";
import Nav from "./components/Nav";
function App() {
useEffect(() => {
(async () => {
try {
const response = await axios.get('user');
const user = response.data;
} catch (e) {
}
})();
}, []);
return (
<div className="App">
<Router>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Routes>
</Router>
</div>
);
}
export default App;
Além de trazer de volta as
mensagens ao
Home, vamos precisar pegar esse
User que será enviado ao
Home pelo
App.tsx.
Para isso vamos receber no
Home a propriedade do
User e também definir suas propriedades, enviado pelo
App.
Remova também os imports do
axios e o
useEffect, porque não vamos usar ele mais nesse arquivo.
src/pages/Home.tsx
import React, { useEffect } from 'react';
import axios from 'axios';
type UserProps = {
user: {
first_name: string;
last_name: string;
email?: string;
} | null; // Indicates that user can be an object with user data or null
};
const Home: React.FC<UserProps> = ({ user }) => {
let message;
if(user){
message = `Hi ${user.first_name} ${user.last_name}`;
} else{
message = 'You are not logged in!';
}
return (
<div className="container">
<h1>{message}</h1>
</div>
);
}
export default Home;
Com as modificações acima, o Home vai ficar assim:
src/pages/Home.tsx
import React from 'react';
type UserProps = {
user: {
first_name: string;
last_name: string;
email?: string;
} | null; // Indicates that user can be an object with user data or null
};
const Home: React.FC<UserProps> = ({ user }) => {
let message;
if(user){
message = `Hi ${user.first_name} ${user.last_name}`;
} else{
message = 'You are not logged in!';
}
return (
<div className="container">
<h1>{message}</h1>
</div>
);
}
export default Home
Já que a chamada ao endpoint
User está sendo no
App.tsx, precisamos passar ele como uma propriedade para o componente
Home.
src/App.tsx
import React, { useState, useEffect } from "react";
import axios from "axios";
import "./App.css";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Login from "./pages/Login";
import Home from "./pages/Home";
import Register from "./pages/Register";
import Nav from "./components/Nav";
function App() {
const [user, setUser] = useState(null);
useEffect(() => {
(async () => {
try {
const response = await axios.get('user');
const user = response.data;
setUser(user);
} catch (e) {
setUser(null);
}
})();
}, []);
return (
<div className="App">
<Router>
<Nav user={user}/>
<Routes>
<Route path="/"element={<Home user={user} />} /> // Passing the user to Home
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Routes>
</Router>
</div>
);
}
export default App;
E da mesma forma que fizemos para receber as propriedades do
User enviado do
App para o
Home, faremos também do mesmo jeito, no
Nav.
Vamos também
transferir a parte em
vermelho e trocar por
{links}.
src/components/Nav.tsx
import React, { useState } from 'react';
import axios from "axios";
import { Link } from "react-router-dom";
const Nav = ({ user }: { user: any }) => {
const [isNavCollapsed, setIsNavCollapsed] = useState(true);
const handleNavCollapse = () => setIsNavCollapsed(!isNavCollapsed);
return (
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
<div className="container-fluid">
<Link className="navbar-brand" to="/">Home</Link>
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded={!isNavCollapsed} aria-label="Toggle navigation" onClick={handleNavCollapse}>
<span className="navbar-toggler-icon"></span>
</button>
<div className={`${isNavCollapsed ? 'collapse' : ''} navbar-collapse`} id="navbarNav">
<ul className="navbar-nav ms-auto">
<li className="nav-item">
<Link className="nav-link" to="/login">Login</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/register">Register</Link>
</li>
</ul>
</div>
</div>
</nav>
);
};
export default Nav;
Vamos modificar o
html do
Nav e também verificar se o usuário está logado, se estiver, será mostrado no
Nav o
Logout, caso contrário, será mostrado
Login e
Register.
src/components/Nav.tsx
import React, { useState } from 'react';
import axios from "axios";
import { Link } from "react-router-dom";
const Nav = ({ user }: { user: any }) => {
const logout = async () => {
await axios.post('logout', {});
}
let links;
if(user){
links = (
<ul className="navbar-nav ms-auto">
<li className="nav-item">
<Link className="nav-link" to="/" onClick={logout}>Logout</Link>
</li>
</ul>
)
} else {
links = (
<ul className="navbar-nav ms-auto">
<li className="nav-item">
<Link className="nav-link" to="/login">Login</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/register">Register</Link>
</li>
</ul>
)
}
const [isNavCollapsed, setIsNavCollapsed] = useState(true);
const handleNavCollapse = () => setIsNavCollapsed(!isNavCollapsed);
return (
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
<div className="container-fluid">
<Link className="navbar-brand" to="/">Home</Link>
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded={!isNavCollapsed} aria-label="Toggle navigation" onClick={handleNavCollapse}>
<span className="navbar-toggler-icon"></span>
</button>
<div className={`${isNavCollapsed ? 'collapse' : ''} navbar-collapse`} id="navbarNav">
<ul className="navbar-nav ms-auto">
{links}
</ul>
</div>
</div>
</nav>
);
};
export default Nav;
Testando
Veja que se você deslogar, ou logar caso esteja deslogado, a página não muda, a não ser que a gente recarregue a página dando um refresh.
Vamos mudar mais algumas coisas no
App,
Login e
Nav para resolver isso.
src/App.tsx
import React, { useState, useEffect } from "react";
import axios from "axios";
import "./App.css";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Login from "./pages/Login";
import Home from "./pages/Home";
import Register from "./pages/Register";
import Nav from "./components/Nav";
function App() {
const [user, setUser] = useState(null); // State to store user data
const [login, setLogin] = useState(false);
useEffect(() => {
(async () => {
try {
const response = await axios.get("user");
const user = response.data;
setUser(user);// Update the state with user data
} catch (e) {
console.error("Error loading user data", e);
setUser(null); // Sets the user to null in case of error
}
})();
}, [login]);
return (
<div className="App">
<Router>
<Nav user={user} setLogin={ () => setLogin(false) }/>
<Routes>
<Route path="/" element={<Home user={user} />} />
<Route path="/login" element={<Login setLogin={() => setLogin(true)} />} />
<Route path="/register" element={<Register />} />
</Routes>
</Router>
</div>
);
}
export default App;
Estado login: A adição da variável
login permite rastrear quando um login ou logout ocorre.
Essa variável é usada como uma dependência no
useEffect, o que significa que cada vez que
login é atualizado (login bem-sucedido ou logout), o
useEffect é disparado novamente.
Objetivo: Recarregar as informações do usuário quando o
status de
login muda, sem precisar recarregar toda a página.
Isso melhora a experiência do usuário e torna a aplicação mais responsiva e eficiente.
A outra mudança foi do
<Route path="/login" element={<Login />} /> para
<Route path="/login" element={<Login setLogin={() => setLogin(true)} />} />.
Essa mudança consiste em adicionar uma propriedade ao componente
Login.
Ao incluir
setLogin={() => setLogin(true)}, você está passando uma função que atualiza o estado de login na aplicação quando o usuário efetua login com sucesso.
Isso permite que o componente
Login interaja diretamente com o estado global de autenticação, atualizando-o conforme necessário sem precisar de recarregamento da página, facilitando o controle de acesso e a resposta imediata da interface ao estado de autenticação do usuário.
Agora no
Login faça as mudanças indicada em
azul no código abaixo.
src/pages/Login.tsx
import React, { useState, SyntheticEvent } from 'react';
import axios from 'axios';
import { Navigate } from 'react-router-dom';
const Login: React.FC<{ setLogin: (loggedIn: boolean) => void }> = ({ setLogin }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [redirect, setRedirect] = useState(false);
const submit = async (e: SyntheticEvent) => {
e.preventDefault();
// Using environment variable for API URL
const apiURL = process.env.REACT_APP_API_URL || 'http://localhost:3000'; // Fallback to localhost if environment variable is not set
try {
// Fix axios.post call with URL and data as separate arguments
const response = await axios.post(`${apiURL}/api/login`, {
email, // Simplification, since the name of the property and variable are the same
password,
});
// Check the response here (example: whether login was successful based on the response status)
if (response.status === 200) {
// If the response is successful, set the state to redirect
setLogin(true);
setRedirect(true);
} else {
// Here you can handle other status codes or set a state to display an error message
console.error("Login falhou com status:", response.status);
// Ideally I would set an error state here to inform the user that login failed
}
} catch (error) {
console.error("Erro ao fazer login:", error);
// Here you could also set an error state to inform the user about the problem
}
};
if(redirect){
return <Navigate to="/"/>;
}
return (
<form className='form-floating' onSubmit={submit}>
<h1 className="h3 mb-3 fw-normal">Please sign in</h1>
<div className="form-signin">
<input type="email" className="form-control" placeholder="name@example.com" required
onChange={e => setEmail(e.target.value)}
/>
</div>
<div className="form-signin">
<input type="password" className="form-control" placeholder="Password" required
onChange={e => setPassword(e.target.value)}
/>
</div>
<button className="form-signin btn btn-primary w-100 py-2" type="submit">Sign in</button>
<p className="mt-5 mb-3 text-body-secondary">© 2017–2024</p>
</form>
);
}
export default Login;