No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Reto: UX de login y logout

16/30
Recursos

Aportes 11

Preguntas 2

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

o inicia sesión.

Para resolver el segundo reto utilizando location lo que debemos hacer (o al menos eso entendí) es:

  1. Dentro de nuestro <AuthRoute> llamar al useNavigate
const location = useLocation();

como AuthRoute envuelve a nuestras rutas protegidas location.pathname tomara el valor de la ruta a la que intentamos entrar antes del redirect a LoginPage.

  1. Lo siguiente es guardar este valor dentro de la propiedad state
Navigate to='/login' state={{ from: location }} replace />

Debemos colocar el atributo replace el cual nos permitirá hacer el redirect.

  1. Ahora debemos modificar nuestro método login dentro del AuthProvider para ello debemos llamar al hook useLocation tal como en el paso uno y crear una variable from que nos ayudara a apuntar al valor guardado en location del AuthRoute
let from = location.state?.from?.pathname || -1;

aqui le decimos que from es de existir (solo existe este valor si fuimos redirigiros desde una ruta protegida) igual al valor guardado en el Navigate del AuthRoute sino sera igual a la ultima ruta no protegida en la que estuvo el usuario (en el ejemplo del recurso se establece que sea siempre el home “/”).

  1. ahora solo nos queda modificar el método navigate para que nos rediría a la dirección del from en lugar de profile.
navigate(from, { replace: true })

notese que como segundo parametro pasamos el replace a true.
esto nos ayuda a que no nos rediría al perfil como establece el método login de LoginPage sino que nos lleve a la ruta del from de nuestro AuthProvider.

Si hay algo de lo que haya escrito que este errado por favor déjamelo saber con tu comentario igual si te fue de ayuda.

Una posible solución al reto usando Redux

Cuado se ingrese a la ruta privada, voy a guardar la ruta en el estado desde location.pathname, al usuario lo redirecciono al login como se hizo en clase, pero cuando haga el login exitosamente valido si en el estado hay una ruta guardada, para saber que pagina le muestro.
Para finalizar, para borrar el estado, valido si el usuario se encuentra en la misma ruta que se guardo la primera vez, y borro el estado.

Repositorio en GitHub

Link del proyecto desplegado

  • auth-router.jsx
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useSelector, useDispatch } from 'react-redux';
import { Navigate, useLocation } from 'react-router-dom';

import { setLastUrlAction } from '@modules/nav/actions';
import { getAuth, isAuthSuccess } from '@modules/auth/selectors';
import { getLastUrl } from '@modules/nav/selectors';

const AuthRoute = ({ children }) => {
  const dispatch = useDispatch();
  const location = useLocation();

  const auth = useSelector(getAuth);
  const isSuccess = useSelector(isAuthSuccess);
  const lastUrl = useSelector(getLastUrl);

  if (!auth?.email && !isSuccess && !lastUrl) {
    dispatch(setLastUrlAction(location.pathname));
    return <Navigate to="/login" />;
  }

  useEffect(() => {
    if (location.pathname === lastUrl) {
      dispatch(setLastUrlAction(undefined));
    }
  }, []);

  return children;
};

AuthRoute.propTypes = {
  children: PropTypes.element.isRequired,
};

export default AuthRoute;

  • login.jsx
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';

import { loginAction } from '@modules/auth/actions';
import {
  getAuth,
  isAuthSuccess,
  getAuthErrorDetail,
  isAuthError,
  isAuthLoading,
} from '@modules/auth/selectors';
import { getLastUrl } from '@modules/nav/selectors';

import Loader from '@components/loader';
import Error from '@components/error';

import Button from '@ui/button';
import Input from '@ui/input';

import './login.scss';

const Login = () => {
  const navigate = useNavigate();
  const dispatch = useDispatch();

  const auth = useSelector(getAuth);
  const isSuccess = useSelector(isAuthSuccess);
  const isLoading = useSelector(isAuthLoading);
  const isError = useSelector(isAuthError);
  const errorDetail = useSelector(getAuthErrorDetail);

  const lastUrl = useSelector(getLastUrl);

  const [email, setEmail] = useState('');

  const handleLogin = (event) => {
    dispatch(loginAction({ email }));
    event.preventDefault();
  };

  useEffect(() => {
    if (isSuccess && auth?.email) {
      if (lastUrl) {
        navigate(lastUrl);
      } else {
        navigate('/');
      }
    }
  }, [auth, isSuccess, lastUrl]);

  return (
    <section className="Login">
      <div className="Login__container">
        <h1>Inicio de sesión</h1>
        <form onSubmit={handleLogin}>
          <Input
            value={email}
            onChange={(event) => setEmail(event.target.value)}
          />
          <Button primary type="submit" onClick={() => {}}>
            Iniciar sesión
          </Button>
        </form>
      </div>
      {isLoading && <Loader />}
      {isError && <Error error={errorDetail} />}
    </section>
  );
};

