Calling functions after state change occurs in reactjs - javascript

My question is this. I have two components. First component is an image cropper. Second component is the one that i should display the cropped image.
The problem i'm facing is i can pass the cropped image to my second component but i have to press the button that crops the image and pass to the second component, twice. On the second click only my image is passing to the second component. But i can display the cropped image in the first component only by one click. I think it is happening because in reactjs state changes are not occurring immediately. So how can i fix this.
My approach was to create a prop function in the 1st component as this.props.croppedImage(this.state.preview.img); here this.state.preview.img is the cropped image. And in the 2nd component i'm getting the cropped image by calling the prop function.
My code
1st component (cropper)
class CropperTest extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "beautiful",
scale: 1,
preview: null,
}
this.handleSave = this.handleSave.bind(this);
}
handleSave = () => {
const img = this.editor.getImageScaledToCanvas().toDataURL();
this.setState({
preview: {
img,
scale: this.state.scale,
}
})
this.props.croppedImage(this.state.preview.img);
}
setEditorRef = (editor) => {
this.editor = editor
}
render() {
return (
<div>
<div className="overlay"></div>
<div className="crop_div">
<AvatarEditor
image={this.props.cropImage}
ref={this.setEditorRef}
width={450}
height={450}
border={50}
color={[255, 255, 255, 0.6]} // RGBA
scale={this.state.scale}
rotate={0}
/>
</div>
<div className="zoom_slider text_align_center">
<input className="crop_btn" type='button' onClick={this.handleSave} value='Save'/>
</div>
</div>
)
}
}
export default CropperTest;
2nd component
Here i'm basically doing the following.
<CropperTest croppedImage = {this.getCroppedImg}/>
getCroppedImg(img){
alert("Perfect Storm");
this.setState({
previewImg:img
})
}

I think it is happening because in reactjs state changes are not occurring immediately. So how can i fix this?
From the React#setState,
setState(updater, [callback])
setState() enqueues changes to the component state.
The setState doesn't immediately update the state. setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.Instead, use componentDidUpdate or a setState callback (setState(updater, callback))
Call the this.props.croppedImage in the setState callback.You will get updated values of component state. In your case its this.state.preview
this.setState({
preview: {
img,
scale: this.state.scale,
}
}, () => {
this.props.croppedImage(this.state.preview.img);
})

Callbacks are no longer supported in setState.
The best way to go about this is to use a useEffect and set the dependency variable to the state variable.
useEffect(() => {
//do operation on state change
},[stateVariable])

Can confirm useEffect is the way to go now - https://reactjs.org/docs/hooks-effect.html

Related

React Warning: Cannot update a component from inside the function body of a different component

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);

React : How to avoid multiple function call

I am working on small react project, actually I have some problem regarding to function call. I am updating the value of URL and invoke the method through button click to display updated values but whenever I click the button first it give me old values when I click again it give me updated values. Could someone please help me how to avoid old values on first click, I want to show updated values on first click not on second click. I am new to ReactJS , Could someone please help me how to fix this problem?
Full Component Code
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
Item: 5,
skip: 0
}
this.handleClick = this.handleClick.bind(this);
}
urlParams() {
return `http://localhost:3001/meetups?filter[limit]=${(this.state.Item)}&&filter[skip]=${this.state.skip}`
}
handleClick() {
this.setState({skip: this.state.skip + 1})
}
render() {
return (
<div>
<a href={this.urlParams()}>Example link</a>
<pre>{this.urlParams()}</pre>
<button onClick={this.handleClick}>Change link</button>
</div>
)
}
}
ReactDOM.render(<Example/>, document.querySelector('div#my-example' ))
State is not updating at time when urlParams is being called. As #jolly told you setState is Asynch function so it's better to use it in a callback or you may simply pass your sortedData type as in argument in getSortedType function instead of updating state. Also, ReactJS community suggests or the best practice is to use state only if props are needed to be updated.
In case, if you dont feel comfortable with CB. you may use setTimeOut which I think is a bad practice but it would solve your problem. your code would be like:
getSortedData=()=>{
this.setState({sortedData:'name desc'});
setTimeout(() => {
this.getData();
}, 200);
}
There's quite lots of code e no working example, so I may be wrong, but I think the problem is in getSortedData(): in that function, you call setState() and right after you call getData() which will perform a fetch, which uses ulrParams() function, which uses property this.state.sortedData.
The problem in this is that setState() is async, so it's not sure that, when urlParams() uses this.state.sortedData, the property is updated (actually, we are sure it's not updated).
Try to rewrite getSortedData() as follow:
getSortedData =() => {
this.setState({sortedData:'name desc'}, () => {
this.getData();
})
}
What I'm doing here is passing a callback to setState(): doing this way, getData() will be called AFTER you've updated the state.
Regarding the question you asked in the comment, if you want to toggle the order, you can add a property order in the state of the Component, assigning to it a default value 'name desc'.
Then, you change getSortedData as follow:
getSortedData =() => {
let newSort = 'name asc';
if (this.state.sorteData === newSort) 'name desc';
this.setState({sortedData: newSort}, () => {
this.getData();
})
}

