How to replace componentWillRecieveProps - reactjs

I'm new to redux and followed this tutorial to create a simple blog app with react and redux. I've completed it, however I noticed that componentWillRecieveProps is being deprecated. I'm trying to replace it with more up-to-date code, but have been unable to understand how to do so. I've read this article about replacing ‘componentWillReceiveProps’ with ‘getDerivedStateFromProps’, but I don't think that this is the correct use for getDerivedStateFromProps as React's blog post on replacing one with the other describes.
My current componentWillRecieveProps code:
import axios from "axios";
import React from "react";
import { connect } from "react-redux";
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
body: "",
author: ""
};
this.handleChangeField = this.handleChangeField.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
//This is deprecated
componentWillReceiveProps(nextProps) {
console.log(this);
if (nextProps.articleToEdit) {
this.setState({
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
});
}
}
handleSubmit() {
const { onSubmit, articleToEdit, onEdit } = this.props;
const { title, body, author } = this.state;
if (!articleToEdit) {
return axios
.post("http://localhost:8000/api/articles", {
title,
body,
author
})
.then(res => onSubmit(res.data))
.then(() => this.setState({ title: "", body: "", author: "" }));
} else {
return axios
.patch(
`http://localhost:8000/api/articles/${articleToEdit._id}`,
{
title,
body,
author
}
)
.then(res => onEdit(res.data))
.then(() => this.setState({ title: "", body: "", author: "" }));
}
}
handleChangeField(key, event) {
this.setState({
[key]: event.target.value
});
}
render() {
const { articleToEdit } = this.props;
const { title, body, author } = this.state;
return (
<div className="col-12 col-lg-6 offset-lg-3">
<input
onChange={ev => this.handleChangeField("title", ev)}
value={title}
className="form-control my-3"
placeholder="Article Title"
/>
<textarea
onChange={ev => this.handleChangeField("body", ev)}
className="form-control my-3"
placeholder="Article Body"
value={body}
/>
<input
onChange={ev => this.handleChangeField("author", ev)}
value={author}
className="form-control my-3"
placeholder="Article Author"
/>
<button
onClick={this.handleSubmit}
className="btn btn-primary float-right"
>
{articleToEdit ? "Update" : "Submit"}
</button>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
onSubmit: data => dispatch({ type: "SUBMIT_ARTICLE", data }),
onEdit: data => dispatch({ type: "EDIT_ARTICLE", data })
});
const mapStateToProps = state => ({
articleToEdit: state.home.articleToEdit
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form);
Full Repo
Codesandbox
If componentWillRecieveProps is being deprecated, how should I update my code?
Edit: using getDerivedStateFromProps, nextProps still has the previous values so when returning the object, state is set back to previous state, no updates are actually made.
Trying to use prevState values doesn't work in this case because the initial prevState values are all empty strings which are called and placed into state anytime the component renders, which occurs on initial page load, and when clicking the edit button.
static getDerivedStateFromProps(nextProps, prevState) {
if (
JSON.stringify(nextProps.articleToEdit) !==
JSON.stringify(prevState.articleToEdit)
) {
console.log(nextProps);
console.log(prevState);
return {
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
}; // <- is this actually equivalent to this.setState()?
} else {
return null;
}
}

componentWillReceiveProps() method is deprecated by introducing a new life cycle method called getDerivedStateFromProps().
Keep in mind that you should always compare current props with previous props like below and if they both are not same then do setState otherwise you will get into infinite setState warning
Replace below code in place of componentWillReceiveProps method
static getDerivedStateFromProps(nextProps, prevState) {
//Below I used JSON.stringify to compare current props with previous props of articleToEdit object
if (JSON.stringify(nextProps.articleToEdit) !== JSON.stringify(prevState.artileToEdit)){
return({
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
});// <- this is setState equivalent
}
return null;
}
Edit:
You cannot access this.props inside getDerivedStateFromProps function. If you want to access some previous props (like for comparison) inside the function, then you can mirror such values inside the state. Then you can compare the nextProps with the value stored in state like above
Check this thread for better understanding
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#updating-state-based-on-props

Alrighty. Here's a working example for your application. I completely rewrote its structure so that each component handles its own set of state and props. The components are also reusable so they can be placed wherever -- provided that it has required props.
Notes:
Don't mix .jsx and .js (see step 6 for fix)
Separate your container-components from your components. Also make reusable components -- this vastly simplifies your state and prop logic.
For your application, you can completely remove redux and instead allow your server to be the "source of truth". So instead of saving to redux state and manipulating redux state, you would simply make AJAX requests to leverage your API for CRUDing your articles (for example, form submission would send post request to API, which would add an article to the DB, then either redirect the user back to /articles or it could simply return all articles after it has posted and update ShowArticles state with new data). Up to you on how you want to handle that. But, with that said, the only time you really need redux is if you're sharing props from two separate heavily nested components.
If using redux, add a types and actions folder as shown in the example below. It makes your code easier to read and more modular.
Avoid using index.js unless the root folder only houses a single file. Otherwise, when the application breaks (and it will), it'll be a hassle to figure out which "index.js" broke if you have many.
The boilerplate you're using is pretty old and you're making your life harder by using it. I've created a MERN Fullstack Boilerplate that allows things like: fat arrow functions in class methods, scss/css node_module imports, component-level scss module imports, runs server and client simultaneously, has an error overlay, eslinting, and plenty more. I highly recommend porting what you have over to it.
Working example: https://codesandbox.io/s/4x4kxn9qxw (unfortunately, the create-react-app template doesn't allow scss module imports... oh well, you'll get the idea)

My advice would be to heed the warning that React is giving us by replacing this lifecycle method because of its misuse — they are telling us only do this if you really have to, because there is probably a better way (including the newer getDerivedStateFromProps).
From what I gather in this code, you are rendering a form, letting the user interact with it, but in some circumstance you replace the users' inputs with props (overwriting component state). Its easy to see why this is an anti-pattern but tricky to re-architect without knowing more about the app.
I'd suggest moving the inputs' values into the parent state (or creating a new parent), and change to value={props.title} etc. If there is a parent that controls a forms' state in any circumstance, you may as well hold form state there all the time.

Related

How read array/object from get Axios in React function [duplicate]

I have recently moved from Angular to ReactJs. I am using jQuery for API calls. I have an API which returns a random user list that is to be printed in a list.
I am not sure how to write my API calls. What is best practice for this?
I tried the following but I am not getting any output. I am open to implementing alternative API libraries if necessary.
Below is my code:
import React from 'react';
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
UserList(){
return $.getJSON('https://randomuser.me/api/')
.then(function(data) {
return data.results;
});
}
render() {
this.UserList().then(function(res){
this.state = {person: res};
});
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">
{this.state.person.map((item, i) =>{
return(
<h1>{item.name.first}</h1>
<span>{item.cell}, {item.email}</span>
)
})}
<div>
</div>
)
}
}
In this case, you can do ajax call inside componentDidMount, and then update state
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
this.UserList();
}
UserList() {
$.getJSON('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
render() {
const persons = this.state.person.map((item, i) => (
<div>
<h1>{ item.name.first }</h1>
<span>{ item.cell }, { item.email }</span>
</div>
));
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{ persons }</div>
</div>
);
}
}
You may want to check out the Flux Architecture. I also recommend checking out React-Redux Implementation. Put your api calls in your actions. It is much more cleaner than putting it all in the component.
Actions are sort of helper methods that you can call to change your application state or do api calls.
Use fetch method inside componentDidMount to update state:
componentDidMount(){
fetch('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
This discussion has been for a while and #Alexander T.'s answer provided a good guide to follow for newer of React like me. And I'm going to share some additional know-how about calling the same API multiple times to refresh the component, I think it's probably a common question for beginners.
componentWillReceiveProps(nextProps), from official documentation :
If you need to update the state in response to prop changes (for
example, to reset it), you may compare this.props and nextProps and
perform state transitions using this.setState() in this method.
We could conclude that here is the place we handle props from the parent component, have API calls, and update the state.
Base on #Alexander T.'s example:
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
//For our first load.
this.UserList(this.props.group); //maybe something like "groupOne"
}
componentWillReceiveProps(nextProps) {
// Assuming parameter comes from url.
// let group = window.location.toString().split("/")[*indexParameterLocated*];
// this.UserList(group);
// Assuming parameter comes from props that from parent component.
let group = nextProps.group; // Maybe something like "groupTwo"
this.UserList(group);
}
UserList(group) {
$.getJSON('https://randomuser.me/api/' + group)
.then(({ results }) => this.setState({ person: results }));
}
render() {
return (...)
}
}
Update
componentWillReceiveProps() will be deprecated.
Here are only some methods (all of them in Doc) in the life cycle I think that they are related to deploying API in the general cases:
By referring to the diagram above:
Deploy API in componentDidMount()
The proper scenario to have API call here is that the content (from the response of API) of this component will be static, componentDidMount() only fire once while the component is mounting, even new props are passed from the parent component or have actions to lead re-rendering.
The component do check difference to re-render but not re-mount.
Quote from doc:
If you need to load data from a remote endpoint, this is a good place to
instantiate the network request.
Deploy API in static getDerivedStateFromProps(nextProps, prevState)
We should notice that there are two kinds of component updating, setState() in current component would not trigger this method but re-rendering or new props from parent component would.
We could find out this method also fires while mounting.
This is a proper place to deploy API if we want to use the current component as a template, and the new parameters to make API calls are props coming from parent component.
We receive a different response from API and return a new state here to change the content of this component.
For example:
We have a dropdown list for different Cars in the parent component, this component needs to show the details of the selected one.
Deploy API in componentDidUpdate(prevProps, prevState)
Different from static getDerivedStateFromProps(), this method is invoked immediately after every rendering except the initial rendering. We could have API calling and render difference in one component.
Extend the previous example:
The component to show Car's details may contain a list of series of this car, if we want to check the 2013 production one, we may click or select or ... the list item to lead a first setState() to reflect this behavior (such as highlighting the list item) in this component, and in the following componentDidUpdate() we send our request with new parameters (state). After getting the response, we setState() again for rendering the different content of the Car details. To prevent the following componentDidUpdate() from causing the infinity loop, we need to compare the state by utilizing prevState at the beginning of this method to decide if we send the API and render the new content.
This method really could be utilized just like static getDerivedStateFromProps() with props, but need to handle the changes of props by utilizing prevProps. And we need to cooperate with componentDidMount() to handle the initial API call.
Quote from doc:
... This is also a good place to do network requests as long as you
compare the current props to previous props ...
I would like you to have a look at redux
http://redux.js.org/index.html
They have very well defined way of handling async calls ie API calls, and instead of using jQuery for API calls, I would like to recommend using fetch or request npm packages, fetch is currently supported by modern browsers, but a shim is also available for server side.
There is also this another amazing package superagent, which has alot many options when making an API request and its very easy to use.
You can also fetch data with hooks in your function components
full example with api call: https://codesandbox.io/s/jvvkoo8pq3
second example: https://jsfiddle.net/bradcypert/jhrt40yv/6/
const Repos = ({user}) => {
const [repos, setRepos] = React.useState([]);
React.useEffect(() => {
const fetchData = async () => {
const response = await axios.get(`https://api.github.com/users/${user}/repos`);
setRepos(response.data);
}
fetchData();
}, []);
return (
<div>
{repos.map(repo =>
<div key={repo.id}>{repo.name}</div>
)}
</div>
);
}
ReactDOM.render(<Repos user="bradcypert" />, document.querySelector("#app"))
1) You can use Fetch API to fetch data from Endd Points:
Example fetching all Github repose for a user
/* Fetch GitHub Repos */
fetchData = () => {
//show progress bar
this.setState({ isLoading: true });
//fetch repos
fetch(`https://api.github.com/users/hiteshsahu/repos`)
.then(response => response.json())
.then(data => {
if (Array.isArray(data)) {
console.log(JSON.stringify(data));
this.setState({ repos: data ,
isLoading: false});
} else {
this.setState({ repos: [],
isLoading: false
});
}
});
};
2) Other Alternative is Axios
Using axios you can cut out the middle step of passing the results of
the http request to the .json() method. Axios just returns the data
object you would expect.
import axios from "axios";
/* Fetch GitHub Repos */
fetchDataWithAxios = () => {
//show progress bar
this.setState({ isLoading: true });
// fetch repos with axios
axios
.get(`https://api.github.com/users/hiteshsahu/repos`)
.then(result => {
console.log(result);
this.setState({
repos: result.data,
isLoading: false
});
})
.catch(error =>
this.setState({
error,
isLoading: false
})
);
}
Now you can choose to fetch data using any of this strategies in componentDidMount
class App extends React.Component {
state = {
repos: [],
isLoading: false
};
componentDidMount() {
this.fetchData ();
}
Meanwhile you can show progress bar while data is loading
{this.state.isLoading && <LinearProgress />}
Render function should be pure, it's mean that it only uses state and props to render, never try to modify the state in render, this usually causes ugly bugs and decreases performance significantly. It's also a good point if you separate data-fetching and render concerns in your React App. I recommend you read this article which explains this idea very well. https://medium.com/#learnreact/container-components-c0e67432e005#.sfydn87nm
This part from React v16 documentation will answer your question, read on about componentDidMount():
componentDidMount()
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. This method is a good place to set up
any subscriptions. If you do that, don’t forget to unsubscribe in
componentWillUnmount().
As you see, componentDidMount is considered the best place and cycle to do the api call, also access the node, means by this time it's safe to do the call, update the view or whatever you could do when document is ready, if you are using jQuery, it should somehow remind you document.ready() function, where you could make sure everything is ready for whatever you want to do in your code...
As an addition/update to Oleksandr T.'s excellent answer:
If you use class components, backend calls should happen in componentDidMount.
If you use hooks instead, you should use the effect hook
For example:
import React, { useState, useEffect } from 'react';
useEffect(() => {
fetchDataFromBackend();
}, []);
// define fetchDataFromBackend() as usual, using Fetch API or similar;
// the result will typically be stored as component state
Further reading:
Using the Effect Hook in the official docs.
How to fetch data with React Hooks? by Robin Wieruch
A clean way is to make an asynchronous API call inside componentDidMount with try/catch function.
When we called an API, we receive a response. Then we apply JSON method on it, to convert the response into a JavaScript object. Then we take from that response object only his child object named "results" (data.results).
In the beginning we defined "userList" in state as an empty array. As soon as we make the API call and receive data from that API, we assign the "results" to userList using setState method.
Inside the render function we tell that userList will be coming from state. Since the userList is an array of objects we map through it, to display a picture, a name and a phone number of each object "user". To retrieve this information we use dot notation (e.g. user.phone).
NOTE: depending on your API, your response may look different. Console.log the whole "response" to see which variables you need from it, and then assign them in setState.
UserList.js
import React, { Component } from "react";
export default class UserList extends Component {
state = {
userList: [], // list is empty in the beginning
error: false
};
componentDidMount() {
this.getUserList(); // function call
}
getUserList = async () => {
try { //try to get data
const response = await fetch("https://randomuser.me/api/");
if (response.ok) { // ckeck if status code is 200
const data = await response.json();
this.setState({ userList: data.results});
} else { this.setState({ error: true }) }
} catch (e) { //code will jump here if there is a network problem
this.setState({ error: true });
}
};
render() {
const { userList, error } = this.state
return (
<div>
{userList.length > 0 && userList.map(user => (
<div key={user}>
<img src={user.picture.medium} alt="user"/>
<div>
<div>{user.name.first}{user.name.last}</div>
<div>{user.phone}</div>
<div>{user.email}</div>
</div>
</div>
))}
{error && <div>Sorry, can not display the data</div>}
</div>
)
}}
As best place and practice for external API calls is React Lifecycle method componentDidMount(), where after the execution of the API call you should update the local state to be triggered new render() method call, then the changes in the updated local state will be applied on the component view.
As other option for initial external data source call in React is pointed the constructor() method of the class. The constructor is the first method executed on initialization of the component object instance. You could see this approach in the documentation examples for Higher-Order Components.
The method componentWillMount() and UNSAFE_componentWillMount() should not be used for external API calls, because they are intended to be deprecated. Here you could see common reasons, why this method will be deprecated.
Anyway you must never use render() method or method directly called from render() as a point for external API call. If you do this your application will be blocked.
You must try "axios" library for API call.
Instead of direct using jQuery.
Thanks.
It would be great to use axios for the api request which supports cancellation, interceptors etc. Along with axios, l use react-redux for state management and redux-saga/redux-thunk for the side effects.

Should a React component use (fixed) values not on state or props?

I am new to React and trying to understand if what I am doing is an anti-pattern. I have a component that renders a modal that contains a <select> input. The options available in that <select> come from a service - they will probably never change but I want those values to live in the service not be hard coded on the component UI. I am tempted to assign this options array to this, rather than this.state, since it will not change. I could assign the options via props, but that seems to be asking the parent component to do too much - it shouldn't have to know about those values.
So the basic question is, is it ever valid in React to have some data stored directly on this instead of on state or props?
There are many ways to handle your scenario, and it is very common in React development. In my opinion, data that has fixed value (that doesn't change over time, eg: date-format, certain select options (gender), etc.) should be better stored in a certain place in your application, so that it can be better managed and maintained (also, that will take out the constant values from the rendering logic in your components). I've always kept my components clean from constants/fixed values (moved them into constants.js) and functions (moved them into utils/ folder and unit test them) out from the component files so that they can be easily read and maintain.
Hoping this below information can help you:
// Storing them into one single file
import { services } from '../constants';
// Global constants - when you're pretty sure it will never change;
// using global constants also make it easier to update from the top of the component file.
const SERVICES = [
'services-a',
'services-b',
];
// Storing in redux store
#connect(state => ({ services: state.services }))
export default class App extends PureComponent {
static propTypes = {
services: PropTypes.array.isRequired,
}
// Using the getter method; I mainly use this when I want to process some information with props data
// NOTE: Some people might think that this is an anti-pattern in React
get services() {
const { someRelevantDetailsFromProps } = this.props;
// process and return
return someRelevantDetailsFromProps.map();
}
render() {
return (
<select>
{/* Importing from constant file */}
{ services.map(service => <option value={service}>{service}</option>) }
{/* Using class getter method */}
{ this.services.map(service => <option value={service}>{service}</option>) }
{/* Using redux store */}
{ this.props.services.map(service => <option value={service}>{service}</option>) }
{/* Using global contant */}
{ SERVICES.map(service => <option value={service}>{service}</option>) }
</select>
);
}
}
Of course you can store your own variables in the class even they don't update by state through. There is no anti-pattern.
Most anti-patterns and articles about anti-patterns are to make you avoid the unknown problems and side effects. But if you really know how to works React then you can play as you want. I think to use your own way might be awful with problems but it's best learning way of the something. I mean, don't barrier yourself to lose your creativity. Already you going to find true way on the way.
Each anti-pattern has the true pattern, also. So you always have a chance. An example; to get "prop value" from parent component for put "initial state" in constructor is anti-pattern. Because it never update. But if you use getDerivedStateFromProps life cycle method to update new state value from props then it won't be anti-pattern.
An example i used many times that logic to avoid unmounted component renders by state:
updateState()
{
// To avoid unmounted setState for unfinished process
if (this.mounted)
{
this.setState(...);
}
}
Already people love React because it's just Javascript even it's have JSX though.
class MyComponent extends React.Component
{
static getDerivedStateFromProps(props, state)
{
// We always getting actual state when parent change the prop value
// Now it became true pattern with keep up-to-date the state from props
if (props.defaultSelectValue !== state.selectValue)
{
return {
selectValue: props.defaultSelectValue,
};
}
return null;
}
constructor(props)
{
super(props);
const { defaultSelectValue } = this.props;
this.state =
{
selectValue: defaultSelectValue,
};
this.cache =
{
SelectComponent: ({ items }) => // We used arrow function to don't lose the this scope
{
const { selectValue } = this.state; // this will be always actual state
return (
<select value={selectValue}>
<option value="0">Yes</option>
<option value="1">No</option>
</select>
);
},
};
}
render()
{
const { SelectComponent } = this.cache;
return (
<SelectComponent />
);
}
}
So if you know what you are doing, then there is no anti-pattern.

How to setState() in React/Apollo with graphQL

I am trying to setState() to a query result I have from graphQL, but I am having difficulty finding out how to do this because it will always be loading, or it's only used from props.
I first set the state
constructor (props) {
super(props);
this.state = { data: [] };
Then I have this query
const AllParams = gql`
query AllParamsQuery {
params {
id,
param,
input
}
}`
And when it comes back I can access it with this.props.AllParamsQuery.params
How and when should I this.setState({ data: this.props.AllParamsQuery.params }) without it returning {data: undefined}?
I haven't found a way to make it wait while it's undefined AKA loading: true then setState. I've tried componentDidMount() and componentWillReceiveProps() including a async function(){...await...} but was unsuccessful, I am likely doing it wrong. Any one know how to do this correctly or have an example?
EDIT + Answer: you should not setstate and just leave it in props. Check out this link: "Why setting props as state in react.js is blasphemy" http://johnnyji.me/react/2015/06/26/why-setting-props-as-state-in-react-is-blasphemy.html
There is more to the problem to update props, but some great examples can be found at this app creation tutorial: https://www.howtographql.com/react-apollo/8-subscriptions/
A simple solution is to separate your Apollo query components and React stateful components. Coming from Redux, it's not unusual to transform incoming props for local component state using mapStateToProps and componentWillReceiveProps.
However, this pattern gets messy with Apollo's <Query />.
So simply create a separate component which fetches data:
...
export class WidgetsContainer extends Component {
render (
<Query query={GET_WIDGETS}>
{({ loading, error, data }) => {
if (loading) return <Loader active inline="centered" />;
const { widgets } = data;
return (
<Widgets widgets={widgets} />
)
}}
</Query>
)
}
And now the Widgets components can now use setState as normal:
...
export class Widgets extends Component {
...
constructor(props) {
super()
const { widgets } = props;
this.state = {
filteredWidgets: widgets
};
}
filterWidget = e => {
// some filtering logic
this.setState({ filteredWidgets });
}
render() {
const { filteredWidgets } = this.state;
return (
<div>
<input type="text" onChange={this.filterWidgets} />
{filteredWidgets.count}
</div>
)
}
}
What is the reason behind setting it to state? Keep in mind, Apollo Client uses an internal redux store to manage queries. If you're trying to trigger a re render based on when something changes in the query, you should be using refetchQueries(). If you absolutely need to store it in local state, I would assume you could probably compare nextProps in componentWillReceiveProps to detect when loading (the value that comes back when you execute a query from apollo client) has changed, then update your state.
I had a similar issue (although it was happening for a totally different reason). My state kept getting set to undefined. I was able to solve it with a React middleware. It made it easy to avoid this issue. I ended up using superagent.
http://www.sohamkamani.com/blog/2016/06/05/redux-apis/

Force React container to refresh data

In my React application, I have two distinct types of components: presentations and containers. It's roughly after Dan Abromov's "Presentational and Container Components", except I don't use Flux or Redux.
Now, I have the following structure:
UsersContainer
├── UsersListContainer
| └── UsersListView
└── AddUserContainer
The UsersListContainer is responsible for loading data from some REST API, and if that succeeds, it delegates presenting that data to the UsersListView.
The AddUserContainer is responsible for adding a new user, again, by invoking an REST API. Now, when that was successful, I would like the UsersListContainer to refresh its data.
The best I can think of is this:
class AddUserContainer extends React.Component {
render() {
// Other UI elements omitted for brevity
return (<button onClick={ e => props.onUserAdded() }>Add user</button>);
}
}
class UsersListContainer extends React.Component {
componentWillMount() {
// start fetching data using window.fetch;
// the promise callback will but retrieved data into this.state
}
render() {
return (<table></table>);
}
}
class UsersContainer extends React.Component {
render() {
const forceUpdate = this.setState({ ...this.state, refreshToken: Math.random() });
// Other UI elements omitted for brevity
<UsersListContainer key={ this.state.refreshToken } />
<AddUserContainer onUserAdded={ forceUpdate } />
}
}
But this approach feels like mis-using the key prop. Is there a better / more elegant way to do this?
Check out react-refetch, which provides a nice API for fetching, and allows you to implement the Presentational and Container Components pattern, without using Flux/Redux for API calls.
It also lets you handle loading and errored states, which is definitely necessary for a decent web application today.
In the example below, I got rid of UsersListContainer but moved AddUserContainer into UsersContainer as well. This makes your UsersListView the presentational component for UsersContainer. Feel free to change the naming as you wish. This is so that I can get the refreshUsers prop to pass into AddUserContainer.
// UsersContainer.js
const Container = ({ usersFetch, refreshUsers }) => {
if (userFetch.pending) {
return <LoadingDisplay />
} else if (usersFetch.rejected) {
return <ErrorDisplay error={ usersFetch.reason } />
} else if (usersFetch.fulfilled) {
return (
<UsersListView users={ usersFetch.value } />
<AddUserContainer handleAddUser={ refreshUsers } />
);
}
};
const refetch = (props) => {
const usersFetch = `/api/users`;
return {
usersFetch: usersFetch,
refreshUsers: () => ({
usersFetch: { ...usersFetch, force: true, refreshing: true }
}),
};
};
export default connect(refetch)(Container);
Check out the documentation for more examples. I personally prefer to use react-refetch for API-heavy applications, rather than implementing the calls in Redux.
If you don't want to use Flux or Redux yet, then updating peer-components (in your case, UsersListContainer and AddUserContainer) is a bit, in my opinion, anti-pattern for React.
The main idea of React is, to me, passing props from parent to children, therefore, Irvin Lim's idea to "got rid of UsersListContainer but moved AddUserContainer into UsersContainer" will make it easier for you to control when to update your component!
Your current approaching and my idea are the same: in your UsersContainer, create a method to forceUpdate it, then pass it along to AddUserContainer, and after this AddUserContainer added a user, you trigger that updating method on the parent:
<AddUserContainer onUserAdded={ this.props.updatingParent } />
For your reference, or for anyone else who wants to understand about how to update the parent component whenever child (or grandchild or great-grandchild) updates, please refer to my answer to another similar issue:
Re-initializing class on redirect
If you still keep your current component-hierarchy, when UsersContainer is updated, its child-components (UsersListContainer and AddUserContainer) will be updated, too. However, AddUserContainer will once again be updated!
As a result, I still think in your case, using Flux or Redux is a nice approaching, which eliminates to complexity of passing props through many levels of deep & complicated component-hierarchy

When to use 'componentDidUpdate' method?

I wrote dozens of Reactjs files, but I never used the componentDidUpdate method.
Is there any typical example of when need to use this method?
I want a real-world example, not a simple demo.
A simple example would be an app that collects input data from the user and then uses Ajax to upload said data to a database. Here's a simplified example (haven't run it - may have syntax errors):
export default class Task extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
name: "",
age: "",
country: ""
};
}
componentDidUpdate() {
this._commitAutoSave();
}
_changeName = (e) => {
this.setState({name: e.target.value});
}
_changeAge = (e) => {
this.setState({age: e.target.value});
}
_changeCountry = (e) => {
this.setState({country: e.target.value});
}
_commitAutoSave = () => {
Ajax.postJSON('/someAPI/json/autosave', {
name: this.state.name,
age: this.state.age,
country: this.state.country
});
}
render() {
let {name, age, country} = this.state;
return (
<form>
<input type="text" value={name} onChange={this._changeName} />
<input type="text" value={age} onChange={this._changeAge} />
<input type="text" value={country} onChange={this._changeCountry} />
</form>
);
}
}
So whenever the component has a state change it will autosave the data. There are other ways to implement it too. The componentDidUpdate is particularly useful when an operation needs to happen after the DOM is updated and the update queue is emptied. It's probably most useful on complex renders and state or DOM changes or when you need something to be the absolutely last thing to be executed.
The example above is rather simple though, but probably proves the point. An improvement could be to limit the amount of times the autosave can execute (e.g max every 10 seconds) because right now it will run on every key-stroke.
I made a demo on this fiddle as well to demonstrate.
For more info, refer to the official docs:
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
Use this as an opportunity to operate on the DOM when the component has been updated. This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
Sometimes you might add a state value from props in constructor or componentDidMount, you might need to call setState when the props changed but the component has already mounted so componentDidMount will not execute and neither will constructor; in this particular case, you can use componentDidUpdate since the props have changed, you can call setState in componentDidUpdate with new props.
This lifecycle method is invoked as soon as the updating happens. The most common use case for the componentDidUpdate() method is updating the DOM in response to prop or state changes.
You can call setState() in this lifecycle, but keep in mind that you will need to wrap it in a condition to check for state or prop changes from previous state. Incorrect usage of setState() can lead to an infinite loop.
Take a look at the example below that shows a typical usage example of this lifecycle method.
componentDidUpdate(prevProps) {
//Typical usage, don't forget to compare the props
if (this.props.userName !== prevProps.userName) {
this.fetchData(this.props.userName);
}
}
Notice in the above example that we are comparing the current props to the previous props. This is to check if there has been a change in props from what it currently is. In this case, there won’t be a need to make the API call if the props did not change.
For more info, refer to the official docs:
componentDidUpdate(prevProps){
if (this.state.authToken==null&&prevProps.authToken==null) {
AccountKit.getCurrentAccessToken()
.then(token => {
if (token) {
AccountKit.getCurrentAccount().then(account => {
this.setState({
authToken: token,
loggedAccount: account
});
});
} else {
console.log("No user account logged");
}
})
.catch(e => console.log("Failed to get current access token", e));
}
}
I have used componentDidUpdate() in highchart.
Here is a simple example of this component.
import React, { PropTypes, Component } from 'react';
window.Highcharts = require('highcharts');
export default class Chartline extends React.Component {
constructor(props) {
super(props);
this.state = {
chart: ''
};
}
public componentDidUpdate() {
// console.log(this.props.candidate, 'this.props.candidate')
if (this.props.category) {
const category = this.props.category ? this.props.category : {};
console.log('category', category);
window.Highcharts.chart('jobcontainer_' + category._id, {
title: {
text: ''
},
plotOptions: {
series: {
cursor: 'pointer'
}
},
chart: {
defaultSeriesType: 'spline'
},
xAxis: {
// categories: candidate.dateArr,
categories: ['Day1', 'Day2', 'Day3', 'Day4', 'Day5', 'Day6', 'Day7'],
showEmpty: true
},
labels: {
style: {
color: 'white',
fontSize: '25px',
fontFamily: 'SF UI Text'
}
},
series: [
{
name: 'Low',
color: '#9B260A',
data: category.lowcount
},
{
name: 'High',
color: '#0E5AAB',
data: category.highcount
},
{
name: 'Average',
color: '#12B499',
data: category.averagecount
}
]
});
}
}
public render() {
const category = this.props.category ? this.props.category : {};
console.log('render category', category);
return <div id={'jobcontainer_' + category._id} style={{ maxWidth: '400px', height: '180px' }} />;
}
}
When something in the state has changed and you need to call a side effect (like a request to api - get, put, post, delete). So you need to call componentDidUpdate() because componentDidMount() is already called.
After calling side effect in componentDidUpdate(), you can set the state to new value based on the response data in the then((response) => this.setState({newValue: "here"})).
Please make sure that you need to check prevProps or prevState to avoid infinite loop because when setting state to a new value, the componentDidUpdate() will call again.
There are 2 places to call a side effect for best practice - componentDidMount() and componentDidUpdate()
#K.tin, if you call setState in componentDidUpdate(), would that cause the same data to be fetched again?
For example, [id, data_for_id] are states, and id can be changed by a click counter and data_for_id is fetched from a web API.
Now we click to change Id by setState(), and componentDidUpdate() is executed, which fetches the data_for_id, we do setState for data_for_id, which will trigger another componentDidUpdate().
The first time componentDidUpdate is called, we have prevState.ID = 0 and state.ID=1, so componentDidUpdate is run. The second time we have prevState.ID = 1 and state.ID = 1, and componentDidUpdate can be avoid entirely, which perhaps could also be implemented with shouldComponentUpdate().
Still, this causes TWO rerenders, one for ID change and one for data_for_id change, Ideally, once we detect ID change, data_for_id should be fetched, and we should have [Id, data_for_id] state changed in a single shot, and the rerender happens only once for this change ID click.
So as a general rule, we should not do any setState in componentDidUpdate(), if the change of two or more state components are related, we should perform the changes together in one place and setState in a single shot.
This is just my reasoning. I am not a react guru, so please comment.
You should be careful when it's used. because it's making multiple API calls. Sometimes unlimited calls
componentDidUpdate should not have setState inside of it. you should use componentDidUpdate specially in manipulating the dom base on this.state values. CRUD or updating state values is not good inside of componentDidUpdate. Also dont fetching new data inside of componentDidUpdate as is can cause multiple http request on server and sometimes can cause laggy specially when the code is not structural or multiple use of setState.

Resources