export default Login;

  • actions.js
import { SET_SEARCH, SET_LAST_URL } from './types';

export const setSearchAction = (payload) => ({
  type: SET_SEARCH,
  payload,
});

export const setLastUrlAction = (payload) => ({
  type: SET_LAST_URL,
  payload,
});

  • reducer.js
import { SET_SEARCH, SET_LAST_URL } from './types';

export const initialState = {
  search: undefined,
  lastUrl: undefined,
};

// eslint-disable-next-line default-param-last
const navReducer = (state = initialState, action) => {
  switch (action.type) {
    case SET_SEARCH: {
      return {
        ...state,
        search: action.payload,
      };
    }
    case SET_LAST_URL: {
      return {
        ...state,
        lastUrl: action.payload,
      };
    }
    default:
      return state;
  }
};

export default navReducer;

  • selectors.js
import { createSelector } from '@reduxjs/toolkit';

export const selectNav = (state) => state.nav;

export const getSearch = createSelector(selectNav, (auth) => auth.search);
export const getLastUrl = createSelector(selectNav, (auth) => auth.lastUrl);

bueno mi solución fue usar un estado inicializado en un arreglo vacío en el auth.jsx, allí mismo en la función login verifico si el arreglo está vacío después de hacer login lo redirijo a home, si el arreglo no está vacío hago un window.history.back() que me lleva a la última página vista. usando el use context llamo ese estado en cada página y con un useEffect cada vez que se renderice una página pongo dentro del arreglo el location.href. es decir si la persona llega directamente al login el arreglo está vacío y lo envía al home, si la persona llega a home y ver por ejemplo la página de blog y de allí pasa a loguearse el arreglo ya no está vacío y lo envía a la última página vista que en este caso sería blog!!!

Yo use navigate(-1) cuando se dispara la funcion del login, fue lo mas sencillo que encontre para mi codigo 😄

<code> 
import React from 'react’
import { useNavigate } from ‘react-router-dom’

export const Login = ({ name, setName, login, setLogin}) => {

const navigate = useNavigate()

const onLogin = (e) => {
e.preventDefault()
setLogin(!login)
navigate(-1)
}

return (
<>
<h3>Inserte su nombre</h3>
<form onSubmit={onLogin}>
<label>Nombre</label>
<input type=“text” value={name} onChange={e => setName(e.target.value)} />
<button type=“submit”>Login</button>
</form>
</>
)
}

location.state

Investigué sobre el hook useLocation y noté que en el objeto que devuelve hay una propiedad state.
Esa propiedad state puede ser manipulada desde varias funciones o componentes, como por ejemplo:

