React/Flux seState not re-rendering component after API call - javascript

I am making a call to an API that allows you to sort the data it returns by different parameters e.g top, latest, popular and so on. What i'm trying to achieve is that when a user clicks a button to sort by a different parameter, the state is changed to the new parameter and the API is called again with that new parameter. Here's my code:
constructor (){
super();
this.state = {
sortType: 'top' //default is to sort by Top
};
this.setSortType = this.setSortType.bind(this);
}
componentWillMount(){
//this retrieves sourceName which is required to make the API call
const parsed = queryString.parse(this.props.location.search);
var sourceName = parsed.sourceId;
//getArticles below is the action that makes the API call with sourcename and sortType as parameters
newsActions.getArticles(sourceName,this.state.sortType);
newsStore.on('articles_change',this.fetchNewsArticles);
}
//Call to setState when user clicks button change sort parameter
setSortType(){
this.setState({
sortType:'latest'
});
}
render() {
return (
<div>
... //logic to display data from the API call
<button onClick={this.setSortType}> Sort By Latest </button>
</div>
);
}
When the button is clicked nothing re-renders. What i'm i missing?

React calls componentWillMount just once on component mount, so the logic it contains won't be updated executed again on state change.
Have a look to React docs, to a better understand of how React licecycle hooks work.

Related

React.js: Parent state values not passing into child properties + Fetch API data cannot be accessed

