How to overcome the React hook useState async behavior? - reactjs

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...

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

React useEffect not updating when switching between pages

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

React - Unhandled Rejection (TypeError): Cannot read property 'front_default' of undefined

I'm new to React and I learn best by building my own projects with things that's "fun".
I'm building a Pokedex and everything has been pretty neat, until today when building out a new function.
It's supposed to search every time the user passes in another letter with the "searchPokemon" function.
When this is assigned to the button it works like a charm, but when I try to add it to the "onChange" handler within the input it generates this:
How does that come?
If I assign an invalid pokemon name (string) and then search when the searchPokemon function is assigned to the button it doesn't generate an error message, but now it does?
I assume I need some sort of if statement, but I'm not sure how to go about it.
import Axios from "axios";
import React, { useState } from "react";
import "./SearchPokemon.css";
function PK() {
const api = Axios.create({
baseURL: "https://pokeapi.co/api/v2/",
});
const [pokemonSearched, setPokemonSearched] = useState(false);
const [search, setSearch] = useState("");
const [pokemon, setPokemon] = useState({});
const [pokemonDescription, fetchDescription] = useState({});
const [evolution, pokemonEvolution] = useState({});
const searchPokemon = () => {
api.get(`pokemon/${search}`.toLowerCase()).then((response) => {
setPokemon({
name: response.data.name,
height: response.data.height,
weight: response.data.weight,
img: response.data.sprites.front_default,
id: response.data.id,
type: response.data.types[0].type.name,
type2: response.data.types[1]?.type.name,
});
api.get(`pokemon-species/${response.data.id}/`).then((response) => {
fetchDescription({
entry: response.data.flavor_text_entries[0].flavor_text,
evolution: response.data.evolution_chain.url,
});
api.get(`${response.data.evolution_chain.url}`).then((response) => {
pokemonEvolution({
evolution: response.data.chain.evolves_to[0]?.species.name,
});
});
});
});
setPokemonSearched(true);
};
return (
<div className="page">
<div className="search-section">
<input
placeholder="Search..."
type="text"
onChange={(event) => {
setSearch(event.target.value);
searchPokemon();
}}
/>
<button onClick={searchPokemon}>Click me</button>
</div>
<div className="main">
{!pokemonSearched ? null : (
<>
<h1 style={{ textTransform: "capitalize" }}>{pokemon.name}</h1>
<h1>No. {pokemon.id}</h1>
<img src={pokemon.img} alt="" />
<div className="info-wrapper">
<div className="info">
<h3 style={{ textTransform: "capitalize" }}>
Type: {pokemon.type} {pokemon.type2}
</h3>
<h3>Height: {pokemon.height * 10} Cm</h3>
<h3>Weight: {pokemon.weight / 10} Kg</h3>
</div>
</div>
<div className="desc">
<div className="desc-info">
<h3 style={{ textTransform: "capitalize" }}>
{pokemonDescription.entry}
</h3>
</div>
</div>
</>
)}
</div>
</div>
);
}
export default PK;
The error tells exactly what the problem is!
You cannot read property type of undefined.
So what does that mean? You have on line 23 the following:
type2: response.data.types[1]?.type.name,
As you are using Typescript, so you have to understand what exactly the ? means on that line, which is just you making an assumption that the properties followed by the ? will be present everytime! Even though the typing says otherwise.
But, as you can see in the docs of the pokemon endpoint, types is a list, and some don't have more than 1 type in the array, so you have to figure out a way of checking weather that typing is present to set the state.
Edit: As you are triggering the searchPokemon function on every keyboard stroke (try doing the same here), you are using the hook setPokemon on every response, even when there's no response.data, so here's what causing you trouble, you just need to find a way to validate the response before updating the state.

useEffect not running on refresh

