React useEffect not updating when switching between pages - reactjs

I have two states (userCustomers and loans) which are depending on a state coming from a custom hook (customers).
const [customers] = useLoanCustomers(state.contents.date)
const [userCustomers, setUserCustomers] = useState<DwCustomerHeadline[]>([])
const [loans, setLoans] = useState<DwLoanContract[]>([])
So I filter data depending on the output of the useLoanCustomers and have an empty array as initial state.
When I refresh the page, the useEffect works fine and sets the states of "userCustomers" and "loans"
But when I switch between pages/routes, the useEffect does not work and keeps the initial state [].
e.g this component is on 'http://localhost:3000/user/overview'.
if I go to another route like 'http://localhost:3000/clients' and then back to '/user/overview', the states "userCustomers" and "loans" are empty. So the useEffect does not set those states.
"useLoanCustomers" is loading as expected and holds all the data.
when I console log inside the useEffect, I can see that useEffect is running properly depending on the dependency change.
But it is not setting the other two states. As said, only when I refresh the page.
Has anyone a clue what's wrong, because I've already tried so much but can't figure it out.
This is my entire component
export const Overview: React.FC<OverviewProps> = ({ user }) => {
const { keycloak } = useKeycloak()
const { state } = useContext(DashboardContext)
const [customers] = useLoanCustomers(state.contents.date)
const [userCustomers, setUserCustomers] = useState<DwCustomerHeadline[]>([])
const [loans, setLoans] = useState<DwLoanContract[]>([])
useEffect(() => {
const filteredCustomers = customers.filter((customer) => customer.stafferEmail === user?.email)
setUserCustomers(filteredCustomers)
Promise.all(filteredCustomers.map((customer) => getCustomerLoans(keycloak, customer.counterpartyId))).then((res) =>
setLoans(res.flat())
)
}, [customers])
return (
<>
<div className="grid gap-4 p-2 md:grid-cols-3 sm:grid-cols-1">
<div className="col-span-2 shadow-sm">
<KeyMetrics />
</div>
<div className="shadow-sm">
<NextReview customers={userCustomers} />
</div>
</div>
<div className="grid gap-4 p-2 md:grid-cols-3 sm:grid-cols-1 grid-rows-2">
<div className="md:h-80 col-span-2 shadow-sm">
<ErpOverview customers={userCustomers} />
</div>
<div className="row-span-2 shadow-sm" style={{ height: '34rem' }}>
<Outflow loans={loans} endDate={state.contents.date} />
</div>
<div className="h-52 col-span-2 shadow-sm">
<LoanArrears customers={userCustomers} />
</div>
</div>
</>
)
}

Related

React cannot get data from fetch api first time. I try my best but it didn't fetch data for the first time

