ES6 - Warning: setState(…): Cannot update during an existing state transition - javascript

I am rewriting some old ReactJS code, and got stuck fixing this error (the error repeats about 1700 times in the console, the DOM does not render at all):
Warning: setState(...): Cannot update during an existing state
transition (such as within render or another component's
constructor). Render methods should be a pure function of props and
state; constructor side-effects are an anti-pattern, but can be moved
to componentWillMount.
I am a Component that passes it's state down to a component that should render some controls. Based on the clicked controls, the state should change, and new controls should render.
So this is my Container component:
class TeaTimer extends Component {
constructor(props) {
super(props);
this.state = {
count: 120,
countdownStatus: 'started'
}
}
componentDidUpdate(prevProps, prevState) {
if (this.state.countdownStatus !== prevState.countdownStatus) {
switch (this.state.countdownStatus) {
case 'started':
this.startTimer();
break;
case 'stopped':
this.setState({count:0});
}
}
}
componentWillUnmount() {
clearInterval(this.timer);
delete this.timer;
}
startTimer() {
this.timer = setInterval(() => {
let newCount = this.state.count -1;
this.setState({
count: newCount >= 0 ? newCount : 0
});
if(newCount === 0) {
this.setState({countdownStatus: 'stopped'});
}
}, 1000)
}
handleStatusChange(newStatus) {
this.setState({ countdownStatus: newStatus });
}
render() {
let {count, countdownStatus} = this.state;
let renderStartStop = () => {
if (countdownStatus !== 'stopped') {
return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/>
} else {
return <div>This will be the slider form</div>
}
};
return(
<div className={styles.container}>
<p>This is the TeaTimer component</p>
<Clock totalSeconds={count}/>
{renderStartStop()}
</div>
)
}
}
And this is my controls component:
class StartStop extends Component {
constructor(props) {
super(props);
}
onStatusChange(newStatus) {
return() => {
this.props.onStatusChange(newStatus);
}
}
render() {
let {countdownStatus} = this.props;
let renderStartStopButton = () => {
if(countdownStatus === 'started') {
return <button onClick={()=> this.onStatusChange('stopped')}>Reset</button>;
} else {
return <button onClick={()=> this.onStatusChange('started')}>Start</button>
}
};
return(
<div className={styles.tt.Controls}>
{renderStartStopButton()}
</div>
)
}
}
StartStop.propTypes = {
countdownStatus: React.PropTypes.string.isRequired,
onStatusChange: React.PropTypes.func.isRequired
};
I am sorry about the wall of text, but I really can;t figure out where the error is coming from - and therefor don't know which part of the code I can leave out.
I have tried implementing the solution found in a seemingly related question, but can't get it to work either.

I think you have a typo in this line:
return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/>
It should be:
return <StartStop countdownStatus={countdownStatus} onStatusChange={() => this.handleStatusChange}/>
You seem to be calling the method handleStatusChange instead of passing it as a callback.

Your metods call each other so you must define two instance of your metods.
class StartStop extends Component {
constructor(props) {
super(props);
this.onStatusChangeReset=this.onStatusChange.bind(this);
this.onStatusChangeStart=this.onStatusChange.bind(this);
}
onStatusChange(newStatus) {
return() => {
this.props.onStatusChange(newStatus);
}
}
render() {
let {countdownStatus} = this.props;
let renderStartStopButton = () => {
if(countdownStatus === 'started') {
return <button onClick={this.onStatusChangeReset('stopped')}>Reset</button>;
} else {
return <button onClick={this.onStatusChangeStart('started')}>Start</button>
}
};
return(
<div className={styles.tt.Controls}>
{renderStartStopButton()}
</div>
)
}
}
StartStop.propTypes = {
countdownStatus: React.PropTypes.string.isRequired,
onStatusChange: React.PropTypes.func.isRequired
};

In this line in your return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/> gives the warning, the handleStatusChanged function is called on pressing a button which tries to change the state by setState keyword. whenever the state is changed render function is called again but in your case render function was in progress of returning while the render function is called again by setState keyword.

