reset component's internal state on route change - reactjs

I am using react-router-v4 along with react 16.
I want to reset the component's internal state when the user go to a different route or comes back to the same route . Route change should destroy the internal state of a component but it doesn't . And I can't even find a way to notify the component when the route changes as it's a nested component not a direct render of a Route component. Please help.
Here's the code or live codepen example --
const initialProductNames = {
names: [
{ "web applications": 1 },
{ "user interfaces": 0 },
{ "landing pages": 0 },
{ "corporate websites": 0 }
]
};
export class ProductNames extends React.Component {
state = {
...initialProductNames
};
animProductNames = () => {
const newArray = [...this.state.names];
let key = Object.keys(newArray[this.count])[0];
newArray[this.count][key] = 0;
setTimeout(() => {
let count = this.count + 1;
if (this.count + 1 === this.state.names.length) {
this.count = 0;
count = 0;
} else {
this.count++;
}
key = Object.keys(newArray[count])[0];
newArray[count][key] = 1;
this.setState({ names: newArray });
}, 300);
};
count = 0;
componentDidMount() {
this.interval = setInterval(() => {
this.animProductNames();
}, 2000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
componentWillReceiveProps(nextProps) {
console.log(nextProps.match);
if (this.props.match.path !== nextProps.match.path) {
this.setState({ ...initialProductNames });
this.count = 0;
}
}
render() {
return (
<section className="home_products">
<div className="product_names_container">
I design & build <br />
{this.createProductNames()}
</div>
</section>
);
}
createProductNames = () => {
return this.state.names.map(nameObj => {
const [name] = Object.keys(nameObj);
return (
<span
key={name}
style={{ opacity: nameObj[name] }}
className="product_names_anim">
{name}
</span>
);
});
};
}

I got the solution . I didn't quit understood why state as property initializer doesn't reset/intialize on remount. I think it only initialize once, not on every route change] -
I wanted to know how to reset a component's state on route change. But it turns out that you don't have to . Each route renders a specific component . When route changes all other components are unmounted and all the state of those components are also destroyed. But see my code. I was using es7+ property initializer to declare state,count . That's why the state wasn't resetting/initializing again when the component remounted on route change.
To fix it, all i did is i put the state,initialProductNames,count; all of those into constructor. And now it's working perfectly .
Now fresh state on every mount and remount!!

You can use a listener on the Route change as the example on this previous question And there you can add a function to update the main state.
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
this.onRouteChanged();
}
}
onRouteChanged() {
console.log("ROUTE CHANGED");
}