I am encountering several issues in a very basic color harmony picker I am developing. I am still a beginner in React and JSX. I initially had it put up on GitHub so the full files are on there, but I moved it over to Codepen instead.
Here is the Codepen
I made a lot of comments so sorry if they're a bit much, but hopefully they help. My problems don't begin until line 41, the displayHarmonies() method of the DataStore class. The values passed to it come from my App (parent) component:
displayHarmonies(color, harmony) {
//color and harmony pass in dynamically just fine...this.data will not return anything, not even "undefined"
console.log(color + " is the color and " + harmony + " is the harmony...and dataStore.displayHarmonies says: " + this.data);
this.registeredWatchers.map((watcher) => {
let result = "not green"; //result and resultHex will be determined with an underscore statement that will associate the color & harmony choice (primary + foreign key concept) and will return correct harmony color(s)
let resultHex = "#HEX";
appState.harmonyColor = result;
appState.harmonyHex = resultHex;
//call to app component's onDataChange() method, where new states will be set using the the appState data we just set in lines 49 and 50
watcher.onDataChange();
})
}
As you can see from my first comment, the only part that doesn't log to the console is this.data, which is set in the constructor for the DataStore:
constructor(data) {
//store that data in the object
//data is not being received from object instance of dataStore on line 187
this.data = data;
On line 187 I make an instance of the DataStore and pass it a variable named data. Prior to being used, this variable is initialized and then assigned to parsed JSON data via Fetch API:
let data = [];
//use polyfill for older browsers to do Ajax request
fetch("data/data.json").then((response) => {
//if we actually got something
if (response.ok) {
//then return the text we loaded
return response.text();
}
}).then((textResponse) => {
data = JSON.parse(textResponse);
});
If I console out the data in the second fetch .then() method, the JSON comes back just fine. As soon as I try to use the data variable anywhere else in the application, it returns nothing, as shown in the displayHarmonies() method's console.log(). So that's my first issue, but before I wanted to get to that, I wanted to solve the other issue I was having.
After the appState object (initialized prior to the DataStore, under the fetch statement) values get set to the result variables, displayHarmonies() runs watcher.onDataChange() (in the App component/parent) where the harmonyColor and harmonyHex states get assigned to the new appState values:
onDataChange() {
console.log("onDataChange() in App called");
this.setState({
harmonyColor: appState.harmonyColor,
harmonyHex: appState.harmonyHex
})
}
If I log these states out to the console, they are the right values, so that's not the problem. I then pass my states to the Display child component to be used as properties:
<Display colorChoice={this.state.currentColor} harmonyChoice={this.state.currentHarmony} harmonyColor={this.state.harmonyColor} harmonyHex={this.state.harmonyHex} />
I then set the Display component states in the constructor, assigning them to the props that are being sent to it with each new rendition of the application. I then display the data onto the DOM with the Display component's render method. What's odd is that the application will display the initial states (color: red, harmony: direct, harmonyColor: green, etc.) just fine, but as soon as a change is made, the data on the DOM does not update. The initial data is loaded in the same way though: by passing the parent's states into the child's properties. I have a few console.log()s in place that seem to prove why this should work, however, it does not. So what am I doing wrong?
Thanks, and hope this is not too much for one question!
First a bit to your current code, at the end of the post, I have added an alternative solution, so if this is tl;dr; just skip to the snippet at the end :)
A first remark would be on the data variable that you wish to pass on to your DataStore, nl (I left out some parts, as they are irrelevant to the discussion)
let data = [];
fetch("data/data.json").then(( response ) => {
data = JSON.parse( response.text() );
});
//... later down the code
var store = new DataStore(data);
Here you are reassigning the data variable inside the then promise chain of your fetch call. Although the assignment will appear to work, the data that now is on store.data will be an empty array, and the global variable will data will now contain the parsed response.text(). You should probably just push in the data you have just parsed (but in my example, I didn't even include the DataStore so this is just for future reference)
In your CodePen, you seem to mixing props & state for your Display component. That is in essence a no-op, you shouldn't mix them unless you really know what you are doing. Also note, that by calling this.setState inside the componentWillReceiveProps life cycle method, the app will automatically re-render more than needed. I am referring to this code:
componentWillReceiveProps(nextProps) {
this.setState({
color: nextProps.colorChoice,
harmony: nextProps.harmonyChoice,
harmonyColor: nextProps.harmonyColor,
harmonyHex: nextProps.harmonyHex
});
}
But you are then rendering like this:
render() {
return (
<div>
{/* these aren't changing even though states are being set */}
<p><b>Color:</b> {this.state.color}</p>
<p><b>Harmony:</b> {this.state.harmony}</p>
<p><b>Harmony Color(s):</b> {this.state.harmonyColor} ({this.state.harmonyHex})</p>
</div>
)
}
Here you should remove the componentWillReceiveProps method, and render values from this.props as you are passing these along from your App.
Alternative solution
As mentioned in the comments, your code currently is doing a lot more than it should do to pass state between parent and child components.
One thing you should keep in mind, is that when a component state gets changed, react will re-render the component automatically. When it sees that the virtual DOM has discrepancies with the real DOM it will automatically replace those components.
In that sense, your DataStore is not necessary. Depending on how you want to manage state, the component will react on those changes.
Since your app uses Component State (which is fine for small applications, once you want to move to bigger applications, you will probably want to move on to something like Redux, or MobX), the only thing you need to do, is to make sure that you set the correct components state to trigger the rendering.
As an example, I remade your code in a cleaner way:
const Choice = ({ header, values, onChange, activeValue }) => {
return <ul>
<li><h1>{ header }</h1></li>
{ values.map( (value, key) => <li
key={key+value}
className={classNames( { active: value === activeValue, item: true } )}
onClick={() => onChange( value )}>{ value }</li> ) }
</ul>
};
const colors = ['red', 'green', 'black', 'blue', 'yellow'];
const harmonies = ['direct', 'split', 'analogous'];
class App extends React.Component {
constructor(...args) {
super(...args);
this.state = {
activeColor: undefined,
activeHarmony: undefined
};
}
onColorChanged( color ) {
this.setState({ activeColor: color });
}
onHarmonyChanged( harmony ) {
this.setState({ activeHarmony: harmony });
}
render() {
let { activeColor, activeHarmony } = this.state;
return <div>
<Choice
header="Choose color"
values={colors}
activeValue={activeColor}
onChange={(...args) => this.onColorChanged(...args)} />
<Choice
header="Choose harmony"
values={harmonies}
activeValue={activeHarmony}
onChange={(...args) => this.onHarmonyChanged(...args)} />
</div>;
}
}
ReactDOM.render( <App />, document.querySelector('#container'));
h1 { margin: 0; padding: 0; }
ul {
list-style-type: none;
}
.item {
cursor: pointer;
padding: 5px;
}
.active { background-color: lightgreen; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
Now, there are some things in this sample code that might need some explanation. For one, this code has 2 component types, 1 presentational component called Choice which is stateless, and one container component called App which delegates it's state to it's children.
A bit more information about container & presentational components can be found on the blog of Dan Abramov (redux creator)
The essence of the above concept is just this, the App component is responsible for the state, and for sharing it with it's children. So, all state changes need to be made on the App component. As you can see in the render, the App simply passes its state along:
render() {
let { activeColor, activeHarmony } = this.state;
return <div>
<Choice
header="Choose color"
values={colors}
activeValue={activeColor}
onChange={(...args) => this.onColorChanged(...args)} />
<Choice
header="Choose harmony"
values={harmonies}
activeValue={activeHarmony}
onChange={(...args) => this.onHarmonyChanged(...args)} />
</div>;
}
The App passes a change handler along to the Choice component that can be called when a selection should occur, this gets forwarded to the App, the state changes, and app re-renders, allowing the Choice component to update it's elements.
const Choice = ({ header, values, onChange, activeValue })
Based on the props passed into it, the Choice component can decide which is the active item at the moment of rendering. As you can see, the props are destructed. header, values, onChange and activeValue are all properties on the props of the component, but to save time, we can assign these values at ones to a variable and use them in the rendering.
I tried cloning your repo, but it seems to be nested in another repo. With your current setup, this may work:
In your App component, you can put this lifecycle method to fetch the data, and then set the state with the received data.:
componentDidMount(){
fetch("data/data.json").then((response) => {
//if we actually got something
if (response.ok) {
//then return the text we loaded
return response.text();
}
}).then((textResponse) => {
this.setState({
data : JSON.parse(textResponse);
})
});
}
In the return statement, you can render the data store as a child so App can pass the data like this:
return (
<div className="App">
<DataStore data={this.state.data} />
<h1>Color Harmonies</h1>
{/* assigns this.colorChosen() & this.harmonyChosen() methods as properties to be called in Picker component */}
<Picker colorChosen={this.colorChosen.bind(this)} harmonyChosen={this.harmonyChosen.bind(this)}/>
{/* give Display component props that are dynamically set with states */}
<Display colorChoice={this.state.currentColor} harmonyChoice={this.state.currentHarmony} harmonyColor={this.state.harmonyColor} harmonyHex={this.state.harmonyHex} />
</div>
);
Then, your data store should receive the data as a prop, so you can use it like this:
displayHarmonies(color, harmony) {
//color and harmony pass in dynamically just fine...this.data will not return anything, not even "undefined"
console.log(color + " is the color and " + harmony + " is the harmony...and dataStore.displayHarmonies says: " + this.props.data); //data is received in the properties so you can use it.
//other code
})
Doing this, you should also be able to remove this.data from the constructor of the DataStore component.
Also in Data store, youll want to to allow it to accept props like this:
constructor(props){
super(props)
}

Updating component state in React-Redux with API calls

I'm trying to set up a React app where clicking a map marker in one component re-renders another component on the page with data from the database and changes the URL. It works, sort of, but not well.
I'm having trouble figuring out how getting the state from Redux and getting a response back from the API fit within the React life cycle.
There are two related problems:
FIRST: The commented-out line "//APIManager.get()......" doesn't work, but the hacked-together version on the line below it does.
SECOND: The line where I'm console.log()-ing the response logs infinitely and makes infinite GET requests to my database.
Here's my component below:
class Hike extends Component {
constructor() {
super()
this.state = {
currentHike: {
id: '',
name: '',
review: {},
}
}
}
componentDidUpdate() {
const params = this.props.params
const hack = "/api/hike/" + params
// APIManager.get('/api/hike/', params, (err, response) => { // doesn't work
APIManager.get(hack, null, (err, response) => { // works
if (err) {
console.error(err)
return
}
console.log(JSON.stringify(response.result)) // SECOND
this.setState({
currentHike: response.result
})
})
}
render() {
// Allow for fields to be blank
const name = (this.state.currentHike.name == null) ? null : this.state.currentHike.name
return (
<div>
<p>testing hike component</p>
<p>{this.state.currentHike.name}</p>
</div>
)
}
}
const stateToProps = (state) => {
return {
params: state.hike.selectedHike
}
}
export default connect(stateToProps)(Hike)
Also: When I click a link on the page to go to another url, I get the following error:
"Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op."
Looking at your code, I think I would architect it slightly differently
Few things:
Try to move the API calls and fetch data into a Redux action. Since API fetch is asynchronous, I think it is best to use Redux Thunk
example:
function fetchHikeById(hikeId) {
return dispatch => {
// optional: dispatch an action here to change redux state to loading
dispatch(action.loadingStarted())
const hack = "/api/hike/" + hikeId
APIManager.get(hack, null, (err, response) => {
if (err) {
console.error(err);
// if you want user to know an error happened.
// you can optionally dispatch action to store
// the error in the redux state.
dispatch(action.fetchError(err));
return;
}
dispatch(action.currentHikeReceived(response.result))
});
}
}
You can map dispatch to props for fetchHikeById also, by treating fetchHikeById like any other action creator.
Since you have a path /hike/:hikeId I assume you are also updating the route. So if you want people to book mark and save and url .../hike/2 or go back to it. You can still put the the fetch in the Hike component.
The lifecycle method you put the fetchHikeById action is.
componentDidMount() {
// assume you are using react router to pass the hikeId
// from the url '/hike/:hikeId'
const hikeId = this.props.params.hikeId;
this.props.fetchHikeById(hikeId);
}
componentWillReceiveProps(nextProps) {
// so this is when the props changed.
// so if the hikeId change, you'd have to re-fetch.
if (this.props.params.hikeId !== nextProps.params.hikeId) {
this.props.fetchHikeById(nextProps.params.hikeId)
}
}
I don't see any Redux being used at all in your code. If you plan on using Redux, you should move all that API logic into an action creator and store the API responses in your Redux Store. I understand you're quickly prototyping now. :)
Your infinite loop is caused because you chose the wrong lifecycle method. If you use the componentDidUpdate and setState, it will again cause the componentDidUpdatemethod to be called and so on. You're basically updating whenever the component is updated, if that makes any sense. :D
You could always check, before sending the API call, if the new props.params you have are different than the ones you previously had (which caused the API call). You receive the old props and state as arguments to that function.
https://facebook.github.io/react/docs/react-component.html#componentdidupdate
However, if you've decided to use Redux, I would probably move that logic to an action creator, store that response in your Redux Store and simply use that data in your connect.
The FIRST problem I cannot help with, as I do not know what this APIManager's arguments should be.
The SECOND problem is a result of you doing API requests in "componentDidUpdate()". This is essentially what happens:
Some state changes in redux.
Hike receives new props (or its state changes).
Hike renders according to the new props.
Hike has now been updated and calls your "componentDidUpdate" function.
componentDidUpdate makes the API call, and when the response comes back, it triggers setState().
Inner state of Hike is changed, which triggers an update of the component(!) -> goto step 2.
When you click on a link to another page, the infinite loop is continued and after the last API call triggered by an update of Hike is resolved, you call "setState" again, which now tries to update the state of a no-longer-mounted component, hence the warning.
The docs explain this really well I find, I would give those a thorough read.
Try making the API call in componentDidMount:
componentDidMount() {
// make your API call and then call .setState
}
Do that instead of inside of componentDidUpdate.
There are many ways to architect your API calls inside of your React app. For example, take a look at this article: React AJAX Best Practices. In case the link is broken, it outlines a few ideas:
Root Component
This is the simplest approach so it's great for prototypes and small apps.
With this approach, you build a single root/parent component that issues all your AJAX requests. The root component stores the AJAX response data in it's state, and passes that state (or a portion of it) down to child components as props.
As this is outside the scope of the question, I'll leave you to to a bit of research, but some other methods for managing state and async API calls involved libraries like Redux which is one of the de-facto state managers for React right now.
By the way, your infinite calls come from the fact that when your component updates, it's making an API call and then calling setState which updates the component again, throwing you into an infinite loop.
Still figuring out the flow of Redux because it solved the problem when I moved the API request from the Hike component to the one it was listening to.
Now the Hike component is just listening and re-rendering once the database info catches up with the re-routing and re-rendering.
Hike.js
class Hike extends Component {
constructor() {
super()
this.state = {}
}
componentDidUpdate() {
console.log('dealing with ' + JSON.stringify(this.props.currentHike))
}
render() {
if (this.props.currentHike == null || undefined) { return false }
const currentHike = this.props.currentHike
return (
<div className="sidebar">
<p>{currentHike.name}</p>
</div>
)
}
}
const stateToProps = (state) => {
return {
currentHike: state.hike.currentHike,
}
}
And "this.props.currentHikeReceived()" got moved back to the action doing everything in the other component so I no longer have to worry about the Hikes component infinitely re-rendering itself.
Map.js
onMarkerClick(id) {
const hikeId = id
// Set params to be fetched
this.props.hikeSelected(hikeId)
// GET hike data from database
const hack = "/api/hike/" + hikeId
APIManager.get(hack, null, (err, response) => {
if (err) {
console.error(err)
return
}
this.props.currentHikeReceived(response.result)
})
// Change path to clicked hike
const path = `/hike/${hikeId}`
browserHistory.push(path)
}
const stateToProps = (state) => {
return {
hikes: state.hike.list,
location: state.newHike
}
}
const dispatchToProps = (dispatch) => {
return {
currentHikeReceived: (hike) => dispatch(actions.currentHikeReceived(hike)),
hikesReceived: (hikes) => dispatch(actions.hikesReceived(hikes)),
hikeSelected: (hike) => dispatch(actions.hikeSelected(hike)),
locationAdded: (location) => dispatch(actions.locationAdded(location)),
}
}

