How react hooks are updating state - javascript

I have a long process that updates the state. I want to show red background when it's running and blue when it's done.
const MapBuilder = (props) => {
const [backgroundColor, setBackgroundColor] = useState(false);
const [fancyResult, setFancyResult] = useState(null);
console.log(`stop 1 backgroundColor ${backgroundColor} fancyResult ${fancyResult}`)
const veryHardWork = () => {
setBackgroundColor("red");
console.log(`stop 2 backgroundColor ${backgroundColor} fancyResult ${fancyResult}`)
for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 1000; j++) {
console.log("So hard")
}
}
setFancyResult("done")
console.log(`stop 3 backgroundColor ${backgroundColor} fancyResult ${fancyResult}`)
setBackgroundColor("blue")
console.log(`stop 4 backgroundColor ${backgroundColor} fancyResult ${fancyResult}`)
}
return (<div style={{background: backgroundColor}}>
<button className="btn btn-primary" onClick={veryHardWork}></button>
</div>)
}
Here is an output of such run
stop 1 backgroundColor false fancyResult null
MapBuilder.js:13 stop 2 backgroundColor false fancyResult null
10000MapBuilder.js:16 So hard
MapBuilder.js:20 stop 3 backgroundColor false fancyResult null
MapBuilder.js:22 stop 4 backgroundColor false fancyResult null
MapBuilder.js:10 stop 1 backgroundColor blue fancyResult done
I understand from this that the state change only happens after the method veryHardWork is finished. In my real project, I actually want to show a spinner the question is how can I do it if the state is only changed at the end of the method.
I think some clarification needs to be added. In reality, I allow the user to choose a file after the user chooses the file it is loaded and some heavy processing is performed on files data while the processing is running I want to show a spinner no Asyn work involved.
Some of the answers sugested to use useEffect and moving it to a promise I tryied both but it did not help here is a different take on it which also did not work
const MapBuilder = (props) => {
const [backgroundColor, setBackgroundColor] = useState(false);
const [fancyResult, setFancyResult] = useState(null);
const [startProcessing, setStartProcessing] = useState(null);
useEffect(() => {
let myFunc = async () => {
if (startProcessing) {
setBackgroundColor("red");
await hardWork();
setBackgroundColor("blue");
setStartProcessing(false);
}
}
myFunc();
}, [startProcessing])
const hardWork = () => {
return new Promise((resolve)=> {
for (let i = 0; i < 500; i++) {
for (let j = 0; j < 100; j++) {
console.log("So hard")
}
}
setFancyResult("sdsadsad")
resolve("dfsdfds")
})
}
return (<div style={{background: backgroundColor}}>
<button className="btn btn-primary" onClick={() => setStartProcessing(true)}></button>
</div>)
}
export default MapBuilder;

The problem with the approach is that the heavy calculation is happening at the main loop with the same priority. The red color change will not ever cause any changes until all things at the event handler have been finished.
With Reach 18 you can make your heavy calculation to be with lower priority and let the UI changes happen with normal priority. You can make this happen with minor change on your code base:
const veryHardWork = () => {
setBackgroundColor("red");
// Set heavy calculation to happen with lower priority here...
startTransition(() => {
console.log(`stop 2 backgroundColor ${backgroundColor} fancyResult ${fancyResult}`)
for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 1000; j++) {
console.log("So hard")
}
}
setFancyResult("done")
setBackgroundColor("blue")
}
}

So I've made you a more real world example as the code you posted doesn't look like what you're actually wanting to achieve.
The scenario to my understanding is you want to preform some setup actions to get your state / data ready before showing it to the user.
cool, so first we will need some state to keep track of when we're ready to show content to the user lets call it isLoading. This will be a boolean that we can use to conditionally return either a loading spinner, or our content.
next we need some state to keep hold of our data, lets call this one content.
each state will be created from React.useState which can be imported with import { useState } from 'react';. We will then create variables in the following format:
const [state, setState] = useState(null);
nice, so now lets do somthing when the component mounts, for this we will use React.useEffect this hook can be used to tap into the lifecycle of a component.
inside our useEffect block we will preform our set up. In this case I'll say it's an async function that get some data from an API and then sets it to state.
lastly we will use our isLoading state to decide when we're ready to show the user something more interesting than a spinner.
All together we get something like this:
import { useState, useEffect } from 'react';
const MyComponent = () => {
// create some state to manage when what is shown
const [isLoading, setIsLoading] = useState(true);
// create some state to manage content
const [content, setContent] = useState(null);
// when the component is mounted preform some setup actions
useEffect(() => {
const setup = async () => {
// do some setup actions, like fetching from an API
const result = await fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(r => r.json());
// update our state that manages content
setContent(result);
// when we are happy everything is ready show the user the content
setIsLoading(false);
};
// run our setup function
setup();
}, [ ]);
// if we are not yet ready to show the user data, show a loading message
if (isLoading) {
return (
<div>
<p>spinner goes in this div</p>
</div>
)
}
// when we are ready to show the user data is will be shown in this return statement
return (
<div>
<p>this div will show when the `isLoading` state is true and do something with the content state is wanted</p>
</div>
)
}
I believe you'll find this more useful than the example you provided

