React component infinite update - reactjs

I have a React component and there will be an infinite update on the component whenever I connect it to redux and reference to properties from the store. If I just reference/connect one property from the store, the infinite update will not occur. It only happens when I connect two or more and I really cannot determine why this is happening.
When I add shouldComponentUpdate, it will actually not stop the infinite updating, it will just slow it down a lot. I really have no idea what is going on.
UPDATE:
Interestingly, if I just take out the componentDidUpdate function, it will not do an infinite loop and crash the browser.
import React, { Component } from 'react';
import { connect } from 'react-redux';
class UnreadMessages extends Component {
constructor(props) {
super(props);
this.state = {
messageReceived: false,
unreadMessages: 0
}
this.unreadMessages = 0;
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.lastViewedMessageTime === this.props.lastViewedMessageTime) {
this.setState({ messageReceived: true },
() => {
setTimeout(
() => {
this.setState({ messageReceived: false });
},
300
);
}
);
}
const conOne = prevProps.messages.length !== this.props.messages.length;
const conTwo = this.props.visible === false && window.innerWidth < 768;
if (conTwo) {
let index = this.props.messages.length - 1;
const conOne = this.props.messages[index].type === 'chat.msg';
const conTwo = this.props.messages[index].member_type === 'agent';
if (conOne && conTwo) {
this.setState({
unreadMessages: this.state.unreadMessages + 1
});
}
}
if (this.props.visible === true) {
this.setState({ unreadMessages: 0 });
}
}
render () {
let displayBadge = this.state.unreadMessages > 0 ? true : false;
console.log('DISPLAY BAD', displayBadge)
let pulse = this.state.messageReceived === true ? 'pulse' : '';
console.log('PULSE', pulse)
if (!displayBadge) {
return null;
}
return (
<span className={`msgBadge ${pulse}`}>{this.state.unreadMessages}</span>
)
}
}
function mapStateToProps(state) {
return {
lastViewedMessageTime: state.lastViewedMessageTime,
messages: state.chats.toArray(),
visible: state.visible
};
}
export default connect(mapStateToProps, null)(UnreadMessages);