Exactly what does handleData do in a reactjs script?

I have made it this far with a FreeCodeCamp project, with a bit of help from some folks who have traveled further down the react.js road.
Before I go on, though, can someone please explain to me exactly what "this.handleData" accomplishes in the following code? (It is someone else's code I was allowed to use, and it helps me get the data I am grabbing from the api to render on the page, but what exactly is handleData doing?)
var LeaderList = React.createClass({
render:function(){
return(
<div >
<h1>Here is the data...</h1>
<AllData />
</div>
);
}
});
var AllData = React.createClass({
dataUrl: 'https://fcctop100.herokuapp.com/api/fccusers/top/recent',
getInitialState: function() {
return {data: []}
},
componentDidMount: function(){
$.getJSON(this.dataUrl, this.handleData)
},
handleData: function(data){
this.setState({data: data});
},
render: function(){
var elems = [];
for(var i=0; i<this.state.data.length; i++){
var subelems = [];
subelems.push(<div><p>{this.state.data[i].img}</p></div>);
subelems.push(<div><p>{this.state.data[i].username}</p></div>);
subelems.push(<div><p>{this.state.data[i].recent}</p></div>);
subelems.push(<div><p>{this.state.data[i].alltime}</p></div>);
elems.push(subelems);
}
return (<div>{elems}</div>);
}
});
ReactDOM.render(
<LeaderList />,
document.getElementById('content')
);
In that code handleData is a custom method that calls setState. setState is part of the React Component API. It causes the Component to re-render, using the data passed to it to set the this.state property available to the component. this.state is accessible in the render method, so the data passed to handleData is being made available to the render method.
As you can see in the render method, this.state is accessed to control what DOM is created.
Firstly, componentDidMount() makes a call to your component's dataUrl, and gets back some data. handleData takes that data and uses it to set the component's state, or internal data store.
That's then used below in the component's render() function, which gets called whenever the state changes.
I think the place to start here is the componentDidMount function. If you take a look at the React Component Lifecycle, you'll see that this function is called after the initial render when the component is placed into the DOM: https://facebook.github.io/react/docs/component-specs.html
The docs advise that:
If you want to integrate with other JavaScript frameworks, set timers
using setTimeout or setInterval, or send AJAX requests, perform those
operations in this method.
It looks like we're making an Ajax call here (Via what I assume is JQuery) to fetch some data from the server. We pass in handleData as a callback which will be called with the results once the Ajax request is complete, which does this:
this.setState({data: data});
To understand what state is in React there are a couple of good links here:
https://facebook.github.io/react/docs/thinking-in-react.html
https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html
State is reserved only for interactivity, that is, data that changes over time
This is applicable to us as the result of the Ajax request will change this data - only once from nothing to something, but it still changes. When you call setState React triggers a "state transition" which will eventually call render again, where it will populate the elems variable with your data and display it on the screen.

Assigning a property with ES6 in a React Component

I am new to ES6 and still trying to grasp the concepts of the new specifications, i am currently working on a component in React where i need to make an ajax call and store this response in an object. Then use this object to the map the necessary elements
My component looks like the following
export class App extends Component {
search(){
//make ajax call
response = obj.responseText;
}
getValues(){}
render(){
let result = response.data.map(this.getValues);
return(
<div onKeyDown={this.search.bind(this)}>{result}</div>
)
}
}
How do i declare the "response" variable globally which gets assigned the data from ajax call "obj.responseText"?
It seems like you know what you want to achieve, but are a little confused about how to get there.
I would highly recommend reading the React documentation before you go any further.
Why not global variables?
How do I declare the response variable globally?
In short, don't. Global variables are well-documented as being evil. One instance of this component in a page with a global variable to store its search results would be fine, but imagine if you had two or more instances - they would all share/overwrite each other's search results.
Introducing state
Instead, you want to use React's component state functionality to store your search results.
You can set an initial state by setting a component's this.state in its constructor, (or in ES5, define a getInitialState method on the component).
Then, any time you want to update the component's state, you can call its this.setState(...) method, passing in a new state object. This will also trigger a re-render of the component.
Example
Here is a simple implementation following the above pattern:
export class App extends Component {
// Set the initial state of the component in the constructor
constructor(props) {
super(props);
this.state = {};
}
// This gets called when your component is mounted
componentDidMount() {
// Here we make our AJAX call. I'll leave that up to you
performMyAjaxMethodDefinedSomewhereElse(result => {
// We call this method to update `this.state` and trigger re-rendering
this.setState({ result });
});
}
render() {
// If we haven't received any results yet, display a message
if (!this.state.result) {
return (
<div>No results!</div>
);
}
// Iterate over the results and show them in a list
const result = this.state.result.map(text => (<li>{text}</li>));
// Display the result
return (
<ul>{result}</ul>
);
}
}
Naturally, if you don't want the AJAX call to fire off immediately, you can use a very similar approach, replacing componentDidMount with an event handler which looks almost identical.

Why componentWillMount is called after rendering?

I am working with React and I am trying to understand the lifecycle. I am doing a componentWillMount method in order to get the props I need before the render occurs. I need to know how to update the state when the view loads.
All I am trying to do is a GET request in order to get a list of dealers for a Casino Game. Basically, I am missing 1 or 2 steps which are for render the dealers's list in the DOM
I will show what I am doing with my code and after that I will explain what I want
Actions part
getDealerActions.js
class GetDealersActions {
constructor () {
this.generateActions('dealerDataSuccess', 'dealerDataFail');
}
getDealers (data) {
const that = this;
that.dispatch();
axios.get('someroute/get-dealers/get-dealers')
.then(function success (response) {
that.actions.dealerDataSuccess({...response.data});
})
}
};
then we move to the stores
getDealersStore.js
class GetDealersStore {
constructor () {
this.state = {
dealerData : null,
};
}
#bind(GetDealersActions.dealerDataSuccess)
dealerDataSuccess (data) {
this.setState({
dealerData : data,
});
console.log(this.state.dealerData);
}
}
in this case that console.log(this.state.dealerData); returns something like this which is exactly what I need
Object {dealersData: Array[3]}
the problems comes in the component part, honestly because I don't know how to handle the data here
#connectToStores
export default class Dealers extends Component {
static contextTypes = {
router : React.PropTypes.func,
}
constructor (props) {
super(props);
this.state = {}
}
static getStores () {
return [ GetDealersStore ];
}
static getPropsFromStores () {
return GetDealersStore.getState();
}
componentWillMount () {
console.log('###', this.props);
GetDealersActions.getDealers();
}
render () {
console.log('>>>', this.props);
let content;
if (this.state.dealerData) {
content = this.state.dealerData.map((item) => {
return <div key={item.CardId}>{item}</div>;
});
} else {
content = <div>Loading . . .</div>;
}
return (
<div>
<div>{content}</div>
</div>
);
}
}
all I get here <div>{content}</div> is Loading . . . because this.state is coming like this Object {}
A weird situation I am getting here, is that this view is rendering twice, the 1st time is rendering, and the console.log('>>>', this.props); returns this >>> Object {params: Object, query: Object} and the second time it renders, fires this >>> Object {params: Object, query: Object, dealerData: Object} which is what I need.
So, why componentWillMount is waiting the render method in order to get fired ?
It's not weird at all. componentWillMount will fire before render, and in the first-pass you are invoking an action to get the dealers GetDealersActions.getDealers(); which is basically an async command. Since it is async, the component will render once before it gets data, and then again after the store publishes a changed event, which will re-trigger rendering.
Here is an approximation of the sequence of actions happening in your example:
componentWillMount invokes getDealers command (which is async)
initial render with default component state
Async operation completed in action creator and store is set with dealer data
store publishes a changed event, which re-triggers rendering
second render invoked with the dealer data in component state.
The problem is that React will run it's lifecycle methods in a certain sequence, not caring about you invoking some async method. So basically you don't have a way to stop rendering just because you invoked a command to get the dealers. That is a limitation of react (or a feature), which surfaces when combined with async programming and you should accept it as is.
If you accept the fact that React will render twice, you can utilize that in your favor, so on first render you could just show a loading indicator (e.g. a spinning wheel) and when the data loads you just display it in the second render.
However, if you are not convinced and still want to avoid double-rendering in the initial load, you could do prefetching of the data before you mount the application component, which would ensure that initial data is loaded in the store before the first render, which would mean that you wouldn't have to invoke getDealers in componentWillMount since the data would already be in the store on the first render.
As a reminder, double-rendering is not a significant performance problem, like it would be in Angular.js or Ember.js, since React is very efficient at DOM manipulation, but it could produce some UX issues if not handled properly.

Categories