How do I render a component when the data is ready? - javascript

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}>
{item.title}
</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()
})

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.

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.

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: []
};
}

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}>
{item.title}
</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}>
{item.title}
</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.

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}>
{item.title}
</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.

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 ...

Related

Fetch data only once per React component

I have a simple component that fetches data and only then displays it:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
stuff: null
};
}
componentDidMount() {
// load stuff
fetch( { path: '/load/stuff' } ).then( stuff => {
this.setState({
loaded: true,
stuff: stuff
});
} );
}
render() {
if ( !this.state.loaded ) {
// not loaded yet
return false;
}
// display component based on loaded stuff
return (
<SomeControl>
{ this.state.stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
);
}
}
Each instance of MyComponent loads the same data from the same URL and I need to somehow store it to avoid duplicate requests to the server.
For example, if I have 10 MyComponent on page - there should be just one request (1 fetch).
My question is what's the correct way to store such data? Should I use static variable? Or I need to use two different components?
Thanks for advice!
For people trying to figure it out using functional component.
If you only want to fetch the data on mount then you can add an empty array as attribute to useEffect
So it would be :
useEffect( () => { yourFetch and set }, []) //Empty array for deps.
You should rather consider using state management library like redux, where you can store all the application state and the components who need data can subscribe to. You can call fetch just one time maybe in the root component of the app and all 10 instances of your component can subscribe to state.
If you want to avoid using redux or some kind of state management library, you can import a file which does the fetching for you. Something along these lines. Essentially the cache is stored within the fetcher.js file. When you import the file, it's not actually imported as separate code every time, so the cache variable is consistent between imports. On the first request, the cache is set to the Promise; on followup requests the Promise is just returned.
// fetcher.js
let cache = null;
export default function makeRequest() {
if (!cache) {
cache = fetch({
path: '/load/stuff'
});
}
return cache;
}
// index.js
import fetcher from './fetcher.js';
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
stuff: null
};
}
componentDidMount() {
// load stuff
fetcher().then( stuff => {
this.setState({
loaded: true,
stuff: stuff
});
} );
}
render() {
if ( !this.state.loaded ) {
// not loaded yet
return false;
}
// display component based on loaded stuff
return (
<SomeControl>
{ this.state.stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
);
}
}
You can use something like the following code to join active requests into one promise:
const f = (cache) => (o) => {
const cached = cache.get(o.path);
if (cached) {
return cached;
}
const p = fetch(o.path).then((result) => {
cache.delete(o.path);
return result;
});
cache.set(o.path, p);
return p;
};
export default f(new Map());//use Map as caching
If you want to simulate the single fetch call with using react only. Then You can use Provider Consumer API from react context API. There you can make only one api call in provider and can use the data in your components.
const YourContext = React.createContext({});//instead of blacnk object you can have array also depending on your data type of response
const { Provider, Consumer } = YourContext
class ProviderComponent extends React.Component {
componentDidMount() {
//make your api call here and and set the value in state
fetch("your/url").then((res) => {
this.setState({
value: res,
})
})
}
render() {
<Provider value={this.state.value}>
{this.props.children}
</Provider>
}
}
export {
Provider,
Consumer,
}
At some top level you can wrap your Page component inside Provider. Like this
<Provider>
<YourParentComponent />
</Provider>
In your components where you want to use your data. You can something like this kind of setup
import { Consumer } from "path to the file having definition of provider and consumer"
<Consumer>
{stuff => <SomeControl>
{ stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
}
</Consumer>
The more convenient way is to use some kind of state manager like redux or mobx. You can explore those options also. You can read about Contexts here
link to context react website
Note: This is psuedo code. for exact implementation , refer the link
mentioned above
If your use case suggests that you may have 10 of these components on the page, then I think your second option is the answer - two components. One component for fetching data and rendering children based on the data, and the second component to receive data and render it.
This is the basis for “smart” and “dumb” components. Smart components know how to fetch data and perform operations with those data, while dumb components simply render data given to them. It seems to me that the component you’ve specified above is too smart for its own good.

When to update local react state based on data from redux?

Lets say my use-case is to print a list of posts. I have the following react component.
class App extends Component {
constructor(props) {
super(props);
this.state = {
loaded: !!(props.posts && props.posts.length)
};
}
componentDidMount() {
this.state.loaded ? null : this.props.fetchPosts();
}
render() {
return (
<ul>
{this.state.loaded
? this.props.posts.length
? this.props.posts.map((post, index) => {
return <li key={index}>{post.title}</li>;
})
: 'No posts'
: 'Loading'}
</ul>
);
}
}
fetchPosts is an action which makes an API call to fetch posts from DB and then updates the redux store with data. Now, my questions are
When should I update my local React state as per the props?
Initially, this.props.posts would either be undefined or [] so this.state.loaded would be false and we will make an API call to fetch. Once, the data is fetched then should I update it as
componentWillReceiveProps(nextProps) {
this.setState({
loaded: nextProps.posts && nextProps.posts.length
});
}
This sets the local state and initially spinner/loader will be shown and then posts or no posts. However, as far as I understand, React documentation discourages to setState in componentWillReceiveProps as that lifecycle hook will be called many times in React 16 and is also deprecated.
So, in which lifecycle hook should I update local state?
Would it be better to maintain the loading mechanism in Redux only?
class App extends Component {
constructor(props) {
super(props);
}
compomentDidMount() {
this.props.loaded ? null : this.props.fetchPosts();
}
render() {
return (
<ul>
{this.props.loaded
? this.props.posts.length
? this.props.posts.map((post, index) => {
return <li key={index}>{post.title}</li>;
})
: 'No posts'
: 'Loading'}
</ul>
);
}
}
Here everything is maintained in Redux store only. If any other approach would be better then I would love to know. Thanks!
The recommended solution would be to move that to mapStateToProps. Most of the time when you need data from your store (here it's posts) or data that is derived from store (here loading) then mapStateToProps is the correct place to inject that. It is usually a good idea to keep the component as dumb as possible that takes data from the store. Also it it kind of violating the single source of truth principle to keep state in a component that is derived from the store because it can get out of sync if you do not pay attention:
class App extends Component {
render() {
const {loading, posts} = this.props;
if (loading) return 'Loading';
if (!posts.length) return 'No Posts';
return (
<ul>
{posts.map((post, index) => (
<li key={index}>{post.title}</li>;
))}
</ul>
);
}
}
const mapStateToProps = ({posts}) => ({
posts
loading: !posts,
});
export default connect(mapStateToProps, /* mapDispatchToProps */)(App);
2 is correct. It is better to maintain the state in Redux only. Otherwise, you have two separate states for this component!

How can I have different data in different ReactJS Component instances?

Let's say I have 2 <Logs/> components. They're only displayed when their parent <Block/> has been clicked. Each <Block/> only has one <Logs/> but there will be many <Blocks/> on the page.
class Block extends React.Component {
toggleExpanded() {
this.setState({
isExpanded: !this.state.isExpanded
});
}
render() {
let blockId = // ...
return (
<div>
<button type="button" onClick={() => this.toggleExpanded()}>Toggle</button>
{this.state.isExpanded && <Logs blockId={blockId} />}
</div>
);
}
}
export default Block;
As the <Logs/> is created, I want to get data from the server using Redux. There could be a lot of <Logs/> some day so I don't want to load in all data to begin with, only data as needed.
class Logs extends React.Component {
componentDidMount() {
if (!this.props.logs) {
this.props.fetchBlockLogs(this.props.blockId);
}
}
render() {
return (this.props.logs && this.props.logs.length)
? (
<ul>
{this.props.logs.map((log, i) => <li key={i}>{log}</li>)}
</ul>
)
: (
<p>Loading ...</p>
);
}
}
SupportLogs.defaultProps = {
logs: null
}
const mapStateToProps = (state) => ({
logs: state.support.logs
});
const mapDispatchToProps = (dispatch, ownProps) => ({
fetchBlockLogs: (blockId) => {
dispatch(ActionTypes.fetchBlockLogs(blockId));
}
});
export default connect(mapStateToProps, mapDispatchToProps)(SupportLogs);
Currently, as I toggle one parent <Block/> closed and then re-opened, the data is remembered. That's a nice feature - the data won't change often and a local cache is a nice way to speed up the page. My problem is that as I toggle open a second <Block/>, the data from the first <Logs/> is shown.
My question is: how can I load in new data for a new instance of <Logs/>?
Do I have to re-load the data each time and, if so, can I clear the logs property so that I get the loading animation back?
Maybe think about changing the logs in your store to be an object with logs loaded into it keyed by block id:
logs: {
'someBlockId': [
'some logline',
'some other logline'
]
]
Have your reducer add any requested logs to this object by block id, so as you request them they get cached in your store (by the way I really like that your component dispatches an action to trigger data fetching elsewhere as a side effect, rather than having the fetch data performed inside the component :) ).
This reducer assumes that the resulting fetched data is dispatched in an action to say it has received the logs for a particular block, {type: 'UPDATE_LOGS', blockId: 'nic cage', receivedLogsForBlock: ['oscar', 'winning', 'actor']}
logsReducer: (prevState, action) => {
switch(action.type)
case 'UPDATE_LOGS':
return Object.assign({}, prevState, {
[actions.blockId]: action.receivedLogsForBlock
})
default: return prevState
}
}
And in your component you can now look them up by id. If the required block is not defined in the logs object, then you know you need to send the action for loading, and can also show loader in meantime
class Logs extends React.Component {
componentDidMount() {
const {logs, blockId} = this.props
if (!logs[blockId]) {
this.props.fetchBlockLogs(blockId);
}
}
render() {
const {logs, blockId} = this.props
return (logs && logs[blockId]) ? (
<ul>
{logs[blockId].map((log, i) => <li key={i}>{log}</li>)}
</ul>
) : (
<p>Loading ...</p>
)
}
}
This has the added bonus of logs[blockId] being undefined meaning not loaded, so it is possible to distinguish between what needs to be loaded and what has already loaded but has empty logs, which your original would have mistaken for requiring a load

