how can i make a component re render in react - javascript

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.

Related

Tips for displaying components based on global state

I'm build game interface that has the following user flow:
user lands on one of the games URL eg. www.name.com/game1, first gets intro screen, than game screen and finally fail or success screen.
I'm trying to figure out the most optimal way to do this. Bellow is the code that works just fine but I'm looking for more elegant and scale-able solution. Any idea?
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
// Import views and components
import Step1 from "../Intro/Step1";
import StatusBar from "../../components/StatusBar/StatusBar";
import Game1 from "./Games/Game1/Game1";
import Game2 from "./Games/Game2/Game2";
import Intro from "./Intro/Intro";
import Password from "./Password/Password";
import Success from "./Success/Success";
import Fail from "./Fail/Fail";
import FailBeginOnStart from "./Fail/FailBeginOnStart";
// Data
function Game() {
const data = {
game1: {
desc: "some description for game 1",
},
game2: {
desc: "some description for game 2",
},
};
// Get global states from redux toolkit
const showIntro = useSelector((state) => state.game.showIntro);
const showSuccess = useSelector((state) => state.game.showSuccess);
const showFail = useSelector((state) => state.game.showFail);
const showPassword = useSelector((state) => state.game.showPassword);
const completedGame = useSelector((state) => state.game.completedGame);
const selectedLanguage = useSelector((state) => state.game.selectedLanguage);
// Get current param from URL (example /game1)
const { game } = useParams();
// Strip slash to get matching game ID (example game1)
const gameId = game.replace(/-/g, "");
const GameScreen = () => {
// show intro screen
if (showIntro === true) {
return (
<>
<StatusBar />
<Intro path={game} id={gameId} data={data[gameId]} />
</>
);
}
// show success screen
if (showSuccess === true) {
return (
<>
<StatusBar />
<Success data={data[gameId]} />
</>
);
}
// show fail screen
if (showFail === true) {
return (
<>
<StatusBar />
<Fail data={data[gameId]} />
</>
);
}
// Show actual game
switch (true) {
case game === "game1":
return <Game1 data={data[gameId]} />;
case game === "game2":
return <Game2 data={data[gameId]} />;
default:
return <Step1 />;
}
};
return <GameScreen />;
}
export default Game;
I'd suggest React Router and changing to class based components for your use case. You'd do a BaseGame class as a template for the concrete games. (if you're using typescript you can make it an abstract class.).
These examples are without further information on the actual flow of your page so you might need to adjust it.
class BaseGame extends React.Component {
// creating dummy members that get overwritten in concrete game classes
desc = "";
id = 0;
data = {}
constructor(){
this.state={
intro: true,
success: false,
fail: false
}
}
/** just dummy functions as we don't have access to
/* abstract methods in regular javascript.
/*
*/
statusBar(){ return <div>Statusbar</div>}
gameScreen(){ return <div>the Game Screen</div>}
render(){
return (
{this.statusBar()}
{if(this.state.intro) <Intro data={this.data} onStart={() => this.setState({intro: false})}/>}
{if(this.state.success) <Success data={this.data}/>}
{if(this.state.fail) <Faildata={this.data}/>}
{if(!this.state.intro && !this.state.fail && !this.state.success) this.gameScreen()}
)
}
}
class Game1 extends BaseGame {
id = 1;
data = {GameData}
// actual implementation of the screens
statusBar(){ return <div>Game1 StatusBar</div>}
gameScreen(){ return (
<div>
<h1>The UI of Game 1</h1>
Make sure to setState success or failure at the end of the game loop
</div>
)}
}
and in your app function
const App = () => (
<Router>
<Switch>
<Route exact path="/" component={Step1} />
<Route path="/game1" component={Game1} />
<Route path="/game2" component={Game2} />
...
</Switch>
</Router>
)
just a quick idea. You propably want to add a centralized Store like Redux depending where you wanna manage the State. But the different Game Classes can very well manage it on their own when they don't need a shared state. Also depending what the statusBar does it can be outside of the game and just in your app function
...
<StatusBar />
<Router>
... etc.
</Router>
in your app function.

React white screen of death, why does commenting out this code fix it?

I'm making a simple react app to take trivia questions and answers from an api and display them as a game.
My development of this app has been running smoothly and updating as per expected, however when I imported a decode function to make the trivia questions present correctly, I noticed that further edits of the code would result in a blank white screen, after commenting out some code I've managed to isolate what code seems to be causing the issue.
App.js
import React from 'react'
import Questions from './Questions'
import { nanoid } from 'nanoid'
import { decode } from 'he'
function App() {
const [tempState, setTempState] = React.useState(false)
const [data, setData] = React.useState({})
React.useEffect(() => {
fetch("https://opentdb.com/api.php?amount=5&category=9&difficulty=medium")
.then(res => res.json())
.then(info => setData(info.results.map(item => {
return {
type: item.type,
question: item.question,
correct_answer: item.correct_answer,
incorrect_answers: item.incorrect_answers,
id: nanoid()
}})))
}, [])
const questionElements = data.map(item => (
<Questions
key={item.id}
type={item.type}
question={item.question}
correct_answer={item.correct_answer}
incorrect_answers={item.incorrect_answers}
/>
))
return (
<main>
<img className="blob--top"
src={require('./blobs.png')}
/>
<img className="blob--bottom"
src={require('./blobs1.png')}
/>
{tempState ?
<div className="quiz--container">
<div>
{questionElements}
</div>
</div> :
<>
<div className="title--container">
<div className="title--init">
<h2 className="title--header">Quizzical</h2>
<h4 className="title--subheader">A battle of the brains</h4>
<button className="game--start"
onClick={() => setTempState(!tempState)}
>
Start quiz</button>
</div>
</div>
</>
}
</main>
);
}
export default App;
Questions.js
import React from 'react'
import { decode } from 'he'
export default function Questions(props) {
return(
<div className="question--container">
<h4>{decode(props.question)}</h4>
<div className="question--items">
<button>{decode(props.correct_answer)}</button>
<button>{decode(props.incorrect_answers[0])}</button>
<button>{decode(props.incorrect_answers[1])}</button>
<button>{decode(props.incorrect_answers[2])}</button>
</div>
</div>
)
}
commenting out the following two code sections in App.js resolves the error
const questionElements = data.map(item => (
<Questions
key={item.id}
type={item.type}
question={item.question}
correct_answer={item.correct_answer}
incorrect_answers={item.incorrect_answers}
/>
))
<div>
{questionElements}
</div>
any ideas on what I'm doing wrong? no error messages show up in react, it just shows a blank white screen.
The blank white screen is caused by the error data.map is not a function, which is caused by your setting default value of the data state to be an empty object while it should be an empty array (so that you can map through).
To fix this error, simply set the default value of data to be an empty array.
const [data, setData] = React.useState([])
Code Sandbox: https://codesandbox.io/embed/inspiring-rhodes-gp5kki?fontsize=14&hidenavigation=1&theme=dark

page crash error because of the delay in data

I am facing an issue and i think a lot but not able to find any solution please help Note: I have removed some html code because i know the issue was not there So first let explain what the issue is i am desctructuring loading and product which take time to resolve initially when i first created it was not giving error loading become true and the loading component render as soon as the loading become false i can access the variable product and then render the data from it..but before some time i just add a feature of login and i have not even touch this page what happening now is until the value of product and loading is resolved the value of both variables is undefined i find it using console.log() using
import React,{useEffect} from "react";
import { getSingleProduct } from '../action/productAction'
import { useSelector,useDispatch } from 'react-redux'
import { Link } from "react-router-dom";
import { change_img } from "./main";
import { useParams } from "react-router-dom";
const ProductPage = () => {
const dispatch = useDispatch();
const {product,loading} = useSelector(state => state.productDetail)
let { id } = useParams();
console.log("The data of the product is",product)
console.log("The value of the laoding ",loading)
useEffect(()=>{
dispatch(getSingleProduct(id));
},[dispatch,id]);
var iloading =true;
return (
<>
{loading ? (
<h1>Loading...</h1>
) : (
<div>
<div className="card">
<div className="row no-gutters">
<aside className="col-md-6">
<h2 className="title">{product.name}</h2>
<div className="mb-3">
<var className="price h4">Price: $ {product.price}</var>
</div>
<p>
{product.description}
</p>
</main>
</div>
</div>
</div>
)}
</>
);
};
export default ProductPage;
Looks like you are not formating the JSX in the return part Try this simplest form after imports :
const ProductPage = () => {
const dispatch = useDispatch()
const { product, loading } = useSelector((state) => state.productDetail)
let { id } = useParams()
useEffect(() => {
dispatch(getSingleProduct(id))
}, [dispatch, id])
return (
<>
{loading ? (
<h1>Loading...</h1>
) : (
<>
{' '}
<h2 className="title">{product.name}</h2>
</>
)}
</>
)
}
export default ProductPage
If that doesn't work then it means there is a problem in the getSingleProduct or in the related reducer, If gives you the product name then means your code is not formatted correctly. Try to fix this then.
Edit: Also, I have noticed there is no handling if the server does not give the data or if loading and product are undefined then your component will also crash, You can handle this like :
<>
{loading ? (
<h1>Loading...</h1>
) : product ? (
<>
{' '}
<h2 className="title">{product.name}</h2>
</>
) : (
<> No data from Server</>
)}
</>

How do I use the output of one axios request as a dependency for another when rendering components in React?

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>
);
}

