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 鈥榬eact-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=鈥渢ext鈥 value={name} onChange={e => setName(e.target.value)} />
<button type=鈥渟ubmit鈥>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 鈥減reviousURL鈥 al inicio.
  2. Inicializarla en la funcion 鈥淎uthRoute鈥.
  3. En la funcion 鈥渓ogin鈥, 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 };