Updating component's state using props with functional components - javascript

I am trying to update the state of my component using props that I get from the parent component, but I get the following error message:
Too many re-renders. React limits the number of renders to prevent an infinite loop.
I want the local state to update if the prop changes.
The similar posts (Updating component's state using props, Updating state with props on React child component, Updating component's state using props) did not fixed it for me.
import React, {useState} from "react"
const HomeWorld = (props) => {
const [planetData, setPlanetData] = useState([]);
if(props.Selected === true){
setPlanetData(props.Planet)
console.log(planetData)
}
return(
<h1>hi i am your starship, type: {planetData}</h1>
)
}
export default HomeWorld

You need to use the useEffect hook to run it only once.
import { useEffect } from 'react'
...
const HomeWorld = (props) => {
const [planetData, setPlanetData] = useState([]);
useEffect(() => {
if(props.Selected === true){
setPlanetData(props.Planet)
console.log(planetData)
}
}, [props.Selected, props.Planet, setPlanetData]) // This will only run when one of those variables change
return(
<h1>hi i am your starship, type: {planetData}</h1>
)
}
Please notice that if props.Selected or props.Planet change, it will re run the effect.
Why Do I Get This Error ?
Too many re-renders. React limits the number of renders to prevent an infinite loop.
What is happening here is that when your component renders, it runs everything in the function, calling setPlanetData wich will rerender the component, calling everything inside the function again (setPlanetData again) and making a infinite loop.

You're generally better off not updating your state with props. It generally makes the component hard to reason about and can often lead to unexpected states and stale data. Instead, I would consider something like:
const HomeWorld = (props) => {
const planetData = props.Selected
? props.Planet
//... what to display when its not selected, perhaps:
: props.PreviousPlanet
return(
<h1>hi i am your starship, type: {planetData}</h1>
)
}
This might require a bit more logic in the parent component, to control what displays when the Selected prop is false, but it's a lot more idiomatic React.

Related

useState setter function is not updating state in react

I am using the useIsDirty hook in two components, CustomCodeEditor and EditorFooter, to track whether the code in the Editor has been modified. The hook returns an isDirty state and a setIsDirty function to update it. When I call setIsDirty(true) in the CustomCodeEditor component, the state is updated, but when I call setIsDirty(false) in the EditorFooter component, it doesn't seem to update the isDirty state. I believe this is because the EditorFooter component does not have access to the updated state. Anyone, please help me with this.
useIsDirty:
import { useEffect, useState } from "react"
const useIsDirty = () => {
const [isDirty, setIsDirty] = useState(false)
useEffect(() => {
const handleBeforeUnload = (event) => {
if (isDirty) {
event.preventDefault()
event.returnValue = ""
alert("You have unsaved changes, are you sure you want to leave?")
}
}
console.log("Diryt:", isDirty)
window.addEventListener("beforeunload", handleBeforeUnload)
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload)
}
}, [isDirty])
return { isDirty, setIsDirty }
}
export default useIsDirty
CustomCodeEditor
import Editor from "#monaco-editor/react"
import useIsDirty from "../../hooks/useIsDirty"
const CustomCodeEditor = () => {
const { isDirty, setIsDirty } = useIsDirty()
console.log("isDirty:", isDirty)
return (
<div className="bg-[#1e1e1e] h-full">
<Editor
onChange={(value) => {
updateCode(value || "")
setIsDirty(true) // updating state
}}
/>
</div>
)
}
export default CustomCodeEditor
EditorFooter
import Button from "../reusable/Button"
const EditorFooter = () => {
const { setIsDirty } = useIsDirty()
const handleSave = async () => {
setIsDirty(false)
}
return (
<div>
<Button
onClick={handleSave}
>
Save
</Button>
<Button
onClick={handleSave}
>
Submit
</Button>
</div>
)
}
export default EditorFooter
Hooks are not singleton instances.. when you use useIsDirty somewhere.. it always create new instance, with unrelated states to other ones. If you want to share this state you need to use Context
const IsDirtyContext = createContext(undefined);
const IsDirtyProvider = ({ children }): ReactElement => {
const [isDirty, setIsDirty] = useState(false)
return <IsDirtyContext.Provider value={{isDirty, setIsDirty}}>{children}</IsDirtyContext.Provider>;
};
and then you should wrap your commponent tree where you wanna access it with IsDirtyProvider
after that, you can even create your custom hook that will just return that context:
const useIsDirty = () => {
return useContext(IsDirtyContext)
}
Looking at your question, it looks like you are trying to use the same state in both components. However, the state doesn't work like that. A new instance is created whenever you make a call to useIsDirty from a different component.
If you want to use the state value across two components. You can do that using one of the following ways.
1 - Use a parent and child hierarchy.
Steps
Create a parent component and wrap the two components inside the parent component.
Manage the state in the parent component and pass it using props to the child component.
Create a function in child components that will execute a function from the parent component. The parent component function will hold the code to update the state based on whatever value you receive from the child component.
Now you should be able to share your state between both components.
2 - Use the context api.
If you are not familiar with what context api is, below is a brief explanation.
Context api helps you share data between components, without the need of passing them as a prop to each and every component.
You can use createContext and useContext hooks from context api.
createContext is used to create a new context provider.
useContext hook is used to manage the state globally.
You can get the value from context using this function.
Whenever the state is updated the value will be reflected globally.
Note - Any component that needs to use the value inside the useContext should be wrapped inside the useContext provider component.
Steps to create a context provider.
To create a context you just need to use the react hook createContext
Create a context using below code
const isDirtyContext = createContext();
Wrap your components in the context provider
import {IsDirtyContext} from './path/filename'
<IsDirtyContext.Provider value={[isDirty, setIsDirty]}>{children}</IsDirtyContext.Provider>
If your context is in a separate file, then you can import it into any child component using the import statement.
import {IsDirtyContext} from './path/filename'
Use the context
const [isDirty] = useContext(IsDirtyContext);
Now the isDirty state value is available globally in all components.
Hope this information helps you. Please upvote if this helps you understand and solve the problem.

