synchronous operation in componentDidMount() - reactjs

I have a stateful component in which i fetch a userlist in componentDidMount(). Right after I'm checking whether the list is empty, and if so, create a placeholder to display.
Since the fetching of the list is an asynchronous operation I should probably not check for the empty list like so:
componentDidMount() {
this.props.dispatch(fetchUsersOfList());
if (this.props.userBases.length === 0) {
...
}
}
I have this currently solved by writing the if statement inside a componentDidUpdate():
componentDidUpdate(prevProps) {
if (this.props.userBases.length === 0) {
if (this.props.currentBase !== prevProps.currentBase) {
...
}
}
}
I am using redux and updating the state after each function has run/completed.
The issue now is that each time I delay an operation like this I delay displaying data to the user. Eventually this adds up and is noticeable. Hence the times for users that they see turning loading-wheels increases.
Is there another, faster way to solve this concept?

I belive you're returning response payload from your action and throwing error as well.
You get your resolved promise in then like this:
componentDidMount() {
this.props.dispatch(fetchUsersOfList()).then((response)=>{
if (// your condition goes here") {
...
}
})
}
Feel free to ask any question

I am not sure what you mean by a placeholder, but most probably you have one of two needs:
If your list is empty, show a message instead of a li. (No data found or something on that vein)
or Show some initial text while you wait for data (for e.x. Loading...)
The first is basically conditional rendering. in your render method, you can have something like:
if (this.props.userBases && this.props.userBases.length > 0 ) {
return <Blah />
} else {
return <Placeholder />
}
The 2nd challenge is essentially setting an initial state. Idea is that you give your component an initial state and then when the async action has finished, it updates the state and react re-renders it. I am not using redux, but they seem to offer a recipe for it.
Update On closer reading of your question, I think you are using the lifecycles in the wrong way as well. I would highly recommend reading the official blog post on async rendering in react to understand the lifecycles better.

You don't need to save things on state. You can return early from render with a different result. When the props change, render will be called again with newer props.
class MyComponent {
render() {
const { items } = this.props;
if (!items || !items.length) return <span>Loading...</span>;
return <List items={items} />;
}
}

Handle your subscription via connect method.
import { connect } from 'react-redux';
const mapStateToProps = (state) => {
const items = cartItemsSelector(state);
return {
items,
fetched: items !== null
}
};
export const CartItemListContainer = connect(
mapStateToProps
)(CartItemListDisplay);

Related

How to properly dispatch the same action twice in a single React Component

