I have a NavBar component which holds login information on the user. When the user is logged in it says "Welcome" along with the user details. I want to implement the same idea in another component so that when a user posts a blog, it says "Posted By: " along with the users log in details. How would I pass the details form NavBar.js to Products.js ?
import React, { useState, useEffect } from 'react';
import { NavLink } from 'react-router-dom';
const NavBar = (props) => {
const providers = ['twitter', 'github', 'aad'];
const redirect = window.location.pathname;
const [userInfo, setUserInfo] = useState();
useEffect(() => {
(async () => {
setUserInfo(await getUserInfo());
})();
}, []);
async function getUserInfo() {
try {
const response = await fetch('/.auth/me');
const payload = await response.json();
const { clientPrincipal } = payload;
return clientPrincipal;
} catch (error) {
console.error('No profile could be found');
return undefined;
}
}
return (
<div className="column is-2">
<nav className="menu">
<p className="menu-label">Menu</p>
<ul className="menu-list">
<NavLink to="/products" activeClassName="active-link">
Recipes
</NavLink>
<NavLink to="/about" activeClassName="active-link">
Help
</NavLink>
</ul>
{props.children}
</nav>
<nav className="menu auth">
<p className="menu-label">LOGIN</p>
<div className="menu-list auth">
{!userInfo &&
providers.map((provider) => (
<a key={provider} href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
{provider}
</a>
))}
{userInfo && <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>Logout</a>}
</div>
</nav>
{userInfo && (
<div>
<div className="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
</div>
)}
</div>
);
};
export default NavBar;
This is a snippet from Products.js, where I want the user details data to be passed to:
<footer className="card-footer ">
<ButtonFooter
className="cancel-button"
iconClasses="fas fa-undo"
onClick={handleCancelProduct}
label="Cancel"
/>
<ButtonFooter
className="save-button"
iconClasses="fas fa-save"
onClick={handleSave}
label="Save"
/> Posted By: {}
</footer>
One way is to use state variable in parent component of both footer and navbar, then passing into navbar as prop function to set the state variable to the userInfo, and in footer you can now use the userInfo
//beginning of parent component
const [userInfo, setUserInfo] = useState(null);
...
//navbar component
<NavBar setUserInfoParent={setUserInfo}/>
...
//footer component
<footer>
Posted By: {userInfo && userInfo.userDetails}
</footer>
There will likely be many opinions on this as there are many ways to accomplish storing some Global state.
Assuming your project will be a decent size and you don't want to keep all of this data in a component and pass it down through/to each component, I would look at these few options:
Context API: https://reactjs.org/docs/context.html
RTK: https://redux-toolkit.js.org/tutorials/quick-start (my preference)
And many others these days including Flux, Zustand, Mobx, Recoil...and on and on..
Related
This question already has an answer here:
React router 6 never unmount component when URL parameter changes
(1 answer)
Closed 13 days ago.
I am making an ecommerce website. If I click a product, the product full description will be displayed in a new page called detailPage and there will be similar product fetched from the API at the bottom of the detailPage. It is working fine till here. But if I click any of the similar product, I want the similar product to be displayed in the detailPage. How can I make it work?
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import useFetch from '../../Component/UseFetch/useFetch'
import { Link } from 'react-router-dom'
import './detailPage.css'
const DetailPage = () => {
const [item, setItem] = useState([])
const [mainImg, setMainImg] = useState('')
const [sameProduct, setSameProduct] = useState([])
const { loading, products } = useFetch('https://dummyjson.com/products')
const { id } = useParams()
useEffect(()=>{
const product = products.find(product => product.id === Number(id))
if(product){
setItem(product)
setMainImg(product.thumbnail)
}
},[products])
useEffect(()=>{
const allProduct = products.filter(product => product.category === item.category)
setSameProduct(allProduct)
}, [item, products])
return (
loading ? <div style={{
width: '100vw',
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '10vw'
}}>Loading...</div> :
<div>
<div>
<h1 className='product-headline'>Product Detail</h1>
</div>
<div className='product-box'>
<div>
{
products && item && (
<div style={{height: '320px'}}>
<img className='main-image' src={mainImg} />
</div>
)
}
<div className='short-img-box'>
{
products && item && item.images && item.images.map((image, id)=>{
return <img className='short-img' key={id} src={image} onClick={()=> setMainImg(image) }/>
})
}
</div>
</div>
<div>
{
products && item && item.price && !isNaN(item.price) && (
<div>
<h1>{item.title}</h1>
<div>
<span>Rating: {item.rating}</span>
<span>Available Stock: {item.stock}</span>
</div>
<div>
<span>{parseInt(item.price + item.price / 100 * 25)}</span><br></br>
<span>{item.price}</span>
</div>
<p>{item.description}</p>
</div>
)
}
<button>Add to cart</button>
</div>
</div>
<div>
<h1 style={{textAlign: 'center'}}>Review section</h1>
</div>
<div>
<h1 className='product-headline'>Similar Products</h1>
<div className='products'>
{
sameProduct.map((product)=>{
const {id, title, description, category, price, thumbnail, images, rating, stock} = product
return (
<div key={id} className='product'>
<Link to={`/detailpage/${id}`}><img src={thumbnail} /></Link>
<div className='product-details'>
<Link className='title' to={`/detailpage/${id}`}><h3>{title}</h3></Link>
<div className='price-rating'>
<p>${price}</p>
<p>Rating: {rating}/5</p>
</div>
</div>
<button className='add-cart'>ADD TO CART</button>
<span className='product-stock'>Stock: {stock}</span>
</div>
)
})
}
</div>
</div>
</div>
)
}
export default DetailPage
It looks to me that your only problem is that id is not included in your useEffect's dependency array. Can you confirm that that the url is updated to the new product id when you click the similar product?
In that case, all you should do is include the id in the dependency array like this to make the useEffect re-run when the id in the URL is changed:
useEffect(()=>{
const product = products.find(product => product.id === Number(id))
if(product){
setItem(product)
setMainImg(product.thumbnail)
}
},[products, id])
I think a good solution for this should be something like this (not find product in array of product, but fetch single product in single request (for details page):
You will add useEffect hook to handle, when id of product changed in URL.
useEffect(()=> {
// i change name of the function, beacuse it's impossible to use hooks inside callbacks
const {loading, poduct} = someFetchFunction('https://dummyjson.com/products');
// save your data in `useReducer` hook (but it's up to you)
}, [id]);
So right now when you change url on this page, this useEffect hook will trigger and get current product
You also have a way to change the useFetch hook, and move useEffect to this hook, so when something changes it will trigger a request, but from my perspective of view my first variant is better
So I have a page with six Link tags which all need to redirect to one page with different props depending on which link is clicked. Here's how I'm passing props:
<Link href={{
pathname: '/products',
query: {category: 'Starters'}
}}>
<div class="container">
<div class="content">
<div class="content-overlay"></div>
<img class="content-image" src="/starters.png" />
<div class="content-details fadeIn-bottom">
<h3 class="content-title text-uppercase">Starters</h3>
</div>
</div>
<h3>Starters</h3>
</div>
</Link>
On the products page, here's how I'm reading these props:
export default function products(){
const router = useRouter()
const {id} = router.query
console.log(id)
return(
<>
<Navbar></Navbar>
<h1 className="text-center">{id}</h1>
<Footer></Footer>
</>
)
}
The problem here is that my id value is read as undefined or empty. I need to take this value, show it on my page and then send it to an API endpoint which will then use this string to execute a conditional SQL query.
Any idea as to why I'm getting an empty or undefined value and how to fix this?
Please try functional component instead like below. Hooks are used in functional components.
import React, { useState } from "react";
const Products = (props) => {
const [myId, setMyId] = useState<any>(null);
const [myCategory, setMyCategory] = useState<any>(null);
useEffect(() => {
console.log(props.router.query)
setMyId(props.router.query.id)
setMyCategory(props.router.query.category)
}, [props.router]);
return(<h1 className="text-center">{myId}</h1>);
}
Alternatively
import React, { useState } from "react";
import router from 'next/router';
const Products = () => {
const {id, category} = router.query
const [myId, setMyId] = useState<any>(null);
const [myCategory, setMyCategory] = useState<any>(null);
useEffect(() => {
if(!id) {
return;
}
console.log(id);
setMyId(id);
setMyCategory(category);
}, [id]);
return(<h1 className="text-center">{myId}</h1>);
}
Can you also modify your jsx a bit? For example
<Link href={{
pathname: '/products',
query: {id: 1, category: 'Starters'}
}}>
Starters
</Link>
<div class="container">
<div class="content">
<div class="content-overlay"></div>
<img class="content-image" src="/starters.png" />
<div class="content-details fadeIn-bottom">
<h3 class="content-title text-uppercase">Starters</h3>
</div>
</div>
</div>
I am making an admin page that lists all current users in the database of my web application. I am trying to make it so that users with the role admin, do not appear in the list, essentially preventing admins from removing other admins. How can I accomplish this? This is my code:
BoardAdmin.jsx:
import React, {useState, useEffect} from 'react';
import UserService from '../../services/user.service';
import './styles/BoardAdmin.css';
const BoardAdmin = () => {
const [content, setContent] = useState('');
const [users, setUsers] = useState([]);
useEffect(() => {
UserService.getAdminBoard().then(
(response) => {
setUsers(response.data);
// console.log(response.data);
},
(error) => {
const _content =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
setContent(_content);
}
);
}, []);
return (
<div className='page'>
<header className='jumbotron'>
<div className='column-names'>
<p className='user-id-column'>User ID</p>
<p className='username-column'>Username</p>
{/* <p>User Role</p> */}
<p className='user-email-column'>Email</p>
</div>
{users.map((user) => (
<ul className='user-list'>
<li class='user'>
<div>
<div class='info'>
<span class='user-id'>{user.id}</span>
<span class='name'>{user.username}</span>
<span class='role'>{user.role}</span>
<span class='email'>{user.email}</span>
<img class='remove' src='https://i.imgur.com/CemzWSg.png' />
</div>
</div>
<div class='expand'></div>
</li>
</ul>
))}
</header>
</div>
);
};
export default BoardAdmin;
I can access the users role if I check the state with an auth file of mine with const { user: currentUser } = useSelector((state) => state.auth); and if (currentUser) { setShowAdminBoard(currentUser.roles.includes("ROLE_ADMIN"));. Can anyone help me figure out how I can accomplish this? Does it need to be done on the backend? Any help would be appreciated, and I will include more code if it is necessary.
You would need to filter out users with that role when rendering them in your JSX.
// Assuming that "ROLE_ADMIN" is the unwanted role
{users.filter(user => user.role !== "ROLE_ADMIN").map((user) => (
<ul className='user-list'>
<li class='user'>
...
Security note: it is recommended to handle this on the backend of your application, as anything performed on the client can be manipulated.
I have been struggling with this for some time and I am not sure how to solve the issue.
Basically, I am trying to render some components onto my Index page, this is my code below:
App.js
import Index from "./Components/Index"
import axios from "axios"
export default function App() {
const [movieList, setMovieList] = React.useState([])
let featured = []
let coming = []
let showing = []
React.useEffect(() => {
console.log("Ran App Effects")
axios.get(`API_CALL_TO_GET_LIST_OF_MOVIES`)
.then(res =>{
setMovieList(res.data)
})
}, [])
return(
<div>
{movieList.map(movie =>{
if(movie.status === 'featured'){
featured.push(movie.api_ID)
} else if (movie.status === 'upcoming'){
coming.push(movie.api_ID)
} else{
showing.push(movie.api_ID)
}
})}
<Index featured={featured} coming={coming} showing={showing}/>
</div>
)
}
In the code above I am receiving an array of Objects and based on what is in their status I am putting them in some empty arrays and sending them as props into my Index component.
This is what my index component looks like:
import React from "react"
import Header from "./Header"
import Footer from "./Footer"
import MovieCard from "./MovieCard"
import axios from "axios"
export default function Index(props) {
const [featuredMovies, setFeaturedMovies] = React.useState([])
const [comingMovies, setComingMovies] = React.useState([])
//const featured = [419704,338762,495764,38700,454626,475557]
//const coming = [400160,514847,556678,508439,524047,572751]
React.useEffect(() => {
console.log("Ran Effect")
axios.all(props.featured.map(l => axios.get(`API_CALL_TO_GET_SPECIFIC_MOVIE/${l}`)))
.then(axios.spread(function (...res){
setFeaturedMovies(res)
}))
.catch((err) => console.log(err))
axios.all(props.coming.map(l => axios.get(`API_CALL_TO_GET_SPECIFIC_MOVIE/${l}`)))
.then(axios.spread(function (...res){
setComingMovies(res)
}))
.catch((err) => console.log(err))
}, [])
return(
<body>
<Header />
<section className="home">
<div className="container">
<div className="row">
<div className="col-12">
<a className="home__title">FEATURED MOVIES</a>
</div>
{ featuredMovies.map(movie =>{
return <MovieCard movie={movie} featured={true} />
}) }
{console.log(props.featured)}
</div>
</div>
</section>
<section className="home">
<div className="container">
<div className="row">
<div className="col-12">
<a className="home__title">COMING SOON</a>
</div>
{ comingMovies.map(movie =>{
return <MovieCard movie={movie} featured={false} />
})}
</div>
</div>
</section>
<Footer/>
</body>
)
}
The issue I am running into is, whenever I run the app for the first time it works fine but then when I hit the refresh button the components do not render anymore
The only time it re-renders when I refresh the page is when I uncomment,
//const featured = [419704,338762,495764,38700,454626,475557]
//const coming = [400160,514847,556678,508439,524047,572751]
and replace the props.featured.map and props.coming.map with featured.map and coming.map hence using the hard coded values and not the values passed in from the props.
Any help with this would be much appreciated as I am completely stuck and currently pulling my hair out.
I took the liberty to tinker with your code. In the example below I've rearranged the data into three sets with the help of useMemo and by checking the status property of each movie. It is important to keep any data related logic outside of the render logic.
I also moved around some of your HTML structure. You were outputting a <body> tag inside of a <div>. The outer layer should be in control of the outer HTML structure, so I moved that HTML to the App component.
import { useState, useEffect, useMemo } from 'react'
import Header from "./Components/Header"
import Footer from "./Components/Footer"
import Index from "./Components/Index"
import axios from "axios"
export default function App() {
const [movieList, setMovieList] = useState([])
const featuredMovies = useMemo(() => {
return movieList.filter(({ status }) => status === 'featured');
}, [movieList]);
const upcomingMovies = useMemo(() => {
return movieList.filter(({ status }) => status === 'upcoming');
}, [movieList]);
const showingMovies = useMemo(() => {
return movieList.filter(({ status }) => status !== 'featured' && status !== 'upcoming');
}, [movieList]);
useEffect(() => {
axios.get(`API_CALL_TO_GET_LIST_OF_MOVIES`)
.then(res =>{
setMovieList(res.data)
})
}, [])
return (
<body>
<Header />
<Index data={featuredMovies} title="Featured Movies" featured={true} />
<Index data={upcomingMovies} title="Coming Soon" />
<Index data={showingMovies} title="Showing Now" />
<Footer/>
</body>
)
}
Since we now have three sets of movies (featured, upcoming, and playing) it would also make sense to have three components that handle those data sets instead of having one that handles multiple. Each Index component gets its own data set and other props to render the movies within it.
import MovieCard from "./MovieCard"
export default function Index({ data, title, featured = false }) {
return (
<section className="home">
<div className="container">
<div className="row">
<div className="col-12">
<a className="home__title">{title}</a>
</div>
{data.map(movie => {
return <MovieCard movie={movie} featured={featured} />
})}
</div>
</div>
</section>
);
}
so i'm creating my first fullstack website and once a user signs in it gets stored in the localStorage and i want to display the name of the user in my header once he is logged in but my header is not re rendering so nothing happens : this is the header before logging in
header
and this is how i want it to Be after signing in :
header after logging in this is my Layout code:
import "../assets/sass/categoriesbar.scss";
import Header from "./Header/Header";
const Layout = (props) => {
return (
<>
<Header/>
<main>
{ props.children}
</main>
</>
);
}
export default Layout;
and this is the toolBar in my Header :
const ToolBar = () => {
const history = useHistory();
let currentUser= JSON.parse(localStorage.getItem("user-info"));
const logoutHandler = () => {
localStorage.clear("user-info");
history.push("/login");
};
return (
<>
<div className={classes.NavigationBar}>
<h1>
<Link to="/">Pharmashop</Link>
</h1>
<NavLinks logout={logoutHandler}/>
{localStorage.getItem("user-info")?
<h5>Welcome {currentUser.name} !</h5>
:
<RegisterButton />
}
</div>
</>
);
};
export default ToolBar;
please help me it's frustrating
PS: this is my first stackoverflow question sorry if it's unorganized and unclear and sorry for my bad english.
Hazem, welcome to Stack Overflow.
In react, if you want the component to re-render when some data changes, that info must be in the component state. In your code the current user is a const, not bind to the component's state. This is how it could auto re-render when the user logs in:
const ToolBar = () => {
const [currentUser, setCurrentUser] = useState(JSON.parse(localStorage.getItem("user-info")));
const logoutHandler = () => {
localStorage.clear("user-info");
history.push("/login");
};
return (
<>
<div className={classes.NavigationBar}>
<h1>
<Link to="/">Pharmashop</Link>
</h1>
<NavLinks logout={logoutHandler}/>
{currentUser?
<h5>Welcome {currentUser.name} !</h5>
:
<RegisterButton />
}
</div>
</>
);
};
export default ToolBar;
See more about state in the official documentation.