I need to have 2 different functions that update 2 different components only once in the beginning. Hence, I'm using useEffect. The code is as follows
const loadCategories = () => {
getCategories().then((c) => setValues({ ...values, categories: c.data }));
}
const loadStores = () => {
getStores().then((c) => setValues({ ...values, stores: c.data }));
}
useEffect(() => {
loadStores();
loadCategories();
}, []);
Both the functions are setting the values of the dropdown elements
The problem is though both functions are exectued, only loadCategories() function logic is reflected in the UI. How to make both functions logic reflect in the UI?
first better practice to add those function in useEffect or to wrap them in useCallback hook.
second both or your function are promises so each may not resolve at same time and when you trying to update state values will keep it initial value that why your first function is not reflecting in the ui instead use setState callback to get the previous state like this :
useEffect(() => {
const loadCategories = () => {
getCategories().then((c) => setValues(prevState=>({ ...prevState, categories: c.data })));
}
const loadStores = () => {
getStores().then((c) => setValues(prevState=>({ ...prevState, stores: c.data })));
}
loadStores();
loadCategories();
}, []);
Promise and useEffect can be challenging as the component might dismount before you promise is full-filled.
Here is a solution which works quite well:
useEffect(() => {
let isRunning = true;
Promise.all([
getCategories(),
getStores()
]).then(([stores, categories]) => {
// Stop if component was unmounted:
if (!isRunning) { return }
// Do anything you like with your lazy load values:
console.log(stores, categories)
});
return () => {
isRunning = false;
}
}, []);
Wait for both promises to resolve and then use the data from both to update your state, combined in whatever way you see fit.
useEffect(() => {
Promise.all([getCategories(), getStores()]).then(([categories, stores]) => {
setValues({categories, stores})
});
}, []);
The problem with what you had before (as you experienced) is that values is always the value at the point at which useState was run on this render.
If you really want to do the updates separately, than you can look into useReducer: https://reactjs.org/docs/hooks-reference.html#usereducer
You are lying to React about dependencies. Both your functions depend on the values state variable. That's also why it does not work: When the hooks get run, values gets closured, then when setValues runs values changes (gets set to a new object), however inside the closure it is still referencing the old values.
You can easily resolve that by passing a callback to setValues, this way you do not have a dependency to the outside and the values update is atomic:
setValues(values => ({ ...values, stores: c.data }));
Related
So I have this:
let total = newDealersDeckTotal.reduce(function(a, b) {
return a + b;
},
0);
console.log(total, 'tittal'); //outputs correct total
setTimeout(() => {
this.setState({ dealersOverallTotal: total });
}, 10);
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1'); //outputs incorrect total
newDealersDeckTotal is just an array of numbers [1, 5, 9] e.g.
however this.state.dealersOverallTotal does not give the correct total but total does? I even put in a timeout delay to see if this solved the problem.
any obvious or should I post more code?
setState() is usually asynchronous, which means that at the time you console.log the state, it's not updated yet. Try putting the log in the callback of the setState() method. It is executed after the state change is complete:
this.setState({ dealersOverallTotal: total }, () => {
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
In case of hooks, you should use useEffect hook.
const [fruit, setFruit] = useState('');
setFruit('Apple');
useEffect(() => {
console.log('Fruit', fruit);
}, [fruit])
setState is asynchronous. You can use callback method to get updated state.
changeHandler(event) {
this.setState({ yourName: event.target.value }, () =>
console.log(this.state.yourName));
}
Using async/await
async changeHandler(event) {
await this.setState({ yourName: event.target.value });
console.log(this.state.yourName);
}
The setState is asynchronous in react, so to see the updated state in console use the callback as shown below (Callback function will execute after the setState update)
this.setState({ email: 'test#example.com' }, () => {
console.log(this.state.email)
)}
I had an issue when setting react state multiple times (it always used default state). Following this react/github issue worked for me
const [state, setState] = useState({
foo: "abc",
bar: 123
});
// Do this!
setState(prevState => {
return {
...prevState,
foo: "def"
};
});
setState(prevState => {
return {
...prevState,
bar: 456
};
});
The setState() operation is asynchronous and hence your console.log() will be executed before the setState() mutates the values and hence you see the result.
To solve it, log the value in the callback function of setState(), like:
setTimeout(() => {
this.setState({dealersOverallTotal: total},
function(){
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
}, 10)
If you work with funcions you need to use UseEffect to deal with setState's asynchrony (you can't use the callback as you did when working with classes). An example:
import { useState, useEffect } from "react";
export default function App() {
const [animal, setAnimal] = useState(null);
function changeAnimal(newAnimal) {
setAnimal(newAnimal);
// here 'animal' is not what you would expect
console.log("1", animal);
}
useEffect(() => {
if (animal) {
console.log("2", animal);
}
}, [animal]);
return (
<div className="App">
<button onClick={() => changeAnimal("dog")} />
</div>
);
}
First console.log returns null, and the second one returns 'dog'
just add componentDidUpdate(){} method in your code, and it will work.
you can check the life cycle of react native here:
https://images.app.goo.gl/BVRAi4ea2P4LchqJ8
As well as noting the asynchronous nature of setState, be aware that you may have competing event handlers, one doing the state change you want and the other immediately undoing it again. For example onClick on a component whose parent also handles the onClick. Check by adding trace. Prevent this by using e.stopPropagation.
I had the same situation with some convoluted code, and nothing from the existing suggestions worked for me.
My problem was that setState was happening from callback func, issued by one of the components. And my suspicious is that the call was occurring synchronously, which prevented setState from setting state at all.
Simply put I have something like this:
render() {
<Control
ref={_ => this.control = _}
onChange={this.handleChange}
onUpdated={this.handleUpdate} />
}
handleChange() {
this.control.doUpdate();
}
handleUpdate() {
this.setState({...});
}
The way I had to "fix" it was to put doUpdate() into setTimeout like this:
handleChange() {
setTimeout(() => { this.control.doUpdate(); }, 10);
}
Not ideal, but otherwise it would be a significant refactoring.
so as part of learning react I am currently converting a class-based App to a functional one, I've encountered some issues with my code since I can't use the callback function in the following context:
class ColorBox extends Component {
constructor(props) {
super(props);
this.state = { copied: false };
this.changeCopyState = this.changeCopyState.bind(this);
}
changeCopyState() {
this.setState({ copied: true }, () => {
**setTimeout(() => this.setState({ copied: false }), 1500);**
});
}
I've tried to change it using the useEffect hook, to the following:
function ColorBox(props) {
const [isCopied, setIsCopied] = useState(false)
useEffect(() => setTimeout(() => setIsCopied(false), 1500), [isCopied])
const changeCopyState = () => {
setIsCopied(true)
};
but the problem is that the useEffect renders at the first render which makes the app glitch if I don't wait for 1500ms before clicking on the copy button.
Any help would be greatly appreciated!!
effects will fire whenever the values of your dependencies change. However, what you want according to your class-based approach is to, after setting isCopied to true, set it to false after 1500 ms.
To do so, check the current value of isCopied in your effect before firing the timeout.
function ColorBox(props) {
const [isCopied, setIsCopied] = useState(false)
useEffect(() => {
if (isCopied) {
setTimeout(() => setIsCopied(false), 1500)
}
}, [isCopied, setIsCopied])
const changeCopyState = () => {
setIsCopied(true)
};
}
In addition to that, for consistency, you might want to use clearTimeout when unmounting your effect (in order to avoid, for instance, calling setIsCopied after the component has unmounted).
To do so, the effect has to be like this
useEffect(() => {
if (isCopied) {
let timeoutId = setTimeout(() => setIsCopied(false), 1500)
return () => clearTimeout(timeout)
}
}, [isCopied, setIsCopied])
When you don't specify a curly braces {} in arrow function, it will return a value. In useEffect you don't need a value to be returned (the only exception is componentWillUnmount lifecycle method). That drove to unpredictable behavior and timeout was fired at the initial render. Use curly braces {} in your useEffect arrow function instead
useEffect(() => {
setTimeout(() => setIsCopied(false),1500)
}, [isCopied]);
Sorry for the newbie question:
I'm using useEffect to avoid setting state on an unmounted component, and I was wondering why does this work:
useEffect(() => {
let isMounted = true
actions.getCourseDetails(fullUrl)
.then(data => {
if (isMounted) {
actions.setOwner(data.course.Student.id);
setDetails(data.course);
}
});
return () => {
isMounted = false;
}
}, [actions, fullUrl]);
...but when I return a variable instead of a callback it doesn't work?:
useEffect(() => {
let isMounted = true
actions.getCourseDetails(fullUrl)
.then(data => {
if (isMounted) {
actions.setOwner(data.course.Student.id);
setDetails(data.course);
}
});
isMounted = false;
return isMounted; //returning a variable instead of a callback
}, [actions, fullUrl]);
Thanks!
The syntax of useEffect is to optionally return a dispose function. React will call this dispose function ONLY when one of the dependencies changes or when it unmounts. to "release" stuff that no longer relevant.
For example, you want to wait X seconds after the render, and then change the state:
useEffect(() => {
setTimeout(() => setState('Timeout!', timeToWait));
}, [timeToWait])
Imagen that this component mounts and then after one second unmounts. Without a dispose function the timer will run and React will try to run setState on unmounted component, this will result in an error.
The proper way to do it is to use the dispose function:
useEffect(() => {
const id = setTimeout(() => setState('Timeout!', timeToWait));
return () => clearTimeout(id);
}, [timeToWait])
So every time the timeToWait dependency changes for some reason, the dispose function will stop the timer and the next render will create a new one with the new value. or when the component unmounts.
In your example, the order of execution will be:
Define isMounted and set it to true
Start async action (this will run next tick)
Set isMounted to false
return a variable (Not a function)
So you have 2 problems in your (Not-working) example. you don't return a dispose function, and you change isMounted to false almost immediately after you define it. when the promise will run the isMounted will be false no matter what. If you'd use a dispose function (The working example), only when React will call it the isMounted to turn to false
My situation is this:
export default function Component({ navigation }) {
const [ item, setItem ] = useState([]);
useEffect(() => {
AsyncStorage.getItem('someItem')
.then(data => JSON.parse(data))
.then(jsonData => {
setItem(jsonData);
})
.catch(error => {});
}, [item]);
The problem is that useEffect seems to be called in a loop, even when "item" doesn't change. If I remove item from the dependencies array, it gets called once only and when item changes the component does not re-render. Is there a solution?
Solved like this:
export default function Component({ navigation }) {
const [ item, setItem ] = useState([]);
const [ update, setUpdate ] = useState(false);
const handleUpdate = () => {
setUpdate(!update);
}
useEffect(() => {
AsyncStorage.getItem('someItem')
.then(data => JSON.parse(data))
.then(jsonData => {
setItem(jsonData);
})
.catch(error => {});
}, [update]);
And then calling handleUpdate (or passing it to a child component and letting the child call it) when I want to update item's state.
You have an infinite loop:
The second argument you send to useEffect() is an array of dependencies. Every time one of these dependencies change - the first argument to useEffect() which is a callback will be invoked. So here you do:
Every time item changes - run code that changes item (because the callback sets value with setItem)
notice: useEffect will also invoke the callback sent to it once at first.
The issue is the same described in this post. When you use an object as a dependency, useEffect thinks it's different on every render. Since you use an array, which is an object, you get the infinite loop. Let's say your state variable was instead a primitive type like a string or a number, then it would not keep rendering as useEffect would be able to figure out that the value has not changed.
So, a possible solution in your specific case, because there is a JSON string being returned, could be using that JSON string as a parallel state variable to check for changes.
Consider something like this:
const simulateCall = () => Promise.resolve('["ds","1234sd","dsad","das"]')
export default function App() {
const [state, setArrayState] = React.useState([])
const [stringState, setStringState] = React.useState('')
React.useEffect(() => {
simulateCall()
.then(data => {
setStringState(data);
setArrayState(JSON.parse(data));
})
.catch(error => console.log(error));
}, [stringState]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{state.map((item, i) => <div key={i}>{item}</div>)}
</div>
);
}
I noticed a behavior I cant understand how to solve, I never had it while I was writing react class based using this.setState but I get it while using useState hook.
for example:
const Main = () => {
const [values, setValues] = useState({val1: '', val2: '', val3: ''})
const func1 = async () => {
await setValues({
...values,
val1: '111111111'
});
await func2();
}
const func2 = async () => {
result = (fetch some data);
await setValues({
...values,
val2: result
});
}
};
now if you run func1, val1 will be changed but as soon as func2 finishes and we setValues the second time val1 will get overwritten and val2 will contain value.
what am I doing wrong and how can I fix this ?
Thanks in Advance!
Edit:
when using Hooks I cant see what is the acctual anme of the value entered in the React Chrome Dev tool.
is there a way to fix this ?
when I was having one useState containing an object I could see the titles of each object key... now its hidden -_-
You're spreading the state before updating the correspondent property, try to chunk your state
const Main = () => {
const [value1, setValue1] = useState(null)
const [value2, setValue2] = useState(null)
const func1 = async () => {
setValue1('foo')
await func2();
}
const func2 = async () => {
result = (fetch some data);
await setValue2('foo')
}
};
Here is what is happening
setValues is called changing val1 (state isn't updated yet)
setValues is called again changing val2 and spreading the rest
By the time setValues spreads values val1 still holds it's initial value, overwriting the first setValues call. Remember, changes in state are reflected asynchronously
React useState also takes a functional updater to update your component state similarly to how class-based component's setState works.
Note
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
So in your code you could update as follows:
const Main = () => {
const [values, setValues] = useState({val1: '', val2: '', val3: ''});
const func1 = async () => {
await setValues(prevState => ({
...prevState,
val1: '111111111'
}));
await func2();
}
const func2 = async () => {
result = (fetch some data);
await setValues(prevState => ({
...prevState,
val2: result
}));
}
};
I haven't tested it yet but maybe it could work as an alternative.
Sometimes, when we need to get the value in the state, we usually do the spread operator within the state but it doesn't guarantees that the value is the correct one. For those cases, we better call setState with a callback which takes the previous value from state. So, you can use something like this:
setValues(prev => ({...prev, val1:'11111'}));
The behaviour of the code becomes clear once we note that values variable in func2 is clousre on values at outer scope, which is really a copy of state taken at the time useState was called.
So what your spreading in func2 is is stale copy of your state.
One way of correcting this would be to use functional update
setValues((values) => ({...values, val2: result}));
This will make sure that you are using updated value of the state.