I have a react project and redux for state management. These are my actions.
const mapDispatchToProps = dispatch => ({
handleChange: (name, value) => { dispatch(handleChange(name, value)) },
filterRooms: (rooms) => { dispatch(filterRooms(rooms)) }
});
I have to use these 2 method one by one.
this.props.handleChange(pets, true); // handle changes
this.filterRooms(); // filtering with changing value
filterRooms = () => {
let {rooms,pets} = this.props; // Here I should get pets === true but it's false.
// filter by pets
if (pets) {
tempRooms = tempRooms.filter(room => room.pets === true);
}
this.props.filterRooms(tempRooms);
}
If I use setTimeout for second method thats ok but I think that's not a correct way.
this.props.handleChange(name, value);
setTimeout(() => {
this.filterRooms();
}, 500);
Seems that below two function run in sequence, one after another
this.props.handleChange(pets, true); // handle changes
this.filterRooms(); // filtering with changing value
First dispatch changed value to Redux. And it is updated there (as sample with setTimeout works). But don't expect updated value from Redux will be immediately available to this.filterRooms().
You have some Reactjs component. Reactjs component is essentially class or function. Then you wrap in in connect. So your code may look like this
class Component1 extends React.Component {
onChange: () => {
this.props.handleChange(pets, true); // handle changes
this.filterRooms(); // filtering with changing value
}
// other staff
}
export default connect(mapStateToProps, mapDispatchToProps)(Component1)
Whats happens in React. It instantiate class Component1, then calls connect which in turn calls some methods of your class (i.e. render() or something else). connect also pass some values from Redux store to your component as props. Method of Component1 is executed and may change Redux state (as it do in your sample). But updated props will not be immediately available. props are just arguments, that have been passed by connect function. To change arguments of any function, you should call it once again. So after receiving updated pets, connect will call your component again with updated pets. But it will be later.
connect() -> calls Component1 and passes props.pets = false -> Compoentn1 sets pets in Redux to true -> connect() receives updated pets and calls Component1 with props.pets = true
That's why trick with setTimeout works. Ste timeout just await for second call of Component1
To solve your exact issue, don't read pets from props if you know that you've updated it.
this.props.handleChange(pets, true); // handle changes
this.filterRooms(true); // filtering with changing value
filterRooms = (pets) => {
let {rooms} = this.props;
// filter by pets
if (pets) {
tempRooms = tempRooms.filter(room => room.pets === true);
}
this.props.filterRooms(tempRooms);
}
Related
I'm working on a todo application. This is a very simplified version of the offending code. I have a checkbox:
<p><input type="checkbox" name="area" checked={this.state.Pencil} onChange={this.checkPencil}/> Writing Item </p>
Here's the function that calls the checkbox:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
});
this.props.updateItem(this.state);
}
updateItem is a function that's mapped to dispatch to redux
function mapDispatchToProps(dispatch){
return bindActionCreators({ updateItem}, dispatch);
}
My problem is that when I call the updateItem action and console.log the state, it is always 1 step behind. If the checkbox is unchecked and not true, I still get the state of true being passed to the updateItem function. Do I need to call another function to force the state to update?
You should invoke your second function as a callback to setState, as setState happens asynchronously. Something like:
this.setState({pencil:!this.state.pencil}, myFunction)
However in your case since you want that function called with a parameter you're going to have to get a bit more creative, and perhaps create your own function that calls the function in the props:
myFunction = () => {
this.props.updateItem(this.state)
}
Combine those together and it should work.
Calling setState() in React is asynchronous, for various reasons (mainly performance). Under the covers React will batch multiple calls to setState() into a single state mutation, and then re-render the component a single time, rather than re-rendering for every state change.
Fortunately, the solution is rather simple - setState accepts a callback parameter:
checkPencil: () => {
this.setState(previousState => ({
pencil: !previousState.pencil,
}), () => {
this.props.updateItem(this.state);
});
}
On Ben Hare's answer, If someone wants to achieve the same using React Hooks I have added sample code below.
import React, { useState, useEffect } from "react"
let [myArr, setMyArr] = useState([1, 2, 3, 4]) // the state on update of which we want to call some function
const someAction = () => {
let arr = [...myArr]
arr.push(5) // perform State update
setMyArr(arr) // set new state
}
useEffect(() => { // this hook will get called every time myArr has changed
// perform some action every time myArr is updated
console.log('Updated State', myArr)
}, [myArr])
When you're updating your state using a property of the current state, React documentation advise you to use the function call version of setState instead of the object.
So setState((state, props) => {...}) instead of setState(object).
The reason is that setState is more of a request for the state to change rather than an immediate change. React batches those setState calls for performance improvement.
Meaning the state property you're checking might not be stable.
This is a potential pitfall to be aware of.
For more info see documentation here: https://facebook.github.io/react/docs/react-component.html#setstate
To answer your question, i'd do this.
checkPencil(){
this.setState((prevState) => {
return {
pencil: !prevState.pencil
};
}, () => {
this.props.updateItem(this.state)
});
}
It's because it happens asynchronously, so means in that time might not get updated yet...
According to React v.16 documentation, you need to use a second form of setState() that accepts a function rather than an object:
State Updates May Be Asynchronous
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
For example, this code may fail to update the counter:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
To fix it, use a second form of setState() that accepts a function
rather than an object. That function will receive the previous state
as the first argument, and the props at the time the update is applied
as the second argument:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
First set your value. after proceed your works.
this.setState({inputvalue: e.target.value}, function () {
this._handleSubmit();
});
_handleSubmit() {
console.log(this.state.inputvalue);
//Do your action
}
I used both rossipedia's and Ben Hare's suggestions and did the following:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
}, this.updatingItem);
}
updatingItem(){
this.props.updateItem(this.state)
}
Ben has a great answer for how to solve the immediate issue, however I would also advise to avoid duplicating state
If a state is in redux, your checkbox should be reading its own state from a prop or store instead of keeping track of the check state in both its own component and the global store
Do something like this:
<p>
<input
type="checkbox"
name="area" checked={this.props.isChecked}
onChange={this.props.onChange}
/>
Writing Item
</p>
The general rule is that if you find a state being needed in multiple places, hoist it up to a common parent (not always redux) to maintain only having a single source of truth
try this
this.setState({inputvalue: e.target.value}, function () {
console.log(this.state.inputvalue);
this.showInputError(inputs[0].name);
});
showInputError function for validation if using any forms
As mentioned above setState() is asynchronous in nature. I solved this issue simply using async await.
Here's an example for refernce:
continue = async (e) => {
e.preventDefault();
const { values } = this.props;
await this.setState({
errors: {}
});
const emailValidationRegex = /^(([^<>()\[\]\.,;:\s#\"]+(\.[^<>()\[\]\.,;:\s#\"]+)*)|(\".+\"))#(([^<>()[\]\.,;:\s#\"]+\.)+[^<>()[\]\.,;:\s#\"]{2,})$/i;
if(!emailValidationRegex.test(values.email)){
await this.setState((state) => ({
errors: {
...state.errors,
email: "enter a valid email"
}
}));
}
}
You can also update the state twice like below and make the state update immediately, this worked for me:
this.setState(
({ app_id }) => ({
app_id: 2
}), () => {
this.setState(({ app_id }) => ({
app_id: 2
}))
} )
Here is React Hooks based solution.
Since React useState updates state asynchronously, check them in the useEffect hook if you need to see these changes.
Make sure to give the initialState in the useState each time using a variable. Like line 1 and 2. If I did not give anything in it it would work on double click to fill the errors variable.
1) let errorsArray = [];
2) let [errors, setErrors] = useState(errorsArray);
3) let [firstName, setFirstName] = useState('');
4) let [lastName, setLastName] = useState('');
let [gender, setGender] = useState('');
let [email, setEmail] = useState('');
let [password, setPassword] = useState('');
const performRegister = () => {
console.log('firstName', isEmpty(firstName));
if (isEmpty(firstName)) {
console.log('first if statement');
errorsArray.push({firstName: 'First Name Cannot be empty'});
}
if (isEmpty(lastName)) {
errorsArray.push({lastName: 'Last Name Cannot be empty'});
}
if (isEmpty(gender)) {
errorsArray.push({gender: 'Gender Cannot be empty'});
}
if (isEmpty(email)) {
errorsArray.push({email: 'Email Cannot be empty'});
}
if (isEmpty(password)) {
errorsArray.push({password: 'Password Cannot be empty'});
}
console.log('outside ERRORS array :::', errorsArray);
setErrors(errorsArray);
console.log('outside ERRORS :::', errors);
if (errors.length > 0) {
console.log('ERROR exists');
}
};
Hi I am using redux in react application and I am using redux hooks with functional components.The problem is I want to call this function and dispatch the action only when the value of unmount gets changed.But it's not working as expected it's calling this function automatically.
const checkHasMessages = () => {
recentUSers.map((ci) => {
if (!ci.hasOwnProperty("mesg")) {
dispatch(removeRecentUser(ci.id));
}
});
};
useEffect(() => {
checkHasMessages();
}, [unmount]);
On component mount the state unmout is being set to undefined, thus triggering the useEffect with eh dependency.
You can add an if statement to check the value of unmount.
useEffect(() => {
if(unmount){
checkHasMessages();
}
}, [unmount]);
This should do it (if unmount is initially set to undefined, else u need to create a check based of the value)
I have the following components:
const ParentComponent: React.FC = () => {
const networkRef: any = useRef();
// Somewhere in the code, I call this
networkRef.current.filter(["id0, id1, id2"]);
return (
...
<VisNetwork
ref={networkRef}
/>
...
)
}
export default ParentComponent;
interface Props {
ref: any;
}
const VisNetwork: React.FC<Props> = forwardRef((props: Props, ref) => {
useImperativeHandle(ref, () => ({
filter(items: any) {
setFilterNodes(items);
nView.refresh();
}
}));
const [filterNodes, setFilterNodes] = useState<any[]>([]);
const filterNodesRef = useRef(filterNodes);
useEffect(() => {
filterNodesRef.current = filterNodes;
}, [filterNodes]);
...
// Some code to create the network (concentrate on the nodesView filter method)
const [nView, setNView] = useState<DataView>();
const nodesView = new DataView(nodes, {
filter: (n: any) => {
if (filterNodesRef.current.includes(n.id)) {
return true;
}
return false;
}
})
setNView(nodesView);
const network = new vis.Network(container, {nodes: nodesView, edges: edgesView}, options);
});
export default VisNetwork;
WHen I call network.current.filter([...]), it will set the filterNodes state. Also, it should set the filterNodesRef inside the useEffect.
However, the filterNodesRef.current remains to be empty array.
But when I call network.current.filter([...]) the second time, only then the filterNodesRef.current got the value and the DataView was able to filter.
Why is it like this? I thought the useRef.current will always contain the latest value.
I finally solved this by calling the refresh() method inside the useEffect instead of the filter() method:
useEffect(() => {
filterNodesRef.current = filterNodes;
nView.refresh();
}, [filterNodes]);
Settings the .current of a reference does not notify the component about the changes. There must be some other reason why it works the second time.
From https://reactjs.org/docs/hooks-reference.html#useref
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.
You may want to use useState, as this does rerender the component.
Two more things
I'm not really sure what networkRef.current.filter(["id0, id1, id2"]) is. Typescript does complain when I try to do ['a'].filter(['a']) and I've never seen this, so are you sure this is what you wanted to do?
If you're passing references around there's probably a better way to do it. Maybe consider re-thinking the relations between your components. Are you doing this because you need access to networkRef inside multiple components? If yes, you might want to look at providers.
If this does not answer your question, write a comment (about something specific please) and I'll be happy to try and help you with it :)
Yes, useRef.current contains latest value, but your filterNodesRef.current in a useEffect that's why you get empty array in initial render.
Initial render of VisNetwork the filterNodes is an empty array ==> filterNodesRef.current remains empty. Because setFilterNodes(items); is asyn function => event you set it in useImperativeHandle it will be updated in second render.
In useImperativeHandle you set setFilterNodes(items); ==> filterNodes is updated and the VisNetwork re-render ==> useEffect is triggered ==> filterNodesRef.current is set to new filterNodes
Let's try this:
....
const filterNodesRef = useRef(filterNodes);
useImperativeHandle(ref, () => ({
filter(items: any) {
filterNodesRef.current = filterNodes;
setFilterNodes(items);
nView.refresh();
}
}));
...
I am using Redux with Class Components in React. Having the below two states in Redux store.
{ spinner: false, refresh: false }
In Parent Components, I have a dispatch function to change this states.
class App extends React.Component {
reloadHandler = () => {
console.log("[App] reloadComponent");
this.props.onShowSpinner();
this.props.onRefresh();
};
render() {
return <Child reloadApp={this.reloadHandler} />;
}
}
In Child Component, I am trying to reload the parent component like below.
class Child extends React.Component {
static getDerivedStateFromProps(props, state) {
if (somecondition) {
// doing some redux store update
props.reloadApp();
}
}
render() {
return <button />;
}
}
I am getting error as below.
Warning: Cannot update a component from inside the function body of a
different component.
How to remove this warning? What I am doing wrong here?
For me I was dispatching to my redux store in a React Hook. I had to dispatch in a useEffect to properly sync with the React render cycle:
export const useOrderbookSubscription = marketId => {
const { data, error, loading } = useSubscription(ORDERBOOK_SUBSCRIPTION, {
variables: {
marketId,
},
})
const formattedData = useMemo(() => {
// DISPATCHING HERE CAUSED THE WARNING
}, [data])
// DISPATCHING HERE CAUSED THE WARNING TOO
// Note: Dispatching to the store has to be done in a useEffect so that React
// can sync the update with the render cycle otherwise it causes the message:
// `Warning: Cannot update a component from inside the function body of a different component.`
useEffect(() => {
orderbookStore.dispatch(setOrderbookData(formattedData))
}, [formattedData])
return { data: formattedData, error, loading }
}
If your code calls a function in a parent component upon a condition being met like this:
const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
const { data, loading, error } = useQuery(QUERY);
if (data && data.users.length === 0) {
return handleNoUsersLoaded();
}
return (
<div>
<p>Users are loaded.</p>
</div>
);
};
Try wrapping the condition in a useEffect:
const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
const { data, loading, error } = useQuery(QUERY);
useEffect(() => {
if (data && data.users.length === 0) {
return handleNoUsersLoaded();
}
}, [data, handleNoUsersLoaded]);
return (
<div>
<p>Users are loaded.</p>
</div>
);
};
It seems that you have latest build of React#16.13.x. You can find more details about it here. It is specified that you should not setState of another component from other component.
from the docs:
It is supported to call setState during render, but only for the same component. If you call setState during a render on a different component, you will now see a warning:
Warning: Cannot update a component from inside the function body of a different component.
This warning will help you find application bugs caused by unintentional state changes. In the rare case that you intentionally want to change the state of another component as a result of rendering, you can wrap the setState call into useEffect.
Coming to the actual question.
I think there is no need of getDerivedStateFromProps in the child component body. If you want to trigger the bound event. Then you can call it via the onClick of the Child component as i can see it is a <button/>.
class Child extends React.Component {
constructor(props){
super(props);
this.updateState = this.updateState.bind(this);
}
updateState() { // call this onClick to trigger the update
if (somecondition) {
// doing some redux store update
this.props.reloadApp();
}
}
render() {
return <button onClick={this.updateState} />;
}
}
Same error but different scenario
tl;dr wrapping state update in setTimeout fixes it.
This scenarios was causing the issue which IMO is a valid use case.
const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
setSomeState(someNewValue);
}).current;
return (
<SomeComponent onSomeUpdate={doUpdate} />
);
fix
const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
setTimeout(() => {
setSomeState(someNewValue);
}, 0);
}).current;
return (
<SomeComponent onSomeUpdate={doUpdate} />
);
In my case I had missed the arrow function ()=>{}
Instead of onDismiss={()=>{/*do something*/}}
I had it as onDismiss={/*do something*/}
I had same issue after upgrading react and react native, i just solved that issue by putting my props.navigation.setOptions to in useEffect. If someone is facing same problen that i had i just want to suggest him put your state changing or whatever inside useEffect
Commented some lines of code, but this issue is solvable :) This warnings occur because you are synchronously calling reloadApp inside other class, defer the call to componentDidMount().
import React from "react";
export default class App extends React.Component {
reloadHandler = () => {
console.log("[App] reloadComponent");
// this.props.onShowSpinner();
// this.props.onRefresh();
};
render() {
return <Child reloadApp={this.reloadHandler} />;
}
}
class Child extends React.Component {
static getDerivedStateFromProps(props, state) {
// if (somecondition) {
// doing some redux store update
props.reloadApp();
// }
}
componentDidMount(props) {
if (props) {
props.reloadApp();
}
}
render() {
return <h1>This is a child.</h1>;
}
}
I got this error using redux to hold swiperIndex with react-native-swiper
Fixed it by putting changeSwiperIndex into a timeout
I got the following for a react native project while calling navigation between screens.
Warning: Cannot update a component from inside the function body of a different component.
I thought it was because I was using TouchableOpacity. This is not an issue of using Pressable, Button, or TouchableOpacity. When I got the error message my code for calling the ChatRoom screen from the home screen was the following:
const HomeScreen = ({navigation}) => {
return (<View> <Button title = {'Chats'} onPress = { navigation.navigate('ChatRoom')} <View>) }
The resulting behavior was that the code gave out that warning and I couldn't go back to the previous HomeScreen and reuse the button to navigate to the ChatRoom. The solution to that was doing the onPress in an inline anonymous function.
onPress{ () => navigation.navigate('ChatRoom')}
instead of the previous
onPress{ navigation.navigate('ChatRoom')}
so now as expected behavior, I can go from Home to ChatRoom and back again with a reusable button.
PS: 1st answer ever in StackOverflow. Still learning community etiquette. Let me know what I can improve in answering better. Thanx
If you want to invoke some function passed as props automatically from child component then best place is componentDidMount lifecycle methods in case of class components or useEffect hooks in case of functional components as at this point component is fully created and also mounted.
I was running into this problem writing a filter component with a few text boxes that allows the user to limit the items in a list within another component. I was tracking my filtered items in Redux state. This solution is essentially that of #Rajnikant; with some sample code.
I received the warning because of following. Note the props.setFilteredItems in the render function.
import {setFilteredItems} from './myActions';
const myFilters = props => {
const [nameFilter, setNameFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
const filterName = record => record.name.startsWith(nameFilter);
const filterCity = record => record.city.startsWith(cityFilter);
const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
props.setFilteredItems(selectedRecords); // <-- Danger! Updates Redux during a render!
return <div>
<input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
<input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
</div>
};
const mapStateToProps = state => ({
records: state.stuff.items,
filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);
When I ran this code with React 16.12.0, I received the warning listed in the topic of this thread in my browser console. Based on the stack trace, the offending line was my props.setFilteredItems invocation within the render function. So I simply enclosed the filter invocations and state change in a useEffect as below.
import {setFilteredItems} from './myActions';
const myFilters = props => {
const [nameFilter, setNameFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
useEffect(() => {
const filterName = record => record.name.startsWith(nameFilter);
const filterCity = record => record.city.startsWith(cityFilter);
const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
props.setFilteredItems(selectedRecords); // <-- OK now; effect runs outside of render.
}, [nameFilter, cityFilter]);
return <div>
<input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
<input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
</div>
};
const mapStateToProps = state => ({
records: state.stuff.items,
filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);
When I first added the useEffect I blew the top off the stack since every invocation of useEffect caused state change. I had to add an array of skipping effects so that the effect only ran when the filter fields themselves changed.
I suggest looking at video below. As the warning in the OP's question suggests, there's a change detection issue with the parent (Parent) attempting to update one child's (Child 2) attribute prematurely as the result of another sibling child's (Child 1) callback to the parent. For me, Child 2 was prematurely/incorrectly calling the passed in Parent callback thus throwing the warning.
Note, this commuincation workflow is only an option. I personally prefer exchange and update of data between components via a shared Redux store. However, sometimes it's overkill. The video suggests a clean alternative where the children are 'dumb' and only converse via props mand callbacks.
Also note, If the callback is invoked on an Child 1 'event' like a button click it'll work since, by then, the children have been updated. No need for timeouts, useEffects, etc. UseState will suffice for this narrow scenario.
Here's the link (thanks Masoud):
https://www.youtube.com/watch?v=Qf68sssXPtM
In react native, if you change the state yourself in the code using a hot-reload I found out I get this error, but using a button to change the state made the error go away.
However wrapping my useEffect content in a :
setTimeout(() => {
//....
}, 0);
Worked even for hot-reloading but I don't want a stupid setTimeout for no reason so I removed it and found out changing it via code works just fine!
I was updating state in multiple child components simultaneously which was causing unexpected behavior. replacing useState with useRef hook worked for me.
Try to use setTimeout,when I call props.showNotification without setTimeout, this error appear, maybe everything run inTime in life circle, UI cannot update.
const showNotifyTimeout = setTimeout(() => {
this.props.showNotification();
clearTimeout(showNotifyTimeout);
}, 100);
I have a React functional component with two state variables (itemsData & itemsCollections). The variables are updated in the useEffect method. But after useEffect occur one of the state variables is null.
Upon switching the setStateFunctions (setItemsData & setItemsCollect) call order both arguments are inialized as expected.
How's that?
const MyComponent = ({itemsIds}) => {
const [itemsData, setItemsData] = useState([]);
const [itemsCollections, setItemsCollect] = useState({});
useEffect(() => {
fetchItemsData({ itemsIds }).then(({ items, itemCollect }) => {
setItemsData(items);
setItemsCollect(itemCollect);
})
}, [itemsIds]);
...
console.log('itemsData', itemsData) // the expected array
console.log('itemCollect', itemCollect) // empty objecy
State after useEffect: itemCollect = {}, itemsData = [{value:...},...]
Switching the order of the calls:
const MyComponent = ({itemsIds}) => {
...
useEffect(() => {
fetchItemsData({ itemsIds }).then(({ items, itemCollect }) => {
setItemsCollect(itemCollect); // <--> switched rows
setItemsData(items); // <--> switched rows
})
}, [itemsIds]);
...
console.log('itemsData', itemsData) // the expected array
console.log('itemCollect', itemCollect) // the expected object
State after useEffect: itemCollect = { someValue: ...} , itemsData = [{value:...},...]
There is a performance optimization called batching, which can change between React versions. When this optimization is applied, multiple setState calls will be batched together before the next render (and the order does not matter).
When not applied (e.g. inside a Promise as in your case, see Does React batch state update functions when using hooks?), then each state update will trigger a new render (and the order matters).
=> console.log('itemCollect', itemCollect) may log different data in each render.
If you need to force a single state update, then calling a single dispatch from useReducer might be the best option.