Managing React component behavior during update - javascript

I'm new to ReactJs, and working with the ExtReact framework. I'm displaying a grid, and made a pagination, which is working fine.
I customed the spinner displayed when datas are loading, and it works fine when the "component did mount".
But, when I update the component (like when I switch the page using the ExtReact paginator plugin), there is another native spinner displayed, and I want to custom it too.
My problem is that I don't find a proper way to do it with lifeCycle components methods, since the componentWillUpdate method is deprecated.
I first had only one 'isLoading' state, but I added 'isUpdating' since 'isLoading' was modified after the first render because of the way the store data are loaded.
The isUpdating state seems to stay false, this is what is displayed by the console:
Snapshot
Is updating:false
Updated!
Is updating:false
Saul Goodman!
​
Here is my Component code:
import React, {Component} from 'react';
import {Grid, Column, Toolbar, SearchField} from '#sencha/ext-modern';
import {withRouter} from "react-router-dom";
import Spinner from '../../resources/components/Spinner';
Ext.require('Ext.grid.plugin.PagingToolbar');
class Subscriptions extends Component {
state = {
isLoading: true,
};
store = Ext.create('Ext.data.Store', {
fields: ['Field1', 'Field2', 'Field3'],
autoLoad: false,
pageSize: 30,
proxy: {
type: 'rest', // refers to the alias "proxy.ajax" on Ext.data.proxy.Ajax
url: 'niceadress',
reader: {
type: 'json'
}
}
});
/**
* Loading the datas into the store and then removes the spinner when fetched successfully
*/
componentDidMount() {
this.store.load({
callback: function (records, operation, success) {
this.setState({
isLoading: (!success),
});
console.log("Saul Goodman!");
},
scope: this
});
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
if (nextState.isLoading === true){
return false;
} else {
return true;
}
};
getSnapshotBeforeUpdate(prevProps, prevState) {
this.setState({
isLoading: true,
});
console.log('Snapshot');
console.log('Is loading:' + this.state.isLoading)
return prevState;
}
componentDidUpdate(prevProps, prevState, snapshot) {
this.setState({
isLoading: (!prevState.isUpdating),
});
console.log('Updated!');
console.log('Is loading:' + prevState.isLoading)
}
render() {
if (this.state.isLoading) return <Spinner/>;
return (
<Grid
store={this.store}
plugins={['pagingtoolbar', 'listpaging']}
>
The Grid
</Grid>
)
}
}
export default withRouter(Subscriptions);
Any idea of what I'm doing wrong?
EDIT: Well, I first thought that the store load wasn't going to trigger the componentDidUpdate method, that's why I wrote the isUploading state separately. I'm removing it but I still didn't solved my problem.
How can I do to prevent the virtualDOM to re-render after the setState call in componentDidUpdate?
I'm looking for an elegant way to break this loop.

The isUpdating state seems to stay false, this is what is displayed by
the console
This is because your shouldComponentUpdate is returning false when isUpdating is true.
There is also a typo in your componentDidUpdate's setState

Page changes using ExtReact Paginator strangely aren't triggering the ComponentDidUpdate method. Then, all this doesn't look to be the proper way to change the component behavior. ExtReact documentation looks a bit confusing about this point, I can't see how to override the pagingtoolbar component easily.
Thanks for your answers!

Related

How to load new axios data in react component

