Updating component state inside array.map() causing too many re-renders - javascript

import useState from "react-usestateref";
const [meta, setMeta, metaRef] = useState({});
Inside component's JSX:
data.result.map((token) => {
const id = token.token_id;
const params = { uri: token.token_uri };
if (token.metadata) {
setMeta(JSON.parse(token.metadata));
} else {
Moralis.Cloud.run("get_token_uri", params).then(
(response) => setMeta(response)
);
}
const { name, description, imageUrl } = metaRef.current;
return (
<Card
key={id}
tokenId={id}
name={name}
description={description}
user_address={user_address}
imageUrl={fixURL(imageUrl)}
calledFrom={calledFrom}
/>
);
})
I want to update the meta state variable from inside data.result.map and use the updated meta object to return a Card component. I'm using react-usestateref to get the current value of the meta state variable. Otherwise, meta still remains {} when de-structuring, even after being updated. If token.metadata is not null I want to set the meta state var to token.metadata obj and if it is null, I want to make an asynchronous request to get back the object from Moralis and then set it to the meta state var. This code is producing the following error: Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
How can I make this code work as intended?

Related

How to fix Data.map is not a function in Reactjs?

So I have a Notesdata array containing object with properties title, tagline, description. Notesdata array is stored in the "data" state variable in the App component and I am sending this data state variable to notes view component to populate on the UI but when I am appending a new object inside the data state variable and then sending it to notesview component, I am getting the error "Data.map is not a function".
When I am printing the "data" state variable I am getting an array but when I am checking its type it's showing "object", I am confused in why it is showing like that.
I also tried using Array.from() on the "data" state variable before passing it to notesview but that is also showing the same error.
------------App component------------------
import React, { useState } from "react";
import './App.css';
import Input from './Components/Input';
import Navbar from './Components/Navbar';
import Notesview from './Components/Notesview';
import Notesdata from "./Data/Notesdata";
function App() {
// const [data, setdata] = useState(Notesdata);
const [data, setData] = useState(Notesdata);
function handleDelete(id) {
let newData = data.filter((item) => item.id !== id)
setData(newData)
}
function handlePost(value) {
let newval = data.push(value)
setData(newval)
console.log(typeof data)
console.log(data)
}
return (
<div className="App">
<header className="App-header">
<Navbar />
<Input data={data} handlePost={(value) => handlePost(value)} />
<Notesview handleDelete={handleDelete} Data={data} />
</header>
</div>
);
}
export default App;
-----------------notesview component-----------------------
import React from 'react'
import Notescard from './Notescard'
import "../Styles/Notes.css"
const Notesview = ({ Data, handleDelete }) => {
return (
<>
<div className='notes'>
{
Data.map((item) => { // here is the Data.map where the error is coming
return <Notescard item={item} handleDelete={handleDelete} />
})
}
</div>
</>
)
}
export default Notesview
There's a lot wrong right here:
let newval = data.push(value)
setData(newval)
console.log(typeof data)
console.log(data)
Array.prototype.push returns the length of the array, so you're setting data to a number. (Which, incidentally, does not have a .map() function.)
You're mutating a state value (data) before trying to update it. Just update it.
You're trying to examine the value after it's been updated, but you're examining the current value and not the new value. State updates are asynchronous.
To update the state correctly, you'd do something more like this:
setData([...data, value]);
If you might have a batch of updates and you want each state update to use the updating state in the batch rather than the current state in the render, you could use the callback version of the state setter:
setData(d => [...d, value]);
This creates a new array reference, which includes all of the elements in the data array as well as the new value element, and sets that reference as the updated state. Without mutating the current state for the current render.
You get this error because Data is null. you can check Data's existence before trying to map on it in Notesview like this:
Data && Data.map(...)

Can't update parent state from child using functional components