I didn't get data for the first time.
Here is my screen shot when I search for the first time it return undefined and when I search for second time it return proper data.
How to I fix this problem. And please also explain what does it happens. I search this behavior from 2 days but I didn't find any solution even from stack overflow.
Here is my code.
import logo from './logo.svg';
import './App.css';
import Navbar from './components/Navbar';
import { useEffect, useMemo, useState } from 'react'
function App() {
const [searchWord, setSearchWord] = useState('');
const [responseWord, setResponseWord] = useState();
const [isLoad, setIsLoad] = useState(false)
const [urlLink, setUrlLink] = useState('')
async function fetchWord(word) {
console.log(isLoad)
const res = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`);
const data = await res.json();
setIsLoad(true)
setResponseWord(data)
console.log(responseWord)
console.log(isLoad)
}
return (
<>
<Navbar />
<div className="container mt-4">
<div className="row">
<div className="column bg-success text-light text-center col-3" style={{ height: "100vh" }}>
<h4> English Dictionary</h4>
</div>
<div className="column col-5 bg-light">
{
isLoad &&
<>
<h3 className='word'>{responseWord.word}</h3>
</>
}
</div>
<div className="row col-3" style={{ height: 50 }}>
<form className="d-flex" role="search" onSubmit={(e) => e.preventDefault()}>
<input className="form-control mr-sm-2" placeholder="Search"
onChange={(e) => setSearchWord(e.target.value)}
value={searchWord}
/>
<button className="btn btn-outline-success my-2 my-sm-0" type="submit" onClick={() => fetchWord(searchWord)} >Search</button>
</form>
</div>
</div>
</div>
</>
);
}
export default App;
When responseWord was printed the first time, responseWord's value was not updated to new value. Because setState operates asynchronously.
Use useEffect hook instead.
async function fetchWord(word) {
console.log(isLoad)
const res = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`);
const data = await res.json();
setIsLoad(true)
setResponseWord(data)
}
useEffect(() => {
console.log(responseWord);
}, [responseWord]);
can you try this one please i think will it help you out
import React, { useEffect, useMemo, useState } from "react";
const App=()=> {
const [searchWord, setSearchWord] = useState("");
const [responseWord, setResponseWord] = useState([]);
const [isLoad, setIsLoad] = useState(false);
const [urlLink, setUrlLink] = useState("");
const fetchWord = async (word) => {
console.log(word);
try {
setIsLoad(false);
const res = await fetch(
`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`
);
const data = await res.json();
setIsLoad(true);
setResponseWord(data);
} catch (er) {
setIsLoad(false);
}
};
const HandleSubmit = (e) => {
e.preventDefault();
fetchWord(searchWord);
};
return (
<>
<Navbar />
<div className="container mt-4">
<div className="row">
<div
className="column bg-success text-light text-center col-3"
style={{ height: "100vh" }}
>
<h4> English Dictionary</h4>
</div>
<div className="column col-5 bg-light">
{isLoad && responseWord.length !== 0 && (
<>
{/* <h3 className="word">{responseWord.word}</h3> */}
{responseWord.map((eg, i) => (
<h3 key={i || eg}>{eg.word}</h3>
))}
</>
)}
</div>
<div className="row col-3" style={{ height: 50 }}>
<form className="d-flex" role="search" onSubmit={HandleSubmit}>
<input
className="form-control mr-sm-2"
placeholder="Search"
onChange={(e) => setSearchWord(e.target.value)}
value={searchWord}
/>
<button
className="btn btn-outline-success my-2 my-sm-0"
type="submit"
>
Search
</button>
</form>
</div>
</div>
</div>
</>
);
}
export default App;
don't console inside the Asynce function cause async function will await until responce came so your results will be previous state
and assign your useState intially with empty array that will work properly if in case empty data
Muhammad, you're off to a great start here. First, let's take a look at your code as it is at the moment. Then, I'll make a couple of recommendations on how to refactor your code.
Congrats! You're actually getting data the first time you click the button and trigger fetchWord function.
You're just calling console.log(responseWord) and console.log(isLoad) too early. You're trying to log responseWord and isLoad right after updating their state within the same function. This happens because "calls to setState are asynchronous inside event handlers" and changes to state variables do NOT reflect the new value immediately after calling setState.
"When state changes, the component responds by re-rendering." And it is in the new re-render that the new state value will be reflected.
Why doesn’t React update state synchronously?
According to React documentation, React intentionally “waits” until all components call setState() in their event handlers before starting to re-render. This boosts performance by avoiding unnecessary re-renders.
When you call setResponseWord("new value") and setIsLoad("new value"), and then try to log the new state values to the console before React re-denders your component, you get false and undefined.
Try console.log(data) instead of console.log(responseWord).
Since you have access to const data = await res.json(); inside your function and before the component re-render happens, you should be able to see your data right away.
The images below ilustrate this example (focus on line 16):
Next, recommendations:
1 . It is recommended to make your AJAX call to an API using useEffect Hook.
This way, you can add the serachWord to the dependency array, and useEffect will execute every time the value of seachWord changes.
In your case, you make your fetch call on button click, but if, for example, you created a web app in which you needed the data to be populated right away without the user having to click a button, useEffect Hook will shine at its best because useEffect automatically runs the side-effect right after initial rendering, and on later renderings only if the value of the variables you passed in the dependency array change.
See the image below from the React documentation to get an idea of how you could refactor your code:
Another recommendation is to get rid of the onClick property in the button, and just let the handleSubmit function call fetchWord (see lines 38 and 21-24).
This information should help you move your app forward. And you're doing great. I see that you're successfully updating state variables, using async/await, making AJAX calls using fetch() and learning React.
Please take a look at the reference list below:
https://reactjs.org/docs/faq-state.html
https://reactjs.org/docs/faq-ajax.html
https://reactjs.org/docs/forms.html

(Theme Toggle) How can I make className sync dynamically to active localStorage value from its key when toggled in React

