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%);
}
Related
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
I'm playing with the new useTransition() hook added in React 18.
I created an app highlighting the matching names. If it matches, the item gets green, otherwise red. Here how I did it:
import { useState, useTransition } from 'react';
import people from './people.json'; // 20k items
const PeopleList = ({ people, highlight, isLoading }) => {
return (
<>
<div>{isLoading ? 'Loading...' : 'Ready'}</div>
<ol>
{people.map((name, index) => (
<li
key={index}
style={{
color: (
name.toLowerCase().includes(highlight)
? 'lime'
: 'red'
)
}}
>
{name}
</li>
))}
</ol>
</>
);
};
const App = () => {
const [needle, setNeedle] = useState('');
const [highlight, setHighlight] = useState('');
const [isPending, startTransition] = useTransition();
return (
<div>
<input
type="text"
value={needle}
onChange={event => {
setNeedle(event.target.value);
startTransition(
() => setHighlight(event.target.value.toLowerCase())
);
}}
/>
<PeopleList
people={people}
highlight={highlight}
isLoading={isPending}
/>
</div>
);
};
export default App;
The JSON file contains an array of 20k people. Unfortunately, the useTransition() doesn't seem to improve performance in this case. Whether I use it or not, typing in the input is very laggy, making about a 0.5s delay between each character.
My first thought was that maybe such a big DOM tree would cause a delay in the input, but it doesn't seem the case in a raw HTML page.
Why doesn't useTransition() make typing smooth in my example?
The reason is because your app is running in React 17 mode.
Source: https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#deprecations
react-dom: ReactDOM.render has been deprecated. Using it will warn and run your app in React 17 mode.
How about wrapping PeopleList component in a memo.
As far as my understanding goes. Everytime when setNeedle is being called <App/> component is rerendering and then <PeopleList/> is also rerendering even though nothing changed in <PeopleList/> component.
I've got a problem with react-query. I've got a component which includes components that show a loading spinner if the integrationDetails query is fetching. But when the query finishes fetching, my component doesn't update at all. I tried creating state and then using that, but the problem persits.
UPDATE: The component does in fact update, but the child component doesn't.
...
const currentWorkspace = useGetCurrentWorkspaceDetail();
const integrationDetails = useGetIntegrationDetails();
useEffect(() => {
integrationDetails.refetch(); //force refetch the query whenever I enter the page.
//problem persists even if I comment this part out
}, []);
useEffect(() => {
console.log(integrationDetails.isFetching); //this logs true and then false.
}, [integrationDetails]);
if (currentWorkspace.isLoading) {
return (
<div className={styles.layout}>
<LoadingSpinner />
</div>
);
}
return (
<div className={styles.layout}>
<div className={styles.rest}>
<div className={styles.column}>
<Card>
<CardTabs tabs={["1"]}>
<div>
<h3>Global Tracking Code</h3>
<div style={{ marginTop: 12 }}>
<StatusStripe
isLoading={integrationDetails.isFetching}
...
/>
</div>
...
Your first useEffect would be just like componentDidMount and it will only be called on first render as you have passed an empty list i.e [] in its second argument.
You could have written your logic in your second useEffect where you know when the data is fetched and you can do your state setting or binding there.
I am trying to create an image gallery using Heroku's new add:on 'simple-file-upload'. I've managed to get everything saved to the database and I can display 1 image at a time, but am having trouble creating the gallery now. I've set everything up how I think it should be, but when I console.log(files) I am not receiving the URL, but rather just the number 1 from the count. Any ideas what I'm doing wrong here? Here is my code below:
import React from "react";
import SimpleFileUpload, { SimpleFileUploadProvider } from "../components/SimpleFileUpload"
import { useState } from 'react'
import "./styles.css"
const API_KEY = ''
let count = 0;
export default function About() {
const [files, setFiles] = useState([]);
console.log(files)
//var Gallery = [];
//Gallery.push(files);
//console.log(files)
return (
<div className="App">
<h1>upload an image</h1>
<SimpleFileUpload apiKey={API_KEY} onSuccess={() => setFiles([...files, `${++count}`])} />
{!!files &&
files.map(a => {
return (
<div
key={a}
style={{
display: "flex",
justifyContent: "space-between",
marginTop: 5
}}
>
<div>{a}</div>
{/*
button to remove entries from the array, this should also make an API call to remove them from your server (unless the files are required for something like an audit).
*/}
<button onClick={() => setFiles(files.filter(f => f !== a))}>
Remove
</button>
</div>
);
})}
</div>
);
}
It looks like your onSuccess handler isn't accepting files. the ...files in () => setFiles([...files, ${++count}]) is the files from const [files, setFiles] = useState([]);, ie, [], leaving you with ${++count}, ie, 1.
changing it to files => setFiles([...files ${++count}]) should fix it---though i don't know what onSuccess does, so i can't say for sure.
I'm new to react and I'm having an issue of multiple renders and I was just wondering if I'm doing this right, so I dispatched an action to get a note list, in my list component which looks like this for now :
import React, {useState, useEffect} from 'react';
export default function NoteList (props){
const [ noteList, updateNoteList ] = useState([]);
useEffect(()=>{
updateNoteList(
props.noteList.map(note => {
return {...note, mode : 'title-mode'};
})
)
},[props.noteList])
console.log(noteList);
return (
<div>
Notes come here
</div>
)
}
this component is connected in another container class but that's irrelevant, so what happens is this component renders 4 times, two times without the useEffect hook and two more with it, what I want to achieve is I need to add an item in the object of each note (which is mode : title-mode) in a state for this component which works fine with this code, as to why I'm adding this mode in a state is that I want to change this inside the note array so I can change the view mode for each note , but this component renders 4 times as I mentioned, and in my head no way that this is the correct way to do this.
Please help if you have the time .
We could achieve the display of the notes list by making a display-mode state in the <Note /> component it self so changing the mode won't affect other notes and this way we won't have extra re-renders, also using this approach will allow also modifying the note locally without dispatching it to the store then we could update the store at the end gaining in perfs.
So basically this is the approach (codesandbox):
const Note = ({ title, content }) => {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div
style={{ border: "1px solid", margin: 5 }}
onClick={() => setIsExpanded(!isExpanded)}
>
{!isExpanded ? (
<div>
<h2>{title}</h2>
</div>
) : (
<div>
<h2>{title}</h2>
<p>{content}</p>
</div>
)}
</div>
);
};
function App() {
// this is the notes state, it could be coming from redux store so
// we could interact with it (modifying it if we need)
const [notes, setNotes] = React.useState([
{ id: 1, title: "note 1", content: "this is note 1" },
{ id: 2, title: "note 2", content: "this is note 2" }
]);
return (
<div className="App">
{notes.map((note) => (
<Note key={note.id} {...note} />
))}
</div>
);
}