React component re-rendering many times

i have a react component thats keep re-rendering idk why but i think the reason is the data fetching
data code :
export function KPI_Stock_Utilisation() {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
setKpi_stock_utilisation((existingData) => {
return response.data;
});
});
}, []);
console.log('data get')
return kpi_stock_utilisation;
}
this log displayed many times , and the log in the component too
component code :
import React from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import { useEffect } from "react";
export default function WarehouseUtilisChart(props) {
let kpi_stock_utilisations =KPI_Stock_Utilisation();
let Stock_utilisation = (kpi_stock_utilisations.length / 402) * 100;
console.log('component render')
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
im new with react i tried useEffect inside the componenets but its not working
Calling the react custom hook KPI_Stock_Utilisation several times will for sure render more than once.
in your case I suggest you use useEffect in the same component as I will show you.
import React,{useEffect,useRef} from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import axios from 'axios';
export default function WarehouseUtilisChart(props) {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
const stock_utilisation= useRef(0);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
stock_utilisation.current = (response.data.length / 402) * 100;
setKpi_stock_utilisation(response.data);
});
//this will guarantee that the api will be called only once
}, []);
//you should see this twice, one with the value 0, and another one, the calculated data
console.log('component render',stock_utilisation.current)
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
To note, if you call this component from more than one location, for sure it will render several times - keep that in mind.
On the other hand, all your variables should always start with a lower case and try to name your variables like this: instead of kpi_stock_utilisation change it to kpiStockUtilisation for a better coding practice
You got into infinite loop.
Its hard to explain why it doesn't work as expected, but I can try.
First of all, useEffect with empty array of dependencies works like componentDidMount and fires only after (!) first render.
So you have some value returned from your let kpi_stock_utilisations =KPI_Stock_Utilisation(); then it rendered, after this your useEffect fires a request and set state, setting of state trigger re-render and new value to return, this new value trigger your parent component to return let kpi_stock_utilisations =KPI_Stock_Utilisation(); might run again.
If you are trying to create a custom hook for fetching some info, follow rules of hooks
I hope it helped you

Invalid hook call error when trying to set state