As #Hamms pointed out in the comments, using this.setState inside componentDidUpdate is most likely going to cause issues.
Take the first few lines of componentDidUpdate.
componentDidUpdate(prevProps, prevState) {
if (prevProps.lastViewedMessageTime === this.props.lastViewedMessageTime) {
this.setState({ messageReceived: true },
...
If the state or any other prop apart from lastViewedMessageTime is changed and causes an update then the lastViewedMessageTime prop will be the same in the current props as in the prevProps.
This will cause a state change - therefore an update - and the same will be true about lastViewedMessageTime.
You are already stuck in an endless update cycle.
You should look into static getDerivedStateFromProps or memoization. These should help avoid these issues.

Related

How to access state value on page refresh in react?

I am implementing time tracker in my project. When I start my tracker then I stored this tracker value into state and when pausing this tracker then change that value into the state. But when I refresh the page I am not getting last updated state value. So how can I get state value on page refresh?
const React = require("react");
const ms = require("pretty-ms");
class TaskTimer extends React.Component {
constructor(props) {
super(props);
this.state = {
time: 0,
isOn: false,
start: 0
};
this.startTimer = this.startTimer.bind(this);
this.stopTimer = this.stopTimer.bind(this);
this.resetTimer = this.resetTimer.bind(this);
}
startTimer() {
this.setState({
isOn: true,
time: this.state.time,
start: Date.now() - this.state.time
});
this.timer = setInterval(
() =>
this.setState({
time: Date.now() - this.state.start
}),
1
);
}
stopTimer() {
this.setState({ isOn: false });
clearInterval(this.timer);
}
resetTimer() {
this.setState({ time: 0, isOn: false });
}
render() {
let start =
this.state.time == 0 ? (
<button onClick={this.startTimer}>start</button>
) : null;
let stop =
this.state.time == 0 || !this.state.isOn ? null : (
<button onClick={this.stopTimer}>stop</button>
);
let resume =
this.state.time == 0 || this.state.isOn ? null : (
<button onClick={this.startTimer}>resume</button>
);
let reset =
this.state.time == 0 || this.state.isOn ? null : (
<button onClick={this.resetTimer}>reset</button>
);
return (
<div>
<h3>timer: {ms(this.state.time)}</h3>
{start}
{resume}
{stop}
{reset}
</div>
);
}
}
module.exports = TaskTimer;
Anyone please suggest me for how to get state value on page refresh?
if you want your state to persist after refresh, then you would need to store the state in localStorage in componentWillUnmount and then reset the state to what it used to be in componentDidMount. That is
componentDidMount() {
const stateObject = JSON.parse(localStorage.getItem("state"));
this.setState(stateObject);
}
componentWillUnmount() {
localStorage.setItem("state", JSON.stringify(this.state));
}
This can have unexpected results however in case the componentWillUnmount isn't able to be called during refresh. So a more robust (but less performant) method would be to update the state to localStorage each time you update your state. This can be done by putting the code in componentDidUpdate.
componentDidUpdate(prevProps, prevState) {
if(prevState != this.state) {
localStorage.setItem("state", this.state);
}
}
UPDATE:
After some research I found the event beforeunload which could work in a fairly performant way.
onUnload = (event) => {
localStorage.setItem("state", JSON.stringify(this.state)
}
componentDidMount() {
window.addEventListener("beforeunload", this.onUnload)
}
componentWillUnmount() {
window.removeEventListener("beforeunload", this.onUnload)
}
However, keep in mind that onbeforeunload is not properly implemented in some browsers (Safari in iOS for example). So you might face some issues with respect to that. This is the compatibility list for the event https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload#Browser_compatibility.

Why is the state change of my component not being detected by componentDidUpdate()?

I have a table of ships, and am trying to implement sorting (using table header clicks) and filtering (using a text field that the user types in).
I am puzzled by how React handles the state of my component.
My understanding is that componentDidUpdate() works like this:
I make a change to the component state somewhere
The state change is detected by the component and componentDidUpdate() runs
Based on this understanding, I expected componentDidUpdate() to
Re-sort when I change the state of ships
Re-filter when I change the state of ships
However, when a sorting is triggered, filtering is not done.
I thought that this would happen:
State is changed, triggering componentDidUpdate()
Ships are sorted
The state is saved
The saving of the state triggers a re-run of componentDidUpdate()
this.state.ships is now different from prevState.ships, triggering a re-filtering
But this seems to happen:
State is changed, triggering componentDidUpdate()
Ships are sorted
The state is saved
The saving of the state triggers a re-run of componentDidUpdate()
this.state.ships is the same as prevState.ships, not triggering a re-filtering
So either my understanding of componentDidUpdate() is spotty, or my understanding of state synchronicity is. I have read that state can be asynchronous in event handlers. Perhaps the sorted ships are not yet saved into the state when I try to detect if I should be filtering?
import React, { Component } from 'react';
import { SearchBar } from '../SearchBar';
import { Table } from '../Table/Table';
import { MoreButton } from '../MoreButton/MoreButton';
export class SearchableSortableTable extends Component {
constructor(props) {
super(props);
this.fetchShips = this.fetchShips.bind(this);
this.filterShips = this.filterShips.bind(this);
this.setSearchExpression = this.setSearchExpression.bind(this);
this.setSort = this.setSort.bind(this);
this.state = {
ships: [],
filteredShips: [],
searchExpression: '',
reverseSort: false
};
}
render() {
return (
this.state.error ?
<div>
<div>There was a problem fetching the ships, sorry.</div>
<div>{this.state.error}</div>
</div>
:
this.state.ships.length === 0 ? <h4>Loading...</h4> :
<div>
<div>
<SearchBar setSearchExpression={this.setSearchExpression} />
<MoreButton className="di" url={this.state.nextUrl} fetchShips={this.fetchShips} />
</div>
<div>
<Table ships={this.state.filteredShips} setSort={this.setSort} sortBy={this.state.columnName} reverse={this.state.reverseSort} />
</div>
</div>
);
}
componentDidMount() {
this.fetchShips(this.props.url);
}
componentDidUpdate(prevProps, prevState) {
if (this.state.columnName !== prevState.columnName || this.state.reverseSort !== prevState.reverseSort) {
this.sortShips();
}
// This conditional block is not entered when I sort.
if (this.state.ships !== prevState.ships || this.state.searchExpression !== prevState.searchExpression) {
this.filterShips();
}
}
async fetchShips(url) {
try {
const response = await fetch(url);
if (response['status'] && response['status'] === 200) {
const json = await response.json();
const ships = json['results'].map(this.mapShip);
this.setState({
ships: this.state.ships.concat(ships),
nextUrl: json['next']
});
} else {
this.setState({ error: `${response['status']} ${response['statusText']}` });
}
} catch (error) {
if (error instanceof TypeError && error.message.includes('NetworkError')) {
this.setState({ error: `${error.name} ${error.message}` });
} else {
throw error;
}
}
}
filterShips() {
const filteredShips = this.state.ships.filter(ship => {
return Object.values(ship).some(shipProp => shipProp.includes(this.state['searchExpression']))
});
this.setState({
filteredShips: filteredShips
});
}
setSearchExpression(event) {
this.setState({ searchExpression: event.target.value });
}
setSort(event) {
if (event && event['currentTarget'] && event['currentTarget']['attributes'] &&
event['currentTarget']['attributes']['name'] && event['currentTarget']['attributes']['name']['nodeValue']) {
const columnName = event['currentTarget']['attributes']['name']['nodeValue'];
this.setState({
columnName,
reverseSort: columnName === this.state.columnName ? !this.state.reverseSort : false
});
}
}
sortShips() {
if (this.state.columnName) {
const sortedShips = this.state.ships.sort((a, b) => {
const propA = a[this.state.columnName];
const propB = b[this.state.columnName];
if (!isNaN(+propA)) {
return this.state.reverseSort ? Number(propB) - Number(propA) : Number(propA) - Number(propB);
}
return this.state.reverseSort ? propB.localeCompare(propA) : propA.localeCompare(propB);
});
this.setState({ ships: sortedShips });
}
}
/**
* Maps a ship to its name, manufacturer, cost and starship class.
* #param ship The ship to be mapped.
*/
mapShip(ship) {
const { name, manufacturer, cost_in_credits, starship_class } = ship;
return Object.assign(
{
name,
manufacturer,
cost_in_credits,
starship_class
},
{}
);
}
}
The shouldComponentUpdate() method works for both props and state. In your example, after the sort/filter events, the following method is fired by React. Try using,
shouldComponentUpdate(nextProps, nextState) {
return this.state.value != nextState.value;
}

How to write a test case for componentWillReceiveProps in my code using React

This my code:
componentWillReceiveProps(nextProps) {
this.commentChanged(nextProps);
if (nextProps.isOpen !== this.state.isOpen) {
this.setState({
disableCancel: nextProps.commentRequired,
isOpen: nextProps.isOpen
});
}
}
commentChanged(nextProps) {
if (nextProps.value !== this.state.comment) {
this.setState({
comment: nextProps.value || ''
});
}
}
Please help me write a test case for this.I have to cover if and else statement both while writing the test case.
you can test your componentWillReceiveProps like this:
import ComponentName from ‘./ComponentName’;
import { shallow } from ‘enzyme’;
describe('componentWillReceiveProps()', () => {
it('call commentChanged once', () => {
const fakeCommentChanged =
jest.spyOn(ComponentName.prototype, ‘commentChanged’);
let defaultProps={isOpen:false}//define your props here
const component = shallow(<ComponentName props={defaultProps}/>;
changedProps={isopen:true}//define your nextProps here
// triggers componentWillReceiveProps
component.setProps(changedProps);
//expectations
expect(fakeCommentChanged).toHaveBeenCalled();
//write more expectations for other checks like when nextProps.value!=this.state.comment
})
})
we can help you with that if-else conditions If only we know your component state structure and props structure

ComponentWillReceiveProps are changing my props which I was getting from API

In my ComponentWillReceiveProps() methods my prop are getting updated which was fetched from API only. Please help how to recover the props.
My ComponentWillReceiveProps():
constructor(props) {
super(props)
this.state = {
filtered_chart_data: {}
}
props.getEngagementInfo()
props.getChartData()
}
componentWillReceiveProps(nextProps) {
const lastSavedCharts = this.state.filtered_chart_data
if (!_.isEmpty(nextProps.diagnose.chart_data)) {
if (_.isEmpty(this.state.filtered_chart_data)) {
return this.setState({
filtered_chart_data: nextProps.diagnose.chart_data
})
}
return this.setState(
{
filtered_chart_data: lastSavedCharts
},
() => this.updateFilters(nextProps.diagnose.chart_data)
)
}
}
updateFilters = chartDataToApplyFilters => {
if (!_.isEmpty(this.state.filtered_chart_data)) {
const { filters } = this.props.diagnose
this.handleFilterPlantRegion(
filters.plant_region,
chartDataToApplyFilters
)
this.handleFilterPlant(filters.plant, chartDataToApplyFilters)
}
}
In my nextProps the variable nextProps.diagnose.chart_data is updating every time, but it is being fetch from API.
Can you help How to not update this props?
you can try shouldComponentUpdate(), from docs

ComponentDidMount() not working

I am making a game using matter.js in reactjs.
I am suffering a problem where I cannot return anything in the render().
Probably this is because matter.js has its own renderer.
So I did not retun anytihng into the render section.
If i don't return anything in the render section componentDidMount do not run.
I am Unable to run componentDidMount() and i cannot call any function even though i bind it.
I am calling a start_game() function to start game but any other function that I call within start_game() an error comes which says that no such function exist even though I have declared it within the class.
import React, { Component } from 'react';
import Matter from 'matter-js';
const World = Matter.World;
const Engine = Matter.Engine;
const Renderer = Matter.Render;
const Bodies = Matter.Bodies;
const Events = Matter.Events;
const Mouse = Matter.Mouse;
const MouseConstraint = Matter.MouseConstraint;
const Body = Matter.Body;
class Walley extends Component
{
constructor(props)
{
super(props);
this.world = World;
this.bodies = Bodies;
this.engine = Engine.create(
{
options:
{
gravity: { x: 0, y: 0,}
}
});
this.renderer = Renderer.create(
{ element: document.getElementById('root'),
engine: this.engine,
options:
{
width: window.innerWidth,
height: window.innerHeight,
background: 'black',
wireframes: false,
}
});
this.state = {
play_game: false,
score:0,
};
this.play_game=this.play_game.bind(this);
}
play_game(){
this.setState
({
score: 50,
});
}
startGame()
{
console.log(this.state.play_game);
//don't know how here the value of play_game is set to true even it was false in componentWillMount()
if(this.state.play_game){
this.play_game();
//unable to access play_game
}
}
componentWillMount()
{
if (confirm("You want to start game?"))
{
this.setState({play_game: true});
}
console.log(this.state.play_game);
//even though play_game is set true above yet output at this console.log remains false
}
render()
{
if(this.state.play_game)
this.startGame();
}
componentDidMount()
{
//unable to access
console.log('Did Mount');
}
}
export default Walley;
render needs to be a pure function, meaning you should not call startGame from inside render. I think you want something closer to this:
// remove this
// componentWillMount() {}
componentDidMount() {
if (confirm("You want to start game?")) {
this.setState({play_game: true}, () => {
console.log(this.state.play_game);
});
startGame();
}
}
render() {
// eventually you will want to return a div where you can put your matter.js output
return null;
}

Resources