React rendering before array is populated from GET Request - javascript

I am new to React, so pointers also welcome.
I am populating an array with the json of an api call:
fetch('/rpa').then(rpa => rpa.json()).then(data => data.rpa).then(nestedData=>nestedData.forEach(item => jsonRespnse.push(item)));
console.log(jsonRespnse)
Logging to the console shows the data as I would expect. However, putting that data in as part of my return, I am not getting anything:
return (
<div>
{rpaName.map((rpaItem, i) => (
<div>
<div className='headerContainer' onClick={()=>toggle(i)}>
<h4 className='rpaHeader'>{rpaItem}</h4><span className='rpaSpan'>{selected === i ? '-': '+'}</span>
</div>
<div className={selected === i ? 'rpaButton show': 'rpaButton'}>
<button onClick={()=>sendData(rpaItem)}>Start{rpaItem}</button><button>Stop{rpaItem}</button>
</div>
<br></br>
</div>))}
</div>);}
I am assuming its a timing thing, with the rendering taking place before the array can be populated, when I hard code an array it works fine.
If anyone could point me in the right direction that would be appreciated.

The only way to make the component rerender within itself is to use state.
In React world, since you didn't provide the full component, I'm assuming you're using functional components, in which you have hooks such as useState, and useEffect.
useState is where you'd place your changing variables to.
Example.
function MyComponent() {
// the first variable here is the actual value of the state, the next is the function to change the state.
const [myState, setMyState] = React.useState();
// when we move over to useEffect, is the hook that'd typically use to perform fetch requests for example.
React.useEffect(() => {
fetch(...).then(response => response.json()).then(setMyState);
}, [])
return <div>{myState}</div>
}
When the state gets a new value, it will rerender the component to reflect the new change.

Since you are using functional components, you can use the useEffect hook of react to perform the API call before rendering your component.
Then you can use the useState hook to declare a state variable to hold the fetched data.
Sample code:
import React, { useState, useEffect } from 'react';
const yourComponent = () => {
const [ data, setData ] = useState([]);
useEffect(() => {
fetch('<URL>').then(response => response.json()).then(responseArr => setData(responseArr)));
}, []);
return(
//Rest of the code (Now you can use the fetched data as an array since "data" state's been populated with the data fetched from the API call)
);
}

you're probably wanting to set the request array as a state object so something like
import React, { useState } from 'react';
function someReactComponent() {
// Declare a new state variable, which we'll call "count"
const [fetchResponse, setFetchResponse] = useState([]);
fetch('/yourfetchurl').then(response => response.json()).then(responseArr => setFetchResponse(responseArr)));
return (
<div>
{fetchResponse.map((res, i) => {
return (
<div key={i}>
{res.whatever}
</div>
);
})}
</div>
);
}

Thanks all for your responses - I managed to get there with a combination of all of them really, so thankyou.
Once I got the useState + useEffect combo in there, it was just a case of how the JSON was being put into the array, this is what was giving me the error, I had to access they key first:
getList().then(items => setRpaList(items.rpa));
Thankyou for your help.

You're going to want to have a useEffect to make the network call in your component, so that it fetches the data after render, and a useState to bind the variable data to. Then once the async call resolves (i.e. in the callback), call the state setter function with the resulting data to refresh the data on the page.
This article provides a good explanation: https://www.digitalocean.com/community/tutorials/how-to-call-web-apis-with-the-useeffect-hook-in-react
You can also start a spinner on render, and kill the spinner once the fetching callback runs.
Note that console.log does not guarantee that it will log the data as it was at the time the statement was executed, which means that the jsonResponse when that line was run may be (in this case, it is) different then what you observed outputted in your console.

Related

How to fix the following "Too many re-renders error" in React?

