How to add a logout to my React navbar with JWT? - reactjs

I am quite new to react and JavaScript. I am trying to make a dynamic navigation bar that shows certain links if the user is logged in or logged out. I want to base it off of if the JWT token is present or not. I am stuck on how to implement 'checking if a user is logged in using tokens' into my Navbar function, so the boolean works to use one component if public or one component if logged in.
import "./navbar.css"
import NavLoggedIn from "./navLoggedIn"
import NavPublic from "./navPublic"
const Navbar = () => {
const token = window.localStorage.getItem("token");
return (
<>
{ token === null ? <NavPublic /> : <NavLoggedIn /> }
</>
);
};
export default Navbar;
import "./navbar.css"
const NavLoggedIn = () => {
return (
<>
<nav className="nav">
Acebook
<li>
profile
</li>
<li>
posts
</li>
<li>
logout
</li>
</nav>
</>
);
}
export default NavLoggedIn ;
import "./navbar.css"
const NavPublic = () => {
return (
<>
<nav className="nav">
Acebook
<ul>
<li>
signup
</li>
<li>
login
</li>
</ul>
</nav>
</>
);
}
export default NavPublic;

So the problem with the current approach is that the NavBar component only checks the token in localStorage when it mounts. It's "unaware" of subsequent changes to the authentication status.
I propose an alternative solution in which we use the Context API. We can start by introducing a new component:
import React, { createContext, useState } from 'react'
const AuthenticationContext = createContext({})
const AuthenticationProvider = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(false)
return (
<AuthenticationContext.Provider value={{isLoggedIn, setIsLoggedIn}}>
{children}
</AuthenticationContext.Provider>
)
}
export default AuthenticationProvider
This component has a named export, which exports a context and a default export which exports the provider. We can wrap the entire app in the provider. Assuming the root component is called <App/> , we can do this:
<AuthenticationProvider>
<App/>
</AuthenticationProvider>
Now, in any component in your app that you can access this context like so:
import React, { useContext } from 'react'
import { AuthenticationContext } from 'path/to/AuthenticationProvider'
const { isLoggedIn, setIsLoggedIn} = useContext(AuthenticationContext)
In your login function you would call setIsLoggedIn(true) and in your logout function you would call setIsLoggedIn(false). Within your NavBar component you would check the value of isLoggedIn. The NavBar component should "see" whenever the value changes and render the correct Nav component.

Related

How to make a "go back" button change state in home screen