You already control state of fancyResult,
or you can use showSpinner state for only reason to show spinner
You can use for long progress Promise [Link] And Async/Await [Link]
const veryHardWork = async () => {
setBackgroundColor("red");
const awaiting = await new Promise((resolve, reject) => {
for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 1000; j++) {
resolve(console.log("So hard"));
}
}
})
// After Finished Awating Hardwork
setFancyResult("done");
setBackgroundColor("blue") ;
}
return (
<div style={{background: fancyResult === 'done' ? 'blue': 'red'}}>
<button className="btn btn-primary" onClick={veryHardWork}></button>
{ fancyResult === 'done' && 'Show Spinner' }
</div>
)

Try this:
import { useState, useEffect } from "react";
import { flushSync } from "react-dom";
const MapBuilder = (props) => {
const [backgroundColor, setBackgroundColor] = useState(false);
const [fancyResult, setFancyResult] = useState(null);
// this will only be called on the first mount
useEffect(() => {
console.log(
`backgroundColor ${backgroundColor} fancyResult ${fancyResult}`
);
}, [backgroundColor, fancyResult]);
const veryHardWork = () => {
setBackgroundColor("red");
setTimeout(() => {
for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 1000; j++) {
for (let k = 0; k < 1000; k++) {
// console.log("So hard");
}
}
}
setFancyResult("done");
// flushSync(() => setFancyResult("done"));
console.log(
`inside closure - OUTDATED: backgroundColor ${backgroundColor} fancyResult ${fancyResult}`
);
setBackgroundColor("blue");
}, 0);
};
return (
<div style={{ background: backgroundColor }}>
<button className="btn btn-primary" onClick={veryHardWork}>
Work!
</button>
</div>
);
};
export default MapBuilder;
CodeSandbox
Explanation:
Unblocking the UI thread
In order for the color to change to red, the UI thread must be freed up to
make the state changes (set background color to red) and
do the re-render.
One way to achieve this is by using setTimeout. This puts a function on a queue to be run later, allowing the UI thread to finish the above 2 tasks before tackling the actual work. You should note though, this doesn’t actually run your work on a new thread, so once the work starts getting done, the UI will be unresponsive until the work is done. Consider using a Web Worker to solve this in the future.
Logging the current state
The other thing to understand about React is that every time a re-render occurs, the entire function ('MapBuilder’ in this case) is re-run. This means that your ‘stop 1’ message will be displayed every re-render, and therefore every time the state changes.
Additionally, logging the state from within veryHardWork will log the state when the function was defined. This means that the value will be outdated, i.e. stale. This is because of a functional concept called closures. From Wikipedia “Unlike a plain function, a closure allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.”
So how should we log the current state when it is changed? By using the useEffect hook. This function will be re-run whenever any of the dependencies change ([backgroundColor, fancyResult] in this case).
Console.log undefined behavior
Another thing to note is that many console.logs should not be used as the ‘work’. The rendering of the log will happen asynchronously, so ‘firing’ the logs will be much quicker than they will actually show up. This leads the observer watching the console to think that the ‘red’ stage has been skipped. Instead, we can just loop more times, or do some math in the loop, etc (which is closer to what your actual synchronous work will be anyway). In fact, console.log seems to be quite unpredictable, as noted here.
Automatic Batching
You might be wondering why “done” and “blue” show up as a single state update (i.e. stop 3 and 4 happen at the same time). This is because of automatic batching. As a performance optimization, react attempts to ‘batch’ state changes to prevent additional re-renders. To prevent this behavior, you can uncomment line 27 flushSync(() => setFancyResult("done”)). This is not necessary for this use-case, as the batching is appropriate here, but it’s helpful to understand what’s going on.

Related

Too many re-renderds | React App | State Variables updated in Component Function Not Working

Problem Summary
I am working on a React app with Node.js, Express.js, MogoDB backend.
I have a progress bar component. The number (i.e. percentage) that I pass to the progress bar comes from a function. This function calculates the percentage of the progress bar and then updates the state variable "progress".
The problem is that I seem to have an infinite re-rendering error. However, I cannot tell where it originates from and how to approach it.
Problem Description
Progress Component
Consider the following react component. This component is a progress bar. The component takes a float done and then shows a progress bar with a bar that takes up done% of the progress bar space
const Progress = ({done}) => {
return (
<div class="progress">
<div class="progress-done"
style={{
opacity: 1,
width: `${done}%`
}}>
{done}%
</div>
</div>
)
}
Use of Progress Bar in App.js
I attempted to implement the progress bar like below:
const App = () => {
const [progress, setProgress] = useState(0)
const [groupOfTasks, setGroupOfTasks] = useState([])
// assume that there’s code here that sets groupOfTasks using data from a database
const countDone = (groupOfTasks) => {
var numOfTasks = groupOfTasks.length
var numOfTasksSatisfied = 0
groupOfTasks.map((task, index) => {
if(task.completed == True)
{
numOfTasksSatisfied++
}
}
setProgress(numOfTasksSatisfied/numOfTasks)
}
return (
{countDone(groupOfTakss))
<Progress done={progress}/>
)
}
but I got this error:
How would you recommend I approach this problem? Any help is very appreciated
It's because you are using the countDone inside the return (where the visual render should be done)
You should use the useEffect hook, with the groupOfTasks as dependency.
So whenever the groupOfTasks is updated, you can have the progress updated at the same time
Just after you countDone method
useEffect(()=>{
countDone(groupOfTasks)
},[groupOfTasks])
By the way if you want to understand a bit more about the useEffect here a great article !
The error comes due to the line {countDone(groupOfTrasks)} inside the return statement. This is what happens:
You -> Render App -> Calculate count -> The countDone function sets a state, so it will rebuild the App -> Render App -> loop...
You can come over this problem by using the hook useEffect. The hook works like this: it takes a block of code and then an array.
If the array is empty -> the code will be executed only once when component is rendered.
If the array has variables -> the code will be executed every time one of those variables changes.
This is called dependencies array. In your case, set groupOfTasks inside the array, and it means: "Every time groupOfTasks changes, execute this code".
The function will be:
const App = () => {
const [progress, setProgress] = useState(0)
const [groupOfTasks, setGroupOfTasks] = useState([])
// assume that there’s code here that sets groupOfTasks using data from a database
const countDone = (groupOfTasks) => {
var numOfTasks = groupOfTasks.length
var numOfTasksSatisfied = 0
groupOfTasks.map((task, index) => {
if(task.completed == True)
{
numOfTasksSatisfied++
}
}
setProgress(numOfTasksSatisfied/numOfTasks)
}
useEffect(()=>{
countDone(groupOfTasks)
},[groupOfTasks])
return (
<Progress done={progress}/>
)
}

Trying to use useRef to run a function on a generated item in React/Remix/Prisma

I've gone through multiple useRef/useEffect instructions but I just can't seem to make it work here.
The code workflow here is: Remix/React, get data from database, display data, turn data into a ticker that can be updated
If anyone could point out any glaring errors they see in this code as to why the useEffect hook isn't firing, or why the useRef hook can never find the {listRef} within the <ul>, I would love to know.
import { Links, redirect, useLoaderData, Outlet } from 'remix'
import { db } from '~/utils/db.server'
import { getUser } from '~/utils/session.server'
import { ReactSortable } from "react-sortablejs"
import { useState, useRef, useEffect } from 'react'
import tickerStylesUrl from '~/styles/tickerDisplay.css'
export const links = () => [{ rel: 'stylesheet', href: tickerStylesUrl }]
export const loader = async ({ request, params }) => {
const user = await getUser(request)
const ticker = await db.ticker.findUnique({
where: { id: params.tickerId },
include: {
headlines: true,
},
})
if (!ticker) throw new Error('Ticker not found')
const data = { ticker, user }
return data
}
export const action = async ({ request, params }) => {
}
// The ticker function displays the items without styling, so it finds the database perfectly and can get the data
function displayTicker() {
const { ticker, user } = useLoaderData()
const headlines = ticker.headlines
const tickerParentStyle = {
width: "1920px",
height: "1080px",
position: "relative",
backgroundColor: "black"
}
const tickerStyle = {
position: "absolute",
padding: "0",
bottom: "0",
color: `${ticker.fontColor}`,
backgroundColor: `${ticker.backgroundColor}`,
fontFamily: `${ticker.font}`,
fontSize: "2em",
}
const tickerHeadlineStyle = {
margin: "auto",
height: "50%",
}
console.log("Headlines: " + headlines)
// So begins the found ticker code I had hoped to integrate
// Source: https://www.w3docs.com/tools/code-editor/2123
function scrollTicker() {
const marquee = listRef.current.querySelectorAll('.tickerHeadlines');
let speed = 4;
let lastScrollPos = 0;
let timer;
marquee.forEach(function (el) {
const container = el.querySelector('.headlineItem');
const content = el.querySelector('.headlineItem > *');
//Get total width
const elWidth = content.offsetWidth;
//Duplicate content
let clone = content.cloneNode(true);
container.appendChild(clone);
let progress = 1;
function loop() {
progress = progress - speed;
if (progress <= elWidth * -1) {
progress = 0;
}
container.style.transform = 'translateX(' + progress + 'px)';
container.style.transform += 'skewX(' + speed * 0.4 + 'deg)';
window.requestAnimationFrame(loop);
}
loop();
});
window.addEventListener('scroll', function () {
const maxScrollValue = 12;
const newScrollPos = window.scrollY;
let scrollValue = newScrollPos - lastScrollPos;
if (scrollValue > maxScrollValue) scrollValue = maxScrollValue;
else if (scrollValue < -maxScrollValue) scrollValue = -maxScrollValue;
speed = scrollValue;
clearTimeout(timer);
timer = setTimeout(handleSpeedClear, 10);
});
function handleSpeedClear() {
speed = 4;
}
}
const listRef = useRef()
console.log("listRef: " + JSON.stringify(listRef))
// This console appears everytime, but is always empty, presumably because DOM has just rendered
useEffect(() => {
console.log("useEffect fired")
// This console NEVER fires, sadly. I thought this would happen ONCE rendered
}, [listRef]);
return (
<>
<Links />
<div style={tickerParentStyle}>
<div style={tickerStyle}>
<div key={ticker.id} style={tickerHeadlineStyle} class="tickerWrapper">
// HERE IS THE TARGET UL
<ul className="tickerHeadlines" ref={listRef} style={{ margin: "10px 0 10px 0" }} >
{/* Hoping to map through the ticker items here, and have them displayed in a list, which would then be manipulated by the useRef/useEffect hook */}
{headlines.map((headline) => (
<>
<li class="headlineItem" key={headline.id}>
<span>
{headline.content} {ticker.seperator}
</span>
</li>
</>
))}
{scrollTicker()}
</ul>
</div>
</div>
</div>
</>
)
}
export default displayTicker
As always, any help is appreciated.
useRef is a hook that is used to access DOM elements, manipulating the DOM directly in a React application breaks the whole point of declarative programming. It is not at all advised to manipulate DOM directly using any dom objects and methods such as document. Coming to the useEffect hook, the useEffect hook runs conditionally depending on what's supplied in the dependency array, if none, the hook runs only once after the component finishes mounting. So you should be careful regarding what needs to be passed to the useEffect dependency array. Considering your case, when you pass listRef, the useEffect runs only when there is a change in the object and not it's properties, because objects are non-primitive, any changes in the property is not treated as a change in the object, and its merely an object property mutation that doesn't cause re-render. To steer clear, you should be sure of, when exactly you want it to be invoked, as you mentioned, you'd want it to run right after the data has rendered, you could instead use headlines in your dependency array.
Change the dependency array to include headlines.
useEffect(() => {
console.log("useEffect fired")
// This console NEVER fires, sadly. I thought this would happen ONCE rendered
}, [headlines]);
Alternatively, you could also leave it empty, making it run only once after the component has mounted.
useEffect(() => {
console.log("useEffect fired")
// This console NEVER fires, sadly. I thought this would happen ONCE rendered
}, []);
A caveat, the former snippet would run every time there's a change in headlines, and the latter would run only once no matter what changes.
So, depending on your use case, you might want to choose the one that best suits your needs.
There are a couple of things to code make code better:
initiate ref with 'null' value
call your 'scrollTicker' function inside useEffect Hook.
always remove listeners when component demount. Follow https://reactjs.org/docs/hooks-reference.html#useeffect for more details
you can use useEffect hook like this:
useEffect(() => {
// use your ref here.
return () => {
// Remove linteners
};
});

React state disappearing

I have a react app that uses the MS Graph API (so it's a bit difficult to post a minimal reproducible example). It has a state variable called chats that is designed to hold the result of fetching a list of chats from the graph API. I have to poll the API frequently to get new chats.
I query the chats endpoint, build an array of newChats and then setChats. I then set a timeout that refreshes the data every 10 seconds (it checks for premature invocation through the timestamp property stored in the state). If the component is unmounted, a flag is set, live (useRef), which stops the refresh process. Each chat object is then rendered by the Chat component (not shown).
Here's the code (I've edited by hand here to remove some irrelevant bits around styles and event propagation so it's possible that typo's have crept in -- it compiles and runs in reality).
const Chats = () => {
const [chats, setChats] = useState({ chats: [], timestamp: 0 });
const live = useRef(true);
const fetchChats = () => {
if (live.current && Date.now() - chats.timestamp < 9000) return;
fetchData(`${baseBeta}/me/chats`).then(res => {
if (res.value.length === chats.chats.length) return;
const chatIds = chats.chats.map(chat => chat.id);
const newChats = res.value.filter(chat => !chatIds.includes(chat.id));
if (newChats.length > 0) {
setChats(c => ({ chats: [...c.chats, ...newChats], timestamp: Date.now() }));
}
setTimeout(fetchChats, 10000);
});
};
useEffect(() => {
fetchChats();
return () => (live.current = false);
}, [chats]);
return (
<div>
{chats.chats.map(chat => (
<Chat chat={chat} />
))}
</div>
);
};
The Chat component must also make some async calls for data before it is rendered.
This code works, for a second or two. I see the Chat component rendered on the screen with the correct details (chat member names, avatars, etc.), but almost before it has completed rendering I see the list elements being removed, apparently one at a time, though that could just be the way its rendered -- it could be all at once. The list collapses on the screen, showing that the chat state has been cleared out. I don't know why this is happening.
I've stepped through the code in the debugger and I can see the newChats array being populated. I can see the setChats call happen. If I put a breakpoint on that line then it is only invoked once and that's the only line that sets that particular state.
So, what's going on? I'm pretty sure React isn't broken. I've used it before without much trouble. What's changed recently is the inclusion of the refresh code. I'm suspicious that the reset is taking away the state. My understanding is that the fetchChats method will be rendered every time the chats state changes and so should see the current value of the chats state. Just in case this wasn't happening, I passed the chats state from the useEffect like this:
useEffect(() => {
fetchChats(chats);
return () => (live.current = false);
}, [chats]);
With the necessary changes in fetchChats to make this work as expected. I get the same result, the chats state is lost after a few seconds.
Edit
Still Broken:
After #Aleks answer my useEffect now looks like this:
useEffect(() => {
let cancel = null;
let live = true;
const fetchChats = () => {
if (Date.now() - chats.timestamp < 9000) return;
fetchData(`${baseBeta}/me/chats`).then(res => {
if (res.value.length === chats.chats.length) return;
const chatIds = chats.chats.map(chat => chat.id);
const newChats = res.value.filter(chat => chat.chatType === "oneOnOne" && !chatIds.includes(chat.id));
if (newChats.length > 0 && live) {
setChats(c => ({ chats: [...c.chats, ...newChats], timestamp: Date.now() }));
}
cancel = setTimeout(fetchChats, 10000);
});
};
fetchChats();
return () => {
live = false;
cancel?.();
};
}, []);
The result of this is that the chats are loaded, cleared, and loaded again, repeatedly. This is better, at least they're reloading now, whereas previously they would disappear forever. They are reloaded every 10 seconds, and cleared out almost immediately still.
Eventually, probably due to random timings in the async calls, the entries in the list are duplicated and the 2nd copy starts being removed immediately instead of the first copy.
There are multiple problems. First this
setTimeout(fetchChats, 10000); will trigger
useEffect(() => {
fetchChats(chats);
return () => (live.current = false);
}, [chats])
You will get 2 fetches one after another.
But the bug you're seeing is because of this
return () => (live.current = false);
On second useEffect trigger, clean up function above with run and live.current will be forever false from now on.
And as Nikki9696 said you you need to clear Timeout in clean up function
The easiest fix to this is, probably
useEffect(() => {
let cancel = null;
let live = true;
const fetchChats = () => {
// not needed
//if ( Date.now() - chats.timestamp < 9000) return;
fetchData(`${baseBeta}/me/chats`).then(res => {
//this line is not needed
//if (res.value.length === chats.chats.length) return;
// remove all the filtering, it can be done elsewhere where
// you can access fresh chat state
//const chatIds = chats.chats.map(chat => chat.id);
//const newChats = res.value.filter(chat =>
//!chatIds.includes(chat.id));
if (res.value?.length > 0&&live) {
setChats(c => ({ chats: [...c.chats, ...res.value], timestamp: Date.now() }));
cancel = setTimeout(fetchChats, 10000);
}
});
};
fetchChats()
return () => { live=false; if(cancel)window.clearTimeout(cancel) };
}, []);
Edit: typo cancel?.() to window.clearTimeout(cancel);
Ok, I have an idea what's happening and how to fix it. I am still not sure why it is behaving like this, so please comment if you understand it better than me.
Basically, for some reason I don't understand, the function fetchChats only ever sees the initial state of chats. I am making the mistake of filtering my newly fetched list against this state, in which the array is empty.
If I change my useEffect code to do this instead:
setChats(c => {
return {
chats: [
...c.chats,
...res.value.filter(cc => {
const a = c.chats.map(chat => chat.id);
return !a.includes(cc.id);
})
],
timestamp: Date.now()
};
});
Then my filter is passed the current value of the state for chats rather than the initial state.
I thought that because the function containing this code is in the function that declares the chat state, whenever that state changed the whole function would be rendered with the new value of chats making it available to its nested functions. This isn't the case here and I don't understand why.
The solution, to only trust the values of the state that is handed to me during the setState (setChats) call, works fine and I'll go with it, but I'd love to know what is wrong with reading the state directly.

useEffect() infinitely runs for some reason

So I'm currently trying to learn react and as practice I was just trying to build a hacker news site using the hacker new API. But, I ran into a issue. For some reason it is currently infinitely looping. I debugged it and found that it has something to do with the useEffect() hook. I tried the solution in this post but it didn't seam to work(I think I probably did it wrong though).
My Code:
const [maindata, setmaindata] = useState("");
useEffect(() => {
axios
.get("https://hacker-news.firebaseio.com/v0/user/jl.json?print=pretty")
.then((repo) => {
const output = [];
// repo.data["submitted"].length
for (let x = 0; x < 30; x++) {
axios
.get(
"https://hacker-news.firebaseio.com/v0/item/" +
repo.data["submitted"][x] +
".json?print=pretty"
)
.then((titledata) => {
//console.log(titledata.data["text"]);
output.push(titledata.data["text"]);
});
}
});
setmaindata(output);
});
I also tried replacing:
}
});
setmaindata(output);
});
With:
}
});
}, [output});
But that didn't seem to work
If you don't pass the dependency array to useEffect, useEffect runs on every render.
By pass empty array([]) as dependency, useEffect runs once when component is mounted.
Ex:
useEffect(() => {
... // your code
}, [])
You should add dependency to useEffect because if you don't add any dependency, this method infinitely runs.
Just implement at the end of method [].
In addition, take care with setMainData because you call it outside of axis request.
Final code could be this:
const [maindata, setmaindata] = useState("");
useEffect(() => {
axios
.get("https://hacker-news.firebaseio.com/v0/user/jl.json?print=pretty")
.then((repo) => {
const output = [];
// repo.data["submitted"].length
for (let x = 0; x < 30; x++) {
axios
.get(
"https://hacker-news.firebaseio.com/v0/item/" +
repo.data["submitted"][x] +
".json?print=pretty"
)
.then((titledata) => {
//console.log(titledata.data["text"]);
output.push(titledata.data["text"]);
// here you have output array with the push action that you did in previous line
setmaindata(output);
});
}
});
}, [dependency]);
Change dependency with your variable that you want to use when this value changes this useEffect will be called