Search bar stopping props from changing

On my site, the <ArticleList> is supposed to update when one navigates between columns. This works when you go from the home page to a column, or from an article to a column. But if you go from column to column, it doesn't work. The page doesn't update at all, but the url changes. The links to each column stay the same, as they are part of the <Layout> component, which every page has.
Edit
I figured out now that I can just use <a> and omit <Link> entirely, but this would slow down the page navigation.
Edit 2
This is part of my <Layout> component where I render the links to the columns:
<nav className={layout.columnContainer}>
{columns.map(({ id, name }) =>
this.props.currentColumn ? (
<a key={id} href={`/columns/${name}`}>
{name}
</a>
) : (
<Link key={id} href="/columns/[name]" as={`/columns/${name}`}>
<a>{name}</a>
</Link>
),
)}
</nav>
Edit 3
My minimal reproducible example is on GitHub, and I get the same unexpected results.
Edit 4
I found that the reason it wasn't working was I implemented a search bar that put the children prop in a state and modified this.
Constructor:
constructor(props) {
super(props);
this.searchArticlesKeyType = this.searchArticlesKeyType.bind(this);
this.state = {displayedMain: props.children};
}
Inside the render method are the column links (nav) and the problematic search input element.
<nav className={layout.columnContainer}>
{
columns.map(({id, name}) => (
<Link key={id} href="/columns/[name]" as={`/columns/${name}`}><a>{name}</a></Link>
))
}
</nav>
<div className={layout.search}>
<input type="search" name="q" onKeyUp={this.searchArticlesKeyType} />
</div>
async searchArticlesKeyType(e) {
// Some code
this.setState({
displayedMain: <ArticleList articles={JSON.stringify(filteredArticles)}/>
// More code
});
}
I think your main issue here is the way you're implementing the search feature, you don't want to store components in state instead you need to pass the search text to the articlelist component and do the filtering there.
There are several ways to implement communication between 2 unrelated components, it could be via context, redux, or even make a portal in the layout to render the seach input from the column component, but in this case I think the best option is to store the search text in the url:
First make the input event update the url using next/router, your layout will look like this:
import { useRouter } from 'next/router'
...
function Layout(props) {
const {columns} = props;
const { push, asPath, query } = useRouter()
const searchArticlesKeyType = (e) => {
const q = e.target.value;
const [url] = asPath.split('?');
push(`${url}?q=${q}`, undefined, { shallow: true });
}
return (
<div>
...
<div>
<input type="search" name="q" defaultValue={query.q} onKeyUp={searchArticlesKeyType} />
</div>
...
</div>
)
}
And then you do the filtering in articlelist component
import Link from "next/link";
import { useRouter } from 'next/router'
export default function ArticleList(props) {
const { query } = useRouter();
const q = query.q || "";
const filteredArticles = props.articles.filter(
(item) => item.title.includes(q) || item.body.includes(q)
);
return (
<ul className="grid">
{filteredArticles.map((item) => (
<div key={item.id}>
<Link
key={item.id}
href="/articles/[title]"
as={`/articles/${item.title}`}
>
<a>
<p>
<strong>{item.title}</strong>
</p>
<p>{item.body.substring(0, 100)}</p>
</a>
</Link>
</div>
))}
</ul>
);
}

Categories