How to get the updated props immediately after service call in reactjs? - 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());

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

synchronous operation in componentDidMount()

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

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

Prevent react component from rendering twice when using redux with componentWillMount

I have a React component that dispatches a redux state change in its componentWillMount function. The reason is that when the component is loaded, it needs to get the id from the url (powered by react-router), and trigger an action that sets up the state with that id's data.
Here is the component:
class Editor extends React.Component {
componentWillMount() {
const { dispatch, params } = this.props
dispatch(editItem(params.id))
}
render() {
const item = this.props.item
console.log("Editing", item)
}
}
export default connect(state => ({item: state.item}))(Editor)
Here's the catch: render is getting called twice. item is undefined on the first call, and valid on the second. Ideally, it should only be called once this.props.item actually exists (after the editItem action has been dispatched and run).
According to the React docs: "If you call setState within this method, render() will see the updated state and will be executed only once despite the state change."
In redux, dispatch is the equivalent of calling setState, as it results in a state change. However, I'm guessing something in the way connect works is still causing render to be called twice.
Is there a way around this besides adding a line like if (!item) return; ?
One thing you might do is create a higher order component that handles the basic pattern of loading a different component (or no component) before the required props are loaded.
export const LoaderWrapper = function(hasLoaded, Component, LoaderComponent, onLoad) {
return props => {
if (hasLoaded(props)) {
return <Component {...props} />
}
else {
if (onLoad) onLoad(props)
return { LoaderComponent ? <LoaderComponent /> : null }
}
}
}
Then you can wrap your component before connecting it to get the desired behaviour.
export default connect(state => ({item: state.item}))(LoaderWrapper(
((props) => !!props.item),
Editor,
null,
(props) => props.dispatch(editItem(props.params.id))
))
You might want to add some currying magic to make sure you can compose these kinds of wrapper functions more nicely. Take a look at recompose for more info.
It looks like there's already an issue in the react-redux library.
https://github.com/rackt/react-redux/issues/210
What does editItem do? Does it add item to the redux state or is it there already?
If it is adding I imagine what is happening is that a render cycle happens with the current props, ie item being blank.
Then it gets rendered again when the props have changed, via setting the item.
One approach to fixing this sort of thing is to create a higher order component that wraps Editor and calls the dispatch action the rendering though is set either to a loading screen or and empty div until item is set. That way you can be assured that Editor will have an item.
But without knowing what editItem does it's sort of hard to know. Maybe you could paste the code for that?

Resources