In react, if I set shouldComponentUpdate to false, will any state I send to children be updated?

I have the following scenario:
1) There is a parent component "ModuleListContainer".
2) A module (in the module list, also a child component, but not interesting in this context) gets selected when hovering over it a module item in the list.
3) When hovering over a module, a menu should be shown in the corner of the module.
4) The whole parent component should NOT be updated when a module is selected, since it can be quite a long list of modules, that is why I set shouldComponentUpdate = false when updating which module should be selected.
5) The menu is loaded when the parent component loads, and only its position is updated when hovering over a module.
This is the parent component (simplified)...
class ModuleListContainer extends Component {
constructor(props) {
super(props);
this.state = {
selectingModule: false,
currentlySelectedModule: nextProps.currentModule
}
}
shouldComponentUpdate(nextProps, nextState) {
if (nextState.selectingModule === true) {
this.setState({
selectingModule: false,
currentlySelectedModule: null
})
return false;
}
return true;
}
mouseEnterModule = (e, moduleItem) => {
const menu = document.getElementById('StickyMenu');
const menuPosition = calculateModuleMenuPosition(e.currentTarget);
if (moduleItem.ModuleId !== this.props.currentModuleId) {
this.props.actions.selectModule(moduleItem);
this.setState({
selectingModule: true
});
}
menu.style.top = menuPosition.topPos + 'px';
menu.style.left = menuPosition.leftPos + 'px';
}
render() {
return (
<div>
<section id="module-listing">
{/* ... list of mapped modules with mouseEnterModule event */}
</section>
<ModuleMenu {... this.props} currentlySelectedModule={this.state.currentlySelectedModule} />
</div>
);
}
}
This is the menu component (simplified)...
class ModuleMenu extends Component {
constructor(props) {
super(props);
this.state = {
currentModule: this.props.currentlySelectedModule
};
}
clickMenuButton = () => {
console.log('CURRENT MODULE', this.state.currentModule);
}
render() {
return (
<div id="StickyMenu">
<button type="button" onClick={this.clickMenuButton}>
<span className="fa fa-pencil"></span>
</button>
</div>
);
}
}
When, in my menu component, I try to console.log the current module from the state, I keep getting null.
My question is if this is because...
I have set the shouldComponentUpdate to false and the menu's state does not get updated?
Or could it be because I do not re-render the whole component?
Or is it because I load the menu together with the parent component
and it does not get re-rendered when a module is selected?
Or is it possibly a combination of some of the above?
The react docs (https://reactjs.org/docs/react-component.html) says:
Returning false does not prevent child components from re-rendering
when their state changes.
Therefore, I am hoping that it is none of the above since I really don't want to have to re-render the entire component when selecting a module.
Your children state doesn't change in this case, you're only changing the state of the parent. What you should probably do is split the render method of your component into two components:
render() {
return (
<div>
<NoUpdateComponent someProps={this.props.someProps}/>
<ModuleMenu {... this.props} currentlySelectedModule={this.state.currentlySelectedModule} />
</div>
);
}
And then in your first costly component, use the shouldComponentUpdate method to prevent it from re rendering
I think that in your code there are other problems you need to solve before looking for a practical solution, starting from the use you make of shouldComponentUpdate().
Official doc says that:
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.
shouldComponentUpdate() is invoked before rendering when new props or state are being received. Defaults to true. This method is not called for the initial render or when forceUpdate() is used.
If you perform a setState() call inside the shouldComponentUpdate() function it might work but essentially you are telling React to start a new render cycle before knowing if in this execution it should render or not.
Also keep in mind that setState() is not guaranteed to be executed immediately:
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
Moreover (not very clear from the code, so I guess) you are separating Component and DOM object for ModuleMenu: its representation must be guided by state or props, here instead you are using HTMLElement.style.x syntax to set its representation properties.
I'd restructure ModuleListContainer to store in its state an object that represents ModuleMenu properties, that will be passed to ModuleMenu component as props, something like this:
moduleMenu {
currentModuleId: ... ,
top: ... ,
left: ...
}
And set the state in mouseEnterModule handler:
mouseEnterModule = (e, moduleItem) => {
const menuPosition = calculateModuleMenuPosition(e.currentTarget);
if (moduleItem.ModuleId !== this.props.currentModuleId) {
this.props.actions.selectModule(moduleItem);
this.setState({
moduleMenu: {
currentModuleId: moduleItem.ModuleId,
left: menuPosition.leftPos + 'px',
top: menuPosition.topPos + 'px'
}
});
}
}
Then ModuleMenu can get the new position like this:
<div id="StickyMenu">
style={{
left: this.props.left,
top: this.props.top
}}
>
...
</div>
Of course you can still use shouldComponentUpdate() to determine which modules in the list should be updated but this time returning just a boolean after a comparison of (once again, I guess) ids of items; avoiding too many calls to setState() method.
Hope this may help you!

Is there a synchronous alternative of setState() in Reactjs

According to the explaination in the docs:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
So since setState() is asyncronous and there is no guarantee about its synchronous performance. Is there an alternative of setState() that is syncronous.
For example
//initial value of cnt:0
this.setState({cnt:this.state.cnt+1})
alert(this.state.cnt); //alert value:0
Since the alert value is previous value so what is the alternative that will give alert value:1 using setState().
There are few questions on Stackoverflow which is similar to this question but no where I am able to find the correct answer.
As you have read from the documentation, there is NO sync alternative, reason as described is performance gains.
However I presume you want to perform an action after you have changed your state, you can achieve this via:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 1
};
console.log('initial state', this.state);
}
updateState = () => {
console.log('changing state');
this.setState({
x: 2
},() => { console.log('new state', this.state); })
}
render() {
return (
<div>
<button onClick={this.updateState}>Change state</button>
</div>
);
}
}
ReactDOM.render(
<MyComponent />,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
You could wrap setState in a function returning a promise, and then use this function with the await keyword to cause your code to wait until the state has been applied.
Personally, I would never do this in real code, instead I would just put the code I wish to execute after the state update in the setState callback.
Nevertheless, here is an example.
class MyComponent extends React.Component {
function setStateSynchronous(stateUpdate) {
return new Promise(resolve => {
this.setState(stateUpdate, () => resolve());
});
}
async function foo() {
// state.count has value of 0
await setStateSynchronous(state => ({count: state.count+1}));
// execution will only resume here once state has been applied
console.log(this.state.count); // output will be 1
}
}
In the foo function, the await keyword causes the code execution to pause until the promise returned by setStateSynchronous has been resolved, which only happens once the callback passed to setState is called, which only happens when the state has been applied. So execution only reaches the console.log call once the state update has been applied.
docs for async/await:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
If this is required I would suggest using a callback in your setState function (and I also suggest using a functional setState).
The callback will be called after the state has been updated.
For example, your example would be
//initial value of cnt:0
this.setState(
(state) => ({cnt: state.cnt+1}),
() => { alert(this.state.cnt)}
)
as per documentation here : https://facebook.github.io/react/docs/react-component.html#setstate
Note: Official docs say, "Generally we recommend using componentDidUpdate() for such logic instead."
No, there is not. React will update the state when it sees fit, doing things such as batching setState calls together for efficiency. It may interest you that you are able to pass a function into setState instead, which takes the previous state, so you may choose your new state with good knowledge of the previous one.
You can use flushSync from ReactDOM to update the state synchronously as suggested by react docs.
https://reactjs.org/docs/react-component.html
It may sound weird but yes setState can work synchronously in react.
How so? This is POC which I've created to demonstrate it.
Pasting the only app JS code.
Maybe it's possible that I'm missing something but this was actually happening in my application that's when I came to know about this effect.
Correct me if this kind of behavior is expected in React which I'm unaware of.
When there are multiple setState on main thread the setState runs a Batch combining all the setState on the main method. Whereas the Scenario is different when the same things go inside the async Function.
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
this.asyncMethod = this.asyncMethod.bind(this);
this.syncMethod = this.syncMethod.bind(this);
}
asyncMethod() {
console.log("*************************")
console.log("This is a async Method ..!!")
this.setState({
counter: this.state.counter + 1
}, () => {
console.log("This is a async Method callback of setState. value of counter is---", this.state.counter);
})
console.log("This is a async Method on main thread. value of counter is---", this.state.counter);
console.log("*************************")
}
syncMethod() {
var that = this;
console.log("*************************")
console.log("This is a sync Method ..!!")
that.setState({counter: "This value will never be seen or printed and render will not be called"});
that.setState({counter: "This is the value which will be seen in render and render will be called"});
setTimeout(() => {
that.setState({counter: "This is part is synchronous. Inside the async function after this render will be called"});
console.log("setTimeout setState");
that.setState({counter: "This is part is aslso synchronous. Inside the async function after this render will be called"});
}, 10)
console.log("This is a sync Method on Main thread. value of counter is---", this.state.counter);
console.log("*************************")
}
render() {
console.log("Render..!!",this.state.counter);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
</header>
<button onClick={this.asyncMethod}>AsyncMethod</button>
<button onClick={this.syncMethod}>SyncMethod</button>
</div>
);
}
}
export default App;
Use React Hooks instead:
function MyComponent() {
const [cnt, setCnt] = useState(0)
const updateState = () => {
setCnt(cnt + 1)
}
useEffect(() => {
console.log('new state', cnt)
}, [cnt])
return (
<div>
<button onClick={updateState}>Change state</button>
</div>
)
}
I was able to trick React into calling setState synchronously by wrapping my code in setTimeout(() => {......this.setState({ ... });....}, 0);. Since setTimeout puts stuff at the end of the JavaScript event queue, I think React detects the setState is within it and knows it can't rely on a batched setState call (which would get added to the end of the queue).
In functional components I do this:
const handleUpdateCountry(newCountry) {
setIsFetching(() => true);
setCompanyLocation(() => newCountry);
setIsFetching(() => false);
}
Correct me if I'm wrong but as far as I know this is synchronous then and it also just worked in my situation.
Yes, there is a method with which we can make our synchronous setState. But its performance maybe not good as normally
For example, we have
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: 0
};
}
changeState(){
console.log('in change state',this.state.data);
this.state.data = 'new value here'
this.setState({});
console.log('in change state after state change',this.state.data);
}
render() {
return (
<div>
<p>{this.state.data}</p>
<a onClick={this.changeState}>Change state</a>
</div>
);
}
}
In this example, we change the state first and then render our component.
Short answer to your question is - NO, react doesn't have sync method setState.
In some cases, an alternative is using refs (createRef or useRef) instead of states.
//initial value of cnt:0
const cnt = React.createRef(0); //or React.useRef(0);
cnt.current++;
alert(cnt.current); //output: 1
So far the best solution for me is to use the callback function.
this.setState({cnt:this.state.cnt+1},() =>{
// other task which we want to run synchronously
alert(this.state.cnt);
})
according to the official documentation of react, we can make setState synchronous by passing the callback function in the second argument.