hook state doesn't update automaticlly

I wanted to create pagination in React. All data comes from store. In this code I wanted to implement search engine. On this time I don't have this but i wrote search method which simulate that. OK, it works but - after I click hello, it display only items from category 2 but it display all the time this same pages (in my case 3). If I click 2 times more, it display only 1 page. I added setCountItems and setPages into search becouse this hooks doesn't update automaticlly.
import React, { useEffect, useState, useRef } from 'react'
import { connect} from 'react-redux'
import Article from './article'
const ArticlesContainer = ({ articles }) => {
const [allItems, setAllItems] = useState(articles.list);
const [pageNumber, setPageNumber] = useState(1);
const [perSite, setPerSite] = useState(10);
const [totalItems, setCountItems] = useState(allItems.length);
const from = (pageNumber - 1) * perSite;
const to = ((pageNumber - 1) * perSite) + perSite;
const [pages, setPages] = useState(Math.ceil(totalItems / perSite));
const handlePageClick = (i) => {
setPageNumber(i);
}
const search = () => {
setAllItems(allItems.filter(x => x.category=== 2 ));
setCountItems(allItems.length);
setPages(Math.ceil(totalItems / perSite));
}
const Pagination = ({pages}) => {
let list = []
for(let i = 1; i<=pages; i++){
list.push(<li key={i} onClick={() => handlePageClick(i)}>{i}</li>)
}
return list;
}
return (
<React.Fragment>
<a onClick={search}>Hello</a>
{allItems.slice(from, to).map(article =>
<Article key={article.id} article={article} />
)}
<div className="row">
<ul>
<Pagination pages={pages} />
</ul>
</div>
</React.Fragment>
);
}
const mapStateToProps = state => ({
articles: state.articles
})
export default connect(mapStateToProps, null)(ArticlesContainer);
Where problem is?
You seem to be suffering from a common misunderstanding of how State works in React. Updating state, whether via this.setState in class or via the "update function" returned by the useState hook, doesn't "automagically" change the relevant state value then and there. In class components, that's because of how React's implementation of setState works (it's asynchronous), but with Hooks it should be perfectly obvious if you stop to think about it. setAllItems is a function, while allItems is an array - and they don't have anything directly to do each other. Calling setAllItems doesn't change the value of allItems - because how could it? allItems is just a variable, the only way to give it a new value is to directly mutate or reassign it - clearly calling a separate function, setAllItems, with an argument that isn't allItems, can't possibly do that.
What it instead does is schedule a rerender of the component - that is, schedules a subsequent call of your function that represents the component - and ensures that the useState call corresponding to allItems will then return value you set. But this is necessarily a rather indirect process. In particular, allItems will have the value you want on the next render of your component, but that search function won't be called (until the user clicks the button again), so the setCountItems(allItems.length); call won't automatically trigger with the "correct" length (the updated length after filtering).
In your case the solution to the problem is very simple. You've overcomplicated your component by introducing far too many state variables, most of which are dependent on each other. Instead of const [totalItems, setCountItems] = useState(allItems.length);, just put const totalItems = allItems.length; - then this will automatically be recalculated to the correct value on every render. You've no need of a setCountItems function, as you know that it will always be equal to allItems.length - it doesn't vary independently.
Similarly, you can vastly simplify much else in this component, since the only things which can vary independently, and therefore which needs to be part of state, are the article list and the page number. This is how I would rewrite your component:
const perSite = 10;
const ArticlesContainer = ({ articles }) => {
const [allItems, setAllItems] = useState(articles.list);
const [pageNumber, setPageNumber] = useState(1);
const totalItems = allItems.length;
const from = (pageNumber - 1) * perSite;
const to = ((pageNumber - 1) * perSite) + perSite;
const pages = Math.ceil(totalItems / perSite);
const handlePageClick = (i) => {
setPageNumber(i);
}
const search = () => {
setAllItems(allItems.filter(x => x.category=== 2 ));
}
const Pagination = ({pages}) => {
let list = []
for(let i = 1; i<=pages; i++){
list.push(<li key={i} onClick={() => handlePageClick(i)}>{i}</li>)
}
return list;
}
return (
<React.Fragment>
<a onClick={search}>Hello</a>
{allItems.slice(from, to).map(article =>
<Article key={article.id} article={article} />
)}
<div className="row">
<ul>
<Pagination pages={pages} />
</ul>
</div>
</React.Fragment>
);
}

Categories

Resources