Im trying to build an small React.js application and my component structure looks like this:
MainComponent
- CategoryList
-Category
- ItemsList
-Item
My MainContent component does an ajax request for its state data in the componentDidRender: which returns this object:
data:[
Object[0]
-name
-items[]
,
Object[1],
Object[2]
]
Now, I want my CategoryList to write out all the Categories by name, which works just fine, but I also want to print out the items of the selected category. This is my ItemsList component:
var ItemsList = React.createClass({
render:function(){
var itemNodes = this.props.category.items.map(function(item){
return (
<Item name={item.name} />
);
});
return(
<div className="itemList">
{itemNodes}
</div>
);
}
});
And this is how I pass on the "category"-property from my the parent component
<ItemsList category={this.state.data[0]} />
I get an error say "Can´t read property items of undefined" meaning that the category prop never was assigned. I know that this.state.data contains an array of objects so I don´t see the error here.
What do I do wrong?
EDIT: Per request, this is my MainComponent:
var MainComponent = React.createClass({
getInitialState:function(){
return {data: []};
},
componentDidMount:function(){
$.ajax({
type:'get',
url: '/categories',
dataType: 'json',
success:function(data){
this.setState({data: data});
}.bind(this)
});
},
render: function(){
return (
<div className="row">
<div className="col-md-6">
<CategoryList categories={this.state.data} />
</div>
<div className="col-md-6">
<ItemsList category={this.state.data[0]} />
</div>
</div>
);
}
});
Your main component initializes the state with an empty array in data. A render would always fail because there is no this.state.data[0].
One would probably reply that the ajax request will provide the value for this state property data (supposing that your web service is providing a valid array). However, this only happens after the response was received from the server, which will not happen after the first render.
If the information was available immediately, one could either setState on the method componentWillMount or the component constructor, so as to avoid triggering a second render:
componentWillMount() is invoked immediately before mounting occurs. It
is called before render(), therefore setting state synchronously in
this method will not trigger a re-rendering. Avoid introducing any
side-effects or subscriptions in this method.
In this case, since we are waiting for remote information, the React documentation still recommends the use of componentDidMount, as well employed here:
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. Setting state in this method will
trigger a re-rendering.
Therefore, the component's render method must be able to handle the missing state variable. There are multiple ways to approach this, but preventing the nested element from being rendered until we have data is the easiest approach. With some additional logic, the application could inform the user that the particular component is loading.
render() {
return (
<div className="row">
<div className="col-md-6">
<CategoryList categories={this.state.data} />
</div>
<div className="col-md-6">
{this.state.data.length > 0 &&
<ItemsList category={this.state.data[0]} />
}
</div>
</div>
);
}
I can corroborate the previous answer.
Modern (2023) React tells you to use functional components. So, if you want to upload data from a server to render it inside a component you need to use so-called 'hooks' named 'useEffect' and 'useState'.
So first you import them:
import React, { useEffect } from "react";
import { useState } from "react";
Next, you create a functional component and initialize 'state' and 'set_state function' inside a component:
export default function FuctionName() {
let [ value, set_value ] = useState([])
}
This would create the 'value' variable which would preserve its state across renders.
Then, you make a fetch request (before the component's return statement) (but you need to put it inside of the useEffect function for a consistent behavior):
useEffect(() => {
fetch('url')
.then(response => response.json())
.then(server_data => {
set_value(server_data);
})}
, [])
Now, you see why we need state: because we need to store data returned from the server inside a variable and we need this data to preserve across renders.
Also, you think that now you can use your data inside the component's return render like this:
return (
<h1>the data from server: {value.name_of_key}</h1>
)
But you encounter the error of 'reading undefined'.
First, check if the server returns the correct type of object. Put a console.log statement inside the fetch request:
useEffect(() => {
fetch('url')
.then(response => response.json())
.then(server_data => {
set_value(server_data);
console.log(server_data);
})}
, [])
It should be a list with objects inside, like: [{}, {}, {}].
If it is, then the problem is that component renders before it receives the data from the server. The component will first render, then receive the data from the server. If you call the state variable inside the render statement it will call the empty state.
So, in order to mitigate it, your code should be ready to render both the empty state and the state with data. The easy way to do so would be conditional rendering. Render the component with data only if there is data to render:
return (
{value && <h1>the data from server: {value.name_of_key}</h1>}
)
the 'value &&' part lets you implement the logic:
if the value (data from the server) is empty don't render anything (thus do not call any keys from data)
if the value is not empty, render the component which calls to data
Why is that so? The '&&' expression (a and b) evaluates from right to left. If first variable evaluates to 'false', then it does not go further and stops there. The initial state of variable 'value' is an empty object ('let [ value, set_value ] = useState([])' - remember?). So, when the server has not returned data yet on the first render, the expression 'value &&' evaluates to 'false' and does not render the right part of the expression. When the server has returned the data, it calls the 'setState' function with data and forces to render the component the second time. The second time expression 'value &&' evaluates to 'true' and React goes to the right part of the expression and actually renders the component with data. You can check this process by placing console.log statement inside the fetch request and inside the return statement and you will see the order of functions resolutions.
Hope it helps.
Also, check out the article:
https://daveceddia.com/react-before-render/
Happy coding >>
Related
Let's say I have a nested URI structure, something like the following:
http://example.com/collections/{id}
http://example.com/collections/{collectionId}/categories/{id}
http://example.com/collections/{collectionId}/categories/{categoryId}/book/{id}
I can use react-router to render the correct component on page load, and when the URI changes.
Let's take the first case:
http://example.com/collections/{id}
Let's assume we have a CollectionShow component.
When the component first loads, I can pull the collection ID out of the URI and load the correct collection:
componentDidMount () {
this.loadCollection(this.props.match.params.id);
}
(Assume that loadCollection loads a collection with an AJAX call and sets it into the component's state.)
However, when the URI changes (through, e.g., the user clicking on a <Link>, react-router doesn't entirely re-build the component, it simply updates its props, forcing it to rerender. So, in order to update the compomnent's state, we also need to update the state on update:
componentDidUpdate(prevProps) {
if (!this.state.collection || this.collectionDidChange(prevProps)) {
this.loadCollection(this.props.match.params.id);
}
}
collectionDidChange(prevProps) {
return String(prevProps.match.params.id) !== String(this.props.match.params.id)
}
So far so good. But what about the second URL?
http://example.com/collections/{collectionId}/categories/{id}
Let's assume we have a CategoryShow component.
Now we don't only have to consider the collectionId changing, but also the category ID. We have to reload the collection if that ID changes, and we also have to reload the category if that changes.
The problem compounds with a third-level nesting (a BookShow component). We end up with something like this:
componentDidUpdate(prevProps) {
if (!this.state.collection || this.collectionDidChange(prevProps)) {
this.loadCollection(this.props.match.params.collectionId);
}
if (!this.state.category || this.collectionDidChange(prevProps) || this.categoryDidChange(prevProps)) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId);
}
if (!this.state.book || this.collectionDidChange(prevProps) || this.categoryDidChange(prevProps) || this.bookDidChange(prevProps)) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId)
.then(() => this.loadBook(this.props.match.params.id);
}
}
Not only is this unwieldy, it also results in a fair amount of code duplication across the three components, CollectionShow, CategoryShow and BookShow.
Using redux won't help matters much, because we still have to update the global state when the URI changes.
Is there a clean, efficient, React-friendly way of handling updates of nested resources such as these?
You could create a CollectionPage component that handles all the AJAX calls and keeps data in state.
This could pass down the collection, category/categories and books to the components (CollectionShow, CategoryShow and BookShow).
In CollectionPage you could use componentDidUpdate and componentDidMount as you presented it.
Your <*>Show components will know nothing about props.match.params.* and will only get the data needed to render the wanted content.
CollectionPage can be use for all your routes or you could change the route to something like
/collections/:collectionId?/:categoryId?/:bookId?
making all params options. You can check for the available ids in CollectionPage.
Hope it helps!
If I understood your problem it is something architectural. The parent component is the one that should be doing this management and injecting the result through subcomponents. Split your component in small components and render each one accordingly.
The code you shared will be splint in 3 others
The mponentDidUpdate(prevProps) method will go to the parent component simply as a componentDidMount().
Then if the router changes the component will be recreated and the new values will be sent across the modules.
If you dont wanna split you code you should at least do the step 2.
//everytime you get to the router this will be triggered and depending of the parameters of your router, you get the values you need and set the state
componentDidMount() {
if (!this.state.collection) {
this.loadCollection(this.props.match.params.collectionId);
}
if (!this.state.category) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId);
}
if (!this.state.book) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId)
.then(() => this.loadBook(this.props.match.params.id);
}
}
render() {
return (
//you can add conditions to render as well
<CollectionComponent {...this.props} {...{
collection: this.collection
}} />
<CategoryComponent {...this.props} {...{
categ: this.categ
}} />
<BookComponent {...this.props} {...{
book: this.book
}} />
)
}
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)
}
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.
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.
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.