I am trying to understand the best approach to a data fetching challenge I am facing with redux in my react app.
In short, I need to dispatch the same fetch (in this case, fetchPlayerSeasonStats) twice, and save both fetches of data. The first fetch grabs statistics for a single player (via the fetches optional 1st paramter thisPlayerId), and the 2nd fetch omits the paramter and fetches a much larger dataset.
What I've attempted to do below is the following:
(a) fetch playerSeasonStats the first time
(b) in componentDidUpdate(), check that the first fetch was completed (the if condition checking array legnths).
(c) if condition met, use state variable thisPlayersSeasonStats to store the original fetch of data.
(d) then, refetch the larger dataset with another dispatched action.
... other than the warning I'm receiving saying "do not update state in componentDidMount", in general I'm not sure if this approach is correct or if it is an "anti-pattern" / bad React/Redux coding style. I'd like to make sure I'm doing this right so any review of the code below (in particular the componentDidUpdate() function) would be greatly appreciated!
Thanks!
// Import React Components
import React, { Component } from 'react';
import { connect } from 'react-redux';
// Import Fetches
import { fetchPlayerSeasonStats } from '../../../actions/...';
// Create The Component
class MyComponentHere extends Component {
constructor(props) {
super(props);
this.state = {
thisPlayerSeasonStats: []
};
}
componentDidMount() {
let thisPlayerId = this.props.playerInfo._id;
this.props.dispatch(fetchPlayerSeasonStats(thisPlayerId, this.props.appSeason.value));
}
componentDidUpdate(prevProps) {
console.log('prevProps: ', prevProps);
if (this.props.appSeason !== prevProps.appSeason) { this.refetchTeamsStats(); }
if (prevProps.playerSeasonStats.length === 0 && this.props.playerSeasonStats.length === 1) {
this.setState({ thisPlayerSeasonStats: this.props.playerSeasonStats });
this.props.dispatch(fetchPlayerSeasonStats(null, this.props.appSeason.value));
}
}
render() {
// Handle Initial Loading Of Data
if (this.state.thisPlayerSeasonStats.length === 0) { return <LoadingSpinner />; }
// The Return
return (
<div> Return Dont Matter For here </div>
);
}
}
function mapStateToProps(reduxState) {
return {
playerSeasonStats: reduxState.playerSeasonStatsReducer.sportsData,
loading: (reduxState.playerSeasonStatsReducer.loading),
error1: reduxState.playerSeasonStatsReducer.error
};
}
export default connect(mapStateToProps)(MyComponentHere);
The answer is simple.
Lets look at how redux-thunk works.
Redux Thunk middleware allows you to write action creators that return a function instead of an action
I think this is what fetchPlayerSeasonStats essentially do. It returns some async function that fetch players. Redux-thunk helps dispatch it (I think you use Redux-thunk. In case of you use some other async middleware, it should work essentially the same).
So we can write action creator that will return function (as fetchPlayerSeasonStats) but inside will dispatch not actions but another function. So we'll have function dispatching function which will dispatch action :-)
For example
fetchAllPlayerStats (thisPlayerId, appSeasonValue) => dispatch => {
dispatch(fetchPlayerSeasonStats(thisPlayerId, appSeasonValue));
dispatch(fetchPlayerSeasonStats(null, appSeasonValue));
}
Then you can use this.props.dispatch(fetchAllPlayerStats(thisPlayerId, this.props.appSeason.value)) from componentWillMount to fetch all data at once.
Tip. Current implementation of fetchAllPlayerStats will get all data at once. If you add async/await keywords you'll get firstly data for a single player and then larger data set. Modified version will look like
fetchAllPlayerStats (thisPlayerId, appSeasonValue) => async dispatch => {
await dispatch(fetchPlayerSeasonStats(thisPlayerId, appSeasonValue));
await dispatch(fetchPlayerSeasonStats(null, appSeasonValue));
}
Here is simple example to showcase logic

How to get the updated props immediately after service call in reactjs?

