How to access state value on page refresh in react? - reactjs

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.

Related

React - Render happening before data is returned and not updating component

I can't get this to work correctly after several hours.
When creating a component that needs data from Firebase to display, the data is returning after all actions have taken place so my component isn't showing until pressing the button again which renders again and shows correctly.
Currently my function is finishing before setState, and setState is happening before the data returns.
I can get setState to happen when the data is returned by using the callback on setState but the component would have already rendered.
How do i get the component to render after the data has returned?
Or what would the correct approach be?
class CoffeeList extends Component {
constructor(props) {
super(props);
this.state = {
coffeeList: [],
}
}
componentDidMount() {
this.GetCoffeeList()
}
GetCoffeeList() {
var cups = []
coffeeCollection.get().then((querySnapshot) => {
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
})
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
}
render() {
const coffeeCups = this.state.coffeeList;
console.log("Rendering component")
return (
<div className="coffee">
<p> This is the Coffee Component</p>
{coffeeCups.map((c) => {
return (
<CoffeeBox name={c.name} />
)
})}
</div >
)
}
}
Thanks
The problem is that you set the state before the promise is resolved. Change the code in the following way:
GetCoffeeList() {
coffeeCollection.get().then((querySnapshot) => {
const cups = []
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
})
}

How to handle errors in React Native

I am developing a react native project.
I am first in React Native.
I have some errors in my project.
I 'd like to know how to handle error in React native.
And how can I see the errors?
if ((this.state.loadedUrl === 'https://www.truthbaron.com/') && (!this.state.newsflag)){
const html = event.nativeEvent.data;
const $ = CheerIO.load(html);
isLoggedIn = Object.keys($(PROFILE_SELECTOR)).includes('0');
if (isLoggedIn) {
if(this.state.messagesflag){
profileLink = $(PROFILE_SELECTOR).eq(0).children().attr('href');
username = profileLink.match(/members\/[a-z]+/)[0].slice(8);
if (username.endsWith('/')) username = username.slice(0, username.length - 1);
this.setState({ url: `${profileLink}messages`, loading: true });
console.log('messages page:' + profileLink);
}else{
profileLink = $(PROFILE_SELECTOR).eq(0).children().attr('href');
console.log('profile page!!!' + profileLink);
this.setState({ url: profileLink, loading: true });
}
}
else {
const loginLink = $(LOGIN_SELECTOR).eq(0).children().attr('href');
console.log('loginLink:' + loginLink);
this.setState({ url: loginLink, loading: true });
}
}
There are 2 ways to handle errors.
1.try {
var test;
test.color;
} catch(err) {
// handle error here
}
2.const previousHandler = ErrorUtils.getGlobalHandler();
ErrorUtils.setGlobalHandler((error, isFatal) => {
// handle the error here
console.log(error);
});
And you can monitor the errors in React Native with Rollbar.
For further more information, you can visit this URL.
https://rollbar.com/blog/react-native-error-monitoring/
There is multiple ways to handle error in react, and it is based on the architecture or code you building.
A standard way might be
try {
//code blocks
catch {
// error .log
}
You can relay on method function such as
console.error
If you are making api request
class IsLoading extends React.Component {
constructor(props) {
super(props);
// initialise our state
this.state = { isLoading: false };
}
componentDidCatch(error, info) {
// if we have a promise then we can deal with it
if(error instanceof Promise) {
// we have a promise so lets update our state
this.setState({ isLoading: true });
// once the promise has resolved we can update our state again and grab the data
error.then((data) => this.setState({ isLoading: false, data }));
}
}
render() {
// as props.children is a function, let's invoke it and p ass in out state
return this.props.children(this.state) }
}
}
const Loader = props => (
<IsLoading>
// this is the function that gets called in the render met hod above
{({isLoading, data}) => (
isLoading
// show some loading text if we're loading
? "Loading..."
// copy our children and pass in the data as a prop :
React.cloneElement(props.children, {data})
)}
</IsLoading>
);

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

React component infinite update

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.

Material UI linear progress animation when using data

The docs at material ui for reactJS proposes this sample code for determinate progress bars.
export default class LinearProgressExampleDeterminate extends React.Component {
constructor(props) {
super(props);
this.state = {
completed: 0,
};
}
componentDidMount() {
this.timer = setTimeout(() => this.progress(5), 1000);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
progress(completed) {
if (completed > 100) {
this.setState({completed: 100});
} else {
this.setState({completed});
const diff = Math.random() * 10;
this.timer = setTimeout(() => this.progress(completed + diff), 1000);
}
}
render() {
return (
<LinearProgress mode="determinate" value={this.state.completed} />
);
}
}
This creates a loading animation until bar is full. I am trying to modify it to use data from a json file so that it stops at the value that I specified for it in each json item. I am getting that part right. That was the easy part. But the animation fails because the animation is scripted using the value of completed in the constructor's state. And it is also located outside of my data.map so I can seem to find the way to make it read the value in the json file so it can se it for it's timeout function. :(
This is what I have (reduced)
JSON
exports.playerItems = [
{
id: 283,
completed: "100",
}
{
id: 284,
completed: "70",
}
{
id: 285,
completed: "20",
}
...
{
id: 295,
completed: "50",
}
]
Data injection
import PLAYERS from 'data/players.js';
const playersData = PLAYERS['playerItems'];
And my table is mapped
<Table>
{playersData.map((row, index) => (
<TableRow id={row.name} key={index}>
<TableRowColumn>
<LinearProgress
mode="determinate"
value={row.completed} />
</TableRowColumn>
</TableRow>
))}
</Table>
How can I modify the progress() function so that it animates the value given to the LinearProgress?
Thanks in advance
You can apply a state change to an array of player data and continually update the array in increments until all of the players have rendered.
First, start from zero:
constructor(props) {
super(props);
this.state = {
playersData: data.map(item => ({ ...item, completed: 0}))
};
};
Then initiate progress at mount:
componentDidMount() {
this.timer = setTimeout(() => this.progress(5), 100);
}
Update until each player has reached 100%:
progress(completion) {
let done = 0;
this.setState({
playersData: data.map((item, i) => {
const { completed: current } = this.state.playersData[i];
const { completed: max } = item;
if (current + completion >= max) {
done += 1;
}
return {
...item,
completed: Math.min(current + completion, max),
};
}),
});
if (done < data.length) {
this.timer = setTimeout(() => this.progress(5), 100);
}
}
Adjust the delay and increment as you see fit. The limitation is that you need all of the player data that will be rendered and it needs to be in state as an array that is updated in a single setState
Here is a working example on codesandbox.

Resources