I'm having an issue with the useEffect hook on this blog site I'm building. Im trying to fetch all the blogs from the backend so I can use them to populate this section with the latest five blogs. When I use the code below, the empty array in the useEffect prevents the infinite amount of fetch calls, which is great.
But then I run into a problem where if I refresh the page or navigate back to it I get an error on line 35 saying "cannot find mainImage of undefined".
My question is how do I have the fetchCall populate the state and do so even on a refresh so that I can still access the info I need. Thanks!
import React, {useState, useEffect} from 'react';
import CardItem from './CardItem';
import './Cards.css';
function Cards() {
const [blogs, setBlogs] = useState();
useEffect(() => {
fetchBlogs();
// eslint-disable-next-line react-hooks/exhaustive-deps
},[]);
const fetchBlogs = async () => {
console.log('ok');
const response = await fetch('http://localhost:3000/blogs');
const data = await response.json();
setBlogs(data);
};
return (
<div className='cards'>
<div className='header-container'>
<img
className='logo'
alt='logo'
src='https://Zi.imgur.com/MaLqLee.png'
/>
<h1 className='cards-title'>Hot takes and hometown bias</h1>
</div>
<div className='cards-container'>
<div className='cards-wrapper'>
<ul className='cards-items'>
<CardItem
src={blogs.length > 0 ? blogs[0].mainImage : ''}
text="Don't look now, but Zach Lavine officially kicks ass."
label='Bulls'
path='/bulls'
/>
<CardItem
src='https://i.imgur.com/EmVgHk2.jpg'
text='The curious case of Mitch Trubisky apologists.'
label='Bears'
path='/bears'
/>
</ul>
<ul className='cards-items'>
<CardItem
src='https://i.imgur.com/ZZ5dLJU.jpg'
text='How Did We Get Here? The Suddenly Bleak State of the Cubs.'
label='Cubs'
path='/cubs'
/>
<CardItem
src='https://i.imgur.com/MwgKmUM.jpg'
text='Pace and Nagy: So, how much can we blame these guys?'
label='Bears'
path='/bears'
/>
<CardItem
src='https://i.imgur.com/Y2Eorvu.jpg'
text='Thad Young: An Ode to the NBA Journeyman.'
label='Bulls'
path='/bulls'
/>
</ul>
</div>
</div>
</div>
);
}
export default Cards;
The fetch call is asynchronous. This means it is not guaranteed to be complete before the program enters the next line.
Because of this the blogs array will be empty at the first render. You can add an check in the src CardItem component to only use the value returned from the fetch call when it is available:
<CardItem
src={blogs.length > 0 ? blogs[0].mainImage : ''}
...
/>
An alternative would be to use the fact that blogs is an array and use the map operator to build one or more CardItems.
<ul className='cards-items'>
{blogs.map(blog => <CardItem
src={blog.mainImage}
...
/>)}
</ul>
I faced the same problem, and here is how I solved it..
First, I created a loading state, and set the initial state to true.
// const [singlePackage, setPackage] = useState([]);
const [isLoading, setLoading] = useState(true);
then, in the useEffect hook, I set the state to false like so..
useEffect(() => {
axios.get(baseURL).then((response) => {
setPackage(response.data);
setLoading(false);
})
}, []);
Then, I used a condition, if the loading state is true, return the spinner else return the component like so...
if (isLoading) {
return (
<div className="loadingContainer">
<Loader
type="ThreeDots"
color="#00b22d"
height={100}
width={100}
//3 secs
/>
</div>
)
} else {
return (
// your code here
)}
I am using react-loader-spinner, and just styled the container
you can install it using...
npm install react-loader-spinner --save
the style for container ...
.loadingContainer{
position: fixed;
top: 50%;
left:50%;
transform: translate(-50%,50%);
}

react.js, how to create a search filter from API data?

I am trying to create my first search bar in React.js. I am trying to implement search functionality with filter method. I faced a problem with filter method, which gives an error like "filter is not defined". I am stuck on it for 2 days, I have looked several tutorials and endless youtube videos. This is the simpliest approach, I guess. Any help will be appreciated.
import React, { useState, useEffect } from "react";
import Recipe from "./Recipe";
import "./styles.css";
export default function RecipeList() {
const apiURL = "https://www.themealdb.com/api/json/v1/1/search.php?f=c";
const [myRecipes, setRecipes] = useState("");
const [search, setSearch] = useState("");
// fetch recipe from API
function fetchRecipes() {
fetch(apiURL)
.then(response => response.json())
.then(data => setRecipes(data.meals))
.catch(console.log("Error"));
}
function onDeleteHandler(index) {
setRecipes(
myRecipes.filter((element, filterIndex) => index !== filterIndex)
);
}
useEffect(() => {
fetchRecipes();
}, []);
const filterRecipes = myRecipe.meal.filter( element => {
return element.name.toLowerCase().includes(search.toLocaleLowerCase())
})
{/* filter method above doesn't work */}
return (
<div>
<label>
<div className="input-group mb-3 cb-search">
<input
type="text"
className="form-control"
placeholder="Search for recipes..."
aria-label="Recipient's username"
aria-describedby="button-addon2"
onChange = {e => setSearch (e.target.value)}
/>
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
id="button-addon2"
>
Search
</button>
</div>
</div>
</label>
<div>
<button
className="btn btn-info cb-button fetch-button"
onClick={fetchRecipes}
>
Fetch Recipe
</button>
<br />
{filterRecipes.map((element, index) => (
<Recipe
key={index}
index = {index}
onDelete={onDeleteHandler}
{...element}
name = {element.strMeal}
/>
))}
{/** name of child component */}
{/** strMeal is the name of Recipe in API object */}
</div>
</div>
);
}
link for code codesandbox
I made some changes on your code updated code
const [myRecipes, setRecipes] = useState([]);
You should declare myRecipes as an array if u intended to use map function.
const filterRecipes = myRecipe.meal.filter( element => {
return element.name.toLowerCase().includes(search.toLocaleLowerCase())
})
You have the wrong variable passing through, it should be myRecipes
filterRecipes.map((element, index) => (
<Recipe
key={index}
index = {index}
onDelete={onDeleteHandler}
{...element}
name = {element.strMeal}
/>
3. You should check whether your filterRecipes is not undefined before you use map function.
Lastly, your fetch API return error which unable to setRecipes.
I could not resolve you task completely because of low count of information according the task, but, i think, my answer will be useful for you.
So, tthe first thing I would like to draw attention to is a initial state in the parameter of useState function. In this task it sould be as:
const [myRecipes, setRecipes] = useState({meals: []});
Because, before fetching data, React has a time to run the code, and, when it come to line 32, it see what in the myRecipes (myRecipes, not a myRecipe. Please, pay attention when you write the code) a string except an array.
And in the line 32 i recommend you to add something checking of have you resolved request of data like:
const filterRecipes = myRecipes.meals.length
? myRecipes.meals.filter(element => {
return element.name.toLowerCase().includes(search.toLocaleLowerCase());
});
: []
And look in the data which you receive, because, i think, there are no elements with propName like name (element.name).
I think, i could help you as possible. If you have any questions, ask in comments. Will answer you as soon as possible. Good luck

Resources