React implementing shouldComponentUpdate on my React app with loading state - reactjs

I'm having a trouble implementing shouldComponentUpdate on my React app with loadingData state. I want to prevent component to load again when nextProps has not changed from the this.props which is working fine and it messes up loadingData value some how and I can't find the reason why.
Why loadingData ended up to be true even there's no change to the data in Redux(nextProps and this.props)?
constructor(props){
super(props)
this.state = {
loadingData: false,
}
}
async componentDidMount() {
this._isMounted = true;
const { username } = this.state
this.setState({ loadingData: true })
try {
await this.props.getUserData(username)
this.setState({ loadingData: false })
} catch (err) {
console.log(err)
this.setState({ loadingData: false })
}
}
shouldComponentUpdate(nextProps, nextState) {
return this.props !== nextProps
}
render() {
return(
<div>{this.state.loadingData ? "Loading..." : this.props.userData}</div>
)
}
Updated code to show the how I set up loadingData state. Some reason, adding shouldComponentUpdate shows, Loading... on the screen instead of userData from Redux. I'm not sure why...

This will always return false because this.props is a different object than nextProps between each render.
For the simplest example of this:
console.log({} === {}) // false
By default (if using React.PureComponent) React will perform a "shallow equality" check. It will check the equality of each prop (but will not do so recursively because of performance reasons). (see the source).
As a first attempt, have you tried using React.PureComponent in place of React.Component?
If that doesn't help, I would recommend sharing the entire code snippet. shouldComponentUpdate usually is considered a "code smell" and commonly means there's a problem with another part of the code. It should not be used as control flow, but only for performance optimization.
If you still must implement shouldComponentUpdate, take a look at the default shallowEqual helper for a bit of inspiration.

Maybe you should look through this.
var jangoFett = {
occupation: "Bounty Hunter",
genetics: "superb"
};
var bobaFett = {
occupation: "Bounty Hunter",
genetics: "superb"
};
var callMeJango = jangoFett;
// Outputs: false
console.log(bobaFett === jangoFett);
// Outputs: true
console.log(callMeJango === jangoFett);

Related

Can't set state in react to array of strings

I'm new to react and having a super hard time. My most recent problem is trying to set the state of 'favMovies' to an array of strings (movie IDs).
States:
export class MainView extends React.Component {
constructor() {
super();
this.state = {
movies: [],
favMovies: [],
user: null,
};
}
Setting states:
onLoggedIn(authData) {
console.log(authData);
console.log(authData.user.FavoriteMovies);
this.setState({
favMovies: authData.user.FavoriteMovies,
});
this.setState({ user: authData.user });
localStorage.setItem('token', authData.token);
localStorage.setItem('user', JSON.stringify(authData.user));
this.getMovies(authData.token);
this.getUsers();
}
I kind of understand that set state is async and doesn't happen until the next render. The part that I'm confused by is the 'user' that get's set after 'favMovies' works as expected, but 'favMovies' is undefined.
I know this is probly a dumb question, but I'm absolutely lost in react right now and struggling. Any help would be appreciated.
It's alaways better to use single setState if possible, because everytime you call setState it will re-render the view. One more tip for you, when you are dealing with object and arrays try to use spread operstor to assign to the state, instead of direct assignment.
onLoggedIn(authData) {
console.log(authData);
console.log(authData.user.FavoriteMovies);
this.setState({
favMovies: [...authData.user.FavoriteMovies],
user: authData.user
});
//this.setState({ user: authData.user });
localStorage.setItem('token', authData.token);
localStorage.setItem('user', JSON.stringify(authData.user));
this.getMovies(authData.token);
this.getUsers();
}
As we know react setState is asynchronous, state won't reflect immediately. We can use callback with setState where we can access updated state.
this.setState(
{
favMovies: [...authData.user.FavoriteMovies],
user: authData.user
// ...
},
() => {
this.doSomethingAfterStateUpdate();
}
);

Best Way to Handle Multiple State Changes

I'm pretty new to react. I'm building a web app that is working pretty well but I'm not sure I'm handling state changes correctly. For instance, I have a method that gets called in componentDidMount.
It's seems inefficient and bad practice to keep calling setState but I'm not really sure of a better way to do it.
I would love some feedback.
class AuditScreen extends Component {
constructor(props) {
super(props);
this.state = {
currentVehicle: null,
currentVehicleIndex: 0,
vehicles: [],
activePictureIndex: 0,
show: false,
loading: false,
next_page: `${props.backend.id}/images`,
};
}
componentDidMount() {
this.getImages()
}
getImages() {
if (!this.state.loading) {
this.setState({
loading: true,
});
this.registerScrollEvent();
axios.get(this.state.next_page)
.then((response) => {
const paginator = response.data;
const vehicles = paginator.data.filter((vehicle) => {
if (vehicle.enhanced && vehicle.enhanced.length) {
return vehicle;
}
});
if (vehicles && vehicles.length) {
this.setState({
vehicles: [...this.state.vehicles, ...vehicles],
next_page: paginator.next_page_url,
});
if (this.state.currentVehicle === null) {
this.setState({
currentVehicle: vehicles[0]
});
}
}
// remove scroll event if next_page_url is null
if (!paginator.next_page_url) {
this.removeScrollEvent();
}
})
.finally((response) => {
this.setState({
loading: false,
});
});
}
}
}
.....
}
Thanks!
Like #Dave Newton says, I don't think this is bad practice. Updating the state doesn't immediately trigger an update. Instead, state updates often (but not always) batch up and then trigger a single update.
This article explains the batching mechanism in detail. Up to React 17, state-update batching only occurs within event handlers and within componentDidMount. They give an explicit example where "setState is called inside componentDidMount which causes only one extra update (and not three)".
So React already does what you want. React 18 is going to give you more control over the batching behavior, and more automatic batching. I found this description helpful for understanding what's coming and also how React 17 and lower currently work.
if you don't want to stop calling the setState method in react, use context for small project or redux to keep states and reducers with functional components. Best regards.