The problem is not the state, it's the initialProductNames. Property initializer is a sugar syntax, in fact it is the same as creating a constructor and moving the code into the constructor. The problem is in the initialProductNames, which is created outside the component, that is, only once for the whole system.
For create a new initialProductNames for any instance of ProductNames, do that:
export class ProductNames extends React.Component {
initialProductNames = {
names: [
{ "web applications": 1 },
{ "user interfaces": 0 },
{ "landing pages": 0 },
{ "corporate websites": 0 }
]
};
state = {
...this.initialProductNames
};
// more code
componentWillReceiveProps(nextProps) {
console.log(nextProps.match);
if (this.props.match.path !== nextProps.match.path) {
this.setState({ ...this.initialProductNames });
this.count = 0;
}
}
Here is an example showing that the state is always recreated every remount: https://codesandbox.io/s/o7kpy792pq
class Hash {
constructor() {
console.log("Hash#constructor");
}
}
class Child extends React.Component {
state = {
value: new Hash()
};
render() {
return "Any";
}
}
class App extends React.Component {
state = {
show: true
};
render() {
return (
<div className="App">
<button
type="button"
onClick={() =>
this.setState({
show: !this.state.show
})
}
>
Toggle
</button>
{this.state.show && <Child />}
</div>
);
}
}

Related

Call a function of one of a list of children components of the parent component using reference

For my website I want to include a feature that helps users randomly click a link programatically. The event happens in the parent component called StreamingPlaza, and its has a list of children components called StreamingCard, each containing a streaming link. Below is my code:
StreamingPlaza
class StreamingPlaza extends Component {
state = {
......
}
roomclicks = [];
componentDidMount() {
//Approach 1//
this.roomclicks[0].current.handleClick();
//Approach 2//
this.roomclicks[0].props.click = true;
......
}
setRef = (ref) => {
this.roomclicks.push(ref);
}
renderRoom = (room) => {
return <StreamingCard info={room} ref={this.setRef} click={false}></StreamingCard>;
}
render () {
const rooms = this.props.rooms;
return (
{ rooms && rooms.map (room => {
return this.renderRoom(room);
})
}
);
}
StreamingCard
class StreamingCard extends Component {
constructor(props){
super(props);
this.state = {
......
}
}
handleClick = () => {
document.getElementById("link").click();
}
render() {
return (
✔️ Streaming Link: <a id="link" href=......></a>
);
}
Regarding Approach 1, the console reported the error Cannot read property handClick of undefined. After I removed "current", it said that this.roomclicks[0].handleClick is not a function. Regarding Approach 2, I was not able to modify the props in this way, as the console reported that "click" is read-only.
Approach 1 is basically how its need to be done, but with React API.
See React.createRef
class StreamingPlaza extends Component {
roomclicks = React.createRef([]);
componentDidMount() {
// 0 is room.id
this.roomclicks.current[0].handleClick();
}
renderRoom = (room) => {
return (
<StreamingCard
info={room}
ref={(ref) => (this.roomclicks.current[room.id] = ref)}
click={false}
></StreamingCard>
);
};
render() {
const rooms = this.props.rooms;
return rooms.map((room) => {
return this.renderRoom(room);
});
}
}

delete array item cause error perform a React state update on an unmounted component

I have google for this problem.
The solution is put is_Mounted in componentWillMount and unmount, but this will let deletion not happened.
I would appreciate it greatly if you could let me know how to deal with this problem which I deal with for about a week.
My pseudocode...
IRPage
class IRPage extends Component {
state = {
companyList: [],
contractListOfList: [],
};
addCompanyArr = (newCompany) => {
this.setState(
state => {
const list = state.companyList.push(newCompany);
return {
list,
};
}
)
};
addContractArr = (newContractList) => {
this.setState(
state => {
const list = state.contractListOfList.push(newContractList);
return {
list,
};
}
);
}
render() {
return (
// pass array method.....to IRContent
)
}
}
IRContent
class IRContent extends React.Component {
constructor(props) {
super(props);
}
addCompany = () => {
const companyNode = <IRCompany setContractArr={this.props.setContractArr} contractList={newContractList} companyList={this.props.companyList} setCompanyArr={this.props.setCompanyArr} addContractArr={this.props.addContractArr}/>;
this.props.addCompanyArr(companyNode);
var newContractList = [];
this.props.addContractArr(newContractList);
}
render() {
return(
<div>
{
this.props.companyList.map((element, index) => {
return <div key={"myCompanyKey_"+index+"_"+this.props.companyList.length} id={index}>{element}</div>;
})
}
<button color="primary" onClick = {this.addCompany}>
add company
</button>
</div>
);
}
}
IRCompany
class IRCompany extends React.Component {
constructor(props) {
super(props);
}
state = {
contractList: this.props.contractList,
};
deleteCompany = event => {
event.preventDefault();
// var targetID = ; I'm sure this is correct in original code
this.props.companyList.splice(targetID, 1);
this.props.contractListOfList.splice(targetID, 1);
this.props.setCompanyArr();
};
addContract = event => {
event.preventDefault();
var newContract = <IRContract contractList={this.state.contractList} setContractList={this.setContractList} setCompanyArr={this.props.setCompanyArr} contractListOfList={this.props.contractListOfList} setContractArr={this.props.setContractArr}/>;
this.props.contractList.push(newContract);
this.setContractList();
};
setContractList = () => {
this.setState(
state => {
const list = state.contractList;
return {
list,
};
}
)
}
render() {
return(
<div>
{
this.state.contractList.map((element, index) => {
return <tbody key={"myContractKey" + index + "_" +this.state.contractList.length} id={index}>{element}</tbody>;
})
}
</div>
);
}
}
IRContract
class IRContract extends React.Component {
constructor(props) {
super(props);
}
deleteMainContract = event => {
event.preventDefault();
this.props.contractList.splice(contractNum, 1);
this.props.setContractList();
};
render() {
return(
<React.Fragment>
<button name="deleteMainContract" type="button" onClick={this.deleteMainContract} style={{borderRadius: "6px", fontSize: "16px"}}>delete</button>
</React.Fragment>
);
}
}
If I click add company, add contract, and delete contract, it will work correctly.
However, if I click add company, add contract, add another company, and delete contract, it will fail.
Error message is "Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.", but I never write something like componentWillUnmount.

How can I test a component click which requires a function from a parent component?

I'm trying to write tests for my Reactjs application ( I'm very new to testing this ), but I can't seem to grasp how it works properly.
Below is my Component, what I want to test is handleRegisterFieldClick().
I've seen examples of mocking a click function but since the function I need to call is one passed from a parent component, how can I simulate that? I want to check if state.class = 'clickedSquare' after calling handleRegisterFieldClick().
export default class Square extends Component {
constructor(props) {
super(props)
this.state = {
fieldcell: props.field,
value: props.value,
class: props.class,
xPos: props.xPos,
yPos: props.yPos,
clicked: props.clicked
}
this.handleClick = this.handleClick.bind(this);
this.handleRegisterFieldClick = this.handleRegisterFieldClick.bind(this);
}
handleClick() {
if(this.state.fieldcell){
if (this.props.clicked) {
this.setState({
class: 'square'
})
} else {
this.setState({
class: 'clickedSquare'
})
}
}
}
handleRegisterFieldClick(){
if(this.state.fieldcell){
this.handleClick()
var params = {
xPos: this.state.xPos,
yPos: this.state.yPos
}
this.props.registerFieldClick(params)
}
}
render() {
return (
<button className={this.state.class} onClick={this.handleRegisterFieldClick} background={this.state.backgroundcolor}>
{this.state.value !== 0 ? this.state.value : null}
</button>
);
}
}
Perhaps something like this
const timeout = 'some value';
const timeout2 = 'some other value';
componentDidMount() {
setTimeout(() => {
this.handleRegisterFieldClick();
setTimeout(() => {
//Since setState is asynchronous were using a second timeout
//check the state.class condition here.
}, timeout2);
}, timeout);
}

React if statement not showing result

In React i have a button that when clicked will decrement state object value by one. When state value is decremented to 0, it should activate alert method but for some reason it only activates after state value one has reached to -1 not 0.
Any help?
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props){
super(props);
this.state = {
numbers:{
one:1
},
};
}
decrement = () => {
this.setState(prevState => {
return {
numbers:{
...prevState.numbers,
one: prevState.numbers.one - 1
}
}
}, () => console.log(
this.state.numbers.one,
));
if(this.state.numbers.one===0){
alert('test');
}
}
render() {
return (
<div className="App">
<div>{this.state.numbers.one}</div>
<br/>
<button onClick={this.decrement}>ok</button>
</div>
);
}
}
export default App;
Like i was saying in the comments. setState is async, you need to wait for the state change or use a lifecycle method.
So in your decrement function you can alert in the callback you are already using, which has the updated state value there.
decrement = () => {
this.setState(prevState => {
return {
numbers:{
...prevState.numbers,
one: prevState.numbers.one - 1
}
}
}, () => {
console.log(this.state.numbers.one)
if(this.state.numbers.one===0){
alert('test');
}
});
}
Alternatively, you can use the componentDidUpdate lifecycle method to check this value
componentDidUpdate(prevProps, prevState) {
if (prevState.numbers.one > 0 && this.state.numbers.one === 0) {
alert('test');
}
}
Because setState is async, you need to add the alert in the call back of setState.
class App extends Component {
constructor(props){
super(props);
this.state = {
numbers:{
one:1
},
};
}
decrement = () => {
this.setState(prevState => {
return {
numbers:{
...prevState.numbers,
one: prevState.numbers.one - 1
}
}
},
() => {
console.log(this.state.numbers.one)
if(this.state.numbers.one===0){
alert('test');
}
});
}
render() {
return (
<div className="App">
<div>{this.state.numbers.one}</div>
<br/>
<button onClick={this.decrement}>ok</button>
</div>
);
}
}
export default App;
you could implement the decrement function as below
decrement = () => {
this.setState({numbers: { ...this.state.numbers, one: this.state.numbers.one -1} },
() => {
this.state.numbers.one===0 && alert("test")
}
)
}

