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(...)
Related
Imagine two components like this in React:
import MyComponent2 from "./components/MyComponent2";
import React from "react";
export default function App() {
const [myState, setMyState] = React.useState([]);
React.useEffect(() => {
console.log("useEffect triggered");
}, [myState]);
return <MyComponent2 myState={myState} setMyState={setMyState} />;
}
import React from "react";
export default function MyComponent2(props) {
const [inputValue, setInputValue] = React.useState("");
function handleChange(e) {
setInputValue(e.target.value);
let list = props.myState;
list.push(`${e.target.value}`);
props.setMyState(list);
console.log(props.myState);
}
return (
<div>
<input
type="text"
value={inputValue}
name="text"
onChange={handleChange}
/>
</div>
);
}
As you can see I am making changes with props.setMyState line in second component. State is changing but Somehow I could not trigger React.useEffect in first component even tough It is connected with [myState]. Why ?
In short form of my question : I can not get "useEffect triggered" on my console when i make changes in input
Instead of providing myState and setMyState to MyComponent2, you should only provide setMyState and use the functional update argument in order to access the current state.
In your handleChange function you are currently mutating the React state (modifying it directly):
let list = props.myState; // This is an array that is state managed by React
list.push(`${e.target.value}`); // Here, you mutate it by appending a new element
props.setMyState(list);
// ^ You update the state with the same array here,
// and since they have the same object identity (they are the same array),
// no update occurs in the parent component
Instead, you should set the state to a new array (whose object identity differs from the current array):
props.setMyState(list => {
const newList = [...list];
newList.push(e.target.value);
return newList;
});
// A concise way to write the above is like this:
// props.setMyState(list => [...list, e.target.value]);
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?
When creating a function component in React and setting a default parameter everything works like expected and the component will be rendered once. But as soon as you add a hook like useEffect and use this parameter in the dependency array the component rerenders forever.
I've created a simple demo here: https://codesandbox.io/s/infinite-useeffect-loop-on-default-value-tv7hj?file=/src/TestComponent.jsx
The reason is quite obvious, because when using an object as default parameter, it will be created again and will not be equal to the previous one. And of course this doesn't happen on primitive default parameter values like number or string.
Is there any better way to avoid this side effect besides using defaultProps?
Yes, instead of setting the default value of value to being an object, just set it to false. Then check if value is truthy, if it is, then access the correct properties, otherwise, just show a default value. New code.
It would be something like:
import { useEffect, useState } from "react";
const TestComponent = ({ value = false }) => {
const [calcValue, setCalcValue] = useState(0);
useEffect(() => {
setCalcValue((cur) => cur + 1);
}, [value]);
return (
<div>
{value ? value.name : "Test"}:{calcValue}
</div>
);
};
The reason you get infinite loops is because the reference of value keeps changing.
The first time the component is rendered, it sees a new reference to value, which triggers the useEffect, which in turns modifies the state of the component, and this leads to a new render, which causes value to be re-created once again because the old reference to that variable has changed.
The easiest way to deal with this is to just create a default value outside the component and use that (basically the same as the defaultProps solution):
import { useEffect, useState } from "react";
const defaultValue = {name: "Test"}; // <-- default here
const TestComponent = ({ value = defaultValue }) => {
const [calcValue, setCalcValue] = useState(0);
useEffect(() => {
setCalcValue((cur) => cur + 1);
}, [value]);
return (
<div>
{value.name}:{calcValue}
</div>
);
};
Doing this will ensure that each time the component renders, it sees the same reference for value, therefore the useEffect hook only runs once.
Another way of dealing with this is to first wrap your component with memo, then create a new state variable which takes on the original value, and make your useEffect hook depend on this new state variable:
const TestComponent = React.memo(({ value = {name: "Test"} }) => {
const [calcValue, setCalcValue] = useState(0);
const [myValue, setMyValue] = useState(value);
useEffect(() => {
setCalcValue((cur) => cur + 1);
}, [myValue]);
return (
<div>
{myValue.name}:{calcValue}
</div>
);
});
The reason why we wrap the component with memo is so that it only re-renders after a state change if the prop had changed in value (instead of reference). You can change the way memo detects props changes by providing a custom comparison function as a second parameter.
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.
i got a issue with my code. My function "getNames" rerender may times, but i want it to render once? have you got any clue ?
import grounds from './../../UballersGroundsData.json';
export default function Groundlist() {
function getNames(jsonObj){
for(let item in jsonObj){
console.log("item = " + item);
for(let property in jsonObj[item] ){
console.log(jsonObj[item]);
// if (property === "groundName"){
// console.log(jsonObj[item][property]);
// }
}
}
}
return(
<div>
<h1>Hello world!</h1>
<ul>
{getNames(grounds)}
</ul>
</div>
)
}
Thank you !
You should put your function inside of a useEffect hook, then set it to a state hook with useState. Then, map out the list items for your list (assuming you are returning an array from your function). If you want it to only run getNames on the first render, you would set it up the useEffect hook with an empty dependency array. Code should look something like this:
import React, { useEffect, useState } from 'react'
import grounds from './../../UballersGroundsData.json';
export default function Groundlist() {
const [names, setNames] = useState([]) // Initial state with empty array
useEffect(() => {
function getNames(jsonObj){
// your function logic here...
}
const result = getNames(grounds) // Call your function
setNames(result) // set it to names state hook
}, []) // Empty array here means it will only use the useEffect on the first render.
return(
<div>
<h1>Hello world!</h1>
<ul>
{Array.from(names).map(name => <li>{name}</li>)}
</ul>
</div>
)
}
You can use useMemo react hook to memoize the returned value i.e. skip unnecessary / heavy calculations due to change in other state, props or context variables.
Example:
import { useMemo } from "react"
export default function Groundlist(props) {
const grounds = props.data // if grounds is passed as props from Parent component
const groundsMemo = useMemo(() => {
// do all the heavy calculations here
// (e.g. do the work of getNames function)
// and return some JSX or Array (data)
// returned value will be memoized;
// means it will be re-calculated only if "grounds" changes
// Hence, no unnecessary calls to getNames (heavy calculations)
}, [grounds])
return (
<div>
{/* Use this if groundsMemo is JSX */}
<ul>{groundsMemo}</ul>
{/* Use this if groundsMemo is an Array (data) */}
<ul>{groundsMemo.map(item => <li key={some_key}>
{item.property}
</li>)}</ul>
</div>
)
}
Try using useMemo and useCallBack you want to optimize your react app.
React Official docs clearly described how to use it: useMemo
useCallBack
You should never call a function inside return scope of render.
It's normal for a component to re-render without proper treatment.
Taking in mind the other 2 answers
You can use the full power of useEffect, useCallback and React.memo to prevent anything from re-render.
import React from 'react';
import grounds from './../../UballersGroundsData.json';
function Groundlist() {
// initiate state
const [names, setNames] = React.useState([]);
// This will prevent the Function from recalculate - useCallback
const getNames = React.useCallback(function (jsonObj) {
for(let item in jsonObj){
console.log("item = " + item);
for(let property in jsonObj[item] ){
console.log(jsonObj[item]);
// if (property === "groundName"){
// console.log(jsonObj[item][property]);
// }
}
}
}, []);
// Will make function run only once and nevermore - useEffect
React.useEffect(() => {
setNames(getNames());
}, [])
return(
<div>
<h1>Hello world!</h1>
<ul>
{names.map(a => <li>{a}</li>)}
</ul>
</div>
)
}
// Will prevent React from try to re-render without changing in props, so as your component has no props, will never re-render without yourself unmounting first
export default React.memo(Groundlist);
In another cases you can control exact when the component should recalculate your names using the last argument of functions
useCallback(() => {}, []) //<---
For example
useCallback(() => {}, [updateState]);
when updateState change the function will be recreated.