I am having an issue with my React app. I am trying to set the state of the parent component based on the child component's value. I can see in the dev tools and log window that the child's value is being received by the parent; however, the setState is not working as it should. I have tried creating a separate function just to set the values; hoping for it to act as a middleware but no luck.
I have been through about a couple of StackOverflow threads but not many cater for functional components. I found the following codegrepper snippet for reference but it does not help either.
link: https://www.codegrepper.com/code-examples/javascript/react+function+component+state
Most of the threads deal with how to get the value to the parent component; however, my issue is more "setting the state" specific.
import React, { useEffect, useState } from "react";
import Character from "../component/Character";
import Filter from "../component/Filter";
import Pagination from "../component/Pagination";
import axios from "axios";
import "./Home.css";
const Home = (props) => {
const [API, setAPI] = useState(`https://someapi.com/api/character/?gender=&status=&name=`);
const [characterData, setCharacterData] = useState([]);
const [pagination, setPagination] = useState(0);
const makeNetworkRequest = (data) => {
setAPI(data);
setTimeout(() => {
axios.get(data).then(resp => {
setPagination(resp.data.info)
setCharacterData(resp.data.results)
})
}, 1000)
}
const handleFormCallBack = (childData) => {
setAPI(childData);
makeNetworkRequest(API);
console.log(`Parent handler data ${childData}`)
console.log(`Parent handler API ${API}`)
}
useEffect(() => {
makeNetworkRequest(API)
}, [characterData.length]);
const mappedCharacters = characterData.length > 0 ? characterData.map((character) => <Character key={character.id} id={character.id} alive={character.status} /* status={<DeadOrAlive deadoralive={character.status} /> }*/ gender={character.gender} name={character.name} image={character.image} />) : <h4>Loading...</h4>
return (
<div className="home-container">
<h3>Home</h3>
<Filter parentCallBack={handleFormCallBack} />
<div className="characters-container">
{mappedCharacters}
</div>
{/* <Pagination pages={pagination.pages}/> */}
</div>
)
}
export default Home;
In the code above I am using a callback function on the parent named "handleFormCallBack", mentioned again below to get the information from the child filter component. When I log the value, the following results are being generated.
const handleFormCallBack = (childData) => {
setAPI(childData);
makeNetworkRequest(API);
console.log(`Parent handler data ${childData}`)
// Parent handler data https://someapi.com/api/character/?gender=&status=&name=charactername
console.log(`Parent handler API ${API}`)
// Parent handler API https://someapi.com/api/character/?gender=&status=&name=
}
I am not sure what I am doing wrong but any sort of help would be much appreciated.
Kind Regards
useState works pretty much like setState and it is not synchronous, so when you set the new value using setAPI(childData); react is still changing the state and before it actually does so both of your console.log() statements are being executed.
Solution - after setting the new value you need to track if it has changed, so use a useEffect hook for the endpoint url and then when it changes do what you want.
useEffect(() =< {
// do anything you want to here when the API value changes. you can also add if conditions inside here.
}, [API])
Just to check what I have explained, after calling setAPI(childData); add a setTimeout like
setTimeout(() => {
// you will get new values here. this is just to make my point clear
console.log(Parent handler data ${childData})
console.log(Parent handler API ${API})
}, 5000);

CoinGecko API data undefined after re-render React JS

Trying to render data from the CoinGekco API in my React component. It works on first render but if I leave the page or refresh, coin.market_data is undefined. I also tried passing coin to the useEffect() dependency array and that didn't work.
import React, { useEffect, useState } from "react";
import axios from "../utils/axios";
import CoinDetail from "./CoinDetail";
function CoinPagePage() {
const [coin, setCoin] = useState({});
useEffect(() => {
const getCoin = () => {
const coinid = window.location.pathname.split("/").splice(2).toString();
axios
.get(`/coins/${coinid}`)
.then((res) => {
setCoin(res.data);
console.log(res.data);
})
.catch((error) => console.log(error));
};
getCoin();
}, []);
return (
<div>
<CoinDetail current_price={coin.market_data.current_price.usd} />
</div>
);
}
export default CoinPagePage;
The GET request only happens when rendering the parent page. Re-rendering the child component will not run the fetch code again. Instead of passing current_price as a prop to your <CoinDetail> component, you could try passing coinid and doing the fetch inside your detail page.
That way, when the page is refreshed, the request will be executed again.
Edit
If you try to access a not existing property on an object, your application will crash. What you could do to prevent this from happening is checking if the request is done, before trying to access the property.
One way you could do this by setting the initial state value to null
const [coin, setCoin] = useState(null);
Then, above the main return, you could check if the value is null, if it is, return some sort of loading screen
if(coin === null) return <LoadingScreen />;
// main render
return (
<div>
<CoinDetail current_price={coin.market_data.current_price.usd} />
</div>
);
This way, when the fetch is done, the state gets updated and the page will re-render and show the updated content.

