Append to infinite scroll in reactJs - javascript

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.

Related

Updating post count the reactive way

I am new to react. I have created a news component that consumes a json url then spits out some news articles. In the client side UI if the clients changes the json url it will update without refreshing the page using this code:
componentDidUpdate(prevProps) {
if (prevProps.jsonUrl !== this.props.jsonUrl) {
this.getPosts();
}
}
However I also need the the news feed to update reactively if the postCount: this.props.postCount is changed in the client side UI. The post count is used in the render method below to choose how many posts to display.
posts
.slice(0, postCount)
.map(post => {
// Variables to use
let { id, name, summary, url, imgUrl} = post;
// Stripping html tags from summary
//let strippedSummary = summary.replace(/(<([^>]+)>)/ig,"");
// What we actually render
return (
<div key={id} className={ styles.post}>
<p>{name}</p>
{/* <p>{summary}</p> */}
<a href={url}>{url}</a>
<img className={ styles.postImage} src={imgUrl} />
</div>
);
})
Any help is much appreciated! - I was thinking something like this inside componentDidUpdate:
if (prevProps.postCount !== this.props.postCount) {
this.setState( this.state.postCount; );
}
EDIT:
I am now using the postCount from the props instead of a state and it updates instantly! :D
// Grabbing objects to use from state
const { posts, isLoading } = this.state;
const { postCount } = this.props;
The components are going to react automatically to the changes in their props, so there's no need to transfer any props to a state. In this case, if postCount is a prop, when it changes it should affect the piece of code that you shared to render the component. However, I don't know if posts is part of the state, in your case it should be and your method getPosts should setState with the new posts.

React.js "global" component that can be created multiple times