I've got a navigation menu that correctly loads components with react-router using <Link to={"/entity"}>Entities</Link>
The components load data using axios and display it in tables. What I'm struggling to accomplish is loading new data when clicking the <link> a subsequent time.
class List extends Component {
constructor() {
super();
this.state = { entities: [], loading: true};
}
componentDidMount() {
this.getData();
console.log('componentDidMount');
console.log(this.state.entities);
}
getData() {
axios.get(this.props.url).then(response => {
this.setState({ entities: response.data, loading: false})
})
}
render() {
...
}
This works well for loading a single set of data. But if I edit a row and open the list again it will have the originally retrieved. Which is the correct behaviour given that code, but how can I reload that? I've tried componentDidUpdate but that creates an infinite loop. I'm assuming due to componentDidUpdate changing the DOM which then again calls componentDidUpdate again.
componentDidUpdate(prevProps) {
this.getData();
console.log('componentDidUpdate');
}
I thought about doing something like adding onClick={this.handleClick} to the menus and changing states or passing values to indicate a new click. But there must be a way to catch an update from router and not just a change of the component.
If you use setState inside componentDidUpdate it updates the component, resulting in a call to componentDidUpdate which subsequently calls setState again resulting in the infinite loop. You should conditionally call setState and ensure that the condition violating the call occurs eventually e.g:
componentDidUpdate(previousProps, previousState) {
if (previousProps.data !== this.props.data) {
this.setState({/*....*/})
}
}
The solution I came up with was to add a datestamp to the link and compare that to the previous timestamp.
<Link to={{"/entity", state: { update: + new Date() } }}>Entities</Link>
componentDidUpdate(prevProps, prevState) {
if (prevProps.update !== this.props.update) {
this.setState({ loading: true })
this.getData();
//console.log('Data Updating - ' + this.props.update);
}
}

Need to Execute Function before render() in ReactJS

I've created a login system with React which stores a session when the user logs in. When the page is reloaded, I have added a function which should check if the session exists and then either setState() to true or to false.
As I'm new to React, I'm not sure how to execute this function. Please see my code below for App.js:
import React from 'react';
import './css/App.css';
import LoginForm from "./LoginForm";
import Dashboard from "./Dashboard";
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
renderLoginForm: true
};
this.handleLoginFormMount = this.handleLoginFormMount.bind(this);
}
handleLoginFormMount() {
this.setState({
renderLoginForm: false
});
}
// Check session function.
checkSession() {
fetch('/check-session', {
credentials: 'include'
})
.then((response) => {
return response.json();
})
.then((sessionResult) => {
if (sessionResult.username) {
console.log('false');
this.setState({
renderLoginForm: false
});
} else {
console.log('true');
this.setState({
renderLoginForm: true
});
}
})
.catch((error) => {
console.log('Error: ', error);
});
}
render() {
checkSession();
return (
<div className="App">
{this.state.renderLoginForm ? <LoginForm mountLoginForm={this.handleLoginFormMount} /> : null}
{this.state.renderLoginForm ? null : <Dashboard />}
</div>
);
}
}
export default App;
Having checkSession() in this position outputs the following in the console when loading the page:
Line 50: 'checkSession' is not defined no-undef
If I put the function outside of the class App extends React.Component {}, then it tells me that I cannot set the state of undefined.
Functional Component: In my case I wanted my code to run before component renders on the screen. useLayoutEffect is a hook provided by React for this exact purpose.
import React, { useLayoutEffect } from "react";
...
const App = () => {
useLayoutEffect(() => {
//check local token or something
}, []);
}
Read More: https://reactjs.org/docs/hooks-reference.html#uselayouteffect
Having checkSession() in this position outputs the following in the console when loading the page:
Line 50: 'checkSession' is not defined no-undef
That's because it's a method, but you're calling it like a freestanding function. The call should be this.checkSession();. But keep reading.
Separately:
The render function must be pure, it cannot have side-effects like changing state. Instead, put any side-effects code in componentDidMount; from the documentation for that lifecycle method:
If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
Be sure that your component renders correctly for the original state (before the session check), as well as for the updated state (after the session check).
More about lifecycle methods and such in the documentation.
Alternately, if this component can't do anything useful without the session, you might move the session check to its parent component, and have the parent only render this child component when it has the session check results.

React and Reloading API Call