className "page" needs to be modified to "page light-theme" or "page dark-theme" on toggle through local storage key "theme-color" with values of light-theme and dark-theme.
The active key value does change in local Storage but updates only show if the pages is refreshed. I need the changes to sync on toggle
Page to be changed
export default function Page({children}){
return(
<div className={`page ${localStorage.getItem('theme-color')}`}>
{/* <div className= "page"> */}
{children}
</div>
)
}
h3 inner text needs to change dynamically depending on the active value from key "theme-color" in local storage. I have place the variable "themeOpener" in between h3 tag. No changes take place
heres is my code
export default function Body() {
let themeOpener;
if (`${localStorage.getItem('theme-color','light-theme')}`) {
themeOpener = "🧛🏼Ahh the light it burns! Please use toggle, I prefer dark mode!";
} else {
themeOpener = "I learnt to design in React and im hooked 🤩";
}
return (
<div className="body">
{/* <h3 id="opener">I learnt to design in React and im hooked 🤩</h3> */}
<h3 id="opener">{themeOpener}</h3>
</div>
);
}
This is code for my toggle where local storage is created
const ToggleMode = () => {
// state
const [isLight, setIsLight] = useState(false);
// effect
useEffect(() => {
// check local storage
const CurrentTheme = localStorage.getItem("theme-color");
if (CurrentTheme === "light-theme") {
setIsLight(true);
} else {
setIsLight(false);
}
console.log(useEffect);
}, []);
const ToggleChecked = () => {
// logic
if (isLight) {
localStorage.setItem("theme-color", "dark-theme");
setIsLight(false);
} else {
localStorage.setItem("theme-color", "light-theme");
setIsLight(true);
}
console.log(ToggleChecked);
};
return (
<div className="toggle--container">
<input
type={"checkbox"}
id="toggle"
className="toggle--checkbox"
checked={isLight}
onChange={ToggleChecked}
/>
<label htmlFor="toggle" className="toggle--label">
<span className="toggle--label-background"></span>
</label>
<div className=""></div>
</div>
);
};
export default ToggleMode;
Yes, this is natural according to your code. Whenever toggling, ToggleMode component will only be re-rendered by changed state value isLight.
But your Body and Page component which are supposed to be ToggleMode's parent will not be re-rendered. Because their props or states never changed by ToggleChecked().
To get it done working, you need to do something to re-render parents in Page and Body component.
How? You need to create a state value in those components or create IsLight and setIsLight at the top level component. And then these two would be drilled into ToggleMode.
Something like followings.
export default function Page({children}){
// state
const [isLight, setIsLight] = useState(false);
return(
<div className={`page ${localStorage.getItem('theme-color')}`}>
{/* <div className= "page"> */}
<Body isLight={isLight} setIsLight={setIsLight} />
{children}
</div>
)
}
export default function Body({isLight, setIsLight}) {
return (
<div>
<ToggleMode isLight={isLight} setIsLight={setIsLight} />
</div>
);
}
export default function ToggleMode ({isLight, setIsLight}){
// This is not needed anymore.
// const [isLight, setIsLight] = useState(false);
return (<>Your toggle code...</>)
}
P.S. Don't you think this is quite irritating? To avoid prop drilling, we use state management utilities such as react context API or 3rd party libraries such as Redux.

How to overcome the React hook useState async behavior?