So ive got a list of restaurant names (say fetched from an api). When I click on a restaurant name, I want it to link to a profile page for that specific restaurant, and this would set the text as "selected". And when I click "Go back" on that profile page to return to the home page, I want the that restaurant name to say "not selected".
So, if I click on the restaurant name, then in the profile page go back to the home page, the restaurant will show "unselected" since it was selected in the home page, then unselected in the profile page. However, if I click on the restaurant name, then instead of going back to the home page by clicking the "go back", I type in the url of the home page, it will show "selected".
I'm struggling with making it so when I click "Go back", the home page shows the restaurant name as having "unselected".
https://codesandbox.io/s/serene-williams-2snv1c?file=/src/App.js
(I would also appreciate if I could get the name of this sort of concept so I can look it up myself)
If I'm understanding the question correctly, you want to set some "selected" state, and only clear it if the link from the detail page is clicked.
You can create a React Context to hold and provide out the clickedRestaurants state and updater functions.
The idea here is to use the selectRestaurant handler when navigating "forward" to the details page, and use the deselectRestaurant handler only when the link from the details page back to the home page is clicked. If a user navigates to the home page using any other method, the restaurant won't be de-selected.
The localStorage API is used to persist state changes and initialize the state. The resolves persisting the selected restaurants state when the page is reloaded or a user directly mutates the URL in the address bar, i.e. like manually navigating back to "/".
RestaurantProvider
import { createContext, useContext, useEffect, useState } from "react";
export const RestaurantContext = createContext();
export const useRestaurantContext = () => useContext(RestaurantContext);
const RestaurantProvider = ({ children }) => {
const [clickedRestaurants, setClickedRestaurants] = useState(() => {
return JSON.parse(localStorage.getItem("clickedRestaurants")) ?? {};
});
useEffect(() => {
localStorage.setItem(
"clickedRestaurants",
JSON.stringify(clickedRestaurants)
);
}, [clickedRestaurants]);
const setRestaurantState = (id, selected) => {
setClickedRestaurants((ids) => ({
...ids,
[id]: selected
}));
};
const selectRestaurant = (id) => setRestaurantState(id, true);
const deselectRestaurant = (id) => setRestaurantState(id, false);
return (
<RestaurantContext.Provider
value={{ clickedRestaurants, selectRestaurant, deselectRestaurant }}
>
{children}
</RestaurantContext.Provider>
);
};
export default RestaurantProvider;
index.js - Import and wrap the application components with the RestaurantProvider component created above.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import RestaurantProvider from "./RestaurantProvider";
import App from "./App";
import Details from "./details";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<RestaurantProvider>
<App />
<Details />
</RestaurantProvider>
</StrictMode>
);
App - Import and use the useRestaurantContext hook to access the state and updater functions.
import "./styles.css";
import { Link, Route } from "wouter";
import data from "./data";
import { useRestaurantContext } from "./RestaurantProvider";
export default function App() {
const { clickedRestaurants, selectRestaurant } = useRestaurantContext();
return (
<Route path="/">
<div className="App">
{data.map((restaurant) => {
return (
<Button
key={restaurant}
restaurant={restaurant}
hasBeenClicked={clickedRestaurants[restaurant]}
setClicked={() => selectRestaurant(restaurant)}
/>
);
})}
</div>
</Route>
);
}
function Button({ restaurant, hasBeenClicked, setClicked }) {
return (
<>
<Link href={`/restaurant/${restaurant}`} onClick={setClicked}>
<button>{restaurant}</button>
</Link>
<p>
{restaurant} has {hasBeenClicked ? "" : "not "}been selected
</p>
</>
);
}
Details
import "./styles.css";
import { Link, Route } from "wouter";
import { useRestaurantContext } from "./RestaurantProvider";
export default function Details() {
const { deselectRestaurant } = useRestaurantContext();
return (
<div className="App">
<Route path="/restaurant/:name">
{(params) => {
const restaurant = decodeURI(params.name);
return (
<Link href="/" onClick={() => deselectRestaurant(restaurant)}>
{restaurant} Go back and unselect
</Link>
);
}}
</Route>
</div>
);
}
You'll need to define some sort of state if you want to be able to tell what has been clicked by the user and what hasn't. Here is one way to do it:
App.js
import "./styles.css";
import data from "./data";
import { Link, Route } from "wouter";
import { useState } from "react";
export default function App() {
const [clickedRestaurants, setClickedRestaurants] = useState([])
return (
<Route path="/">
<div className="App">
{data.map((restaurant) => {
return (
<Button
restaurant={restaurant}
hasBeenClicked={clickedRestaurants.includes(restaurant)}
setClicked={() => {
if (!clickedRestaurants.includes(restaurant)) {
setClickedRestaurants([...clickedRestaurants, restaurant])
}
}}
/>
);
})}
</div>
</Route>
);
}
function Button({ restaurant, hasBeenClicked, setClicked }) {
return (
<>
<Link href={`/restaurant/${restaurant}`} onClick={setClicked}>
<button>{restaurant}</button>
</Link>
<p>{restaurant} has {hasBeenClicked ? "" : "not "}been clicked</p>
</>
);
}

Using props with loops to pass multiple data from parent to child component

App.jsx
import React from "react";
import Navbar from "./Navbar";
function App() {
return (
<div>
<Navbar
navLink1="about-us"
navLink2="projects"
navLink3="services"
navLink4="blog"
/>
</div>
);
}
export default App;
Navbar.jsx
import React from "react";
export default function Navbar(props) {
const navHeader = "Logo";
const navItems = ["About Us", "Projects", "Services", "Blog"];
const navLinks = navItems.map(addNavLink);
function addNavLink(value, index) {
return "props.navLink" + String(index + 1);
}
return (
<nav>
<h3 className="nav-header">{navHeader}</h3>
{navItems.map((itemValue, itemIndex) =>
navLinks.map(
(linkValue, linkIndex) =>
itemIndex === linkIndex && (
<a
href={linkValue.replaceAll('"', '')}
className="nav-links"
key={itemIndex}
>
<div className="nav-items">{itemValue}</div>
</a>
)
)
)}
</nav>
);
}
I want to use props along with loops so that its name can be changed slightly to match each custom attributes of Navbar component. For instance:
props.navLink1
props.navLink2
props.navLink3
props.navLink4
I tried to use props by first storing them as a string and then removed the double inverted commas in order to use the functionality of props. However, this didn't work. I'm relatively new to React and I'm not sure if it's even possible but if you have any idea to accomplish this then help me out.
This is the output I'm looking for by using loops and props.

rendering component, after another distant component renders