How to avoid using setProps in React?

I'm working on application using react & redux and I need setProps but it's deprecated.
Take a look the error below:
Warning: setProps(...) and replaceProps(...) are deprecated. Instead,
call render again at the top level.
Uncaught Invariant Violation: setProps(...): You called setProps on
a component with a parent. This is an anti-pattern since props will
get reactively updated when rendered. Instead, change the owner's
render method to pass the correct value as props to the component
where it is created.
So how can I set the props? basically is to manage some tabs, take a look below my code:
export default React.createClass({
render: function() {
return (
<div className="demo-tabs">
<Tabs activeTab={this.props.activeTab}
onChange={(tabId) => this.setProps({ activeTab: tabId })} ripple>
<Tab>Stack</Tab>
<Tab>GitHub</Tab>
<Tab>Twitter</Tab>
</Tabs>
<section>
<div className="content">Content for the tab: {this.props.activeTab}</div>
</section>
</div>
);
}
});
Container
import React from 'react';
import TimeLine from './timeline';
import { connect } from 'react-redux';
import { getStackoverflow } from 'api/timeline';
const TimeLineContainer = React.createClass({
componentWillMount: function() {
getStackoverflow();
},
render: function() {
return (
<TimeLine {...this.props} />
)
}
});
const stateToProps = function(state) {
return {
timeline: state.timelineReducer.timeline
}
}
const dispatchToProps = function(dispatch) {
return {
onClick: () => {console.log('timeline was clicked')}
}
}
export default connect(stateToProps, dispatchToProps)(TimeLineContainer)
Reducer
var timelineInitialState = {
github: [],
gist: [],
stackoverflow: [],
twitter: [],
activeTab: 2
}
export default function(state = timelineInitialState, action) {
switch (action.type) {
case 'GET_GITHUB':
//TODO: implement.
break;
case 'GET_GIST':
//TODO: implement.
break;
case 'GET_STACKOVERFLOW':
var stackoverflowState = Object.assign({}, state)
stackoverflowState.stackoverflow = action.stackoverflow;
return stackoverflowState;
break;
case 'GET_TWITTER':
//TODO: implement.
break;
default:
return state;
}
}
It looks like you dived into using Redux without first getting a firm grip of React. I wouldn’t recommend doing this because you appear to be somewhat confused now.
Thinking in React is a great guide and I recommend you to go through it first and get comfortable with the idea of state ownership in React, before using Redux.
In React, things that change over time (“state”) are always “owned” by some component. Sometimes it’s the same component that uses this state for rendering. Sometimes many components need to be synchronized so the state gets “hoisted” to some parent component that can manage them all, and pass that information via props. Whenever state changes, they all get updated.
The important part here is that props are meant to be received by parent. If a component wants to change its own props, it is a symptom of a problem:
Either you should have used state for this component, so you can call setState
Or your component needs to access a callback prop like onChange so it can “ask” the value to be changed
In the first case, your code would look like this, and it would be valid React:
export default React.createClass({
getInitialState: function() {
return { activeTab: 0 }
},
render: function() {
return (
<div className="demo-tabs">
<Tabs activeTab={this.state.activeTab}
onChange={(tabId) => this.setState({ activeTab: tabId })} ripple>
<Tab>Stack</Tab>
<Tab>GitHub</Tab>
<Tab>Twitter</Tab>
</Tabs>
<section>
<div className="content">Content for the tab: {this.state.activeTab}</div>
</section>
</div>
);
}
});
However, you can continue the pattern you are already using with <Tabs> component inside, and hoist the state even higher. Then your component would need to accept an onChange prop which it would pass down to <Tabs>. Now it doesn’t know how the state is updated, and doesn’t hold the state:
export default React.createClass({
render: function() {
return (
<div className="demo-tabs">
<Tabs activeTab={this.props.activeTab}
onChange={this.props.onChange} ripple>
<Tab>Stack</Tab>
<Tab>GitHub</Tab>
<Tab>Twitter</Tab>
</Tabs>
<section>
<div className="content">Content for the tab: {this.props.activeTab}</div>
</section>
</div>
);
}
});
Neither approach is better or worse, they are used for different purposes. The first one is more convenient when the state is irrelevant to the rest of the app, the second one is convenient when other distant components happen to need it too. Make sure you understand the tradeoffs of both approaches.
Even with the second approach, something’s got to hold the state. It could be another component, or (and this is where Redux comes in) it could be a separate data storage like a Redux store. In this case, rather than supply onChange from a parent component, you would use connect() to bind an onChange prop it injects to dispatching a Redux action:
const mapStateToProps = (state) => {
return {
activeTabId: state.activeTabId
}
}
const mapDispatchToProps = (dispatch) => {
return {
onChange: (tabId) => dispatch({ type: 'SET_ACTIVE_TAB', tabId })
}
}
And in your reducers, you can handle this action:
const activeTabId = (state = 0, action) => {
switch (action.type) {
case 'SET_ACTIVE_TAB':
return action.tabId
default:
return state
}
}
const reducer = combineReducers({
activeTabId,
// other reducers
})
const store = createStore(reducer)
In neither case we need setProps. You can either:
let the component own the state and use setState,
let it accept the activeTabId and onChange props and manage them from a component higher in the tree that would use setState, or
you can move the state handling completely out of the components into something like Redux, but your components would still accept activeTabId and onChange as props.
Provide onTabChange callback to Timeline component from Container.
Container:
const dispatchToProps = function(dispatch) {
return {
onClick: () => {console.log('timeline was clicked')},
onTabChange: (tabId) => { setActiveTabAction(tabId) }
}
}
const stateToProps = function(state) {
return {
timeline: state.timelineReducer.timeline,
activeTab: state.timelineReducer.activeTab
}
}
Timeline component:
export default React.createClass({
render: function() {
return (
<div className="demo-tabs">
<Tabs activeTab={this.props.activeTab}
onChange={this.props.onTabChange} ripple>
<Tab>Stack</Tab>
<Tab>GitHub</Tab>
<Tab>Twitter</Tab>
</Tabs>
<section>
<div className="content">Content for the tab: {this.props.activeTab}</div>
</section>
</div>
);
}
});
Presentational components (like your Timeline component) usually shouldn't manage application state. All data and callbacks they must receive by props.
I suggest you to read article about Presentational and Container components:
https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.sijqpzk93