I can't get my head wrapped around this.
The problem: let's say there's an app and there can be some sort of notifications/dialogs/etc that i want to create from my code.
I can have "global" component and manage it, but it would limit me to only one notification at a time, this will not fit.
render() {
<App>
// Some components...
<Notification />
</App>
}
Or i can manage multiple notifications by the component Notification itself. But state management will not be clear.
The other problem if i have some sort of user confirmation from that component (if it's a confirmation dialog instead of simple notification) this will not be very convinient to handle with this solution.
The other solution is to render a component manually. Something like:
notify(props) {
const wrapper = document.body.appendChild(document.createElement('div'))
const component = ReactDOM.render(React.createElement(Notification, props), wrapper)
//...
// return Promise or component itself
}
So i would call as:
notify({message: '...'})
.then(...)
or:
notify({message: '...', onConfirm: ...})
This solution seems hacky, i would like to let React handle rendering, and i have an additional needless div. Also, if React API changes, my code breaks.
What is the best practice for this scenario? Maybe i'm missing something completely different?
You could use React Context for this.
You create a React context at a high level in your application and then associate a values to it. This should allow components to create / interact with notifications.
export const NotificationContext = React.createContext({
notifications: [],
createNotification: () => {}
});
class App extends Component {
constructor() {
super();
this.state = {
notifications: []
};
this.createNotification = this.createNotification.bind(this);
}
createNotification(body) {
this.setState(prevState => ({
notifications: [body, ...prevState.notifications]
}));
}
render() {
const { notifications } = this.state;
const contextValue = {
notifications,
createNotification: this.createNotification
};
return (
<NotificationContext.Provider value={contextValue}>
<NotificationButton />
{notifications.map(notification => (
<Notification body={notification} />
))}
</NotificationContext.Provider>
);
}
}
The notifications are stored in an array to allow multiple at a time. Currently, this implementation will never delete them but this functionality can be added.
To create a notification, you will use the corresponding context consumer from within the App. I have added a simple implementation here for demonstration purposes.
import { NotificationContext } from "./App.jsx";
const NotificationButton = () => (
<NotificationContext.Consumer>
{({ notifications, createNotification }) => (
<button onClick={() => createNotification(notifications.length)}>
Add Notification
</button>
)}
</NotificationContext.Consumer>
);
You can view the working example here.

wait until all elements in map function have been rendered react

I have a react component that maps over an array of objects whose content is then displayed on screen. This works perfectly, however when I check the ul children in the componentDidMount lifecycle method it is an empty array, however, a second later it contains all the items.
Does anyone know how I can wait until everything has been rendered?
I have tried componentDidUpdate but as there is a setInterval method running regularly this is firing too often.
componentDidMount() {
// this is an empty array here
console.log(this.items.children);
setInterval(() => {
this.setState((prevState) => {
return {
left: prevState.left - 1
};
});
}, 20);
}
render() {
let items = this.props.itemss.map(item => {
return (
<Item left={this.state.left} content={item.content} key={item.date} />
);
});
return (
<div>
<ul ref={(el) => { this.items = el; }} >
{ items }
</ul>
</div>
);
}
The reason you're having this issue (besides the spelling errors) is because the ref callback will not assign a value to this.items until after that component has rendered. componentDidMount is called after the first time render is called and not again until the component unmounts and remounts.
Instead, use componentDidUpdate which is called after a fresh render, in that method check to see if this.items has a value. If so, proceed, if not, just return from the function until the next the render where the ref callback succeeds.

Having trouble with React rendering a series of images from a map function

I'm trying to design a component where the user can click a button which will trigger a giphy API call that will eventually render a series of gif images.
So far I'm able to successfully get everything done except the actual image rendering. Here's my code so far:
retrieveURLs() {
*bunch of stuff to make API call and return an array of URLs related
to the category of the button the user pushes*
}
renderGifs() {
this.retrieveURLs().then(function(results) {
console.log(results); //logs array of URLs
return results.map(function(url, index) {
console.log(url); //logs each url
return (<img key={index} src={url} alt="" />)
}, this);
});
}
render() {
return(
<div id="gif-div">
{this.renderGifs()}
</div>
)
}
Nothing gets rendered despite each console.log() event indicating that the URL(s) are at least being passed properly.
I do something similar for the parent component to render the buttons from an array of categories that looks like this:
btnArray = [*bunch of categories here*];
renderButtons() {
return btnArray.map(function(item, i) {
let btnID = btnArray[i].replace(/\s+/g, "+");
return <button type='button' className="btn btn-info" onClick={this.handleCategorySelect} key={i} id={btnID} value={btnID}>{btnArray[i]}</button>
}, this)
}
The buttons are rendered properly, but my images are not. Neither the renderbuttons nor the rendergifs metohds alter the state. Honestly I can't see a meaningful difference between the two, so I'd like to have some help figuring out why one works but the other doesn't.
This is the nature of asynchronous functions; you can't return a value from within a callback to the original call site. If you were to write:
const res = this.retrieveURLs().then(function(results) {
return results;
});
you'd only be changing the resolution value of the promise. res won't be assigned the value of results, but rather it will be assigned the promise created by this.retrieveURLs(), and the only way to retrieve the value of a resolved promise is by attaching a .then callback.
What you could do is this:
this.retrieveURLs().then(results => {
this.setState({ images: results });
});
Now your internal state will be updated asynchronously, and your component will be told to re-render with the new data, which you can use in your render function by accessing the state.
Note: I'm using an arrow function to create the callback in the above example, because otherwise this won't be bound to the right context. Alternatively, you could do the old that = this trick, or use Function#bind.
The problem lies with the rendering function for the images and the way React does diffing between the previous and current state. Since the fetch is asynchronous and the render is not, when the fetch is completed React doesn't know that it needs to re-render you component. There are ways to force a re-render in this case, but a better solution is to use the functionality that's already part of the shouldUpdate check.
Your implementation might change to look something like the following:
class Images extends Component {
state = {
images: [],
error: null
}
componentDidMount() {
return this.retrieveImages()
.then(images => this.setState({images}))
.catch(error => this.setState({error}))
}
... rest of your class definition
render () {
return (
<div>
{this.state.images.map(image => <img src={image.url} />)}
</div>
)
}
}
I would also have some handling for bad results, missing key / values, etc. I hope that works for you, if not let me know! :)
first of all you forgot the return statement:
renderGifs() {
return this.retrieveURLs().then(function(results) {
...
}
but this won't solve anything as it is returning a Promise.
You need to save request results in the state and then map it:
constructor(props){
super(props);
this.state = { images: [] };
}
componentDidMount() {
this.renderGifs();
}
renderGifs() {
this.retrieveURLs().then(function(results) {
console.log(results); //logs array of URLs
this.stateState({ images: results });
});
}
render() {
return(
<div id="gif-div">
{
this.state.images.map((url, index) => (<img key={index} src={url} alt="" />);
}
</div>
)
}

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

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

Categories