I have a scenario where I am forced to call a trigger method to show a modal from two different places, one using a hotkey combination and another by clicking on a toolbar button. In order to do so I have the following code, where I call the triggerCustomLinkModal to set the state but then I am hit with the Invalid Hook call error.
import { useState, useCallback, useEffect } from "react"
import { Dialog } from "#blueprintjs/core"
const useLocalState = () => {
const [isShown, setIsShown] = useState(false)
const setState = useCallback((state) => {
setIsShown(state)
})
const getState = useCallback(() => {
return isShown
})
return {
setState,
getState
}
}
export const CustomLinkModalUI = () => {
const { getState } = useLocalState()
return (
<>
<Dialog isOpen={getState()} />
</>
)
}
export const triggerCustomLinkModal = () => {
const { setState } = useLocalState()
setState()
}
Expanding from Chris answer in the comments ( You can't use hooks outside React components. -> so you can't call useLocalState() inside triggerCustomLinkModal since triggerCustomLinkModal is not a React component ):
You don't really need the useCallback hook or even the functions itself. Aaccording to react docs :
Note
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
This also means that using useCallback hook to set a state it doesn't really make sense (because useCallback role is just to return a memoized callback)
What you basically need is a state set up in the closest parrent component and pass the setIsShown as a prop as well as the isShown function.
Your current implementation, even if it weren't for the error, it wouldn't refer to the same state since on each useLocalState() you are initializing a fresh new state (so you are not pointing to the same state in CustomLinkModalUI and triggerCustomLinkModal)

Why setState(hook) in function component cause an infinite loop?

