Reputation: 6363
I'm trying to figure out how to populate/render a component when the data is ready? Essentially I have a script that queries my server which returns data, then I parse it and make it into an collection with the properties I need. Then in another file, I have the react component that's looking for that object but they're running at the same time so the object doesn't exist when the component is looking for it.
I'm not sure how to proceed.
This is my component:
let SliderTabs = React.createClass({
getInitialState: function() {
return { items: [] }
},
render: function() {
let listItems = this.props.items.map(function(item) {
return (
<li key={item.title}>
<a href="#panel1">{item.title}</a>
</li>
);
});
return (
<div className="something">
<h3>Some content</h3>
<ul>
{listItems}
</ul>
</div>
);
}
});
ReactDOM.render(<SliderTabs items={home.data.slider} />,
document.getElementById('slider-tabs'));
How I'm getting my data:
var home = home || {};
home = {
data: {
slider: [],
nav: []
},
get: function() {
var getListPromises = [];
$.each(home.lists, function(index, list) {
getListPromises[index] = $().SPServices.SPGetListItemsJson({
listName: home.lists[index].name,
CAMLViewFields: home.lists[index].view,
mappingOverrides: home.lists[index].mapping
})
getListPromises[index].list = home.lists[index].name;
})
$.when.apply($, getListPromises).done(function() {
home.notice('Retrieved items')
home.process(getListPromises);
})
},
process: function(promiseArr) {
var dfd = jQuery.Deferred();
$.map(promiseArr, function(promise) {
promise.then(function() {
var data = this.data;
var list = promise.list;
// IF navigation ELSE slider
if (list != home.lists[0].name) {
$.map(data, function(item) {
home.data.nav.push({
title: item.title,
section: item.section,
tab: item.tab,
url: item.url.split(",")[0],
path: item.path.split("#")[1].split("_")[0]
})
})
} else {
$.map(data, function(item) {
home.data.slider.push({
title: item.title,
url: item.url.split(",")[0],
path: item.path.split("#")[1]
})
})
}
})
})
console.log(JSON.stringify(home.data))
dfd.resolve();
return dfd.promise();
}
}
$(function() {
home.get()
})
Upvotes: 3
Views: 12493
Reputation: 6363
I got a few answers but I was still having a lot of trouble understanding how to accomplish what I was asking for. I understand that I should be retrieving the data with the components but I currently don't know enough about React to do that. I obviously need to spend more time learning it but for now I went with this:
Essentially, I added the property ready
to my home object:
home.ready: false,
home.init: function() {
// check if lists exist
home.check()
.then(home.get)
.then(function() {
home.ready = true;
})
}
Then I used componentDidMount
and a setTimeout
to check when the data is ready and set the results to the this.state
let SliderTabs = React.createClass({
getInitialState: function() {
return {items:[]}
},
componentDidMount: function() {
let that = this;
function checkFlag() {
if(home.ready == false) {
window.setTimeout(checkFlag, 100); /* this checks the flag every 100 milliseconds*/
} else {
that.setState({items: home.data.slider})
}
}
checkFlag();
},
render: function() {
let listItems = this.state.items.map(function(item) {
return (
<li key={item.title}>
<a href="#panel1">{item.title}</a>
</li>
);
});
return (
<div className="something">
<h3>Some content</h3>
<ul>
{listItems}
</ul>
</div>
);
}
});
ReactDOM.render(<SliderTabs/>,
document.getElementById('slider-tabs'));
Probably not the React way but it seems to work.
Upvotes: 0
Reputation: 2231
You should test the length of the data collection. If the collection is empty, return a placeholder (a loading wheel for exemple). In other cases, you can display the data collection as usual.
const SliderTabs = ({items}) => {
let listItems = <p>Loading data...</p>
if(items.length != 0)
listItems = items.map(item =>
<li key={item.title}>
<a href="#panel1">{item.title}</a>
</li>
)
return (
<div className="something">
<h3>Some content</h3>
<ul>
{listItems}
</ul>
</div>
)
}
ReactDOM.render(
<SliderTabs items={home.data.slider} />,
document.getElementById('slider-tabs')
)
I have use the functional way to define a React Component as it's the recommended way while you don't need of a state, refs or lifecycle methodes.
If you want to use this in a ES6 classe or with React.createCompnent (shoud be avoid), just use the function as the render function. (don't forget to extract items
form the props)
EDIT : By reading the new answers, I've realised that I haven't fully answered.
If you want the view to be updated when the data are loaded, You have to integrate a little more your data fetching code. A basic pattern in React is to separate your components in tow type : the Containers Component and the Presentational Component.
The Containers will only take care of the logic and to fetch the useful data. In the other hand, the Presentational Components will only display the data given by the Container.
Here a little example : (try it on jsfidle)
Test utilities
var items = [{title: "cats"},{title: "dogs"}]
//Test function faking a REST call by returning a Promise.
const fakeRest = () => new Promise((resolve, reject) =>
setTimeout(() => resolve(items), 2000)
)
Container Component
//The Container Component that will only fetch the data you want and then pass it to the more generic Presentational Component
class SliderTabList extends React.Component {
constructor(props) { //
super(props)
//You should always give an initial state (if you use one of course)
this.state = { items : [] }
}
componentDidMount() {
fakeRest().then(
items => this.setState({ items }) //Update the state. This will make react rerender the UI.
)
}
render() {
//Better to handle the fact that the data is fetching here.
//This let the Presentational Component more generic and reusable
const {items} = this.state
return (
items.length == 0
? <p>Loading Data...</p>
: <List items={items} />
)
}
}
Presentational Component
//The Presenational Component. It's called List because, in fact, you can reuse this component with other Container Component. Here is power of React.
const List = ({items}) => {
//Prepare the rendering of all items
const listItems = items.map(item =>
<li key={item.title}>
<a href="#panel1">{item.title}</a>
</li>
)
//Simply render the list.
return (
<div className="something">
<h3>Some content</h3>
<ul>
{listItems}
</ul>
</div>
)
}
Rendering the App
//Mount the Container Component. It doesn't need any props while he is handling his state itself
ReactDOM.render(
<SliderTabList />,
document.getElementById('slider-tabs')
)
Rather than checking for the length to not being 0, you also can initialise items
to null
in the state, to be able to differentiate fetching data from empty data. An other common way os to put a flag (a boolean in fetchingData
int the state) to know if a data is fetching or not. But, in lots of articles, it's generaly recomended to have a state as litle as possible and then calculate all you need from it. So here, I sugest you to check for the length or the null
.
Upvotes: 0
Reputation: 8680
A common way to do this in React is to keep track of when data is being fetched. This can be done e.g. by having a isFetching
field in your state:
// This would be your default state
this.state = {
isFetching: false
};
Then, when you fire off the request (preferably in componentDidMount) you set isFetching
to true using:
this.setState({ isFetching: true });
And finally, when the data arrives, you set it to false again:
this.setState({ isFetching: false });
Now, in your render function you can do something like this:
render () {
return (
<div className="something">
<h3>Some content</h3>
{this.state.isFetching ? <LoadingComponent /> : (
<ul>
{listItems}
</ul>
)}
</div>
)
}
By using state, you don't have to worry about telling your component to do something, instead it reacts to changes in the state and renders it accordingly.
Update:
If you plan on actually using React, I'd suggest you change your approach into what I've described above (read more in the React docs). That is, move the code you have in your get
function into your React component's componentDidMount
function. If that's not possible, you can probably just wait to call
ReactDOM.render(
<SliderTabs items={home.data.slider} />,
document.getElementById('slider-tabs')
);
until your data has arrived.
Upvotes: 3
Reputation: 7621
Put that data loading in parent component that updates the props of your component as the data is being loaded.
Use default props instead of default state, since you are not using state at all in your example. Replace the 'getInitialState' with this:
getDefaultProps: function() {
return {
items: []
};
}
Upvotes: 0
Reputation: 4201
Here is the explaination of React's way of doing these type of things, tl;dr - render the component immediately and either display loading indicator until the data is ready or return null
from the render
method.
Upvotes: 0
Reputation: 8955
Ordinarily, you would arrange a response to an OnLoad
event, which will "fire" once the data has been loaded.
I agree with Emrys that your code should also be prepared to test whether the data is available yet, and to "do something reasonable" on the initial draw (when it probably isn't). But no, you would not then "poll" to see if the data has arrived: instead, you arrange to be notified when the event fires. At that time, you would (for example) re-draw the UI components to reflect the information.
I kindly refer you to the React documentation to see exactly how this notification is done ...
Upvotes: -2