In component I want to get the data immediately in props after calling
webapi service call and do some operation,but issue is that it is not
updating the props immediately because as we know that call will be
async, So what will be the solution? My codes in component are like
this:-
openPreviewClick=(event) => {
this.props.GetReport();
console.log(this.props.reportData);
}
function mapStateToProps (allReducers) {
return {reportData: allReducers.reportData
}}
const matchDispatchToProps = (dispatch) => ({
GetReport: () => dispatch(LoadReportData())
})
export default connect(mapStateToProps, matchDispatchToProps)(MyContainer)
Now I have to open a pdf for this I have tried with two solution:-
Handling life cycle of the page
componentWillReceiveProps(nextProps) {
if(nextProps.reportPath!=undefined){
window.open(nextProps.reportPath,"thePop","menubar=1,resizable=1,scrollbars=1,status=1,top=280,width=850,height=600");
}
Writing the code in render
render () {
if(this.props.reportPath!=undefined && this.props.reportPath!=""){}
window.open(this.props.reportPath,"thePop","menubar=1,resizable=1,scrollbars=1,status=1,top=280,width=850,height=600");
}
openPreviewClick is my button click on which I want to access the
props named as reportData.But console.log(this.props.reportData); is
giving me the null value for the first time,second time if I will
click then we are getting the data.How we can manage this? I already tried above two solution but it is not working.
Simple answer, you don't ^1
If this is truely an async request, there is no guarantee when the data will come back, so your component needs to "understand" that is can exist in a "without data" state.
Simplest form of this is:
render() {
if( ! this.props.reportData) return null;
// normal render code, at this point we have data
return <div>{this.props.reportData.map(foo, ...)}</div>
}
A better form, would be something like:
render() {
if( ! this.props.reportData) {
return <div><img src="loading.gif" /></div>;
}
// normal render code, at this point we have data
return <div>{this.props.reportData.map(foo, ...)}</div>
}
^1 Note: You could technically use async functions, but I feel that would complicate the problem, especially without a fundamental understanding of what is already going on.
In your main file where you create the store you can dispatch action and set initial value just like
import configureStore from './store/configureStore;
import {LoadReportData} from './actions/LoadReportData';
const store = configureStore();
store.dispatch(LoadReportData());

React | Redux | Thunk - How data should be loaded from within a component?

I've got a component that uses componentWillMount to make an API call through redux to get data and update the state.
Before calling this method, I need to go to the DB and get a property upon which I'll decide if the data retrieval (from the 1st paragraph) should happen.
What I was thinking of doing (using promises) -
Fetch the property (from paragraph 2)
then, if data is needed, dispatch the normal flow (paragraph 1).
if data is not needed, carry on.
My question is WHERE should it go in your opinon.
On the one hand, it feels like a mega overkill to do it through the store. On the other hand, any chance I'll encounter side effect problems.
In addition, I could implement the logic in the component or in the action creator. What do you think is best?
Additional info:
1. I'm using redux-thunk. Changing to sagas is out of the question.
2. The property that I'm checking is in 1 reducer, while the data that needs to be fetched is in another reducer (dunno, might be problematic for some solutions.).
Option 1:
import {getData} from '....../dataActions';
import {getToken} from '......../userActions';
const MegaComponent extends React.Component {
componentWillMount() {
getToken(uid)
.then(shouldUpdate => {
if (shouldUpdate) {
getData(uid);
} else {
console.log('no need to get data');
}
})
}
}
funciton mapStateToProps(state, ownProps) {
return {
user: state.user
}
}
function mapDispatchToProps(dispatch) {
return {
getToken: (uid) => dispatch(getToken(uid)),
getData: (uid) => dispatch(getData(uid))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(MegaComponent);
Option 2 (what I think should be done)
export function getToken(uid) {
return dispatch => {
return api.getToken(uid)
.then(token => {
if (token === 'what it should') {
return {type: 'NO_DATA_CHANGE_NEEDED', action: null};
} else {
// the action that handle getting data is in a different action creator.
// I either import it here and call it, or there's a better way.
}
})
}
}
UPDATE
This might come in handy for future visiotrs - getting state inside an action creator
Hey this is a very broad question, but I want to give a short advice on how I would solve it although I am not quite sure if i got it totally right.
So I would start by separating the concerns of point 1. (database call) and 2. (everything else)
You could start by writing a Component-Wrapper (HoC) that only does the database call to get the prop you need for further processing.
Once you fetched the data from db you can render the InnerComponent with the data as prop and do everything else you need there just by checking the prop you injected and trigger additional actions.
I wouldn't let my component deal with logic. keep your components all about View. Think of a component as a visual representation of a point-in-time state.
In componentWillMount you can check if the data exists, if it's not then you call your action which will fetch the data through an API and pass the data to the reducer. the reducer will 'update' the state.
the dispatch calls should be invoked from actions.js and not from your component.
So, my feeling was right - option 2 it is. Much like #guruPitka suggested.
In my action creator (that is being called by the component), I get the current app state, make an API call, compare the tokens and act accordingly.
export function getToken(uid) {
return (dispatch, getState) => {
const currentToken = getState().user.token;
return api.lastAppLaunch(uid)
.then(newToken => {
if (newToken === currentToken) {
// All is good, carry on.
} else {
dispatch(getData(uid)); //getData is from a different action creator.
}
});
}
}
And the call in the component -
this.props.getToken(uid);

Can react-redux connect() -ed containers implement lifecyle methods like componentDidMount?

I've come across a repeated pattern in my react-redux site:
A component displays data from a web api, and it should be populated on load, automatically, without any user interaction.
I want to initiate the async fetch from a container component, but as far as I can tell the only way to do it is from a lifecycle event in a display component. This seems to make it impossible to put all the logic in the container and only use dumb stateless functional components for display.
This means I can't use a stateless functional component for any component that needs async data. That doesn't seem right.
It seems like the "right" way to do this would be to somehow initiate async calls from the container. Then when the call returned, the state would be updated and the container would get the new state and would in turn pass those to its stateless component via mapStateToProps().
Making async calls in mapStateToProps and mapDispatchToProps (I mean actually calling the async function, as opposed to returning it as a property) doesn't make sense.
So what I've ended up doing is putting the async call(s) in a refreshData() function exposed by mapDispatchToProps(), then calling it from two or more of the React lifecycle methods: componentDidMount and componentWillReceiveProps.
Is there a clean way to update the redux store state without putting lifecycle method calls in every component that needs async data?
Should I be making these calls higher up the component heierarchy (thereby reducing the scope of this issue, since only "top-level" components would need to listen to lifecycle events)?
Edit:
Just so there's no confusion what I mean by a connect()ed container component, here's a very simple example:
import React from 'react';
import { connect } from 'react-redux';
import {action} from './actions.js';
import MyDumbComponent from './myDumbComponent.jsx';
function mapStateToProps(state)
{
return { something: state.xxxreducer.something };
}
function mapDispatchToProps(dispatch)
{
return {
doAction: ()=>{dispatch(action())}
};
}
const MyDumbComponentContainer = connect(
mapStateToProps,
mapDispatchToProps
)(MyDumbComponent);
// Uh... how can I hook into to componentDidMount()? This isn't
// a normal React class.
export default MyDumbComponentContainer;
Jamie Dixon has written a package to do this!
https://github.com/JamieDixon/react-lifecycle-component
Usage would look like this:
const mapDispatchToProps = {
componentDidMount: getAllTehDatas
}
...
export default connectWithLifecycle(mapStateToProps, mapDispatchToProps)(WrappedComponent)
edit
With hooks you are now able to implement lifecycle callbacks in a stateless functional component. While this may not directly address all of the points in the question, it may also get around some of the reason for wanting to do what was originally proposed.
edit to original answer
After the discussion in comments and thinking about it more, this answer is more exploratory and can serve as a piece of the conversation. But I don't think it's the right answer.
original answer
On the Redux site there's an example that shows you don't have to do both mapStateToProps and mapDispatchToProps. You can just leverage connect's awesomeness for the props, and use a class and implement the lifecycle methods on the dumb component.
In the example, the connect call is even in the same file and the dumb component isn't even exported, so to the user of the component it's looks the same.
I can understand not wanting to issue async calls from the display component. I think there's a distinction between issuing the async calls from there and dispatching an action which, with thunks, moves the issuing of the async calls up into the actions (even more decoupled from the React code).
As an example, here's a splash screen component where I'd like to do some async action (like asset preloading) when the display component mounts:
SplashContainer.js
import { connect } from 'react-redux'
import Splash from '../components/Splash'
import * as actions from '../actions'
const mapStateToProps = (state) => {
return {
// whatever you need here
}
}
const mapDispatchToProps = (dispatch) => {
return {
onMount: () => dispatch(actions.splashMount())
}
}
const SceneSplash = connect(
mapStateToProps,
mapDispatchToProps
)(Splash)
export default SceneSplash
Splash.js
import React from 'react'
class Splash extends React.Component {
render() {
return (
<div className="scene splash">
<span className="fa fa-gear fa-spin"></span>
</div>
)
}
componentDidMount() {
const { onMount } = this.props
onMount()
}
}
export default Splash
You can see the the dispatch happens in the connected container, and you can imagine in the actions.splashMount() call we issue an async http request or do other async things via thunks or promises.
edit to clarify
Allow me to try to defend the approach. I re-read the question and am not 100% sure I'm addressing the main thing it's after, but bear with me. If I am still not quite on track, I have a modified approach below that may be closer to the mark.
"it should be populated on load" - the example above accomplishes this
"I want to initiate the async fetch from a container" - in the example it's not initiated from the display component or the container, but from an async action
"This seems to make it impossible to put all the logic in the container" - I think you can still put any additional logic needed in the container. As noted, the data loading code isn't in the display component (or the container) but in the async action creator.
"This means I can't use a stateless functional component for any component that needs async data." - in the above example the display component is stateless and functional. The only link is the lifecycle method invoking a callback. It need not know or care what that callback does. It is not a case of the display component trying to be the owner of async data fetching - it's merely letting the code that does handle that know when a particular thing has happened.
So far I'm attempting to justify how the example given meets the question's requirements. That said, if what you're after is having a display component that contains absolutely no code related to the async data load even by indirect callbacks - that is, the only link it has is to consume that data via the props it's handed when that remote data comes down, then I would suggest something like this:
SplashContainer.js
import { connect } from 'react-redux'
import Splash from '../components/Splash'
import * as actions from '../actions'
const mapStateToProps = (state) => {
return {
// whatever you need here
}
}
const mapDispatchToProps = (dispatch) => {
dispatch(actions.splashMount())
return {
// whatever else here may be needed
}
}
const SceneSplash = connect(
mapStateToProps,
mapDispatchToProps
)(Splash)
export default SceneSplash
Splash.js
import React from 'react'
class Splash extends React.Component {
// incorporate any this.props references here as desired
render() {
return (
<div className="scene splash">
<span className="fa fa-gear fa-spin"></span>
</div>
)
}
}
export default Splash
By dispatching the action in mapDispatchToProps you are letting the code for that action reside entirely in the container. In fact, you are starting the async call as soon as the container is instantiated, rather than waiting for the connected display component to spin up and be mounted. However, if you cannot begin the async call until the componentDidMount() for the display component fires, I think you're inherently bound to have code like in my first example.
I haven't actually tested this second approach to see if react or redux will complain about it, but it should work. You have access to the dispatch method and should be able to call it without problems.
To be honest, this second example, while removing all code related to the async action from the display component does kind of strike me as a bit funny since we're doing non-mapping-of-dispatch-to-props things in the eponymous function. And containers don't actually have a componentDidMount to run it in otherwise. So I'm a bit squirmy with it and would lean toward the first approach. It's not clean in the "feels right" sense, but it is in the "simple 1-liner" sense.
Check out redux-saga https://github.com/yelouafi/redux-saga. It's a redux middleware component that creates long-lived watchers that look for specific store actions and can trigger functions or generator functions in response. The generator syntax is particular nice for handling async, and redux-saga has some nice helpers that allow you to treat async code in a synchronous fashion. See some of their examples. https://github.com/yelouafi/redux-saga/blob/master/examples/async/src/sagas/index.js . The generator syntax can be hard to grok at first, but based on our experience this syntax supports extremely complex async logic, including debounce, cancellation and joining/racing multiple requests.
You can do it from a container. Just make a component that extends React.Component but name it with "Container" somewhere in the name. Then use that container's componentDidMount instead of using componentDidMount in the presentational (dumb) component that the container component renders. Reducer will see that you've dispatched an action still, and still update state so your dumb component will be able to get at that data..
I TDD but even if I didn't TDD I separate out my dumb vs container components via file. I hate having too much in one file, especially if mixing dumb vs. container stuff in the same file, that's a mess. I know people do it, but I think that's awful.
I do this:
src/components/someDomainFolder/someComponent.js (dumb component)
src/components/someDomainFolder/someComponentContainer.js (for example you might use React-Redux..have connected container not a connected presentational component..and so in someComponentContainer.js you DO have a react class in this file, as stated, just call it someComponentContainer extends React.Component for example.
Your mapStateToProps() and mapDispatchToProps() would be global functions of that connected container component outside that container class. And connect() would render the container, which would render the presentational component but this allows you to keep all your behavior in your container file, away from dumb presentational component code.
that way you have tests around someComponent that are structural/state based and you have behavior tests around the Container component. Much better route to be going with maintaining and writing tests, and maintaining and making things easy for yourself or other devs to see what's going on and to manage dumb vs. behavioral components.
Doing things this way, your presentational stuff is separated both physically by file AND by code convention. AND your tests are grouped around the right areas of code...not an intermingled mess. AND if you do this, and use a reducer that listens to update state, your presentational component can remain totally stupid....and just look for that updates state via props...since you're using mapStateToProps().
Following up on #PositiveGuy suggestion, here is example code of how to implement a container component that can utilize lifecycle methods. I think this is a pretty clean approach that maintains separation of concerns keeping the presentation component "dumb":
import React from 'react';
import { connect } from 'react-redux'
import { myAction } from './actions/my_action_creator'
import MyPresentationComponent from './my_presentation_component'
const mapStateToProps = state => {
return {
myStateSlice: state.myStateSlice
}
}
const mapDispatchToProps = dispatch => {
return {
myAction: () => {
dispatch(myAction())
}
}
}
class Container extends React.Component {
componentDidMount() {
//You have lifecycle access now!!
}
render() {
return(
<MyPresentationComponent
myStateSlice={this.props.myStateSlice}
myAction={this.props.myAction}
/>
)
}
}
const ContainerComponent = connect(
mapStateToProps,
mapDispatchToProps
)(Container)
export default ContainerComponent
You CAN initiate an async fetch from the parent container (the smart container). You write the function in the smart container and you pass the function as a prop for the dumb container. For example:
var Parent = React.createClass({
onClick: function(){
dispatch(myAsyncAction());
},
render: function() {
return <childComp onClick={this.onClick} />;
}
});
var childComp = React.createClass({
propTypes:{
onClick: React.PropTypes.func
},
render: function() {
return <Button onClick={this.props.onClick}>Click me</Button>;
}
});
childComp is stateless since the onClick definition is determined by the parent.
EDIT: Added connected container example below, excluded other stuff for brevity. Doesn't really show much, and it's a bit cumbersome to setup on fiddle and stuff, point is, I do use lifecycle methods in connected containers and it works out fine for me.
class cntWorkloadChart extends Component {
...
componentWillReceiveProps(nextProps){
if(nextProps.myStuff.isData){
if (nextProps.myStuff.isResized) {
this.onResizeEnd();
}
let temp = this.updatePrintingData(nextProps)
this.selectedFilterData = temp.selectedFilterData;
this.selectedProjects = temp.selectedProjects;
let data = nextProps.workloadData.toArray();
let spread = [];
if(nextProps.myStuff.isSpread) {
spread = this.updateSelectedProjectSpread(nextProps);
for (var i = 0; i < data.length; i++) {
data[i].sumBillableHrsSelectedProjects = spread[data[i].weekCode] ? Number(spread[data[i].weekCode].sumBillableHrsSelectedProjects.toFixed(1)) : 0;
data[i].sumCurrentBudgetHrsSelectedProjects = spread[data[i].weekCode] ? Number(spread[data[i].weekCode].sumCurrentBudgetHrsSelectedProjects.toFixed(1)) : 0;
data[i].sumHistoricBudgetHrsSelectedProjects = spread[data[i].weekCode] ? Number(spread[data[i].weekCode].sumHistoricBudgetHrsSelectedProjects.toFixed(1)) : 0;
}
}
if (nextProps.potentialProjectSpread.length || this.props.potentialProjectSpread.length) { //nextProps.myStuff.isPpSpread) { ???? - that was undefined
let potential = nextProps.potentialProjectSpread;
let ppdd = _.indexBy(potential, 'weekCode');
for (var i = 0; i < data.length; i++) {
data[i].sumSelectedPotentialProjects = ppdd[data[i].weekCode] ? ppdd[data[i].weekCode].sumSelectedPotentialProjects.toFixed(1) : 0;
}
}
for (var i = 0; i < data.length; i++) {
let currObj = data[i];
currObj.sumCurrentBudgetHrs = currObj.currentBudgeted.sumWeekHours;
currObj.sumHistoricBudgetHrs = currObj.historicBudgeted.sumWeekHours;
currObj.fillAlpha = .6; //Default to .6 before any selections are made
//RMW-TODO: Perhaps we should update ALL line colors this way? This would clean up zero total bars in all places
this.updateLineColor(currObj, "sumSelectedPotentialProjects", "potentialLineColor", potentialLineColor);
this.updateLineColor(currObj, "sumHistoricBudgetHrs", "histLineColor", histBudgetLineColor);
this.updateLineColor(currObj, "sumHistoricBudgetHrsSelectedProjects", "histSelectedLineColor", selectedHistBudgetFillColor);
}
if(nextProps.myStuff.isSelectedWeek){
let currWeekIndex = nextProps.weekIndex.index;
let selectedWeek = data[currWeekIndex].fillAlpha = 1.0;
}
if(data.length > 0){
if(data[0].targetLinePercentages && data.length > 9) { //there are target lines and more than 10 items in the dataset
let tlHigh = data[0].targetLinePercentages.targetLineHigh;
let tlLow = data[0].targetLinePercentages.targetLineLow;
if (tlHigh > 0 && tlLow > 0) {
this.addTargetLineGraph = true;
this.upperTarget = tlHigh;
this.lowerTarget = tlLow;
}
}
else {
this.addTargetLineGraph = false;
this.upperTarget = null;
this.lowerTarget = null;
}
}
this.data = this.transformStoreData(data);
this.containsHistorical = nextProps.workloadData.some(currObj=> currObj.historicBudgeted.projectDetails.length);
}
}
...
render() {
return (
<div id="chartContainer" className="container">
<WorkloadChart workloadData={this.props.workloadData}
onClick={this.onClick}
onResizeEnd={this.onResizeEnd}
weekIndex={this.props.weekIndex}
getChartReference={this.getChartReference}
//projectSpread={this.props.projectSpread}
selectedRows={this.props.selectedRows}
potentialProjectSpread={this.props.potentialProjectSpread}
selectedCompany={this.props.selectedCompany}
cascadeFilters={this.props.cascadeFilters}
selectedRows={this.props.selectedRows}
resized={this.props.resized}
selectedFilterData={this.selectedFilterData}
selectedProjects={this.selectedProjects}
data={this.data}
upperTarget={this.upperTarget}
lowerTarget={this.lowerTarget}
containsHistorical={this.containsHistorical}
addTargetLineGraph={this.addTargetLineGraph}
/>
</div>
);
}
};
function mapStateToProps(state){
let myValues = getChartValues(state);
return {
myStuff: myValues,
workloadData: state.chartData || new Immutable.List(),
weekIndex: state.weekIndex || null,
//projectSpread: state.projectSpread || {},
selectedRows: state.selectedRows || [],
potentialProjectSpread: state.potentialProjectSpread || [],
selectedCompany: state.companyFilter.selectedItems || null,
brokenOutByCompany: state.workloadGrid.brokenOutByCompany || false,
gridSortName: state.projectGridSort.name,
gridSortOrder: state.projectGridSort.order,
cascadeFilters: state.cascadeFilters || null,
selectedRows: state.selectedRows || [],
resized: state.chartResized || false,
selectedPotentialProjects: state.selectedPotentialProjects || []
};
}
module.exports = connect(mapStateToProps)(cntWorkloadChart);

Re-render React component when prop changes

I'm trying to separate a presentational component from a container component. I have a SitesTable and a SitesTableContainer. The container is responsible for triggering redux actions to fetch the appropriate sites based on the current user.
The problem is the current user is fetched asynchronously, after the container component gets rendered initially. This means that the container component doesn't know that it needs to re-execute the code in its componentDidMount function which would update the data to send to the SitesTable. I think I need to re-render the container component when one of its props(user) changes. How do I do this correctly?
class SitesTableContainer extends React.Component {
static get propTypes() {
return {
sites: React.PropTypes.object,
user: React.PropTypes.object,
isManager: React.PropTypes.boolean
}
}
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
render() {
return <SitesTable sites={this.props.sites}/>
}
}
function mapStateToProps(state) {
const user = userUtils.getCurrentUser(state)
return {
sites: state.get('sites'),
user,
isManager: userUtils.isManager(user)
}
}
export default connect(mapStateToProps)(SitesTableContainer);
You have to add a condition in your componentDidUpdate method.
The example is using fast-deep-equal to compare the objects.
import equal from 'fast-deep-equal'
...
constructor(){
this.updateUser = this.updateUser.bind(this);
}
componentDidMount() {
this.updateUser();
}
componentDidUpdate(prevProps) {
if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID (this.props.user.id !== prevProps.user.id)
{
this.updateUser();
}
}
updateUser() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Using Hooks (React 16.8.0+)
import React, { useEffect } from 'react';
const SitesTableContainer = ({
user,
isManager,
dispatch,
sites,
}) => {
useEffect(() => {
if(isManager) {
dispatch(actions.fetchAllSites())
} else {
const currentUserId = user.get('id')
dispatch(actions.fetchUsersSites(currentUserId))
}
}, [user]);
return (
return <SitesTable sites={sites}/>
)
}
If the prop you are comparing is an object or an array, you should use useDeepCompareEffect instead of useEffect.
componentWillReceiveProps() is going to be deprecated in the future due to bugs and inconsistencies. An alternative solution for re-rendering a component on props change is to use componentDidUpdate() and shouldComponentUpdate().
componentDidUpdate() is called whenever the component updates AND if shouldComponentUpdate() returns true (If shouldComponentUpdate() is not defined it returns true by default).
shouldComponentUpdate(nextProps){
return nextProps.changedProp !== this.state.changedProp;
}
componentDidUpdate(props){
// Desired operations: ex setting state
}
This same behavior can be accomplished using only the componentDidUpdate() method by including the conditional statement inside of it.
componentDidUpdate(prevProps){
if(prevProps.changedProp !== this.props.changedProp){
this.setState({
changedProp: this.props.changedProp
});
}
}
If one attempts to set the state without a conditional or without defining shouldComponentUpdate() the component will infinitely re-render
You could use KEY unique key (combination of the data) that changes with props, and that component will be rerendered with updated props.
componentWillReceiveProps(nextProps) { // your code here}
I think that is the event you need. componentWillReceiveProps triggers whenever your component receive something through props. From there you can have your checking then do whatever you want to do.
I would recommend having a look at this answer of mine, and see if it is relevant to what you are doing. If I understand your real problem, it's that your just not using your async action correctly and updating the redux "store", which will automatically update your component with it's new props.
This section of your code:
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Should not be triggering in a component, it should be handled after executing your first request.
Have a look at this example from redux-thunk:
function makeASandwichWithSecretSauce(forPerson) {
// Invert control!
// Return a function that accepts `dispatch` so we can dispatch later.
// Thunk middleware knows how to turn thunk async actions into actions.
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
You don't necessarily have to use redux-thunk, but it will help you reason about scenarios like this and write code to match.
A friendly method to use is the following, once prop updates it will automatically rerender component:
render {
let textWhenComponentUpdate = this.props.text
return (
<View>
<Text>{textWhenComponentUpdate}</Text>
</View>
)
}
You could use the getDerivedStateFromProps() lifecyle method in the component that you want to be re-rendered, to set it's state based on an incoming change to the props passed to the component. Updating the state will cause a re-render. It works like this:
static getDerivedStateFromProps(nextProps, prevState) {
return { myStateProperty: nextProps.myProp};
}
This will set the value for myStateProperty in the component state to the value of myProp, and the component will re-render.
Make sure you understand potential implications of using this approach. In particular, you need to avoid overwriting the state of your component unintentionally because the props were updated in the parent component unexpectedly. You can perform checking logic if required by comparing the existing state (represented by prevState), to any incoming props value(s).
Only use an updated prop to update the state in cases where the value from props is the source of truth for the state value. If that's the case, there may also be a simpler way to achieve what you need. See - You Probably Don't Need Derived State – React Blog.

Resources