Related

How to call child's method from parent without using Refs?

Let's say I've a parent component A and a child B:
A:
class A {
constructor() {
this.state = {data: []};
}
handleClick = () => {
// api call
// set data state to the returned value from api
// call B's createTable method
}
render() {
return(
<div>
<button onClick={()=> this.handleClick()}>Fetch data</button>
<B data={this.state.data} />
</div>
}
}
B:
class B {
constructor() {
this.state = {...};
}
createTable = () => {
const { data } = this.props;
// do smth
}
render() {
return(...);
}
}
I want to call createTable method from A without using Refs.
What I've done so far is using componentDidUpdate life cycle method in B to check if data prop has changed or not, If it changed call createTable method but I want to know is this right? or there's a better way of doing it because I feel it is kinda hacky or maybe bad design.
class B {
constructor() {
this.state = {...};
}
componentDidUpdate(prevProps) {
const { data } = this.props;
if (data !== prevProps.data) {
this.createTable();
}
}
createTable = () => {
const { data } = this.props;
// do smth
}
render() {
return(...);
}
}
NOTE I don't want to use hooks either just class based component.
The following example might be useful
class Parent extends Component {
render() {
return (
<div>
<Child setClick={click => this.clickChild = click}/>
<button onClick={() => this.clickChild()}>Click</button>
</div>
);
}
}
class Child extends Component {
constructor(props) {
super(props);
this.getAlert = this.getAlert.bind(this);
}
componentDidMount() {
this.props.setClick(this.getAlert);
}
getAlert() {
alert('clicked');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}

React - is it bad practice to pass up a callback?

I have a REACT app which is basically a till for adding items to an order. I have my OrderSection which does most of the grunt work, including having a barcode scanner, and I have my Search component which is a child of OrderSection, and if someone clicks on a search result it passes that back up to OrderSection via a prop callback.
Now, this is what I initially had, but it had problems:
#autobind
class OrderSection extends React.Component {
constructor(props) {
super(props);
this.state = {
orderItems: [],
multiMatch: [],
};
}
async barcodeScanner(barcode) {
let response;
try {
response = await serverApi.getItemsFromBarcode(barcode);
} catch(e) {
return toast.error(e.message || e.toString());
}
let {items} = response;
if (items.length === 0) {
toast.info('no matching items found');
} else if (items.length === 1) {
this.addItem(items[0]);
} else {
// show results in the 'search' section
this.setState({multiMatch: items})
}
}
addItem(item) {
// doesn't really matter what happens here
}
async lookupAdd(no, code) {
try {
let {items} = await serverApi.getItems(no, code);
let item = items[0];
if (item) {
this.addItem(item);
} else {
}
} catch(e) {
toast.error(e.toString());
}
}
render() {
return (
<section>
// render items up here
<Search
onItemClick={this.lookupAdd}
results={this.state.multiMatch} />
</section>
)
}
}
#autobind
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
searchResults: [],
show: false // change to true to show the search
}
}
// code for updating search results on form submit
// updating this.state.searchResults
render() {
return (
<React.Fragment>
// form with search text input here
// render searchResults here
</React.Fragment>
)
}
componentWillReceiveProps(props) {
if (props.results.length) {
this.setState({searchResults: props.results, show: true});
}
}
}
Search.propTypes = {
onItemClick: PropTypes.func.isRequired,
results: PropTypes.array
};
The main issue here is how in OrderSection, in barcodeScanner, when I have multiple matches, I pass them down as a prop into Search, and then Search sees that prop and updates itself in the componentWillReceiveProps function.
I wasn't entirely happy with what was happening there -- it was actually fine most of the time, but there was some annoying unexpected behaviour of Search showing itself when the prop actually hadn't changed.
So I came up with the idea of passing a callback up from Search to OrderSection:
#autobind
class OrderSection extends React.Component {
constructor(props) {
super(props);
this.state = {
orderItems: []
};
}
async barcodeScanner(barcode) {
let response;
try {
response = await serverApi.getItemsFromBarcode(barcode);
} catch(e) {
return toast.error(e.message || e.toString());
}
let {items} = response;
if (items.length === 0) {
toast.info('no matching items found');
} else if (items.length === 1) {
this.addItem(items[0]);
} else {
// show results in the 'search' section
this.sendMultiMatchToSearch(items);
}
}
setSearchResultsFunc(func) {
this.sendMultiMatchToSearch = func;
}
addItem(item) {
// doesn't really matter what happens here
}
async lookupAdd(no, code) {
try {
let {items} = await serverApi.getItems(no, code);
let item = items[0];
if (item) {
this.addItem(item);
} else {
}
} catch(e) {
toast.error(e.toString());
}
}
render() {
return (
<section>
// render items up here
<Search
onItemClick={this.lookupAdd}
manuallySetResultsFunc={this.setSearchResultsFunc}
/>
</section>
)
}
}
#autobind
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
searchResults: [],
show: false // change to true to show the search
};
if (typeof this.props.manuallySetResultsFunc === "function") {
const func = (results) => {
this.setState({searchResults: results, show: true});
this.flash();
};
this.props.manuallySetResultsFunc(func);
}
}
render() {
return (
<React.Fragment>
// render searchResults here
</React.Fragment>
)
}
}
Search.propTypes = {
onItemClick: PropTypes.func.isRequired,
manuallySetResultsFunc: PropTypes.func
};
But I feel like this is probably bad react practice. It's producing the behavior I want but I think if a React expert looks at this they wouldn't like it.
Can I get some advice on the proper way to pass search results down to Search to trigger it, while still otherwise allowing the SEARCH element to control its own searchResults code
You're right in that you shouldn't have to 'intervene' in this way to modify how your state is updated. You should just set up your state and props and then things should take care of themselves.
Here are some straightforward approaches that I'd typically use:
1) From the OrderSection parent to conditionally render your Search only when there are items:
render() {
return (
<section>
{this.state.multiMatch && <Search
onItemClick={this.lookupAdd}
manuallySetResultsFunc={this.setSearchResultsFunc}
/>}
</section>
)
}
2) Within the <Search> child:
render() {
return (
<React.Fragment>
{this.state.searchResults && this.state.searchResults.map(result=> // map to SearchResults)}
</React.Fragment>
)
}
3) From the OrderSection parent pass in 'isShowing' as a prop:
render() {
const isShowing = !!this.state.multiMatch; // add other logic here if necessary
return (
<section>
<Search
onItemClick={this.lookupAdd}
isShowing={isShowing}
/>
</section>
)
}
Then in your Search, extract isShowing from props.
The idea is that you only need to update the state and the rendering should take care of itself.
I would introduce additional props to Search component showMultiMatch and onSearchClose and add showSearch to OrderSection component(which is set to true when you receive multiMatch and set to false in the onSearchClose handler). Remove componentWillReceiveProps and check condition this.props.showMultiMatch || this.state.show in the render function to render search conditionally.

React reusing a component with different state

I'm pretty new to React and trying to write my first app to get a better understanding.
What I'm trying to build is a simple time tracking tool where the user can start and stop a work timer.
Here you can see the design I came up with:
If the user clicks on the "start" button the working time Timer component should update every second. If the user clicks then on the "take a break" button the timer should stop and instead the break time Timer component should start ticking.
I would like to reuse the Timer component for both working and break timer and just set different states.
I already managed to do this but I don't know if this is a nice way or if this can be improved and make it more generic?
My Tracker component looks like this:
class Tracker extends Component {
constructor(props) {
super(props);
this.state = {
workTime: 0,
breakTime: 0,
isRunning: false,
timerType: 'workTimer'
}
}
startTimer(type) {
this.setState({
isRunning: true,
timerType: type
});
this.timerInterval = setInterval(() => {
this.updateTimer()
}, 1000);
}
stopTimer() {
this.setState({
isRunning: false
});
clearInterval(this.timerInterval);
}
toggleBreak(type) {
this.setState({
timerType: type
});
if (!this.state.isRunning && this.state.timerType === 'breakTimer') {
this.startTimer('breakTimer');
} else if (this.state.isRunning && this.state.timerType === 'breakTimer') {
this.stopTimer();
this.startTimer('workTimer');
} else {
this.stopTimer();
this.startTimer('breakTimer');
}
}
updateTimer() {
let state = null;
if (this.state.timerType === 'workTimer') {
state = {
workTime: this.state.workTime + 1000
};
} else {
state = {
breakTime: this.state.breakTime + 1000
};
}
this.setState(state);
}
render() {
return (
<div className="tracker">
<Timer time={ this.state.workTime }/>
<Timer time={ this.state.breakTime }/>
<TimerControls
isRunning={ this.state.isRunning }
start={ () => this.startTimer('workTimer') }
stop={ () => this.stopTimer() }
toggleBreak={ () => this.toggleBreak('breakTimer') }
/>
</div>
);
}
}
Controls component:
class TimerControls extends Component {
constructor(props) {
super(props);
}
render() {
const {isRunning, start, stop, toggleBreak} = this.props;
return (
<div className="tracker__control">
<button onClick={ start } disabled={ isRunning }>Start</button>
<button onClick={ toggleBreak }>Break</button>
<button onClick={ stop } disabled={ !isRunning }>Stop</button>
</div>
);
}
}
Timer component:
class Timer extends Component {
constructor(props) {
super(props);
}
render() {
const { time } = this.props;
return (
<div className="tracker__timer">{ timeFormat(time) }</div>
);
}
}
Is there a way to get rid of the timerType conditions?

How to write "Expire in" HOC?

I'm attempting to write a HOC that changes its state attribute visible to false after some time.
Here is what I've got so far:
const withExpire = (WrappedComponent) =>
class extends Component {
constructor(props) {
super(props);
this.state = {
visible: props.visible,
};
}
render() {
const expire_in = this.props.expire_in || 3000;
if (this.state.visible) {
setTimeout(() => {
this.setState({ visible: false });
}, 1000);
}
return <WrappedComponent visible={this.state.visible} {...this.props} />;
}
}
I've checked that code inside if (this.state.visible) runs, but it doesn't change my visible attribute.
Can someone explain me what I'm missing?
EDIT
SOLUTION:
const withExpire = (WrappedComponent) =>
class extends Component {
constructor(props) {
super(props);
this.state = {
visible: props.visible,
};
this.timeoutID = null;
}
componentWillMount () {
const expire_in = this.props.expire_in || 3000;
if (this.state.visible) {
this.timeoutID = setTimeout(() => {
this.setState({ visible: false });
}, expire_in);
}
}
componentWillUnmount () {
if (this.timeoutID) {
window.clearTimeout(this.timeoutID);
}
}
render () {
return <WrappedComponent {...this.props} visible={this.state.visible} />;
}
}
You actually implemented the perfect example against HOC usage. You can't be sure about what's coming in from the outside as props.
In this example the external visible property through {...this.props} overrides the visbile={this.state.visible} property.
A quick fix (by swapping the order of property definitions):
...
return <WrappedComponent {...this.props} visible={this.state.visible} />;
...
Also, don't forget to properly handle the Timer. You should store any timers and if still active cancel them in componentWillUnmount. Otherwise a still running timer in an unmounted component might cause errors thrown around.

on React Button onClick, start and stop a function(method)

Got a nagging issue and was wondering if anyone can shed some light.
I made a function that automates the routing for my react app...but i am trying to attach a button to this function to ensure it starts and stops on button click. However, when i try the code below...nothing happens
class App extends React.Component {
constructor (props) {
super(props);
this.state = { tabControl: true };
this.handleClick = this.handleClick.bind(this);
this.tabControl = this.tabControl.bind(this);
}
tabControl(props){
RoutePaths(this.props);
}
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
render() {
return (
<div className="clearfix" id="topContent">
<Sidebar />
<div className="white-bg" id="page-wrapper">
<Header tagline="Welcome to JuDGE" />
<button className="AutoTab" onClick={this.handleClick}>
Toggle
</button>
........
but when i try the second code, the tabbing function starts onClick of the button but of course doesn't stop when you click the button again.
class App extends React.Component {
constructor (props) {
super(props);
this.state = { tabControl: true };
this.handleClick = this.handleClick.bind(this);
this.tabControl = this.tabControl.bind(this);
}
tabControl(props){
RoutePaths(this.props);
}
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
render() {
return (
<div className="clearfix" id="topContent">
<Sidebar />
<div className="white-bg" id="page-wrapper">
<Header tagline="Welcome to JuDGE" />
<button className="AutoTab" onClick={this.tabControl}>
Toggle
</button>
Try using the current state instead of the optional callback inside setState:
handleClick() {
this.setState({ tabControl: !this.state.tabControl });
}
I'm not sure i fully get what you are trying to do but it seems to me that you forgot a condition.
You say if you invoke this method:
tabControl(props){
RoutePaths(this.props);
}
it works but won't stop.
Well, you are not running it conditionally.
In this method:
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
You are setting the tabControl state. I think you forgot to check it before running tabControl().
tabControl(props){
const {tabControl} = this.state;
tabControl && RoutePaths(this.props); // invoke of tabControl is true
}
Edit
After seeing the code for RoutePaths as you posted on comments:
function RoutePaths(props) {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let index = 0;
let interval = 3000;
setInterval(() => {
props.history.push(pathUrls[index]);
index = (index + 1) % paths;
}, interval);
}
It seems to me that you will have another problem. you need the id of the interval that returned from setInterval in order to stop it, but you didn't stored it anywhere.
Quote from the docs:
... It returns an interval ID which uniquely identifies the interval,
so you can remove it later by calling clearInterval() ...
So you will need to store it somewhere and call clearInterval with ID.
this.intervalId = setInterval(() => {...});
And somewhere else in your class:
clearInterval(this.interval);
Edit #2
As a followup to your comment, here is a simple usage of interval with react:
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
ticks: 0
};
}
onStart = () => {
this.intervalId = setInterval(() => {
this.setState({ ticks: this.state.ticks + 1 })
}, 500);
}
onStop = () => {
clearInterval(this.intervalId)
}
render() {
const { ticks } = this.state;
return (
<div>
<button onClick={this.onStart}>Start</button>
<button onClick={this.onStop}>Stop</button>
<div>{ticks}</div>
</div>
);
}
}
ReactDOM.render(<Timer />, document.getElementById("root"));
<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>
<div id="root"></div>
So you can try this approach,
RoutePaths will return the interval id:
function RoutePaths(props) {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let index = 0;
let interval = 3000;
return setInterval(() => {
props.history.push(pathUrls[index]);
index = (index + 1) % paths;
}, interval);
}
and tabControl will store the id and conditionally call or clear the interval:
tabControl() {
const { tabControl } = this.state;
if (tabControl && this.intervalId) { // i'm not sure this is the condition you want, but you can play with it
clearInterval(this.intervalId);
} else {
this.intervalId = RoutePaths(this.props);
}
}
I haven't tested this code but i think it can lead you to a good start.
You don't need tabControl state for what you are trying to do. However, you need to call clearInterval somewhere. Change your handleClick to something like this:
handleClick() {
// change RoutePath to return the id that setInterval returns.
if (this.routePathInterval) {
clearInterval(this.routePathInterval);
this.routePathInterval = null;
} else {
this.routePathInterval = RoutePath(this.props);
}
}
Also, when you call clearInterval and then start it again, your index will start over from zero. You may want to keep the current index in state and pass it to RoutePaths, if you want to resume from the index that you were on.
edit:
On second thought, you don't need to keep the index in state, since you don't want to trigger a re-render when you increment it. However, you should make index an instance variable and make RoutePath an instance method of your App component.
First, initialize this.index = 0; in your constructor and then:
routePaths() {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let interval = 3000;
return setInterval(() => {
this.props.history.push(pathUrls[index]);
this.index = (this.index + 1) % paths;
}, interval);
}

Categories

Resources