Creé un componente de inicio de sesión en el que tengo todas las cosas lógicas.
El reductor de inicio de sesión es:
const openState = { loggedIn: null, user: null } export default (state = openState, action) => { switch (action.type) { case LOGIN: return { ...state, loggedIn: true, user: action.payload } case LOGOUT: return { ...state, loggedIn: false, user: null } default: return openState } }La acción :
export const logIn = (user) => { return { type: LOGIN, payload: user } } export const logOut = () => { return { type: LOGOUT } } todo funciona bien, pero no estoy seguro de cómo pasar los accesorios de inicio de loggedIn y de user de la acción al componente de rutas para asegurar todas las rutas:
const MainRoutes = props => { const { loggedIn } = props; console.log(props.loggedIn) return ( <Router history={history}> <Baseline /> <Menu/> <Container maxWidth="md"> <Switch> <Route exact path="/Login" component={Login} /> <Route exact path="/Carousel" component={Carousel} /> <Route exact path="/Stepper" component={Stepper} /> <Route component={NotFound} /> </Switch> </Container> </Router> ); } const mapStateToProps = (state) => { return { loggedIn: state.loggedIn }; }; export default connect(mapStateToProps)(MainRoutes); Si loggedIn . registraré los accesorios de inicio de sesión, obtendré undefined :| Basado en el inicio de loggedIn , puedo crear una lógica en el componente de rutas.
Simplemente podría definir un componente ProtectedRoute personalizado que se conectará al estado redux. En su caso, debería mapear state.auth.loggedIn y state.auth.user a accesorios y realizar una Redirect si esos valores son falsos:
import React from "react"; import { Route, Redirect } from "react-router-dom"; import PropTypes from "prop-types"; const ProtectedRoute = (props) => { const { redirectPath, component, user, loggedIn, ...routeProps} = props; const Component = component; const isAccessible = Boolean(user) && loggedIn; return ( <Route {...routeProps} render={props => { if (isAccessible) return <Component {...props} />; return <Redirect to={{ pathname: redirectPath || "/Login" }} />; }} /> ); }; ProtectedRoute.propTypes = { path: PropTypes.string.isRequired, redirectPath: PropTypes.string, component: PropTypes.oneOfType([ PropTypes.shape({ render: PropTypes.func.isRequired }), PropTypes.func ]), }; const mapStateToProps = (state) => { return { loggedIn: state.auth.loggedIn, user: state.auth.user }; }; export default connect(mapStateToProps)(ProtectedRoute); Con esto en su lugar, sus MainRoutes ya no necesitarán connect :
const MainRoutes = props => { return ( <Router history={history}> ... <Container maxWidth="md"> <Switch> <Route exact path="/Login" component={Login} /> <ProtectedRoute exact path="/Carousel" component={Carousel} /> <ProtectedRoute exact path="/Stepper" component={Stepper} /> <Route component={NotFound} /> </Switch> </Container> </Router> ); } export default MainRoutes;Actualizar:
Si desea mantener el estado de auth después de actualizar la página, deberá realizar una configuración adicional de su tienda redux. La idea principal es suscribirse en cada acción y colocar una copia nueva del estado en localStorage o cookies y también initialState del almacenamiento seleccionado antes de que se inicie la aplicación. Aquí hay un ejemplo:
function getFromStorage(key) { try { return JSON.parse(window.localStorage.getItem(key)) || {}; } catch (err) { return {}; } } const initialState = getFromStorage("APP_STATE"); const store = createStore( rootReducer, initialState, // should go after root reducer ... ); store.subscribe(() => { window.localStorage.setItem("APP_STATE", JSON.stringify(store.getState())); });Y aquí está la caja de arena de trabajo (arrancada por SuleymanSah )
Lo que haría en este escenario es crear una ruta privada, básicamente, a la que solo se pueda acceder si está conectado o, en su caso, si login en su loggedIn: state.loggedIn es verdadero y si un usuario existe en state.auth.user . Y si ambos son falsos, lo redirigirá a la página de inicio de /login para iniciar sesión o registrarse antes de acceder a esas rutas privadas.
Entonces, para crear una ruta privada o una <PrivateRoute /> ,
import React from 'react'; import { Route, Redirect } from 'react-router-dom'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; const PrivateRoute = ({ component: Component, auth: { user, loggedIn }, ...rest }) => ( <Route {...rest} render={props => loggedIn && Boolean(user) ? ( <Component {...props} /> ) : ( <Redirect to="/login" /> ) } /> ); PrivateRoute.propTypes = { auth: PropTypes.object.isRequired } const mapStateToProps = state => ({ auth: state.auth }); export default connect(mapStateToProps)(PrivateRoute);
Esto creará esa ruta privada o <PrivateRoute /> para usted que puede importar a su archivo MainRoutes y luego puede pasarle los componentes que se supone que están protegidos por la ruta privada.
const MainRoutes = props => { return ( <Router history={history}> <Baseline /> <Menu/> <Container maxWidth="md"> <Switch> <Route exact path="/Login" component={Login} /> <PrivateRoute exact path="/Carousel" component={Carousel} /> <PrivateRoute exact path="/Stepper" component={Stepper} /> <Route component={NotFound} /> </Switch> </Container> </Router> ); } export default MainRoutes;
De esta forma, los componentes a proteger comprobarán si el usuario existe y si está logueado o no y, si es cierto, le permitirá acceder a esos componentes.
El siguiente código funcionó para mí.
const SecretRouter = ({ component: Component, location, loggedIn , ...rest }) => ( <Route location={location} {...rest} render={(props) => loggedIn === true ? ( <Component {...props} /> ) : ( <Redirect to={{ pathname: "/Login" }} /> ) } /> ); const mapStateToProps = (state) => { return { loggedIn: state.loggedIn }; }; export const PrivateRouter = connect(mapStateToProps)( SecretRouter ); const MainRoutes = props => { return ( <Router history={history}> <Baseline /> <Menu/> <Container maxWidth="md"> <Switch> <Route exact path="/Login" component={Login} /> <PrivateRouter exact path="/Carousel" component={Carousel} /> <PrivateRouter exact path="/Stepper" component={Stepper} /> <Route component={NotFound} /> </Switch> </Container> </Router> ); } export default MainRoutes;Como otros ya describieron, es mejor que cree una ruta protegida para lograr lo que desea. Simplemente redirige al usuario a la ruta de inicio de sesión si no ha iniciado sesión.
Aquí está mi implementación: ( codesandbox )
import React from "react"; import { Route, Redirect } from "react-router-dom"; import { connect } from "react-redux"; const ProtectedRoute = ({ path, component: Component, render, loggedIn, ...rest }) => { return ( <Route path={path} {...rest} render={(props) => { if (loggedIn) { return Component ? <Component {...props} /> : render(props); } return ( <Redirect to={{ pathname: "/login", state: { from: props.location } }} /> ); }} /> ); }; const mapStateToProps = (state) => { const { loggedIn } = state.auth; return { loggedIn }; }; export default connect(mapStateToProps)(ProtectedRoute);Puede crear un Guard que obtenga el estado de inicio de sesión de la tienda Redux y represente a los niños si está conectado; de lo contrario, redirigirá a otro lugar. Para obtener el estado de la tienda, puede usar useSelector React Hook.
import { useSelector } from 'react-redux'; function AuthGuard({children}) { const loggedIn = useSelector(store => store.loggedIn); if (loggedIn) { return children; } else { return <Redirect to="/login" /> } }y en la ruta
<Container maxWidth="md"> <Switch> <Route exact path="/Login" component={Login} /> <AuthGuard> <Route exact path="/Carousel" component={Carousel} /> <Route exact path="/Stepper" component={Stepper} /> </AuthGuard> <Route component={NotFound} /> </Switch> </Container>Este enfoque utiliza el ejemplo oficial del enrutador de reacción, básicamente necesita conectar su tienda redux con este componente ProtectedRoute
import { connect } from "react-redux"; import { Route, Redirect } from "react-router-dom"; function PrivateRoute({ children, loggedIn, ...rest }) { return ( <Route {...rest} render={({ location }) => loggedIn ? ( children ) : ( <Redirect to={{ pathname: "/login", state: { from: location } }} /> ) } /> ); } const mapStateToProps = (state) => { return { loggedIn: state.auth.loggedIn }; }; export default connect(mapStateToProps)(PrivateRoute);Como puede ver, está pasando el valor de inicio de sesión a este nuevo componente de ruta, por lo que si es falso, lo regresará a la página de inicio de sesión.
Para usarlo, simplemente llame al componente en su enrutador:
import PrivateRoute from "./PrivateRoute"; const MainRoutes = () => { return ( <Router history={history}> <Baseline /> <Menu/> <Container maxWidth="md"> <Switch> <Route exact path="/Login" component={Login} /> <PrivateRoute exact path="/Carousel" component={Carousel} /> <PrivateRoute exact path="/Stepper" component={Stepper} /> <Route component={NotFound} /> </Switch> </Container> </Router> ); }Es un buen enfoque porque no está conectando el componente MainRoutes a la tienda, que en mi opinión no es algo que necesitaría en ese componente, terminará usándolo en los componentes dentro de las Rutas.
También olvidó agregar state.auth.loggedIn a su ruta principal mapStateToProps, es por eso que no está definido cuando intenta usarlo.
Aquí hay una pequeña caja de arena que hice para ilustrar el proceso: https://codesandbox.io/s/unruffled-keller-pwlib