I am trying to create basic authentication and route protection for pages in Next.Js, before I start connecting them to the backend (Express.Js).
However I am getting this error:
Uncaught (in promise) Error: Cancel rendering route
I have 2 layouts, Main Layout, which renders the PageHeader, SideBar and the content of the page, and Login layout, which only renders PageHeader and Login Form.
It tells me that the problem is in useUserSession.tsx, but I have no idea what is the error.
My useUserSession.tsx looks like this:
import { useRouter } from "next/router"
import { useCallback, useState } from "react"
const useUserSession = () => {
const[username, setUsername] = useState<any>()
const[userLoggedIn, setUserLoggedIn] = useState<any>()
const router = useRouter();
const login = useCallback(() => {
setUsername('Admin');
setUserLoggedIn(true);
router.push("/")
}, [])
const loggout = useCallback(() => {
setUsername('');
setUserLoggedIn(false);
router.push("/login")
}, [])
return {username, userLoggedIn, login, loggout }
}
export default useUserSession
And my userSession looks like this:
import React, { createContext } from 'react'
const userSession = createContext({
isLoggedIn: false,
username: String,
login: () => {
},
loggout: () => {
}
})
export default userSession;
My _app.tsx
import { useContext, useEffect } from 'react';
import { useRouter } from 'next/router';
export default function App({ Component, pageProps }: AppPropsWithLayout) {
const getLayout = Component.getLayout ?? ((page) => page)
const { username, userLoggedIn ,login, loggout } = useUserSession();
return getLayout(
<userSession.Provider value={{isLoggedIn: userLoggedIn, username, login: login, loggout}} >
<Component {...pageProps} />
</userSession.Provider>
)
}
My MainLayout.tsx
function MainLayout({children} : {children: ReactNode}) {
const { username, userLoggedIn ,login, loggout } = useUserSession();
return (
<userSession.Provider value={{isLoggedIn: userLoggedIn, username, login: login, loggout}} >
<PageHeader/>
<UserSideBar/>
<div className="pageContent">
{children}
</div>
</userSession.Provider >
)
}
export default MainLayout
My LoginLayout.tsx
function LoginRegisterLayout({children} : {children: ReactNode}) {
const { username, userLoggedIn ,login, loggout } = useUserSession();
return (
<userSession.Provider value={{isLoggedIn: userLoggedIn, username, login: login, loggout}} >
<PageHeader/>
{children}
</userSession.Provider>
)
}
export default LoginRegisterLayout
And this is my Login.tsx
const Login: PageWithLayout = () =>{
const errorMessagesClass = `${styles.disabled}`;
const userSessionData = useContext(userSession);
return (
<div className={styles.LoginPage}>
<div className={styles.LoginForm}>
<h1>Login</h1>
<form>
<div className={styles.LoginFormInputs}>
<div>
<label htmlFor='usernameInput'>Username or Email: </label>
<input type={'text'} id='usernameInput'></input>
</div>
<div>
<label htmlFor='passwordInput'>Password: </label>
<input type={'password'} id='passwordInput'/>
</div>
</div>
<div className={`${styles.ErrorMessages} ${errorMessagesClass}`}>
</div>
<button type='submit' >Login in to your account</button>
<Link href="#" onClick={userSessionData.login}>login</Link>
</form>
<p className={styles.FormFooter}>Don't have a profile ?
<Link href='/register'>Create one now
<i className="fa-solid fa-user-plus"></i>
</Link>
</p>
</div>
</div>
)
}
Login.getLayout = function getLayout(page: ReactElement){
return(
<LoginRegisterLayout>
{page}
</LoginRegisterLayout>
)
}
export default Login
And this is my Index.tsx that the user can visit only when he has logged in.
const Home: PageWithLayout = () => {
const userSessionData = useContext(userSession);
const router = useRouter();
useEffect(() => {
if(!userSessionData.isLoggedIn){
router.push("/login");
}
}, [])
return (
<>
<Head>
<title>Kiwi | Home page</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className={styles.NewPostContainer} id="NewPostContainer">
<div className={styles.NewPostFormContainer} id="textAreaContainer">
<Image src='/images/user_icon.png' width="512" height="512" alt='User profile image'/>
<textarea id='newPostForm' onChange={changeFormHeight}/>
</div>
<div className={styles.NewPostToolbar}>
<p></p>
<button>Post</button>
</div>
</div>
<div className='ContentContainer'>
<Post/>
</div>
</>
)
}
Home.getLayout = function getLayout(page: ReactElement){
return(
<MainLayout>
{page}
</MainLayout>
)
}
export default Home
Related
By using console.log(responseData.places) I have checked the fetching works since I am using a hook for this and seems to work fine until I setLoadedPlaces with is the method I use to update the loadedPlaces which I later use to get the values to fill the frontend part of the website.
This is the output I get from this console.log I did and the values are correct.
[{…}]
0: address: "sis se puede
busrespect: 'tu puedes',
creator: "6384e2f543f63be1c560effa"
description: "al mundial"
id: "6384e30243f63be1c560f000"
image:"https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/Empire_State_Building_%28aerial_view%29.jpg/400px-Empire_State_Building_%28aerial_view%29.jpg"location: {lat: -12.086158, lng: -76.898019}
title: "Peru"
__v: 0
_id: "6384e30243f63be1c560f000"[[Prototype]]:
Objectlength: 1[[Prototype]]: Array(0)
So after this this the code I have in the frontend (SINCE the backend works properly) Let me know if you have any doubts with this logic
This is UserPlaces.js
import React, {useState, useEffect } from 'react';
import PlaceList from '../components/PlaceList';
import { useParams } from 'react-router-dom';
import { useHttpClient } from '../../shared/hooks/http-hook';
import ErrorModal from '../../shared/components/UIElements/ErrorModal';
import LoadingSpinner from '../../shared/components/UIElements/LoadingSpinner';
const UserPlaces = () => {
const {loadedPlaces, setLoadedPlaces} = useState();
const {isLoading, error, sendRequest, clearError } = useHttpClient();
const userId = useParams().userId;
useEffect(() => {
const fetchPlaces = async () => {
try {
const responseData = await sendRequest(
`http://localhost:5000/api/places/user/${userId}`
);
console.log(responseData.bus_stops)
setLoadedPlaces(responseData.bus_stops);
} catch (err) {}
};
fetchPlaces();
}, [sendRequest, userId]);
return (
<React.Fragment>
<ErrorModal error={error} onClear={clearError} />
{isLoading && (
<div className="center">
<LoadingSpinner />
</div>
)}
{!isLoading && loadedPlaces && <PlaceList items={loadedPlaces} />}
</React.Fragment>
);
};
export default UserPlaces;
This is Place-List.js
import React from 'react';
import "./PlaceList.css"
import Card from '../../shared/components/UIElements/Card'
import PlaceItem from './PlaceItem';
import Button from '../../shared/components/FormElements/Button';
const PlaceList = props => {
if (props.items.length === 0) {
return (
<div className='place-list-center'>
<Card>
<h2>No bus stops available. Be the first one to create one!</h2>
<Button to='/places/new'> Create Bus Stop </Button>
</Card>
</div>
);
}
return (
<ul className="place-list">
{props.items.map(bus_stops => (
<PlaceItem
key={bus_stops.id}
id={bus_stops.id}
image={bus_stops.image}
title={bus_stops.title}
busrespect={bus_stops.busrespect}
description={bus_stops.description}
address={bus_stops.address}
creatorId={bus_stops.creator}
coordinates={bus_stops.location}
/>
))}
</ul>
);
};
export default PlaceList;
This is PlaceItem.js
import React, { useState } from 'react';
import { useContext } from 'react';
import Card from '../../shared/components/UIElements/Card';
import Button from '../../shared/components/FormElements/Button';
import Modal from '../../shared/components/UIElements/Modal';
import Map from '../../shared/components/UIElements/Map';
import {AuthContext} from '../../shared//context/auth-context'
import "./PlaceItem.css";
const PlaceItem = props => {
const auth = useContext(AuthContext);
const [showMap, setShowMap] = useState(false);
const [showConfirmModal, setShowConfirmModal] = useState(false);
const openMapHandler = () => setShowMap(true);
const closeMapHandler = () => setShowMap(false);
const showDeleteWarningHandler = () => {
setShowConfirmModal(true);
};
const cancelDeleteHandler = () => {
setShowConfirmModal(false);
};
const confirmDeleteHandler = () => {
setShowConfirmModal(false); //when clicked close the new Modal
console.log('DELETING...');
};
return (
<React.Fragment>
<Modal show={showMap}
onCancel={closeMapHandler}
header={props.address}
contentClass="place-item__modal-content"
footerClass="place-item__modal-actions"
footer={<Button onClick={closeMapHandler}>Close </Button>}
>
<div className='map-container'>
<Map center={props.coordinates} zoom={16}/> {/* Should be props.coordinates but we writing default data for now until geocoding solved. */}
</div>
</Modal>
<Modal
show={showConfirmModal}
onCancel={cancelDeleteHandler}
header="Are you entirely sure?"
footerClass="place-item__modal-actions"
footer={
<React.Fragment>
<Button inverse onClick={cancelDeleteHandler}>
CANCEL
</Button>
<Button danger onClick={confirmDeleteHandler}>
DELETE
</Button>
</React.Fragment>
}
>
<p>
Do you want to proceed and delete this place? Please note that it
can't be undone thereafter.
</p>
</Modal>
<li className='"place=item'>
<Card className="place-item__content">
<div className='place-item__image'>
<img src={props.image} alt={props.title}/>
</div>
<div className='place-item__info'>
<h2>{props.title}</h2>
<h3>{props.address}</h3>
<p>{props.description}</p>
<p>{props.busrespect}</p>
</div>
<div className='place-item__actions'>
<Button inverse onClick={openMapHandler}> VIEW ON MAP</Button>
{auth.isLoggedIn && (<Button to={`/places/${props.id}`}> EDIT</Button> )}
{auth.isLoggedIn &&<Button danger onClick={showDeleteWarningHandler}> DELETE </Button>}
</div>
</Card>
</li>
</React.Fragment>
);
};
export default PlaceItem;
This is auth-context:
import { createContext } from "react";
export const AuthContext = createContext({
isLoggedIn: false,
userId: null,
login: () => {},
logout: () => {}});
This is is Modal.js
import React from 'react';
import ReactDOM from 'react-dom';
import Backdrop from './Backdrop';
import { CSSTransition } from 'react-transition-group';
import './Modal.css';
const ModalOverlay = props => {
const content =(
<div className={`modal ${props.className}`} style = {props.style}>
<header className={`modal__header ${props.headerClass}`}>
<h2>{props.header}</h2>
</header>
<form
onSubmit={
props.onSubmit ? props.onSubmit : event => event.preventDefault()
}
>
<div className={`modal__content ${props.contentClass}`}>
{props.children}
</div>
<footer className={`modal__content ${props.footerClass}`}>
{props.footer}
</footer>
</form>
</div>
);
return ReactDOM.createPortal(content, document.getElementById('modal-hook'));
};
const Modal = props => {
return (
<React.Fragment>
{props.show && <Backdrop onClick={props.onCancel} />}
<CSSTransition in={props.show}
mountOnEnter
unmountOnExit
timeout={200}
classNames="modal"
>
<ModalOverlay {...props}/>
</CSSTransition>
</React.Fragment>
);
};
export default Modal;
Also Trust the routing is correct since I have checked it already and I am just wondering if the logic in REACT with loadedPlaces, PlaceItema and PlaceList makes sense and it working. Let me know please. It will be really helpful.
Summary: Not getting any error but no visual data appears in the scren just the header of my website and the background (rest is empty) even though logic is functional.
const {loadedPlaces, setLoadedPlaces} = useState();
change the above line to
const [loadedPlaces, setLoadedPlaces] = useState();
I am creating quote app using react,graphql,apollo-client,mongodb atlas.
I am storing authentication token in the localStorage when user login and remove it when user logout.I have a profile page in which it shows the detail of loggedin user.
The issue is that when i logout and then login another user,it shows the detail of previous user and when i refresh the page then it shows the current user details.so, how can i get the new user's detail without refreshing the page?
CreateQuote component :
import { useMutation } from "#apollo/client";
import React, { useState } from "react";
import { CREATE_QUOTE } from "../gqloperations/mutations";
function CreateQuote() {
const [quote, setQuote] = useState("");
const [createQuote, { loading, error, data }] = useMutation(CREATE_QUOTE, {
refetchQueries: ["getAllQuotes", "getMyProfile"],
});
data && console.log(data);
const handleSubmit = () => {
createQuote({
variables: {
name: quote,
},
});
};
if (loading) {
return <h1>loading...</h1>;
}
return (
<div className="container">
{error && <div className="red card-panel">{error.message}</div>}
{data && <div className="green card-panel">{data.quote}</div>}
<form onSubmit={handleSubmit}>
<input
type="text"
value={quote}
onChange={(e) => setQuote(e.target.value)}
placeholder="write your quote"
/>
<button className="btn green" type="submit" onClick={handleSubmit}>
create
</button>
</form>
</div>
);
}
export default CreateQuote;
Login Component :
import React, { useState } from "react";
import { useNavigate } from "react-router";
import { Link } from "react-router-dom";
import { LOGIN_USER } from "../gqloperations/mutations";
export default function Login() {
const [formData, setFormData] = useState({ email: "", password: "" });
const [signinUser, { loading, data, error }] = useMutation(LOGIN_USER, {
refetchQueries: ["getMyProfile"],
onCompleted(data) {
localStorage.setItem("token", data.user.token);
navigate("/");
},
});
const navigate = useNavigate();
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
signinUser({
variables: {
userSignin: formData,
},
});
setFormData({ email: "", password: "" });
};
if (loading) return <h1>Loading</h1>;
// if (data) {
// localStorage.setItem("token", data.user.token);
// navigate("/");
// }
return (
<>
{error && <div className="red card-panel">{error.message}</div>}
{data && data.signupUser && (
<div className="green card-panel">
{data.user.token} login successfully
</div>
)}
<div>
<h1>Login</h1>
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="email"
/>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="password"
/>
<Link to="/login">
<p>Don't have an account ?</p>
</Link>
<button type="submit" className="green btn">
Login
</button>
</form>
</div>
</>
);
}
Profile component:
import React, { useEffect } from "react";
import { useQuery } from "#apollo/client";
import { GET_MY_PROFILE } from "../gqloperations/queries";
import { useNavigate } from "react-router";
export default function Profile() {
const { loading, error, data, refetch } = useQuery(GET_MY_PROFILE);
useEffect(() => {
refetch();
}, [data]);
const navigate = useNavigate;
if (!localStorage.getItem("token")) {
navigate("/login");
}
if (loading) return <h1>Loading...</h1>;
if (error) console.log(error);
return (
<div className="container">
<div className="center-align">
<img
style={{ border: "2px solid black", marginTop: "10px" }}
className="circle"
src={`https://robohash.org/${data.user.firstName}.png?size=200x200`}
alt=""
/>
<h5>
{data.user.firstName} {data.user.lastname}
</h5>
<h5>Email-{data.user.email}</h5>
</div>
<h3>Your quotes</h3>
{data.user.quotes.map((quote) => {
return (
<blockquote>
<h6>{quote.name}</h6>
</blockquote>
);
})}
</div>
);
}
Navbar component: (contains Logout button)
import React from "react";
import { useNavigate } from "react-router";
import { Link } from "react-router-dom";
function NavBar() {
const token = localStorage.getItem("token");
const navigate = useNavigate();
return (
<div>
<nav>
<div className="nav-wrapper">
<Link to="/" className="brand-logo left">
Quotes
</Link>
<ul id="nav-mobile" className="right">
{token ? (
<>
<li>
<Link to="/profile">Profile</Link>
</li>
<li>
<Link to="/create">Create</Link>
</li>
<li>
<button
className="red btn"
onClick={() => {
localStorage.removeItem("token");
navigate("/login");
}}
>
Logout
</button>
</li>
</>
) : (
<>
<li>
<Link to="/login">Login</Link>
</li>
<li>
<Link to="/signup">Signup</Link>
</li>
</>
)}
</ul>
</div>
</nav>
</div>
);
}
export default NavBar;
Edit :
after slothOverlord's answer ,the updates are as below:
Navbar component
import React from "react";
import { useNavigate } from "react-router";
import { Link } from "react-router-dom";
import { client } from "../index.js";
function NavBar() {
const token = localStorage.getItem("token");
const navigate = useNavigate();
return (
<div>
<nav>
<div className="nav-wrapper">
<Link to="/" className="brand-logo left">
Quotes
</Link>
<ul id="nav-mobile" className="right">
{token ? (
<>
<li>
<Link to="/profile">Profile</Link>
</li>
<li>
<Link to="/create">Create</Link>
</li>
<li>
<button
className="red btn"
onClick={async() => {
localStorage.removeItem("token");
await client.resetStore();
navigate("/login");
}}
>
Logout
</button>
</li>
</>
) : (
<>
<li>
<Link to="/login">Login</Link>
</li>
<li>
<Link to="/signup">Signup</Link>
</li>
</>
)}
</ul>
</div>
</nav>
</div>
);
}
export default NavBar;
Login component :
import { useMutation } from "#apollo/client";
import React, { useState } from "react";
import { useNavigate } from "react-router";
import { Link } from "react-router-dom";
import { LOGIN_USER } from "../gqloperations/mutations";
export default function Login() {
const [formData, setFormData] = useState({ email: "", password: "" });
const [signinUser, { loading, data, error ,client}] = useMutation(LOGIN_USER, {
refetchQueries: ["getMyProfile"],
onCompleted(data) {
localStorage.setItem("token", data.user.token);
client.clearStore();
navigate("/");
},
});
const navigate = useNavigate();
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
signinUser({
variables: {
userSignin: formData,
},
});
setFormData({ email: "", password: "" });
};
if (loading) return <h1>Loading</h1>;
// if (data) {
// localStorage.setItem("token", data.user.token);
// navigate("/");
// }
return (
<>
{error && <div className="red card-panel">{error.message}</div>}
{data && data.signupUser && (
<div className="green card-panel">
{data.user.token} login successfully
</div>
)}
<div>
<h1>Login</h1>
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="email"
/>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="password"
/>
<Link to="/login">
<p>Don't have an account ?</p>
</Link>
<button type="submit" className="green btn">
Login
</button>
</form>
</div>
</>
);
}
other components are same as above.
You can clear the store of apollo cache.
either use client.clearStore() /Reset the store
or client.resetStore() /Reset the store and Refetch any active queries
const [signinUser, { loading, data, error, client }] = useMutation(LOGIN_USER, {
refetchQueries: ["getMyProfile"],
onCompleted(data) {
localStorage.setItem("token", data.user.token);
client.clearStore //Make sure you reset it AFTER you are done adding/clearing tokens
navigate("/");
},
I also recommend you to use client.clearStore on your LOGOUT mutation, not on login.
Note that resetStore is async and you should await it.
More info at apollo docs: https://www.apollographql.com/docs/react/caching/advanced-topics/
If you are using a normal function, you can import the cache from your Apollo cache (Where you exported it), and clear the store that way.
import {cache} from '../path/to/your/apollo/cache'
//...
<button
className="red btn"
onClick={() => {
localStorage.removeItem("token");
client.resetStore()
navigate("/login");
}}
>
Logout
</button>
I have a navbar component that has a submenu. Once logged in, the submenu of the navbar should change.
I used the hook useContext, but it doesn't refresh the navbar component when the user logs. It works fine when I refresh the page.
Where is my code problem?
APP COMPONENT
import React, { useState, useEffect } from "react";
import logo from "./assets/logoBusca.png";
import "./App.css";
import { Route } from "wouter";
import Home from "./components/Home";
import Login from "./components/Login";
import Post from "./components/Post";
import NavbarUser from "./components/NavbarUser";
import { AuthContext } from "./context/AuthContext";
import logic from "../src/logic";
function App() {
const [user, setUser] = useState(false);
useEffect(() => {
(async () => {
const loggedIn = await logic.isUserLoggedIn;
if (loggedIn) setUser(true);
})();
}, [user]);
return (
<AuthContext.Provider value={user}>
<div className="App">
<header className="App-header">
<div className="images">
<div className="logo">
<a href="/">
<img src={logo} alt="logo" />
</a>
</div>
<div className="user_flags">
<NavbarUser />
</div>
</div>
</header>
<Route path="/">
<Home />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path="/nuevabusqueda">
<Post />
</Route>
</div>
</AuthContext.Provider>
);
}
export default App;
NAVBAR COMPONENT
import React, { useContext } from "react";
import userIcon from "../../assets/userIcon.png";
import { AuthContext } from "../../context/AuthContext";
export default function NavbarUser() {
const isAuthenticated = useContext(AuthContext);
return (
<>
{!isAuthenticated ? (
<div className="navbar-item has-dropdown is-hoverable">
<img src={userIcon} alt="user" />
<div className="navbar-dropdown">
<a href="/login" className="navbar-item" id="item_login">
Login
</a>
<hr className="navbar-divider" />
<a href="/registro" className="navbar-item" id="item_register">
Registro
</a>
</div>
</div>
) : (
<div className="navbar-item has-dropdown is-hoverable">
<img src={userIcon} alt="user" />
<div className="navbar-dropdown">
<a href="/datos" className="navbar-item" id="item_login">
Perfil
</a>
<hr className="navbar-divider" />
<a href="/user" className="navbar-item" id="item_register">
Logout
</a>
</div>
</div>
)}
</>
);
}
CONTEXT COMPONENT
import { createContext } from "react";
export const AuthContext = createContext();
LOGIC COMPONENT
import buscasosApi from "../data";
const logic = {
set userToken(token) {
sessionStorage.userToken = token;
},
get userToken() {
if (sessionStorage.userToken === null) return null;
if (sessionStorage.userToken === undefined) return undefined;
return sessionStorage.userToken;
},
get isUserLoggedIn() {
return this.userToken;
},
loginUser(email, password) {
return (async () => {
try {
const { token } = await buscasosApi.authenticateUser(email, password);
this.userToken = token;
} catch (error) {
throw new Error(error.message);
}
})();
},
};
export default logic;
I think Login component doesn't call setUser(true)
Here's an example how it might work.
const { useState, useEffect, createContext, useContext } = React;
const AuthContext = createContext();
const Login = () => {
const [isAuthenticated, setAuth] = useContext(AuthContext);
const onClick = () => setAuth(true);
return <button disabled={isAuthenticated} onClick={onClick}>Login</button>
}
const App = () => {
const [isAuthenticated] = useContext(AuthContext);
return <div>
<Login/>
<button disabled={!isAuthenticated}>{isAuthenticated ? "Authenticated" : "Not Authenticated"}</button>
</div>;
}
const AuthProvider = ({children}) => {
const [isAuthenticated, setAuth] = useState(false);
return <AuthContext.Provider value={[isAuthenticated, setAuth]}>
{children}
</AuthContext.Provider>;
}
ReactDOM.render(
<AuthProvider>
<App />
</AuthProvider>,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
It seems to me that you are not updating state. Initially your state is false. After first render, your effect is fired and state is again set to false. After that your effect don't run anymore, since it depends on state that it should change. No state change - no effect - no state change again. Also, if state will change, it will trigger effect, but you will have no need for this.
Your goal here is to build a system that will:
Check current status on load
Update (or recheck) status when changed
To do this, you need some way to asynchronously send messages from logic to react. You can do this with some kind of subscription, like this:
const logic = {
set userToken(token) {
sessionStorage.userToken = token;
},
get userToken() {
if (sessionStorage.userToken === null) return null;
if (sessionStorage.userToken === undefined) return undefined;
return sessionStorage.userToken;
},
get isUserLoggedIn() {
return this.userToken;
},
loginUser(email, password) {
return (async () => {
try {
const { token } = await buscasosApi.authenticateUser(email, password);
this.userToken = token;
this.notify(true);
} catch (error) {
throw new Error(error.message);
}
})();
},
subscribers: new Set(),
subscribe(fn) {
this.subscribers.add(fn);
return () => {
this.subscribers.remove(fn);
};
},
notify(status) {
this.subscribers.forEach((fn) => fn(status));
},
};
function useAuthStatus() {
let [state, setState] = useState("checking");
let setStatus = useCallback(
(status) => setState(status ? "authenticated" : "not_authenticated"),
[setState]
);
useEffect(function () {
return logic.subscribe(setStatus);
}, []);
useEffect(function () {
setStatus(logic.isUserLoggedIn);
}, []);
return state;
}
Notice, that now there is three possible states - 'checking', 'authenticated' and 'not_authenticated'. It is more detailed and will prevent some errors. For example if you would want to redirect user to login page when they are not authenticated.
I have small problem with React Router and redirecting. After i have created a admin protected route, I want the user to be redirected to "user dashboard" after login. But my issue is that redirect is not working.
All is happening like this:
My Navigation componente, but this one i think is okay:
import React from 'react';
import { Link, withRouter } from 'react-router-dom';
import { signOut, isAuthUser } from '../../../utils/utils';
const isActive = (history, path) => {
if (history.location.pathname === path) {
return { color: '#ff9900' };
} else {
return { color: '#ffffff' };
}
};
const Navigation = ({ history }) => {
return (
<nav>
<ul className='nav nav-tabs bg-primary'>
<li className='nav-item'>
<Link className='nav-link' style={isActive(history, '/')} to='/'>
Home
</Link>
<Link
className='nav-link'
style={isActive(history, '/user/dashboard')}
to='/user/dashboard'
>
Dashboard
</Link>
{!isAuthUser() && (
<div>
<Link
className='nav-link'
style={isActive(history, '/signup')}
to='/signup'
>
Signup
</Link>
<Link
className='nav-link'
style={isActive(history, '/signin')}
to='/signin'
>
Signin
</Link>
</div>
)}
{isAuthUser() && (
<Link
className='nav-link'
style={isActive(history, '/signout')}
onClick={() => signOut()}
to='/'
>
Sign Out
</Link>
)}
</li>
</ul>
</nav>
);
};
export default withRouter(Navigation);
My app.js with Routes
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import MainLayout from '../src/components/layout/MainLayout/MainLayout';
import Signup from './components/views/Signup/Signup';
import Signin from './components/views/Signin/Signin';
import Home from './components/views/Home/Home';
import PrivateRoute from './components/common/ProvateRoute/PrivateRoute';
import UserDashboard from './components/views/UserDashboard/UserDashboard';
function App() {
return (
<BrowserRouter>
<MainLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/signin' component={Signin} />
<Route exact path='/signup' component={Signup} />
<PrivateRoute exact path='/user/dashboard' component={UserDashboard} />
</Switch>
</MainLayout>
</BrowserRouter>
);
}
export default App;
My utils.js with some helper functions
export const signOut = () => {
if (typeof window !== 'undefined') {
localStorage.removeItem('jwt');
return fetch('http://localhost:8000/api/signout', { method: 'GET' }).then(res => {
console.log('signout', res);
});
}
};
export const authenticateUser = data => {
if (typeof window !== 'undefined') {
localStorage.setItem('jwt', JSON.stringify(data));
}
};
//check if user is auth and there is jwt item in localstorage. menu render
export const isAuthUser = () => {
if (typeof window == 'undefined') {
return false;
}
if (localStorage.getItem('jwt')) {
return JSON.parse(localStorage.getItem('jwt'));
} else {
return false;
}
};
So those looks okay in my opinion, but still i decided to post those here.
As all things that are most related are in my tow files: UserDashboard and Signin
My Signin.js looks like this:
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import axios from 'axios';
import Layout from '../../layout/Layout/Layout';
import { authenticateUser, isAuthUser } from '../../../utils/utils';
class Signin extends Component {
state = {
formData: {
email: '',
password: '',
},
userRedirect: false,
};
onChange = e => {
const { formData } = this.state;
//assign form data to new variable
let newFormData = { ...formData };
newFormData[e.target.name] = e.target.value;
this.setState({
formData: newFormData,
});
};
signIn = user => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
axios
.post('http://localhost:8000/api/signin', user, config)
.then(res => authenticateUser(res.data));
this.setState({
formData: { email: '', password: '' },
userRedirect: true,
});
};
onSubmit = e => {
const { password, email } = this.state.formData;
e.preventDefault();
this.signIn({ email, password });
};
signInForm = (email, password) => (
<form onSubmit={this.onSubmit}>
<div className='form-group'>
<label className='text-muted'>Email</label>
<input
type='email'
name='email'
value={email}
onChange={this.onChange}
className='form-control'
></input>
</div>
<div className='form-group'>
<label className='text-muted'>Password</label>
<input
type='password'
name='password'
minLength='6'
value={password}
onChange={this.onChange}
className='form-control'
></input>
</div>
<button className='btn btn-primary'>Submit</button>
</form>
);
redirecUser = () => {
const { userRedirect } = this.state;
const { user } = isAuthUser();
if (userRedirect === true) {
if (user && user.role === 1) {
return <Redirect to='/admin/dashboard' />;
} else {
return <Redirect to='/user/dashboard' />;
}
}
};
render() {
const { email, password } = this.state.formData;
return (
<Layout
title='Signin'
description='Login to your account'
className='container col-md-8 offset-md-2'
>
{this.signInForm(email, password)}
{this.redirecUser()}
</Layout>
);
}
}
export default Signin;
Here I am rendering all with signInForm and passing all data that i want with signIn(). From this i get user data: _id, email, password, role and token. This is sent to local storage.
Based on that what i get i want admin dashboard or user dashboard.
I have now olny user Dashboard
import React from 'react';
import { isAuthUser } from '../../../utils/utils';
import Layout from '../../layout/Layout/Layout';
const UserDashboard = () => {
const {
payload: {
user: { name, email, role },
},
} = isAuthUser();
return (
<Layout
title='User Dashboard'
description={`Wlecome ${name}`}
className='container col-md-8 offset-md-2'
>
<div className='card mb-5'>
<h3 className='card-header'>User information</h3>
<ul className='list-group'>
<li className='list-group-item'>{name}</li>
<li className='list-group-item'>{email}</li>
<li className='list-group-item'>
{role === 1 ? 'Admin' : 'Registered User'}
</li>
</ul>
</div>
<div className='card'>
<h3 className='card-header'>Purchase history</h3>
<ul className='list-group'>
<li className='list-group-item'>History</li>
</ul>
</div>
</Layout>
);
};
export default UserDashboard;
I have created PrivateRoute component based on documentation
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isAuthUser } from '../../../utils/utils';
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
isAuthUser() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/signin', state: { from: props.location } }} />
)
}
/>
);
export default PrivateRoute;
I do get all data in local storage, but after signin user is not redirected
Thanks for any help
Since you are trying to redirect from a function in your Signin.js try using history api.
this.props.history.push('/admin/dashboard')
instead of
<Redirect to='/admin/dashboard' />;
import React, { Fragment, useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import { getPosts } from '../redux/actions/#posts';
import PostItem from '../components/posts/PostItem';
import CommentForm from '../components/posts/CommentForm';
import Comment from '../components/posts/Comment';
import '../styles/posts/postComponent.scss';
const Posts = ({
getPosts,
posts: { posts, isLoading },
isAuthenticated,
user
}) => {
useEffect(() => {
getPosts();
}, []);
const [show, toggleShow] = useState(false);
console.log(show);
const postsList = isLoading ? (
<div>posts are loading</div>
) : (
posts.map(post => {
return (
<div className='post'>
<PostItem
key={post._id}
auth={isAuthenticated}
user={user}
id={post._id}
title={post.title}
body={post.text}
author={post.name}
avatar={post.avatar}
date={post.date}
likes={post.likes}
comments={post.comments.map(comment => comment)}
toggleShow={toggleShow}
show={show}
/>
<CommentForm id={post._id} />
{post.comments.map(
comment =>
show && (
<Comment
key={comment._id}
comment={comment}
auth={isAuthenticated}
admin={user}
show={show}
/>
)
)}
</div>
);
})
);
return (
<Fragment>
<Link to='/add-post'>add Post</Link>
<div>{postsList}</div>
</Fragment>
);
};
Posts.propTypes = {
getPosts: PropTypes.func.isRequired,
posts: PropTypes.object.isRequired,
isAuthenticated: PropTypes.bool.isRequired
};
const mapStateToProps = state => {
// console.log(state.posts.posts.map(post => post.likes));
// console.log(state);
return {
posts: state.posts,
isAuthenticated: state.auth.isAuthenticated,
user: state.auth.user
};
};
export default connect(mapStateToProps, { getPosts })(Posts);
import React, { Fragment } from 'react';
import '../../styles/posts/postComponent.scss';
const Comment = ({
comment: { user, avatar, name, date, text },
admin,
auth
}) => {
return (
<Fragment>
<div className='c-container'>
<div className='c-img-text'>
<img className='c-img' height={'40px'} src={avatar} />
<div className='c-nt'>
<a href='#' className='c-n'>
{name}
</a>
<span className='c-t'> {text}</span>
<i className='c-d'>{date}</i>
</div>
{auth && admin
? admin._id === user && <div className='c-toggle'>...</div>
: ''}
</div>
</div>
</Fragment>
);
};
export default Comment;
I have a list of posts stored in redux, and mapped through it to create components. Now each component has a some body and comments.
I want to show the comments only after onClick event .
Below is the code I have come up with , and on Click it is toggling all the comments of all the Components.How can I toggle comments of an individual Component.