React - state is inconsistent

What I'm trying to achieve: Pass data from child to parent.
How I'm trying to achieve it: Using this.state as described here
Not sure how to word the title: When I console.log(this.state) in the function in which I modify the state, the correct values in this.state are printed out. However, when I try to read the state in another function, this.state is apparently still at empty values.
constructor(props) {
super(props);
this.state = {
titleInputValue: "",
};
}
<form onSubmit={this.handleSubmit.bind(this)}>
<TextInput
val={this.state.titleInputValue}
changeHandler={this.textInputChangeHandler} />
</form>
// This is the function which can apparently only get the initial state
// title and body are empty strings, they should have values
handleSubmit(event) {
event.preventDefault();
const title = this.state.titleInputValue;
const body = this.state.bodyInputValue;
console.log(this.state);
Meteor.call('stories.insert',title,body);
}
// However, here the state is retrieved just fine.
// This is the function the child calls to update the state.
textInputChangeHandler(event) {
// This console.log call shows the correct state!
console.log(this.state);
this.setState({
titleInputValue: event.target.value,
});
}
TextInput has attribute onChange={this.props.changeHandler.bind(this)}
For illustration:
I wrote asd, and the state was successfully retrieved in textInputChangeHandler, which is the first two console.log calls, but then it's empty in handleSubmit.
This is because the event handler scope is not Component class level. When your component handles the event, it's context is the component (in your case TextInput ) not the parent.
You have to bind that function to this of Component class scope:
<TextInput
val={this.state.titleInputValue}
changeHandler={this.textInputChangeHandler.bind(this) } />
Using JavaScript bind you can specify the function context as well.

Categories

Resources