Here is the code:
import React, { useState } from 'react'
function App() {
const [a, setA] = useState(1)
setA(2)
return (
<div>
<h1>{a}</h1>
</div>
);
}
Error. Too many re-renders. React limits the number of renders to prevent an infinite loop.
Why it can cause an infinite loop?
I think the reason is that function component just as a render function, so it will cause infinite loop when setState in render functions.
Is there any official explanation?
On each state update React will re-render the component and run all the function body, as the setA(2) is not enclosed in any hook or function and is a part of the function/component body. React will execute this on each render cycle. This make an infinite loop.
On Component mount React will set the state and go for the component update as the state updated, again there is state update React will again re-render the component. This cycle will continue until react reaches the re-render limit.
You could avoid this by wrap the state update in hook.
import React, { useState } from 'react'
function App() {
const [a, setA] = useState(1)
useEffect(() => {
setA(2)
},[]);
return (
<div>
<h1>{a}</h1>
</div>
);
}
When calling setA you actually update a state variable and trigger a re-render of you component.
When the component re-render it will call setA (just before the rendering) and will trigger a re-render again.
Can you see the infinite loop ?
Traditionally you update a state variable into a callback (i.e when the user click on a button) or under certains conditions.
In your example, you can directly set a to 2
function App() {
const [a, setA] = useState(2)
return (
<div>
<h1>{a}</h1>
</div>
);
}
If you want to have the first render with a = 1, then immediatly have a = 2, you can use an effect which will be executed only once (because of the empty array for the second argument)
function App() {
const [a, setA] = useState(2)
useEffect(() => setA(2), [])
return (
<div>
<h1>{a}</h1>
</div>
);
}
Because you are setting a new value to the state with setA(2), so each time the component is rendered the state receives a new value, forcing the component to render again
const [rows, setRows] = useState([]);
return {
<Component data= {someData}
selectCallback ={ (rows) => {
console.log(rows);
setRows(rows); // if set State going infinity loop
}}
...remainProps
/>
Its giving infinity loop when trying to setState and if we didn't use the setRows then in console giving right answers means array of objec
Help appreciated.
Because calling setA(2) causes the component to re-render.

implementation of component did mount in stateless component

Im new to react, i'm trying to implement/learn stateless component, Im having difficulty in using component will mount in stateless component.
My code
const Terms = (actions, commonReducer) => {
componentDidMount() {
actions.userActions()
}
return (
<div className="jobUpdate">
<form onSubmit={(e) => {
e.preventDefault(); actions.userInput(document.getElementById('enteredVal').value)
}}>
<input type="text" id="enteredVal" />
<button type="submit"></button>
</form>
</div>
);
};
I know the stateless component does not have life cycle hooks, but wanted alternate approach to preform component did mount in stateless component.
Any help with this is much appreciated. Thanks in advance
You could always wrap the component in another component, using a pattern known as higher-order components.
A higher-order component (HOC) is a function that takes a component and returns a new component.
Perhaps the most widely used example of this technique is react-redux, which uses the connect() method to create components connected to the redux store.
Instead of creating your own HOC, there are also libraries out there that can do it for you, such as react-redux-lifecycle.
However, what you are attempting with this component is not a very common pattern -- it is much more common to instead keep the handling of business and data in a container component, and leave presentational components to inherit store actions and data from props. Check out Dan Abramov's Presentational and Container Components for a very good overview on how and why to break down components into these two categories!
Starting in React 16.8, you can accomplish the same kind of functionality using a useEffect hook.
In your specific example, we'd have something like this:
import React, { useEffect } from 'react';
// other imports and declarations
function Example() {
// Similar to componentDidMount
useEffect(() => {
// This function will be run on component mount
myAction();
}, []); // The second argument of [] tells react to only perform the effect on mount
return (
<div>
... your component
</div>
);
}
export default Example;
The docs do a great job of explaining this, and I'd encourage you to read up on it. Keep in mind that it's not exactly the same thing going on behind the scenes, and so the patterns will not be a one-to-one correspondence; but these patterns should help with the majority of your cases.
Just know the following basic ideas:
The first argument of a useEffect hook is a "side effect" function. It is always run after the first component's render, and then conditionally afterwards.
This "side effect" function can return a "cleanup" function. The "cleanup" function is run just before the next time the "side effect" function is run. It will always be run before unmounting the component.
The second, optional, argument of a useEffect hook is an array of dependencies.
If any value in the dependency changes, the "side effect" function will be run after the next render.
Anyway, in the meantime, here's a few patterns to emulate class component behavior.
componentDidMount + componentDidUpdate
useEffect(() => {
console.log("This line will be run after each render ");
});
componentDidUpdate when a given value changes
// get myValue from component props
const { myValue } = props;
useEffect(() => {
console.log("This line will be run after each render where myValue changed");
}, [myValue]);
componentDidUpdate when a given value changes, pt. 2
// get myValue from component props
const { myValue } = props;
const myCondition = myValue === "yes";
useEffect(() => {
console.log('This line will be run after each render where the returned value of the statement `myValue === "yes"` changes from false to true or true to false ');
}, [myCondition]);
componentDidUpdate when a given value changes, pt. 3
// get myValue from component props
const { myValue, myValue2 } = props;
useEffect(() => {
console.log("This line will be run after each render where myValue OR myValue2 changed");
}, [myValue, myValue2]);
componentDidMount
useEffect(() => {
console.log("This line will be run only after the first render ");
}, []);
componentWillUnmount
useEffect(() => {
// nothing will be run as a side effect...
return () => {
// but this will be run as clean up
console.log("This line will be run just before the component unmounts");
};
}, []);
componentDidUpdate (without componentDidMount)
import React, {useEffect, useRef} from 'react';
export default function MyComponent() {
// create a reference value which does not trigger a re-render when changed
const isMounted = useRef(false);
useEffect(() => {
if( isMounted.current === false ){
// on mount, set the ref to true
isMounted.current = true;
} else {
// the component is already mounted
console.log("This line will be run after each render except the first ");
}
});
return (<div />);
}
Hope this will be useful to someone.
Throw a dumb work around using HOC (high order component)
const withLifecycles = (MyStatelessComp) => class extends React.PureComponent {
static propTypes = {}
static displayName = "withPure(xxx)"
state = {}
componentDidMount() {}
render() {
return <MyStatelessComp {..this.state, ...this.props} />
}
}
then use it as
MyStatelessWithLifecycles = withLifecycles(props => {
...
return <Bla />
}
Though not sure what's the reason for a stateless component to have the lifecycles there, it's meant to be pure and simple (presentation only).

Categories

Resources