In navigation menu app, down the component tree, there is a dropdown menu component DropdownMenu2, with menu items, which are <NavLinks> components. Every time an item is clicked, it points to one of the <Route>s in main App. Every <Route> is a page, containing Infofield component. So every time <NavLink> is clicked, Infofield is rendered.
My puzzle is: I need the HeaderLogo component be rendered, everytime Infofield is rendered (HeaderLogo contains animation). I failed when constructing useEffect hook in Infofield. That hook was intended to contain custom hook, producing a variable with changing state. That hook could be then lifted up to App, from there variable would be passed to HeaderLogo, inline to the key property. If that idea is legit, I'm experiencing difficulties with construction of custom hook inside of useEffect. Maybe (probably) there is a better way...
Apps most basic structure looks like this:
App
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
return (
<Router>
<div className="App">
<HeaderLogo />
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
NaviMain
import "./NaviMain.css";
import NaviMainButton from "./NaviMainButton";
import NaviMainButtonDrop2 from "./NaviMainButtonDrop";
const NaviMain = () => {
return (
<nav>
<ul>
<NaviMainButtonDrop2 />
</ul>
</nav>
)
}
export default NaviMain
NaviMainButtonDrop2
import DropdownMenu2 from "./DropdownMenu2";
const NaviMainButtonDrop2 = () => {
return (
<li>
<a>
title
</a>
<DropdownMenu2 />
</li>
)
}
export default NaviMainButtonDrop2
DropdownMenu2
import "./DropdownMenu.css"
import { NavLink } from "react-router-dom";
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
const DropdownMenu2 = () => {
return (
<div className=dropdown-holder-us>
{/* here menu unfolds */}
{MenuItemContentSchool.map((item) => {
return (
<NavLink
to={item.link}
className={(navData) => (navData.isActive ? "d-content-us active-style" : 'd-content-us')}
key={item.id}
>
{item.title}
</NavLink>
)
})}
</div>
)
}
export default DropdownMenu2
Info (one of the <Route>'s )
import InfoField from "../components/InfoField"
const Info = () => {
return (
<section className="intro-index">
<InfoField text={"welcome"} />
</section>
)
}
export default Info
HeaderLogo
import "./HeaderLogo.css";
const HeaderLogo = () => {
return (
<header>
<h1 className="head-main">learning curve</h1>
</header>
)
}
export default HeaderLogo
From what I can gather you simply want to "rerun" an animation in the HeaderLogo component when the path changes. Import and use the useLocation hook and use the pathname value as a React key on the header element with the animation to want to run when it mounts. The idea here is that when the React key changes, React will remount that element.
Example:
import { useLocation } from "react-router-dom";
import "./HeaderLogo.css";
const HeaderLogo = () => {
const { pathname } = useLocation();
return (
<header>
<h1 key={pathname} className="head-main">
learning curve
</h1>
</header>
);
};
export default HeaderLogo;
This is a classic job for a global state. You can declare a boolean state, i.e showHeader, and add conditional rendering to the tag.
The global state variable showHeader will be changed each time you click on a dropdown item, and in the App functional component you should listen for a change in this variable. (For example, using Redux, you'll use useSelector(state=>state.showHeader) in App.
For an example, this is the App component with conditional rendering for the HeaderLogo. In order for this to be useable, you need to build a Redux store and reducer functions. Read the official Redux docs for more
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useSelector } from 'react-redux';
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
const showHeader = useSelector(state=>state.showHeader)
return (
<Router>
<div className="App">
{showHeader ? <HeaderLogo /> : null}
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
</Router>

Fetch navigation from API in the next.js and render it at initial load of the page for better SEO

i am very new to React/NEXT.JS and i guess i am facing a issue where i want to fetch data for my dynamic navigation bar, which is available on all the pages. I am using context API to set the value and fetch 1 time at the initial render of the whole document. Then it should show navigation items directly.
Here is how i am trying to do it.
import { createContext, useState, useEffect } from "react";
import Layout from "../components/layout";
import "../styles/globals.css";
import BasicProviders from "../helpers/BasicProviders";
export const NavContext = createContext();
function MyApp({ Component, pageProps, navigationItems }) {
const [navitems, setNavitems] = useState(navigationItems);
return (
<NavContext.Provider value={{ navitems }}>
<Layout>
<Component {...pageProps} />
</Layout>
</NavContext.Provider>
);
}
MyApp.getInitialProps = async (ctx) => {
const res = await fetch("http://www.myserver.com/navigation");
console.log(res);
return { navigationItems: res };
};
export default MyApp;
Now i have folder structure like this:
- components (in root directory of next.js)
- layout.js
- /Header
- DesktopNav.js <<<<<--------------- here i want to send my value of **navitems**
- Header.js
This is how my DesktopNav.js is looking :
import Link from "next/link";
import React, { useContext } from "react";
import NavContext from "../../stores/NavContext";
import DesktopSubMenuLoop from "./DesktopSubMenuLoop";
export default function DesktopNav() {
const { navitems } = useContext(NavContext);
return (
<nav>
<DesktopSubMenuLoop navitems={navitems}></DesktopSubMenuLoop>
</nav>
);
}
Somehow it is sending the data to the component. But everytime i click on the Navigation Link it reloads the complete page. Not sure how i can achieve this. For the time being i am using useEffect to do achieve the same. But that is not SEO compatible.
Problems need to be solved here:
API should be called once for whole page. As the navigation will stay same for all the pages.
Page routing should be as smooth as it is with static tags. Currently it reloads the page like a static HTML web page.
Possible solutions i though of:
is there any way i can fetch the navitems on _document.js and send to _app.js?
Calling the API on each page with getServerSideProps but that will make all of my pages non-static for internal pages as well at the build time.
Any solution i can get on this will be really helpful and thankful.
#juliomalves for you:
MainNavigation Loop currently i am using useEffect to display the navigation, but as per my understanding its not good for SEO.
You can check my loop here DesktopSubMenuLoop.js
import Link from "next/link";
import React from "react";
export default function DesktopSubMenuLoop({ navitems, isSubmenu = false }) {
return (
<ul className={isSubmenu ? "sub-menu-style" : ""}>
{navitems.map((item, index) => (
<li key={item.id}>
<Link href={item.slug !== "/" ? `/${item.slug}` : item.slug}>
<a>
{item.title}{" "}
{item.children && item.children.length > 0 && (
<i className="icon-arrow-down"></i>
)}
</a>
</Link>
{item.children && item.children.length > 0 && (
<DesktopSubMenuLoop
key={item.id}
navitems={item.children}
isSubmenu={true}
></DesktopSubMenuLoop>
)}
</li>
))}
</ul>
);
}

React - Show only the clicked user

In the following app, I'm accessing the random user API and show a list of 12 users.
App.js
import React, { useState } from 'react'
import UserList from './components/UserList'
const App = props => {
const [id, setID] = useState(null)
console.log(`Passed variable to App.js is: ` + id)
return (
<>
<UserList setID={setID} />
</>
)
}
export default App
UserList.js
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const UserList = ({ setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me/?results=12'
)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
return (
<ul>
{resources.map(item => (
<li key={item.name.first}>
<div>
<h2>{item.name.first} {item.name.last}</h2>
<button
onClick={() => setID(item.login.uuid)}
>
Details
</button>
</div>
</li>
))}
</ul>
)
}
export default UserList
The above code is working. But now I want that if I click on the button for any of those listed users, only that user get showed.
How can I do that?
The response JSON looks like this:
Easiest way would be to apply a filter on your ressources variable to only display the user with selected uuid.
To do that, first you need to share selected id with UserList component:
App.js
<UserList id={id} setID={setID} />
Then update UserList accordingly:
UserList.js
const UserList = ({ id, setID }) => {
return (
<ul>
{ resources
.filter(user => Boolean(id) ? user.login.uuid == id : true )
.map(item => (
<li key={item.name.first}>
<div>
<h2>{item.name.first} {item.name.last}</h2>
{ Boolean(id) ?
<button onClick={() => setID(null)}>
Hide
</button>
:
<button onClick={() => setID(item.login.uuid)}>
Details
</button>
}
</div>
</li>
)
}
</ul>
)
}
That way, you will only display the select user in you <ul>. To unselect your user, just call setID(null)
Show user profile instead of list
If that solution work to filter your list, I guess you might want to adapt your page to show all details from your user. Next step would be to implement multi pages using react-router-dom with a url container your user uuid.
You can look at the url-params example which might be exactly what you are looking for.
Here's a slightly detailed option that extends beyond a single component but more easy to scale on account of modularity.
Create a new react component in a new file say, UserDetails.js
Now you need a way to navigate to this new page when the button is clicked.
So in your App.js you need a router like
import { BrowserRouter, Switch} from 'react-router-dom'
Then in your App.js file wrap all your components in the router:
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<Switch>
<Route exact path="/user-list" component={UserList} />
<Route exact path="/detail" component={UserDetails}/>
</Switch>
</div>
</BrowserRouter>
);
}
}
export default App;
Now you are ready to navigate to the user details page, when the button is clicked. So add a function like goToDetails like:
<button onClick={() => goToDetails(item)}>
Next define the function that navigates to the next page
goToDetails(item) {
this.props.history.push('/detail', {selectedUser:item:});
}
The history prop is available above because we earlier wrapped the entire app in BrowserRouter.
In the details page, you get the selectedUser details as a prop:
const selectedUser = this.props.location.state.selectedUser;
Now you can render it however you want.

Resources