React functional component test file unable to update state value and call its props function

i do have a functional parent component and it do have a state value , which have default value as false and it is calling another component which is visible only when the state value changes to true and the child component do have 2 function in it.
Illustrating with code
export const MainSearch = (props) => {
const [search, setSearch] = useState(false);
const closeSearch = () => {
setSearch(false);
ANALYTICS.trackEvent('popup_collpsed');
}
const toggleSearch = async () => {
await setSearch(true);
ANALYTICS.trackEvent('popup_expanded');
}
};
return (
<React.Fragment>
<searchBar toggleSearch={toggleSearch} />
{search &&
<childSearch
toggleSearch={toggleSearch}
closeSearch={closeSearch}
/>}
</React.Fragment>
)
}
And its test file with one test case
describe('MainSearch',()=>{
it('AdvancedSearch - Toggle popup_expanded and popup_collapsed ', async () => {
const component = shallow(<MainSearch {...props} />);
const searchBar = component.find('searchBar');
await searchBar.props().toggleSearch(); // calling function
expect(ANALYTICS.trackEvent).toHaveBeenCalledWith('popup_expanded');
const childSearchComponent = component.find('childSearch'); // not working ,since state value hides
expect(childSearchComponent).toBeDefined();
await advancedSearchComponent.props().closeSearch();// here getting null for .props()
expect(ANALYTICS.page.trackEvent).toHaveBeenCalledWith('popup_collapsed');
});
});
i know its possible with component.update for CLASS COMPONENTS, but here am using functional components and am getting error
if i remove the state value search , am getting my test case PASS, but cant remove that , its needed. so my test case need to make the state value true and call the function closeSearch and then check the analytics.
BUT am getting error Method “props” is meant to be run on 1 node. 0 found instead.
I guess state value if false and its not getting that particular node .
Can you guys please help me on same , since am stuck with it and can give more info if needed
Take a look at your toggleSearch function. You're awaiting setSearch, which isn't a promise. Remove the await Keyword and you should be fine!
If you would want to trigger your Analytics call only after the State has been set, you would need to hook in to Reacts useEffect Hook.
Then you could do something like this:
useEffect(() => {
if(search){
ANALYTICS.trackEvent('popup_expanded');
}
}, [search])
https://reactjs.org/docs/hooks-effect.html

React-Redux child props not updating with parent

Redux store changes are reflected in my parent/wrapper class, but the prop passed to the child is not updated.
I've tried mutating the store differently to try and coax redux updates but realized this isn't the issue because my parent component is getting the updated information just fine. I've also tried storing the data in the parent's state as I've read but I've had issues with the asynchronous hook calls.
Parent
const ReportWrapper = ({ report: { loading, report, error }, match, getReport, editReport, finalizeReport }) => {
useEffect(() => {
getReport(match.params.id);
}, [getReport, match.params.id]);
const [displayFollowup, toggleDisplayFollowup] = useState(false);
// deal with invalid urls/report not found
if (error && error.status === 404) return <Redirect to="/reports/404" />;
// loading case
if (loading || report === null) return <Spinner />;
const meta = Object.assign({}, report.meta);
const initialReport = Object.assign({}, report.initialReport);
const followup = Object.assign({}, report.followup);
const initialValues = {
id: report._id,
meta,
initialReport,
followup
};
// after adding a followup, it is properly presented in the initialvalues and followup variables here and matches the redux store
return (
<Report
initialValues={initialValues}
editreport={editReport}
displayfollowup={displayFollowup}
toggledisplayfollowup={toggleDisplayFollowup}
finalizereport={finalizeReport}
/>
);
};
Child
const Report = ({ initialValues, editreport, finalizereport, history, displayfollowup, toggledisplayfollowup }) => {
.....
console.log(initialValues); <--- Doesn't reflect the new redux store state
.....
}
All code for Report.js: https://gist.github.com/arranw/2bd90c4dd07ab65d1c914e2320791c58
I would like both components to reflect the updated store state, but the value only makes it to the parent and the child doesn't receive the new values through the prop.
So this wasn't actually a react or redux issue, but an issue with Formik.
Solution:
enableReinitialize prop on my Formik component allowed the form state to update with it's props. Thanks everyone for your help.

Categories

Resources