Append to infinite scroll in reactJs

I'm making a infinite scroll in ReactJs with posts.
I have a react class called AllPosts and Post. AllPosts render multiple Posts.
I have this code:
ReactDOM.render(
<AllPosts data={posts} />,
document.querySelector(render_to)
);
And below is the method
// Render all posts
var AllPosts = React.createClass({
render: function () {
return (
<div>
{this.props.data.map(function(element, i) {
return <Post data={element} />
})}
</div>
); .....
But, I have an event in scroll and I want to append another react Post. How can I do this?
This is one of those awesome things React is great at :)
On the assumption that you don't want to use a Flux/Redux implementation, I would set the posts data as the state on your root component. That way, when posts changes, the component will re-render:
var AllPosts = React.createClass({
getInitialState() {
// Start with an empty array of posts.
// Ideally, you want this component to do the data fetching.
// If the data comes from a non-react source, as in your example,
// you can do `posts: this.props.posts`, but this is an anti-pattern.
return { posts: [] }
},
componentWillMount() {
// Fetch the initial list of posts
// I'm assuming here that you have some external method that fetches
// the posts, and returns them in a callback.
// (Sorry for the arrow functions, it's just so much neater with them!)
ajaxMethodToFetchPosts(posts => {
this.setState({ posts: posts });
})
},
componentDidMount() {
// Attach your scroll handler here, to fetch new posts
// In a real example you'll want to throttle this to avoid too many requests
window.addEventListener('scroll', () => {
ajaxMethodToFetchPosts( posts => {
this.setState({
posts: this.state.posts.slice().concat(posts)
});
});
});
},
render() {
// Render method unchanged :)
return (
<div>
{this.props.data.map(function(element, i) {
return <Post data={element} />
})}
</div>
);
}
});
With other frameworks, you have to deal with scroll position (if the element gets completely re-rendered, the elements disappear momentarily and your scroll position is reset).
React's render function doesn't actually just render its output to the DOM; it compares the potential output with what's already there, and only applies the difference. This means that only new posts are added to the DOM, and your scroll position will remain unaffected.

Categories