I am having some problems with the useState async behavior that can also be related to Redux which I am new at it.
import React, { useState, useEffect } from "react"
import { useSelector, useDispatch } from "react-redux"
import { Link } from "react-router-dom"
import {
getAlltopics,
joinAtopic,
leaveAtopic,
} from "../../../redux/actions/topicActions"
import Icon from "../Icon"
const TopicCard = ({ topics }) => {
const user = useSelector((state) => state.user)
const [join, setJoin] = useState(false)
const dispatch = useDispatch()
console.log(join)
const leaveTopicHandler = async () => {
setJoin(false)
dispatch(leaveAtopic(topics._id))
}
const JoinTopicHandler = () => {
setJoin(true)
dispatch(joinAtopic(topics._id))
}
useEffect(() => {
const checkJoinedUser = () => {
topics.members.map((member) => {
if (member._id === user?._id) setJoin(true)
})
}
checkJoinedUser()
dispatch(getAlltopics())
}, [join, dispatch])
return (
<div
key={topics._id}
className="flex flex-col justify-between w-48 h-72 bg-white shadow-xl rounded-br-3xl rounded-bl-3xl rounded-tr-3xl"
>
<Link to={`/topics/${topics._id}`}>
<section className="">
<img
src={topics.bannerImage}
alt="topic_Image"
className="object-cover h-48 w-full rounded-tr-3xl"
/>
</section>
<section className="border-b-2 border-grey-light ml-3 mr-3 h-12 flex items-center">
<h1 className="text-lg">{topics.title}</h1>
</section>
</Link>
<section>
<div className="flex justify-between">
<div className="flex p-3">
<Icon iconName="member" iconStyle="fill-inactive text-grey-dark" />
<span>{topics.members?.length}</span>
<Icon iconName="file" iconStyle="fill-inactive text-grey-dark" />
<span>{topics.recources?.length}</span>
</div>
<div className="p-3">
{join ? (
<button type="button" onClick={leaveTopicHandler}>
<Icon
iconName="follow"
iconStyle="fill-active text-grey-dark"
/>
</button>
) : (
<button type="button" onClick={JoinTopicHandler}>
<Icon
iconName="follow"
iconStyle="fill-inactive text-grey-dark"
/>
</button>
)}
</div>
</div>
</section>
</div>
)
}
I have defined a join variable to handle a button that depending on if it is true or false will show or not some aspect, also, if it is false the user can join the topic, if it is true the user can leave the topic as it is noticeable in the functions JoinTopicHandler and leaveTopicHandler. Before joining a topic it looks like this:before joining a topic, as it is possible to see, the join variable it's set to false, because I am not in the topic. When joining the topic, after joining the topic, the joinvariable is set to true, the button changed, although the user count didn't changed for 2 (sometimes it does, sometimes I have to refresh the page for it to render), but the weirdest thing is when leaving the topic, as it's shown in the console,leaving the topic the join variable turns to false but then by it self turns again to true and the button still looks the same and I can not fix this...
Without knowning what the leaveAtopic function does exactly, my guess is that since join is in your useEffect hook dependencies, what happens from clicking the leave button is:
leaveTopicHandler is run
setJoin(false) causes re-render and since join is a dependency of the useEffect, we run it again
dispatching leaveAtopic starts but I'm assuming there is async logic there
topics hasn't changed yet so when the useEffect runs topics.members still contains the user
You probably don't even need useState/useEffect for join, and instead could do something like:
const join = !!topics.members.find((member) => member._id === user?._id))
So I had a few mistakes starting from the backend of my app, first of all, I wasn't returning the updated topic because I didn't set the call correctly.
const addUserToTopicDb = async (topicId, user) => {
try {
return await Topic.findByIdAndUpdate(topicId, {
$push: { members: user },
}, {new: true});
} catch (error) {
throw new Error(error);
}
};
I had to add the {new: true} to get the updated topic. This was a major error. Second, my code on the reducer wasn't working properly as I am new at it and learning by solving this kind of problems.
I changed my reducer code to:
case JOIN_TOPIC:
return {
allTopics: state.allTopics.map((eachTopic) =>
eachTopic._id === action.payload.topic._id
? action.payload.topic
: eachTopic
),
}
case LEAVE_TOPIC:
return {
allTopics: state.allTopics.map((eachTopic) =>
eachTopic._id === action.payload.topic._id
? action.payload.topic
: eachTopic
),
}
This basically means that if I have a topic with the same _id, replace it with the new updated topic that I wasn't returning in the beginning.
After this, everything started to work smoothly. Shame on me to assume that the problem was due to the useState... when it was all along in my database set up and reducer...

UseEffect not rerendring page once products loaded in redux store

