Is it appropriate to use
useEffectto fetch new state when a prop changes?
Quite simply, yes, this is an appropriate and correct usage of the useEffect hook. The useEffect hook is used to issue side-effects, e.g. fetching data from an external resource, and its dependency array is used to control when the side-effect may be triggered again during the life of the component, e.g. when a prop value changes. An example scenario is fetching a user's post by their id.
const Component = ({ userId }) => {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch(`/api/posts/${userId}`)
.then(response => response.json())
.then(posts => setPosts(posts));
}, [userId]);
...
You do need Effects to synchronize with external systems. For example, you can write an Effect that keeps a jQuery widget synchronized with the React state. You can also fetch data with Effects: for example, you can synchronize the search results with the current search query.
What you might be getting tripped up on in the rest of the "you might not need an effect" section is that it's trying to point out some general React anti-patterns and correct pattern(s) to use.
A few examples:
"You don't need Effect to transform data for rendering"
Just compute the transformed data directly and render. If it's "expensive" then use the
useMemohook to memoize the computed value."You don't need Effects to handle user events"
Don't set some state that an event happened so an effect can be issued, just issue the effect from the event handler. Think of a form
onSubmithandler.Don't store props into local state and use a
useEffecthook to synchronize the state to the prop value, just use the prop value directly.
For your specific code example this isn't an appropriate usage of the useEffect hook. The child component should just directly reference the currChildVal prop value and add 2 to it.
const Child = ({ parentVal }) => {
return (
<div style={{ border: "1px solid blue", margin: 30, padding: 10 }}>
<span>childVal: {parentVal + 2}</span>
</div>
);
};
Answer from Drew Reese on Stack OverflowIs it appropriate to use
useEffectto fetch new state when a prop changes?
Quite simply, yes, this is an appropriate and correct usage of the useEffect hook. The useEffect hook is used to issue side-effects, e.g. fetching data from an external resource, and its dependency array is used to control when the side-effect may be triggered again during the life of the component, e.g. when a prop value changes. An example scenario is fetching a user's post by their id.
const Component = ({ userId }) => {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch(`/api/posts/${userId}`)
.then(response => response.json())
.then(posts => setPosts(posts));
}, [userId]);
...
You do need Effects to synchronize with external systems. For example, you can write an Effect that keeps a jQuery widget synchronized with the React state. You can also fetch data with Effects: for example, you can synchronize the search results with the current search query.
What you might be getting tripped up on in the rest of the "you might not need an effect" section is that it's trying to point out some general React anti-patterns and correct pattern(s) to use.
A few examples:
"You don't need Effect to transform data for rendering"
Just compute the transformed data directly and render. If it's "expensive" then use the
useMemohook to memoize the computed value."You don't need Effects to handle user events"
Don't set some state that an event happened so an effect can be issued, just issue the effect from the event handler. Think of a form
onSubmithandler.Don't store props into local state and use a
useEffecthook to synchronize the state to the prop value, just use the prop value directly.
For your specific code example this isn't an appropriate usage of the useEffect hook. The child component should just directly reference the currChildVal prop value and add 2 to it.
const Child = ({ parentVal }) => {
return (
<div style={{ border: "1px solid blue", margin: 30, padding: 10 }}>
<span>childVal: {parentVal + 2}</span>
</div>
);
};
It's mostly a question of the desired encapsulation of Child. If it is a reusable thing that should be agnostic of why parentVal changed, then yes it might be appropriate.
That tends to be the case if the component is going to be used in unknown situations, such as a lib, or something that's used a lot in your app to the point where you need the decoupling.
But if you do know about how that changes (button click) ahead of time, then yes arguably you can utilise that to remove the effects which would likely to lead to (pretty minor) perf improvements.
React Component UseEffect isn't rerendering upon prop change
useEffect is not executed when the prop changes
reactjs - useEffect isn't executed when prop changes - Stack Overflow
reactjs - react hooks props in useEffect - Stack Overflow
You can write a custom hook to provide you a previous props using useRef
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
and then use it in useEffect
const Component = (props) => {
const {receiveAmount, sendAmount } = props
const prevAmount = usePrevious({receiveAmount, sendAmount});
useEffect(() => {
if(prevAmount.receiveAmount !== receiveAmount) {
// process here
}
if(prevAmount.sendAmount !== sendAmount) {
// process here
}
}, [receiveAmount, sendAmount])
}
However its clearer and probably better and clearer to read and understand if you use two useEffect separately for each change if you want to process them separately
In case anybody is looking for a TypeScript version of usePrevious:
In a .tsx or .ts module:
import { useEffect, useRef } from "react";
const usePrevious = <T>(value: T): T | undefined => {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
"use client";
import { useEffect, useState } from "react";
import Header from "./header";
export default function Layout({ children }: { children: React.ReactNode }) {
useEffect(() => { console.log('changed'); }, [children]);
return (
<>
<Header />
<div>{children}</div>
</>
);
}Update:
Why you don't store connectionParams in a useState? This way you can pass them and rerender correctly.
const [connectionParams, setConnectionParams] = useState({});
return <SqlSchema connectionParams={connectionParams} setConnectionParams={setConnectionParams} />
You are creating a new connection of every render of the Parent sqlConnection={new SqlConnection()}.
Try creating the connection on mount and then pass the variable to the child.
Passing the SqlConnection won't trigger component update when the connectionParams update within the class. You'd have to somehow subscribe to the connectionParams changes. Example below passes the state setter to the SqlConnection class, so when the params are updated, the state change is triggered, the useEffect is now able to listen to the params change and now you are able to use them and set the sql connection with proper params.
class SqlConnection {
stateSetter;
constructor(setter) {
this.stateSetter = setter;
}
connectionParams = {};
setConnection(params) {
this.connectionParams = params;
this.stateSetter(this.connectionParams);
}
}
const SqlSchema = () => {
const [value, setValue] = React.useState({});
const ref = React.useRef(null);
React.useEffect(() => {
ref.current = new SqlConnection(setValue);
}, []);
React.useEffect(() => {
console.log('Current params - ', value); // do stuff with params
}, [value]);
const setParams = () => {
ref.current.setConnection({ foo: 'bar' });
};
return <div>
<button onClick={setParams}>Update params</button>
{JSON.stringify(value)}
</div>
}
ReactDOM.render(<SqlSchema />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
you need to have a useEffect in your child component to copy over the changing prop that parent sends down since you are initializing your local state from that prop.
import React, {useEffect, useState } from 'react';
export default ({ initialMap }) => {
const [map, setMap] = useState(new Map(initialMap));
console.log(`parent map: ${initialMap.get('name')} --- child map: ${map.get('name')}`);
// this is what you need
useEffect(() => {
setMap(new Map(initialMap))
}, [initialMap])
const onChange = (value) => {
setMap(prevMap => {
prevMap.set('name', value);
return prevMap;
});
};
return (
<div>
<label>Input initial value should be 'world':</label>
<input value={map.get('name')}
onChange={e => onChange(e.target.value)} />
</div>
);
};
Copying state like that is an antipattern because it creates two, unnecessary, sources of truth. It's better to pass value, along with onChange handler to children. This way you have one source of truth, but you can access, and control value from children component.
I have example here: https://stackblitz.com/edit/react-hpxvcf
Also, you have to create new Map() when changing state, so React know to rerender components.
First off, I am painfully aware this is not an ideal scenario. I have an app using OpenUI5 with some React functional components/hooks inside:
private _myReactWindow = window["studioStepComponents"];
private _myReactView;
private someFunction(someNewValue) {
this._myReactView.someProp = someNewValue
}
private someRenderFunction() {
this._myReactView = this._myReactWindow.createElement(this._myReactWindow.MyReactComponent,
{
someProp: this.someValue
});
} createElement is working fine and in "someFunction" I can see the correct values for this._myReactView.someProp, even after setting it to a different value, but the useEffect() hook is not triggering on the React side. Is there any way I can get it to pick up the change? I realize these are different frameworks with different bindings but with the value changing I still would have thought React would see the prop change.