this.props and prevProps are equal

Inside componentDidUpdate, alert is not triggered when props change.
You can run this code in codePen (https://codepen.io/anon/pen/BMmqKe?editors=1011)
const state = observable({
isOpen: false
})
state.close = function () {
state.isOpen = false
}
state.open = function () {
state.isOpen = true
}
const Home = observer(class home extends Component {
componentDidUpdate(prevProps) {
if (this.props.store.isOpen !== prevProps.store.isOpen) {
// this line is not executed
alert('reset');
}
}
render() {
return (
this.props.store.isOpen
? <button onClick={this.props.store.close}>close</button>
: <button onClick={this.props.store.open}>open</button>
);
}
})
render(<Home store={state} />, document.getElementById('app'))
this.props.store and prevProps.store will always refer to the same store object, so isOpen will always be the same on both sides of the equals operator.
You could instead use the componentWillReact life cycle hook to run some code when the component will update because of a change in an observable.
const Home = observer(class home extends Component {
componentWillReact() {
alert('reset');
}
render() {
return (
this.props.store.isOpen
? <button onClick={this.props.store.close}>close</button>
: <button onClick={this.props.store.open}>open</button>
);
}
})
You can just change the if statement in you componentDidUpdate():
!this.props.store.isOpen ? alert("reset"): null;

Resources