I am working on e-commerce project (MERN).So i got Molla e-commerce React template for better UX/UI.
In my root function am getting all products and store them using redux like this :
const updateStore = () => {
store.dispatch( getAllProducts() );
}
Everything was working fine until i found out that if i try to access product page for the first time (with empty localstorage as in incognito mode) i get nothing and the product object was undefined , if i refresh the page then it works fine.
The problem is when i try to access the products page with empty redux store , it doesn't wait or rerender when the data are stored.
I tryed to use useEffect() to wait for product change to rerender but it's not working .
This is my product page code :
function SingleProduct( props ) {
let productLink= props.match.params.link;
const {product} = props;
const [productLoaded,setProductLoaded] = useState(false);
useEffect( () => {
if(productLoaded){
productGallery();
document.querySelector( '.skel-pro-single' ).classList.remove( 'loaded' );
let imgLoad = imagesLoaded( ".product-main-image", { background: true } );
imgLoad.on( 'done', function ( instance, image ) {
document.querySelector( '.skel-pro-single' ).classList.add( 'loaded' );
} );
}
}, [ productLink ] )
useEffect(()=>{
if(product){
setProductLoaded(true);
}
},[product])
return (
productLoaded ?
<>
<Helmet>
<title></title>
</Helmet>
<h1 className="d-none"></h1>
<div className="main">
<div className="page-content">
<div className="container">
<div className="product-details-top skeleton-body">
<div className="row skel-pro-single">
<div className="col-md-6">
<div className="skel-product-gallery">
</div>
<MediaOne link={ productLink } />
</div>
<div className="col-md-6">
<div className="entry-summary row">
<div className="col-md-12">
<div className="entry-summary1"></div>
</div>
<div className="col-md-12">
<div className="entry-summary2"></div>
</div>
</div>
<ProductDetailOne link={ productLink } />
</div>
</div>
</div>
<DescOne link={ productLink } />
<h2 className="title text-center mb-4">You May Also Like</h2>
<RelatedProducts />
</div>
</div>
<StickyBar link={ productLink } />
<QuickView />
</div>
</>
:
<></>
)
}
function mapStateToProps( state, props ) {
return {
product: state.data.products.filter( product => product.link == props.link )[ 0 ]
}
}
export default connect(mapStateToProps)(SingleProduct);
I tried to wait for product change using the useEffect and productLoaded state since it's undefined on first page render but it still showing undefined.
'product' variable, I think, is an object, be aware about what could happen if you use useEffect with that.
const a = {a:123};
const b = {a:123};
(a === b) === false
if it is possible see if the id or a string/number changes
The first useEffect will run before the second one so when this will run it will have productLoaded on false.
Try to gain productLoaded from props (you will not have to manage the asyncronicity of useEffect).
assuming that product is undefined before loading
const productLoaded = !!product;
useEffect( () => {
if(productLoaded){
..."do here what you need"
}
}, [ productLink, productLoaded ] )
maybe you can achieve a change in the css classes
<div className={`row skel-pro-single ${productLoaded?"loaded":""}`}>
as a suggestion try to use react as a templating with variables in the returning jsx, please don't mix js dom interaction and react interactions, they could disturb each other (react will try to report the part of the dom that he manages to the state he calculated, so could overwrite your changes made by
document.querySelector( '.skel-pro-single' ).classList.remove

state is not updating when the component re renders?

I'm making a Nextjs flashcard app. I'm passing a deck structure like this:
const deck = {
title: 'React 101',
flashcards: [flashcardOne, flashcardTwo],
};
as props to the Deck component. This component shows the first card in flashcards and a "next" button to increment the index and showing the next card in flashcards.
The Card component is very simple and shows the front and the back of the card depending of the state front.
This is what I got so far and it's working but if I click "next" when the card is showing the answer (flashcard.back), the next card is going to appear with the answer. And I'm not sure why, isn't the Card component re rendering when I click "next"? And if the component re renders, front is going to be set to true?
export default function Deck({ deck }) {
const [cardIndex, setCardIndex] = useState(0);
const { title, flashcards } = deck;
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>{title}</h1>
{cardIndex < flashcards.length ? (
<>
<div className={styles.grid}>
<Card flashcard={flashcards[cardIndex]} />
</div>
<button onClick={() => setCardIndex((cardIndex) => cardIndex + 1)}>
Next
</button>
</>
) : (
<>
<div>End</div>
<button>
<Link href='/'>
<a>Go to Home</a>
</Link>
</button>
<button onClick={() => setCardIndex(0)}>Play again</button>
</>
)}
</main>
</div>
);
}
export function Card({ flashcard }) {
const [front, setFront] = useState(true);
return (
<>
{front ? (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(false)}
>
<p className={styles.front}>{flashcard.front}</p>
</div>
) : (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(true)}
>
<p className={styles.back}>{flashcard.back}</p>
</div>
)}
</>
);
}
When state changes, the card will re-render, but it will not re-mount. So, existing state will not be reset.
Call setFront(true) when the flashcard prop has changed:
const [front, setFront] = useState(true);
useLayoutEffect(() => {
setFront(true);
}, [flashcard]);
I'm using useLayoutEffect instead of useEffect to ensure front gets set ASAP, rather than after a paint cycle (which could cause flickering).
You can also significantly slim down the Card JSX:
export function Card({ flashcard }) {
const [front, setFront] = useState(true);
const face = front ? 'front' : 'back';
return (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(!front)}
>
<p className={styles[face]}>{flashcard[face]}</p>
</div>
);
}
Okay, I guess I had the same issue. Since you're using functional components, and you're re-using the same component or in better words, you're not unmounting and remounting the component really, you're just changing the props, this happens. For this, you need to do useEffect() and then setFront(true).
Here's the code I used in my App.
useEffect(() => {
setFront(true);
}, [flashcard]);
This is what I have used in my Word.js file.

Resources