const location = useLocation()
const navigate = useNavigate()
navigate("/login", { 
	state: 
		{ prevPath: location.pathname } 
	}

ó…

<Navigate to="/login" state={ { prevPath: location.pathname } }

.
Esta propiedad state la recibo desde mi función de login, y cuando el usuario se loggea, verifico si viene desde una redirección que setteó un state con prevPath!

const login = (username, role) => {
		setUser({ username, role });

		if (state) {
			navigate(state.prevPath);
			delete state.prevPath;
			return;
		}
		navigate("/profile");
	};

Esa fue mi solución!

Espero haberme dado a entender

Costó, pero está terminado.

Cambios en auth.js

// ...
  const login = ({ username, locationAfterLogin }) => {
        const isModerator = moderators.find(mod => mod === username)
        const isSpellChecker = spellCheckers.find(sp => sp === username)
        const isEditor = editors.find(ed => ed === username)

        setUser({ username, isModerator, isEditor, isSpellChecker })
        navigate(locationAfterLogin)
    }
// ...
export function AuthRoute({ children }) {
    const auth = useAuth()
    const { pathname } = useLocation()

    if (!auth.user)
        return <Navigate to="/login" state={{ locationAfterLogin: pathname }} />
    return children
}
// ...

Cambios en LoginPage.js

// ...
  const { state } = useLocation()
    let locationAfterLogin
    state
        ? (locationAfterLogin = state.locationAfterLogin)
        : (locationAfterLogin = '/profile')

    if (auth.user) return <Navigate to="/profile" replace />

    const login = e => {
        e.preventDefault()
        auth.login({ username, locationAfterLogin })
    }
// ...

Esta es mi solución usando useLocation y useRef

Aca dejo el reto:

Pero antes de verlo te reto a pienses un rato. Yo te doy otra pista. Tenes que usar el estado de la navegacion.

=====

=====

=====

=====

=====

=====

// App.js
<Route path="/" element={<RequireAuth />}>
	<Route index element={<Home />} />
	{/* Mis rutas */}
</Route>
<Route path="auth" element={<Unauthenticated />}>
	<Route path="login" element={<Login />} />
	{/* Mis rutas signup, login, forgotPassword, etc*/}
</Route>
// RequireAuth.js
import { useLocation, Navigate, Outlet } from 'react-router-dom'
const RequireAuth = () => {
  const location = useLocation()
  return isAuthenticated ? (
    <Layout>
       <Outlet />
    </Layout>
    : (
      <Navigate to="/auth/login" state={{ from: location }}  />
    )
}

export default RequireAuth
// Unauthenticated.js
import { Outlet, useLocation } from 'react-router-dom'
const location = useLocation()
const from = location.state?.from?.pathname || '/'
return isAuthenticated ? (
    <Navigate to={from} replace />
  ) : (
    <Outlet />
  )

Para resolver el reto usé un useLocation y el buen useEffect. En mi caso tengo un custom hook llamado useAuth.jsx. Dentro del hook cree un estado para actualizar la localización dependiendo de donde esté el usuario (/home, /blog /blopost/:plug).

en el return mando la función upDateLocation y todos estos datos los recibo en mi componente <Layout> el cual contiene las rutas hijas con un <Outlet> (excepto el <Menu> que está por fuera)

Gracias al context que admite <Outlet> estoy compartiendo los datos com todos los demás componentes. La idea ahora es actualizar con setLocation dependiendo en que ruta estuviere el usuario de esta manera:

Aquí estoy en el componente <BlogPage> e intento actualizar el estado location que está en mi custom hook useAuth enviando por parámetro la ruta con el location.pathname que me ofrece useLocation(). Pero obtengo el siguiente error.

Dice que no puedo actualizar al componente layout mientras trato de renderizar por primera vez al componente blogpage. Espero se pueda apreciar en la foto.
Entonces lo pude corregir de esta manera:

Uso un useEffect que se active siempre luego de renderizarse mi componente blogpage y con eso no tuve más ese error. Para terminar así es como desde el componente <LoginPage> hago la navegación

Le mando como parámetro a navigate el estado actual con la última ruta visitada por el usuario. De esta manera usé el useEffect en los demás componentes y de la misma forma comparto el estado con "const [authData] = useOutletContext();". La aplicación funciona haciéndolo así pero claro que no sé si será la mejor idea. Bueno pero se logró el reto XD.

Mi solucion no es nada elegante. Elimine el componente Navigate y lo reemplace con el hook useNavigate.

function AuthRoute({ children }) {
    const auth = useAuth();
    const notAuthorized = () => {
	return auth.navigate('/login')
}
    if(!auth.user) {

        return (
            <>
            <p>Deberías iniciar sesión primero</p>
            <button
             onClick={notAuthorized}
             >Loging</button>
            </>
        )
    }
    return children
}

En la funcion login de AuthProvider cambie navigate(’/profile’)
a navigate(-1) asi:

const login = ({ username }) => {
        const staff = users.find(user => user.name === username);
        staff !== undefined ?
        setUser(staff) :
        setUser({ name: username, role: roles.visitor });
        navigate(-1);
    }

Además también quite el condicional en LoginPage

 // if(auth.user) {
    //     return <Navigate to={'/profile'}/>
       
    // }

Mi solucion fue:

  1. Declarar la variable “previousURL” al inicio.
  2. Inicializarla en la funcion “AuthRoute”.
  3. En la funcion “login”, si la variable es distinta de vacio se navega a esa pagina, en caso contrario se va a la pagina de profile
import React, { createContext, useContext, useState } from "react";
import { Navigate, useNavigate, useLocation } from "react-router-dom";
import BlogData from "./BlogData";

const adminList = ["Irisval", "RetaxMaster", "freddier"];
let previousURL = '';

const AuthContext = createContext();

function AuthProvider({ children }) {
  const navigate = useNavigate();
  const [data, setData] = useState(BlogData);
  const [user, setUser] = useState(null);

  const login = ({ username }) => {
    const isAdmin = adminList.find((admin) => admin === username);
    setUser({ username, isAdmin });

    navigate(previousURL || "/profile");
  };

  const updateBlog = (idPost, input, value) => {
    let newPost = data.find((post) => post.id === idPost);
    newPost = { ...newPost, [input]: value };

    const newData = data.map((post) => {
      if (post.id === idPost) return newPost;
      return post;
    });
    setData(newData);
  };

  const deletePost = (idPost) => {
    const newData = data.filter((post) => post.id !== idPost);
    setData(newData);
    navigate("/");
  };

  const logout = () => {
    setUser(null);
    navigate("/");
  };

  const auth = { user, login, logout, data, updateBlog, deletePost };

  return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
}

function useAuth() {
  const auth = useContext(AuthContext);
  return auth;
}

function AuthRoute({ children }) {
  const auth = useContext(AuthContext);
  const { pathname } = useLocation();
  previousURL = pathname;

  if (!auth.user) {
    return <Navigate to="/login" replace/>;
  }

  return children;
}

export { AuthProvider, useAuth, AuthRoute };