Why is my if statement always giving the same result in React?

I have a button that changes the state of sort to either true or false. This is being passed to the App component from a child using callbacks. The first console log in the refinedApiRequest gives the value of true and then false and then true and so on as the button is click. However, the if statement keeps resulting in the else result regardless of the state of sort (true or false). What is causing this?
I want to assign a variable a value depending on the state of sort so that I can feed this value into the params of a async api get request. Thanks in advance.
class App extends React.Component {
state = { pictures: '', sort: '' };
onSortSelect = sort => {
this.setState({ sort: sort }, () => this.refinedApiRequest(sort));
}
async refinedApiRequest(sort) {
console.log(sort);
if(sort == 'true') {
console.log('its true');
} else {
console.log('its false');
}
const res = await axios.get('url', {
params: {
order: {a variable}
}
});
this.setState({ pictures: res.data });
console.log(this.state.pictures);
}
While the optional callback parameter to this.setState is guaranteed to only be executed when the component is re-rendered, in your case it still closes over the sort value of the previous render (ref: How do JavaScript closures work?).
I'd suggest following reacts advice:
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
from: https://reactjs.org/docs/react-component.html#setstate
(emphasis by me)

re-render triggered in componentDidMount

In the life cycle of a component, if a re-render is triggered by some synchronous operation in componentDidMount(), would the user have a chance to see the first render content on browser?
e.g. If I toggle a start downloading boolean flag in componentDidMount() through redux, which then causes the re-render because the flag is mapped to redux for the component.
-------Update Info-----
The sync operation is just changing the start downloading flag to true, and the flag is mapped to the component, where the flag is checked to determine the JSX contents in render(). In redux, right after the flag is set to true, then the downloading operation begins. When downloading is completed, redux sets the flag to false.
Consider the following lifecycle sequence:
render() //JSX A
componentDidMount() // the flag is set
render() // re-render JSX B
Will JSX A be displayed in the browser, regardless of how quick it is?
the action creator called in componentDidMount():
export const downloadArticleList = () => {
return (dispatch, getState) => {
// set start flag to true synchronously, before axios.get
dispatch(listDownloadStart());
axios.get('/articles')
.then(response => {
//set the flag to false and update the data
dispatch(saveArticleList(response.data))
})
.catch(err => {
dispatch(serverFail(err))
console.log("[downloadArticleList]] axios", err);
})
}
}
It is a SPA, no SSR.
It depends on a few things:
How long sync operation takes
Are you doing SSR (thus there will be time dedicated for DOM rehydrating)
Generally, I'd consider this as an antipattern
As we discuss in the comment here is the example :
interface ExampleComponentProps {
}
interface ExampleComponentState {
loading: boolean;
}
export class ExampleComponent extends React.Component<ExampleComponentProps, ExampleComponentState>{
constructor(props, context) {
super(props, context);
this.state = { loading: true };
}
componentDidMount() {
//some method {}
//after get result
this.setState({
loading: false
})
}
render() {
return (
<div>
<Spin spinning={this.state.loading} >
//Your COmponent here
</Spin>
</div>
)
}
}
If your project is complicated, the easiest way is using
setTimeout(() => {
this.setState({
// your new flag here
})
}, 0);

this.setState does not update state

I'm trying to use this.setState within handleFormSubmit however this.setState isn't updating and I'm not sure why. If I run console.log(updatePosition) before this.setState I can that all the data is there. What am I missing? I use similar code for handleChange and I don't have problems.
constructor(props) {
super(props);
let uniqueId = moment().valueOf();
this.state = {
careerHistoryPositions: [{company: '', uniqueId: uniqueId, errors: {} }],
};
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
handleFormSubmit(event) {
event.preventDefault();
const { careerHistoryPositions } = this.state;
const updatePosition = this.state.careerHistoryPositions.map((careerHistoryPosition) => {
const errors = careerHistoryValidation(careerHistoryPosition);
return { ...careerHistoryPosition, errors: errors };
});
console.log(updatePosition)
this.setState({ careerHistoryPositions: updatePosition });
}
Keep in mind that the state isn't updated immediately. If you want to check if it's updated use callback function. Something as follows:
this.setState({ careerHistoryPositions: updatePosition }, () => console.log(this.state.careerHistoryPositions);
From the docs :
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Hope this helps.
You should show how you are calling handleFormSubmit chances are that it's bound to a Dom event. So this is not the class/component, instead if you console.log(this); you'll see that it's the Dom element, the form element.
To make your code work as intended, in your component constructor() method, add this to rebind the handler function to the react component's class method, and you'll have access to this.state and this.setState()
this.handleFormSubmit = this.handleFormSubmit.bind(this);

Resources