I'm working on a project in React that pulls random data from an API and processes it. Currently, on loading the page, that component that actually pulls the API doesn't load by design.
So to keep things simple, I have two Components. I load Component1 and using a state showDiv: false I don't load Component2 with the API call. I have a button that when clicked changes the state to true, and by doing that, loads Component2 inside Component1.
Now what I want to do is have Component2 hidden again, and then brought back, and call a new set of data from the API and process it. I wrongly assumed that once Component2 was pulled off the page, that bringing it back would load it from scratch eg. rerun the componentWillMount() function inside Component2, which is where my API call is located. Now that I see that's not the case, I'm not sure how to accomplish that without reloading the page entirely.
EDIT:
Here's the component to be rendered:
(This has been heavily simplified. The API pulls an array, and that array is processed into an array of objects, and then it is placed in the state. The component's render is then populated by content taken from that processed array from the state)
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
isQuestionLoaded: false,
questions: [],
};
}
componentWillMount() {
fetch(this.props.category)
.then(results => {
return results.json();
}).then(data => {
let questions = data.results.map((question) => {
return question;
})
this.setState({questions: questions, isQuestionLoaded: true});
})
}
Here's the App.js render of this component:
render() {
let questionRender = this.state.showQuestions ?
<Questions
category={ this.state.categoryActive }
questionNumber={ this.state.questionNumber }
onClickCorrect={ this.nextQuestionCorrect }
onClickCorrectLast={ this.lastQuestionCorrect }
onClickIncorrect={ this.nextQuestionIncorrect }
onClickIncorrectLast={ this.lastQuestionIncorrect }
score={ this.state.score }
correct={ this.state.correct }
answerCorrect={ this.state.answerCorrect } />
: null;
Here's the function that takes the component away:
lastQuestionCorrect() {
this.setState({
showQuestions: false,
showFinal: true
});
}
Here's the function that brings the component back:
onClickReplay() {
this.setState({
showQuestions: true,
showFinal: false,
});
}
A new array was being loaded, the problem was one of my states in App.js was not being reset correctly by onClickReplay()

React router + redux navigating back doesn't call componentWillMount

Currently I pre-load data from api in container component's lifecycle method componentWillMount:
componentWillMount() {
const { dept, course } = this.props.routeParams;
this.props.fetchTimetable(dept, course);
}
It is called when user navigates to route /:dept/:course, and it works fine, until you navigate from let's say: /mif/31 to /mif/33 and then press back button. The component is not actually reinitialized, so the lifecycle method is not called, and the data isn't reloaded.
Is there some sort of way to reload data in this case? Should I maybe use another method of preloading data? I see react router emits LOCATION_CHANGE event on any location change, including navigating back, so maybe I can somehow use that?
If it matters, here's is how I implement data loading:
import { getTimetable } from '../api/timetable';
export const REQUEST_TIMETABLE = 'REQUEST_TIMETABLE';
export const RECEIVE_TIMETABLE = 'RECEIVE_TIMETABLE';
const requestTimetable = () => ({ type: REQUEST_TIMETABLE, loading: true });
const receiveTimetable = (timetable) => ({ type: RECEIVE_TIMETABLE, loading: false, timetable });
export function fetchTimetable(departmentId, courseId) {
return dispatch => {
dispatch(requestTimetable());
getTimetable(departmentId, courseId)
.then(timetable => dispatch(receiveTimetable(timetable)))
.catch(console.log);
};
}
You need to use componentWillReceiveProps to check if new props (nextProps) are same as existing props (this.props). Here's relevant code in Redux example: https://github.com/reactjs/redux/blob/e5e608eb87f84d4c6ec22b3b4e59338d234904d5/examples/async/src/containers/App.js#L13-L18
componentWillReceiveProps(nextProps) {
if (nextProps.dept !== this.props.dept || nextProps.course !== this.props.course) {
dispatch(fetchTimetable(nextProps.dept, nextProps.course))
}
}
I might be wrong here, but I believe the function you are looking for is not componentWillMount but componentWillReceiveProps,
assuming you are passing down variables (like :courseId) from redux router to your component, using setState in componentWillReceiveProps should repaint your component.
Otherwise, you can subscribe to changes in your store: http://redux.js.org/docs/api/Store.html
Disclaimer: I probably know less about redux then you.

Updating React state after Finished Rendering

I'm learning about react's lifecycle methods and I'm wondering how to best address this scenario:
I have a component that fetches information about someone's hobbies
componentWillMount() {
this.props.fetchHobbies(this.props.params.id);
}
The data is sent to a reducer which is mapped as a prop at bottom of the component
function mapStateToProps(state) {
return {
hobbies:state.hobbies.user
};
}
I have a component called twitterbootstrap which relies on my component's state to see what rows are selected.
const selectRowProp = {
mode: 'radio',
clickToSelect: true,
bgColor: "rgba(139, 195, 74, 0.71)",
selected: this.state.selected,
};
Here's my question. I want to access this.props.hobbies.selected.
--UPDATE I'm using componentWillRecieveProps as it has a setState option.
componentWillReceiveProps(nextProps){
this.setState({selected:next.props.hobbies })
}
I want to use this.props.hobbies.selected as the next prop to be recieved. How would I incorporate that?
nextProps is the props object that is passed after the first rendering. there is nothing called next.props in the function componentWillReceiveProps. you can do something like that.
componentWillReceiveProps(nextProps){
this.setState({selected:nextProps.hobbies })
}

Categories

Resources