I'm trying to render the string array keys into a React component. keys are the keys that the user presses (but I just hard-coded them for the sake of this example).
import { useState } from "react";
import * as ReactDOM from "react-dom";
let keys = ["a", "b"];
function App() {
let [keysState, setKeysState] = useState([]);
setKeysState((keysState = keys));
return (
<div>
{keysState.map((key) => (
<li>{key}</li>
))}{" "}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
But I'm getting this error:
Too many re-renders. React limits the number of renders to prevent an infinite loop.
I know I can avoid this error by creating and onClick handler ... but I don't want to display keysState on click. I want it to display and re-render immediately when keys changes.
Live code: https://codesandbox.io/s/react-18-with-createroot-forked-vgkeiu?file=/src/index.js:0-504
when the page loads, the setKeysState function gets invoked and it updates the state, updating the state in reactjs causes a re-render and it keeps doing that infinitely. which produces Too many re-renders error.
just pass an initial value to the useState() hook to initialize the state. like this :
let [keysState, setKeysState] = useState(keys);
NOTE : In your case You do not need The React useState Hook because you're not tracking the data (keys in your case, you are not be updating it )
just change your component like this :
let keys = ["a", "b"];
function App() {
return (
<div>
{keys?.map((key) => (
<li>{key}</li>
))}
</div>
);
}
While #monim's answer is great, I feel that you don't need a useState hook if you don't need setKeysState
import * as ReactDOM from "react-dom";
let keys = ["a", "b"];
function App() {
return (
<div>
{keys.map((key) => (
<li>{key}</li>
))}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
EDIT: I put together a series of links that I did not want to lose. If you want to deep dive, here's what I personally saved on the hooks: https://github.com/criszz77/react-js-links#hooks
There seems to be a bit of confusion as to how useState works.
You are getting infinite refreshes, because the component re-renders if there is a change to a state. And what is happening there is, that you component loads, you init your state and set value, which triggers re-render and that runs in loop. To solve it just set initial value to keys instead of []
Things that no one mentioned
Make states constants as there is, I think, never a good reason to change the fact that it's a state and if you want to change the value you use setKeysState
setKeysState is a function where that you call with the new value of keysState, so do never change value of that state by anything else but setKeysState(newValue). You are passing something that I'd describe as ?function?. Just pass the value.
Solution:
const [keysState, setKeysState] = useState(keys);
Many different problems with this code, and this allow me to tell you that you haven't got the way of React hooks yet.
First of all, you don't need to write this setKeyState function. The useState hook will return it when invoked.
Second, if you want to provide an initial value to your keyState, you should do it using the setState hook, just like this:
const [keyState, setKeyState] = useState(["a","b"]);
This would create the keyState variable and initialize it with ["a","b"], as well as provide the setKeyState function.
I believe this is the main problem with this code. This redundance is causing the perpetual re-render.
Finally, you should have some way to react to state changes. This will be given by the useEffect hook.
useEffect(() => {
// Things to perform when `stateKeys` changes
},[stateKeys])
The second parameter this hook receives [stateKeys] is exactly to prevent a perpetual re-rendering. It tells the hook it should run only when the variables inside this array changes.
It seems to me, allow please don't be offended for me to say, that you missed something in React way of doing things. Specially when it comes to React Hooks. I suggest you to read the documentation again:
https://reactjs.org/docs/hooks-state.html

React useEffect with axios

Below is a snippet of code to fetch data from url by axios,
import React, { useState, setEffect, useEffect } from 'react';
import axios from "axios";
import LoadingPage from "./LoadingPage";
import Posts from "./Posts";
const url = "https://api-post*****";
function App() {
const [posts, setPosts] = useState([]);
const fetchPost = async() => {
try {
const response = await axios(url);
return response.data;
} catch (error) {
console.error(error);
}
};
let data = fetchPost();
setPosts(data);
return (
<main>
<div className="title">
<h2> Users Posts </h2>
{posts.length
? <Posts posts={posts} />
: <Loading posts={posts} />
}
</div>
</main>
);
}
export default App;
However, it got the error of
uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite
Question 1: How could this be of too many re-render, there is no loop or something?
To solve this bug, we can use below changes:
const [posts, setPosts] = useState([]);
const fetchPost = async () => {
try {
const response = await axios(url);
setPosts(response.data);
} catch (err) {
console.error(err);
}
};
useEffect(()=> {
fetchPost();
}, [posts])
Question 2: how the useEffect work to avoid too many calls?
Question 3: I always treat react hooks under hood as web socket communications, etc. If that is the case?
When you call setPosts the component will render again, and fetch the data again, at which point you set state with the data forcing a new render which fetches the data...etc.
By using useEffect you can fetch the data, and set state once when the component is first rendered using an empty dependency array.
useEffect(() => {
// Fetch the data
setposts(data);
}, []);
You probably don't want to watch for posts in this useEffect (you can have many) like you're doing in your updated example because you may run into the same issue.
I will only answer number one.
Caveat: the answer is a bit long.
I really hope it will help you to understand a topic that took me a long time to grasp.
Answer one:
To answer this question we should ask our selves two things, a) what is a side effect? and b) how the life cycle of components works?
so, we know that React functional component are pure functions and they should stay that way, you can pass props as parameters and the function will do stuff and return JSX, so far so good.
and for the second part we cannot control React virtual DOM, so the component will render many times during it's lifecycle, so imagine that the virtual DOM decided to check the code and compare between the virtual DOM and the real DOM and in order to do that he will have to check and "run" the code that resides inside that specific component.
the virtual DOM will run the API call, he will find a different result which will cause a new render to that specific component and this process will go on and on as an infinite loop.
when you are using usEffect you can control when this API call will take place and useEffect under the hood makes sure that the this API call ran only one your specific change take place and not the virtual DOM V.S real DOM change.
to summarize, useEffect basically helps you to control the LifeCycle of the component
Please first check your state like this.
useEffect(()=> {
fetchPost();
}, [posts]);

Calling useEffect in a functional component within a functional component causes this message: Rendered more hooks than during the previous render

first off - Happy Friday!
I just came on here to see if anyone had any input to an issue that I am seeing in my ReactJs application. So I have a functional component renderViews and in that functional component, there are multiple views to render. Then within the renderViews I have another functional component carDetailsView and I try to make a call to an api when that particular component appears(as a modal). requestCarsDetails() should only be called when that component appears so thats why I nested a useEffect hook in the carDetailsView. But that causes an issue:
Rendered more hooks than during the previous render
.Please see code below:
const renderViews = () = > {
useEffect(()=> {
requestCarInfos()
.then((res) => {
setCars(cars);
});
}, []);
const carDetailsView = () => {
useEffect(() => {
requestCarDetails()
.then((res) => {
setDetails(res.details);
});
}, []);
return (<div>carDetailsView</div>)
}
return (<div>{determineView()}</div>)
}
The useEffect that is being used at the top level works fine. The issue only appeared after I added the second useEffect which is in the carDetailsView. Any help or advice is appreciated. Thanks!
Its a rule of hooks.
https://reactjs.org/docs/hooks-rules.html
Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.
React relies on the order in which Hooks are called.
As long as the order of the Hook calls is the same between renders, React can associate some local state with each of them.
if we put a Hook call inside a condition we can skip the Hook during rendering, the order of the Hook calls becomes different:
React wouldn’t know what to return for the second useState Hook call. React expected that the second Hook call in this component corresponds to the persistForm effect, just like during the previous render, but it doesn’t anymore. From that point, every next Hook call after the one we skipped would also shift by one, leading to bugs.
This is why Hooks must be called on the top level of our components. If we want to run an effect conditionally, we can put that condition inside our Hook:
use the lint https://www.npmjs.com/package/eslint-plugin-react-hooks
this is a caveat of using functional components, on each render everything inside the functional component gets kind of executed. so react needs to maintain the list of all hooks which have been defined when the component was created. think of it as an array.
on each render, useState will return the value for you. if you understand this, you will understand what stale state also means. ( stale state can happen, when closures occur within these components )
Something like that?
const CarDetailsView = () => {
React.useEffect(() => {
console.log("Running CarDetailsView useEffect...") ;
},[]);
return(
<div>I amCarDetailsView</div>
);
};
const Views = () => {
const [showCarDetails,setShowCarDetails] = React.useState(false);
const toggleCarDetails = () => setShowCarDetails(!showCarDetails);
React.useEffect(() => {
console.log("Running Views useEffect...") ;
},[]);
return(
<div>
<div>I am Views</div>
<button onClick={toggleCarDetails}>Toggle car details</button>
{showCarDetails && <CarDetailsView/>}
</div>
);
};
const App = () => {
return(<Views/>);
};
ReactDOM.render(<App/>,document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id="root"/>

Infinite Re-rendering of React Functional Component using Axios and useState/useEffect?

I am trying to create a React Functional Component using Typescript that will get data from an API and then send that data to another component, however, I am getting an error of "Error: Too many re-renders. React limits the number of renders to prevent an infinite loop."
Please offer any advice!
useEffect(() => {
await axios.get('https://quote-garden.herokuapp.com/api/v3/quotes/random').then((resp) => {
setQuote1(resp.data.data[0]);
});
getQuote();
}, []);
Snippet of Code Causing Error
EDIT:
Here is the entire File where the error is occurring.
import React, { useEffect, useState } from 'react';
import { Row, Col, CardGroup } from 'reactstrap';
import axios from 'axios';
import QuoteCard from './QuoteCard';
const MainQuotes = () => {
const [quote1, setQuote1] = useState();
useEffect(() => {
await axios.get('https://quote-garden.herokuapp.com/api/v3/quotes/random').then((resp) => {
setQuote1(resp.data.data[0]);
});
getQuote();
}, []);
return (
<Row>
<Col>
<CardGroup>
<QuoteCard quoteData={quote1} />
</CardGroup>
</Col>
</Row>
);
};
export default MainQuotes;
Depending on the type of quote1 (I'm assuming it's a string) update to:
const [quote1, setQuote1] = useState('')
useEffect(() => {
function fetchQuote() {
await axios.get('https://quote-garden.herokuapp.com/api/v3/quotes/random')
.then((resp) => {
setQuote1(resp.data.data[0]);
});
}
if (!quote1) {
fetchQuote();
}
}, []);
Because the response from the API is a random quote, you have to handle it based on the value of quote1. This should work because quote1 will only be updated after the component mounts (when the value is an empty string), and the following render won't update state, so useEffect won't loop infinitely.
Before the edit, I assumed the axios request was inside of the getQuote function. I have updated my answer to reflect the code you have posted.
If the API response was static, because the items in the dependency array only cause a re-render if they are changed from their current value, it only causes a re render immediately after the first state update.
Can we get your entire code please? It seems the problem is going to be with your state hook and figuring out exactly what its doing.
My guess as of now is your state hook "setQuote1" is being invoked in a way that causes a re-render, which then would invoke your useEffect() again (as useEffect runs at the end of the render cycle) and thus, calling upon your setQuote1 hook again. This repeats itself indefinitely.
useEffect() allows for dependencies and gives you the power to tell exactly when useEffect() should be invoked.
Example: useEffect(() { code here }, [WATCH SPECIFICALLY THIS AND RUN ONLY WHEN THIS UPDATES]).
When you leave the dependency array empty, you tell the hook to run whenever ANYTHING updates state. This isn't necessarily bad but in some cases you only want your useEffect() to run when a specific state updates.
Something in your application must be triggering your useEffect() hook to run when you aren't wanting it to.

Triggering useEffect only certain conditions

I have basic understanding of useEffect. Without second parameter (dependency array) it runs on every render. With empty array, it runs on first render. With parameters in array, it runs whenever some of parameters changes.
Say I have useEffect with two dependencies (from GraphQL query): result.data and result.loading. I want useEffect to run if result.data changes, and result.loading is false. Purpose is for example to update Redux store:
useEffect(() => {
if (result.loading) return;
dispatch(updatePhotos([...photos, ...result.data.photos]));
}, [result.data, result.loading]);
But there's a catch: I have to include photos to list of dependencies. However, photos variable will be updated in other place, and it triggers this useEffect again.
How can I run useEffect only when those two variables changes?
I can of course use useState to store variable resultFetched, set it to true in useEffect and then dispatch only if it is false. But at some point I have to change it back to true, and useEffect runs again, since I can't manually change result.data or result.loading.
I'm lost how to properly use useEffect in these situations when there is lots of variables to handle.
Currently I'm building infinite scrolling photo list, where list is loaded part by part via GraphQL. But when user opens some photo and eventually returns to photo list, it is restored from Redux to same state and scroll position as it was before opening the photo.
I have spent countless hours trying to get it work, but this useEffect-thing is spoiling my every attempt. :) They always gets triggered before I want them to trigger, because there is so many changing variables.
Also, sometimes I want to run a function within useEffect (function added to dependency array), and I use useCallback for that function to memoize it. But then I also have to add all variables that function uses to dependency array of that useCallback, so function gets regenerated when those variables changes. That means that useEffect suddenly runs again, because the function in dependency array changes.
Is there really no way to use functions/variables in useEffect, without them to trigger useEffect?
It all depends on how updatePhotos works. If that creates an action then the problem is you are creating the new state in the wrong place. The previous value of photos shouldn’t be used here because as you pointed out, that causes a dependency.
Instead your reducer will have the old value of photos you can use and you simply pass the new request data to your reducer.
Described in more detail here: https://overreacted.io/a-complete-guide-to-useeffect/#decoupling-updates-from-actions
You can have two separate useEffect functions inside the same component and they will work independent one of another. use one for photos and one for data loading. I hope this example helps you to wrap your head around this.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
const [step, setStep] = useState(1);
useEffect(() => {
const id = setInterval(() => {
setCount((c) => c + step);
}, 1000);
return () => clearInterval(id);
}, [step]);
useEffect(() => {
const id = setInterval(() => {
setCount2((c) => c + step);
}, 1500);
return () => clearInterval(id);
}, [step]);
return (
<div>
<h1>{count}</h1>
<h1>{count2}</h1>
<input value={step} onChange={(e) => setStep(Number(e.target.value))} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("container"));
Please refer to this example in sandbox
https://codesandbox.io/s/react-playground-forked-6h0oz

Categories

Resources