get next Next/Previous item in array using react - javascript

I'm new to react and programming in general, I have searched and only found solutions for js not react specific.
Having trouble displaying next or previous item in an array passed via props. When Next button is clicked I only see the same item in the array being returned not the next item, I understand previous will return null as displaying first item on load.
import React, { Component } from 'react'
import VideoPlayer from './Video'
import axios from 'axios'
export default class App extends Component {
constructor(props) {
super(props);
this._TogglePrev = this._TogglePrev.bind(this);
this._ToggleNext = this._ToggleNext.bind(this);
// app state
this.state = {
videos: [],
selectedVideo: null
}
}
componentDidMount() {
axios.get('http://localhost:5000/v1/video?id=287948764917205')
.then((result)=> {
var videos = result.data.payload
this.setState({
videos: videos,
selectedVideo: videos[0]
});
})
}
componentWillUnmount() {
this.serverRequest.abort()
}
// State transitions
_ToggleNext() {
console.log("something worked");
// take a copy of our state
const selectedVideo = this.state.selectedVideo;
// next video
var i = 0,
max = selectedVideo.length;
for (i; i < max; i += 1) {
if (selectedVideo[i]) {
return selectedVideo[i + 1];
}
}
//set our state
this.setState( selectedVideo );
console.log(selectedVideo)
}
_TogglePrev() {
console.log("something worked");
var current = this.state.selectedVideo;
var prev = current - 1;
if (prev < 0) {
prev = this.state.videos.length - 1;
}
// update our state
this.setState({ prev });
}
render() {
return (
<div className="App" style={{width: '100%', height: '100%'}}>
<div className="controls">
<button className="toggle toggle--prev" onClick={this._TogglePrev}>Prev</button>
<button className="toggle toggle--next" onClick={this._ToggleNext}>Next</button>
</div>
<VideoPlayer video={this.state.selectedVideo} />
</div>
)
}
}
The returned data
[
{ eventId: "287948764917205"
userName: "Jon Doe"
videoLink: "https://"https:s3.amazonaws.com/...""
userPhotoLink: "https://"https:s3.amazonaws.com/...""
},
{ eventId: "287948764917205"
userName: "Jane Thompson"
videoLink: "https://"https:s3.amazonaws.com/...""
userPhotoLink: "https://"https:s3.amazonaws.com/...""
}
]

Mistakes:
1. If you use return keyword inside for loop it will not only break the loop, it will return from that function also, so in these lines:
for (i; i < max; i += 1) {
if (selectedVideo[i]) {
return selectedVideo[i + 1];
}
}
this.setState( selectedVideo );
....
If if(selectedVideo[i]) will return true then it will break the loop and return from the function, so the lines after this for loop will never executes because of that return statement.
2. setState is a function and we need to pass an object (key-value pair, key will be the state variable names) in this, so you need to write it like this:
this.setState({ selectedVideo }); or this.setState({ selectedVideo: selectedVideo }); //both are same
Another way of writing the code by maintaining index:
1. Instead of maintaining selectedVideo in state variable maintain the index only, index of item of the array.
2. On click of next and prev button, increase or decrease the value of index and use that index to pass specific object of the state videos array to child component.
Like this:
import React, { Component } from 'react'
import VideoPlayer from './Video'
import axios from 'axios'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
videos: [],
selectedIndex: 0
}
this._TogglePrev = this._TogglePrev.bind(this);
this._ToggleNext = this._ToggleNext.bind(this);
}
componentDidMount() {
axios.get('http://localhost:5000/v1/video?id=287948764917205')
.then((result)=> {
var videos = result.data.payload
this.setState({
videos: videos,
selectedIndex: 0
});
})
}
componentWillUnmount() {
this.serverRequest.abort()
}
_ToggleNext() {
if(this.state.selectedIndex == this.state.videos.length - 1)
return;
this.setState(prevState => ({
selectedIndex: prevState.selectedIndex + 1
}))
}
_TogglePrev() {
if(this.state.selectedIndex == 0)
return;
this.setState(prevState => ({
selectedIndex: prevState.selectedIndex - 1
}))
}
render() {
let {selectedIndex, videos} = this.state;
return (
<div className="App" style={{width: '100%', height: '100%'}}>
<div className="controls">
<button className="toggle toggle--prev" onClick={this._TogglePrev}>Prev</button>
<button className="toggle toggle--next" onClick={this._ToggleNext}>Next</button>
</div>
<VideoPlayer video={videos[selectedIndex]} />
</div>
)
}
}

Use document.activeElement in order to get the currently focused element. Then, use nextElementSibling on order to get the next element then focus() just like thisdocument.activeElement.nextElementSibling.focus()
Full example:
export default function TextField() {
return (
<div
onKeyDown={(e:any)=>{
if (e.keyCode==13){
const active:any = document.activeElement
active.nextElementSibling.focus()
}
}}
>
<input/>
<input/>
<input/>
</div>
);
};

It's better to write in the constructor:
constructor(props) {
super(props);
this._TogglePrev.bind(this);
this._ToggleNext.bind(this);
// app state
this.state = {
videos: [],
selectedVideo: null,
selectedVideoIndex:0
}
}
and also change
_ToggleNext() {
console.log("something worked");
// take a copy of our state
const selectedVideo = this.state.selectedVideo;
// next video
var selectedVideoIndex = this.state.selectedVideoIndex; //i always starts with zero ????? you need also to save the index
max = selectedVideo.length;
for (selectedVideoIndex; selectedVideoIndex < max; selectedVideoIndex++) {
if (selectedVideo[selectedVideoIndex]) {
const retval = selectedVideo[selectedVideoIndex + 1];
this.setState( selectedVideoIndex+1 );
this.setState(retval );
return retval;
}
}
console.log(selectedVideo)
}

Related

ReactJS remove child element created through count

I have two components, a TrackSection(the Parent element) which has a button that creates a TrackItem(child) every time it is clicked. The child elements are built through a variable numTracks which increments every time the button is clicked. The add button works fine but i'm having issues deleting a TrackItem from the array. I tried referencing the track_items directly but it won't let me.
I'm very new to React and Frontend development. Any other tips would be appreciated!
TrackSection.js
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
numTracks: 0,
};
}
onAddTrack = () => {
this.setState({
numTracks: this.state.numTracks + 1,
});
};
onDeleteTrack = () =>{
//????
};
render() {
const track_items = [];
for (var i = 0; i < this.state.numTracks; i += 1) {
track_items.push(<TrackItem key={i} id={i} onDeleteTrack = {this.onDeleteTrack(i)}/>);
}
return (
<div>
<Button onClick={this.onAddTrack}>
+new track
</Button>
{track_items}
</div>
);
}
}
TrackItem.js
class TrackItem extends Component{
constructor(props){
super(props);
this.state = {
id: this.props.id,
name: '',
}
}
render(){
var onDeleteTrack = this.props.onDeleteTrack
return(
<Grid container direction="row">
<Grid item direction="column">
//Dummy
</Grid>
<button onClick={() => onDeleteTrack(this.props.id)}>Delete</button>
</Grid>
);
}}
Issue
You are using an array index as the React key, and the id. When you remove an element from the array you may remove it from the array, but since the items shift up to fill the "hole" now all the elements in the array have incorrect "id"/index values.
Solution
Don't use a mapped array index as a React key.
Example solution uses an incrementing id, as before, but also stores the array in state. This allows you to consistently increment the id key and retain a static id with each element.
class TrackItem extends Component {
constructor(props) {
super(props);
this.state = {
id: this.props.id,
name: ""
};
}
render() {
var onDeleteTrack = this.props.onDeleteTrack;
return (
<Grid container direction="row">
<Grid item direction="column">
//Dummy
</Grid>
<button onClick={() => onDeleteTrack(this.props.id)}>Delete {this.props.id}</button>
</Grid>
);
}
}
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
tracks: [],
id: 0,
};
}
onAddTrack = () => {
this.setState(prevState => ({
tracks: [...prevState.tracks, prevState.id],
id: prevState.id + 1,
}));
};
onDeleteTrack = (id) =>{
this.setState(prevState => ({
tracks: prevState.tracks.filter(el => el !== id)
}))
};
render() {
return (
<div>
<button onClick={this.onAddTrack}>
+new track
</button>
{this.state.tracks.map(track => (
<TrackItem key={track} id={track} onDeleteTrack = {this.onDeleteTrack}/>
))}
</div>
);
}
}
Be careful about doing too much logic in your render function, as your current solution would recreate all the TrackItem's every time you add a new item. So React can't do optimization magic.
Second remark, now you are just having a counter, so removing a element in the middle would probably not have the effect you are looking for. I assume the track items will be having some data to them. Like name, etc. So just store those values in the state and render each item.
Here is a sample solution, modify for your needs:
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
tracks: []
};
}
onAddTrack = () => {
// Probably not the best way to create a id
const randomId = Math.random().toString();
const newTrack = {
id: randomId,
name: "Some name" + randomId
};
const newTracks = [
// the tracks we allready have added
...this.state.tracks,
// add a new track to the end
newTrack
];
// Replace state
this.setState({
tracks: newTracks
});
};
onDeleteTrack = (id) => {
// Keeps all tracks that don't match 'id'
const tracksWithOutDeleted = this.state.tracks.filter(
(track) => track.id !== id
);
// Replace the tracks, so now its gone!
this.setState({
tracks: tracksWithOutDeleted
});
};
render() {
return (
<div>
<button onClick={this.onAddTrack}>+new track</button>
{
// Loop over tracks we have in state and render them
this.state.tracks.map((track) => {
return (
<TrackItem
id={track.id}
name={track.name}
onDeleteTrack={this.onDeleteTrack}
/>
);
})
}
</div>
);
}
}
And the TrackItem.js:
class TrackItem extends Component {
render() {
const { onDeleteTrack, id, name } = this.props;
return (
<>
<button onClick={() => onDeleteTrack(id)}>Delete {name}</button>
</>
);
}
}

how to merge local setState list to Redux list into one list - redux react

Here i have a difficult situation. I have a locationData json in JobsPanel component which is saving location details based on one id(jobId). Now, in my component i have a part 'Configured Location' where i am calling the saved location data and make a setState list ('configuredList') from that json. Now, i have one more part in my application preliminary locations data using redux action calling other api and save into a list 'conLocations'.
Now, i am adding one location item 'conLocation' list (redux state) to 'configuredList'(setState) and updating the changes. It is working fine but last added item showing two times. After trial, i do understand that i have rendered two mapped list. How to merge that into one ? I have done so far this.
configLocation function where i am retrieving last saved location from locationData json.
/** Currently initialize and configure configuredList for retrieving existing job's location data */
configLocation(locationData) {
let configuredList = [];
if (locationData.locations.locationDetails != null && locationData.locations.locationDetails != undefined) {
locationData.locations.locationDetails.map(item => {
let listitem = { ...item };
configuredList.push(listitem);
});
}
this.setState({ configuredList });
}
getLocationData function where i am merging two list that retrieved list and conLocations list and i am calling this function to other component where save changes or update changes operation is happening. It is working fine.
getLocationData() {
let saveableLocationlist = [];
if (this.props.conLocations != null && this.state.configuredList != null) {
const { configuredList } = this.state;
const { conLocations } = this.props;
let totalList = configuredList.concat(conLocations);
saveableLocationlist = totalList;
}
const locationData = {
locationDetails: saveableLocationlist
}
return locationData;
}
here you can see i am updating the locationData json . By calling this function in jobspanel that updated locationData json is now available for my component in 'configLocation' function.
My component code:
export class NewLocationPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
configuredList: [],
chkitems: []
};
this.configLocation = this.configLocation.bind(this);
this.togglePanel = this.togglePanel.bind(this);
this.handleClick = this.handleClick.bind(this);
this.allLocations = this.allLocations.bind(this);
this.clearall = this.clearall.bind(this);
this.getLocationData = this.getLocationData.bind(this);
this.handleRemove = this.handleRemove.bind(this);
this.removeConfigLocation = this.removeConfigLocation.bind(this);
this.removeLocationAll = this.removeLocationAll.bind(this);
this.handleChecklocation = this.handleChecklocation.bind(this);
this.handleCheckedAdded = this.handleCheckedAdded.bind(this);
this.handleCheckedRemove = this.handleCheckedRemove.bind(this);
this.handleActionButton = this.handleActionButton.bind(this);
}
componentDidMount() {
this.props.loadData();
if (this.props.locationData != null && this.props.locationData != undefined) {
this.configLocation(this.props.locationData);
}
}
componentDidUpdate(prevProps, prevState) {
if ((prevProps.jobId != this.props.jobId || prevProps.locationData != this.props.locationData)) {
this.configLocation(this.props.locationData);
}
}
//other codes
/** Currently initialize and configure configuredList for retrieving existing job's location data */
configLocation(locationData) {
let configuredList = [];
if (locationData.locations.locationDetails != null && locationData.locations.locationDetails != undefined) {
locationData.locations.locationDetails.map(item => {
let listitem = { ...item };
configuredList.push(listitem);
});
}
this.setState({ configuredList });
}
/** updating locationData by saving changes - calling this function into jobsPanel */
getLocationData() {
let saveableLocationlist = [];
if (this.props.conLocations != null && this.state.configuredList != null) {
const { configuredList } = this.state;
const { conLocations } = this.props;
let totalList = configuredList.concat(conLocations);
saveableLocationlist = totalList;
}
const locationData = {
locationDetails: saveableLocationlist
}
return locationData;
}
//other codes
render() {
//const{configuredList} = this.state;
const _labels = store.getLabels();
let collapsedToggle = this.props.open ? 'collapsed' : ''
return (
{this.state.open ? (
<div className="panel-body">
<div className="row grid-divider">
<div className="col-sm-6">
<div className="col-padding">
<div className="pos-div"><h4>Configured Location</h4>
<div><table className="table configTableColor"><thead>{this.state.configuredList.map((locc, index) => <tr key={index}><th><input type="checkbox" onClick={() => this.handleCheckedRemove(locc.mruCode)} /><label></label></th><th className="configLocationInfo">{locc.mruCode} - {_labels[locc.division]} - {locc.country}</th><th className="text-right"><img alt="DeleteIcon" onClick={() => { this.removeConfigLocation(index) }} className="deleteIconStyle" src="img/delete_large_active.png" /></th></tr>)}</thead>
<tbody>
{this.props.conLocations.map((loct, index) => <tr key={index}>
<td><input type="checkbox" /><label></label></td>
<td className="configLocationInfo">{loct.mruCode} - {_labels[loct.division]} - {loct.country}</td>
<td className="text-right"><img alt="DeleteIcon" onClick={() => this.handleRemove(loct.mruCode)} className="deleteIconStyle" src="img/delete_large_active.png" /></td>
</tr>
)}
</tbody></table></div>
</div>
</div>
</div>
</div>) : null}
</div>
);
}
}
const mapStateToProps = state => {
return {
location: state.locationRed.location,
conLocations: state.locationRed.conLocations,
isChecked: state.locationRed.isChecked
};
};
const mapDispatchToProps = (dispatch) => {
return {
loadData: () => { dispatch(loadData()) },
addLocation: (mruCode) => { dispatch(addLocation(mruCode)) },
addAllLocation: () => { dispatch(addAllLocation()) },
removeLocation: (mruCode) => { dispatch(removeLocation(mruCode)) },
removeAllLocation: () => { dispatch(removeAllLocation()) },
checkboxState: (mruCode) => { dispatch(checkboxState(mruCode)) },
checkedLocation: () => { dispatch(checkedLocation()) }
}
}
export default connect(mapStateToProps, mapDispatchToProps, null, { withRef: true })(NewLocationPanel);
As you can see i am rendering two list. How to merged into one?
Jobs Panel component where i am initialize and saving locationData details
import React from 'react';
import ReactDOM from 'react-dom';
import LocationPanel from '../panels/NewLocationPanel';
class JobsPanelComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
jobDetailJson: this.props.jobDetailJson
};
this.setLocationPanelRef = cRef =>{this.locationPanel = cRef;};
}
componentWillUnmount() {
this.clearStates();
this.clearRefs();
this.clearBindings();
}
clearStates() {
this.state.jobDetailJson = null;
}
clearRefs(){
this.locationPanel = null;
}
clearBindings(){
this.setLocationPanelRef = null;
}
componentWillMount() {
this.state.jobDetailJson = this.props.jobDetailJson;
}
componentWillReceiveProps(nextProps) {
this.state.jobDetailJson = nextProps.jobDetailJson;
}
saveJobData(jobData){
var locationData = null;
if(some conditions){
locationData = this.locationPanel.getWrappedInstance().getLocationData();
}
//more other lines not related to my mine
}
render(){
var locationDataJson= null;
if(this.state.jobDetailJson != null){
locationDataJson =this.state.jobDetailJson;
}
return(<div className="panel-group" id="jobsPanelGroup">
<LocationPanel ref={this.setLocationPanelRef} locationData ={locationDataJson} jobDetailJson={this.state.jobDetailJson} versionId={versionId} jobName={jobName} jobId={jobId} isForViewOnly={this.props.isForViewOnly} parentJobId={this.props.parentJobId} title="Location"/>
//More coded lines for other things not related to my part
);
}
}
My application flow will be like - Configured Location(initial) configuredList -> conLocations (redux list) -> conLocations(add item) -> Configured Location(intermediate) configuredList + added item(conLocations) -> save changes -> Configured Location(final) - merged List
save changes /update locationData everything is in Jobs Panel but working fine. There is no problem. How to make changes in my component.
The mapStateToProps function is passed both the redux state and the component's props. So you can combine your locations from redux and from props inside mapStateToProps:
// destructuring only the things we need from state (locationRed) and props (locationData)
const mapStateToProps = ({ locationRed }, { locationData }) => ({
location: locationRed.location,
// get a merged set
conLocations: [...locationRed.conLocations, ...(locationData.locations.locationDetails || [])],
isChecked: locationRed.isChecked
})
With this setup you could most likely eliminate your configuredList state and related update functions, your componentDidUpdate function and just render from props.conLocations instead of from state and props in two separate loops.
You could also dedupe locations or do any job id checks you need inside of mapStateProps when merging your lists. If it starts to gets a bit complicated in your mapStateToProps, you could take a look at memoized selectors like reselect that would make that a lot nicer.

How to render extra components inside a component after button click?

I have blogposts that I need to render. The first 4 are shown. When clicking on the button underneath it, two more need to show up. When the button is clicked again, two more need to show up and so on.
Unfortunately, I am not able to do so.
Here is my code:
import React from 'react';
import axios from 'axios';
import Blogpost from './Blogpost.js';
class BlogpostReader extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
blogposts: [],
};
}
componentDidMount() {
// Loading all blogposts in state
}
renderBlogpost(i) {
// Render one blogpost
}
//This function has to be replaced by one that renders extra blogposts
showAlert(){
alert("Im an alert");
}
render() {
const {error, isLoaded} = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
for (let i = 1; i < this.state.blogposts.length && i < 5; i++) {
this.state.renderedBlogposts.push(
<div key={this.state.blogposts[i].id} className="col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 whole-blogpost">
{this.renderBlogpost(this.state.blogposts[i])}
</div>)
}
return (
<div>
<div className="row">
{this.state.renderedBlogposts}
</div>
<div className="centered-button">
<button className="styled-button" onClick={this.showAlert}>Meer laden</button>
</div>
</div>
);
}
}
}
export default BlogpostReader;
How can I show extra blogposts after clicking the button? Please help me out!
You can do something like this :
import React from 'react';
import axios from 'axios';
import Blogpost from './Blogpost.js';
class BlogpostReader extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
blogposts: [],
count:5
};
}
componentDidMount() {
// Loading all blogposts in state
if(blogposts.length<5){
this.setState({
count:blogposts.length
})
}
}
renderBlogpost(i) {
// Render one blogpost
}
renderBlogposts(){
const blogposts=[];
const count=this.state.count;
for (let i = 1; i < count; i++) {
blogposts.push(
<div key={this.state.blogposts[i].id} className="col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 whole-blogpost">
{this.renderBlogpost(this.state.blogposts[i])}
</div>)
}
return blogposts;
}
//This function has to be replaced by one that renders extra blogposts
addMore=()=>{
let newCount=this.state.count + 2;
if(this.state.count===this.state.blogposts.length) return;
if(this.state.count+1 === this.state.blogposts.length){
newCount=this.state.count+1
}
this.setState({
count:newCount
})
}
render() {
const {error, isLoaded} = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
}
return (
<div>
<div className="row">
{this.renderBlogposts()}
</div>
<div className="centered-button">
<button className="styled-button" onClick={this.addMore}>Meer laden</button>
</div>
</div>
);
}
}
}
Oh no honey. In React the recommended approach is to make things as declarative as possible. Which means that instead of imperatively pushing items onto an array and then render that array you can just render a slice of the array.
I.e. try something like this
class BlogpostReader extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
blogposts: [], // this will contain all posts
postsToShow: 2, // a simple number controls how many posts are shown
};
}
componentDidMount() {
// Loading all blogposts in state
}
increasePostsShown() {
this.setState(({ postsToShow }) => {
postsToShow: postsToShow + 1;
});
}
render() {
const { error, isLoaded, blogposts, postsToShow } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
}
const postsShown = blogposts.slice(0, postsToShow); // get only the ones you want to show
return (
<div>
<div className="row">
{postsShown.map(blog => (
<div>{blog}</div> {/* or display them however you like */}
))}
</div>
<div className="centered-button">
<button className="styled-button" onClick={this.increasePostsShown}>
Meer laden
</button>
</div>
</div>
);
}
}
Is your blogpost array containing already all the blog posts? My suggestion would be that everytime the user clicks on the button, you increment a value from the state.
this.state = {
error: null,
isLoaded: false,
blogposts: [],
nbPostToDisplay: 4
};
In your loop:
for (let i = 0 /* start at 0, not 1 */; i < this.state.blogposts.length && i < nbPostToDisplay; i++) {
Some function to increment:
function incrementNbPosts() {
this.setState(prevState => return ({nbPOstsToDisplay: prevState.nbPostsToDisplay + 2});
}
Use function above in your button callback. This will trigger a re-render of your component.
IMPORTANT: do not forget to bind your functions in the construrtor or (better) use ES6 notation.
I would better keep things simple, so that button would just set new state.posts with +1 post, thus triggering render(), that in turn will render added element.
addPost = () => {
...
this.setState({
posts: [...posts, { id: posts.length + 1 }]
});
};
renderPosts = () => {
...
};
render() {
return (
<div>
<button onClick={this.addPost}>Add</button>
{this.renderPosts()}
</div>
);
}
Made a quick sandbox illustrating provided code.
https://codesandbox.io/embed/vjlp468jk7
Here's all you need. I also cleaned up your code a little bit
class BlogpostReader extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
blogposts: [],
limit: 4
};
this.showMore = this.showMore.bind(this);
this.renderBlogpost = this.renderBlogpost.bind(this);
}
componentDidMount() {
// Loading all blogposts in state
}
renderBlogpost(i) {
// Render one blogpost
}
//This function has to be replaced by one that renders extra blogposts
showMore() {
this.setState(state => ({ limit: state.limit + 2 }));
}
render() {
const { error, isLoaded, blogpost, limit } = this.state;
if (error) {
return <div>Error: {error.message}</div>);
}
if (!isLoaded) {
return <div>Loading...</div>;
}
return (
<div>
<div className="row">
{
blogposts.map((post, index) => {
if (index + 1 !== limit) {
return (
<div key={post.id} className="col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 whole-blogpost">
{ this.renderBlogpost(post) }
</div>
);
}
})
}
</div>
<div className="centered-button">
<button className="styled-button" onClick={this.showMore}>Meer laden</button>
</div>
</div>
);
}
}
If you want to also make showMore to accept any number of posts, you can do this...
showMore(value = 2) {
this.setState(state => ({ limit: state.limit + value }));
}
Then now you can call it with any number of posts you want. If you don't specify any value, the limit will be incremented by 2.
UPDATE
Since you've mentioned that you have to start when index is 1, then you can update your blogposts.map in the render like this
{
blogposts.map((post, index) => {
if (index && index !== limit) {
// the condition above ensures that the posts at index 0, and also when index equals limit, are not returned
// the rest of the code goes here...
}
})
}
After doing that, you can set limit to 5 in the constructor if you want to show only 4 entries at first load.
The following was the working code:
class BlogpostReader extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
blogposts: [],
limit: 5,
start: 1
};
this.showMore = this.showMore.bind(this);
}
componentDidMount() {
// Loading all blogposts into state
}
renderBlogpost(i) {
// Render a single blogost
}
showMore(){
this.setState(state => ({
start: state.limit,
limit: state.limit + 2
}));
}
render() {
const {error, isLoaded, limit} = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
var startedAt = this.state.start
for (startedAt; startedAt < this.state.blogposts.length && startedAt < limit; startedAt++) {
this.state.renderedBlogposts.push(
<div key={this.state.blogposts[startedAt].id} className="col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 whole-blogpost">
{this.renderBlogpost(this.state.blogposts[startedAt])}
</div>
)
}
return (
<div>
<div className="row">
{this.state.renderedBlogposts}
</div>
<div className="centered-button">
<button className="styled-button" onClick={this.showMore}>Meer laden</button>
</div>
</div>
);
}
}
}

I'm unable to change the array of an existing state

I'm trying to change one value inside a nested state.
I have a state called toDoItems that is filled with data with componentDidMount
The issue is that changing the values work and I can check that with a console.log but when I go to setState and then console.log the values again it doesn't seem like anything has changed?
This is all of the code right now
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
toDoItems: null,
currentView: "AllGroup"
};
}
componentDidMount = () => {
fetch("/data.json")
.then(items => items.json())
.then(data => {
this.setState({
toDoItems: [...data],
});
})
};
changeToDoItemValue = (givenID, givenKey, givenValue) => {
console.log(this.state.toDoItems);
let newToDoItems = [...this.state.toDoItems];
let newToDoItem = { ...newToDoItems[givenID - 1] };
newToDoItem.completedAt = givenValue;
newToDoItems[givenID - 1] = newToDoItem;
console.log(newToDoItems);
this.setState({
toDoItems: {newToDoItems},
})
console.log(this.state.toDoItems);
};
render() {
if (this.state.toDoItems) {
// console.log(this.state.toDoItems[5 - 1]);
return (
<div>
{
this.state.currentView === "AllGroup" ?
<AllGroupView changeToDoItemValue={this.changeToDoItemValue}/> :
<SpecificGroupView />
}
</div>
)
}
return (null)
};
}
class AllGroupView extends Component {
render() {
return (
<div>
<h1 onClick={() => this.props.changeToDoItemValue(1 , "123", "NOW")}>Things To Do</h1>
<ul className="custom-bullet arrow">
</ul>
</div>
)
}
}
So with my console.log I can see this happening
console.log(this.state.toDoItems);
and then with console.log(newToDoItems)
and then again with console.log(this.state.toDoitems) after setState
State update in React is asynchronous, so you should not expect updated values in the next statement itself. Instead you can try something like(logging updated state in setState callback):
this.setState({
toDoItems: {newToDoItems},// also i doubt this statement as well, shouldn't it be like: toDoItems: newToDoItems ?
},()=>{
//callback from state update
console.log(this.state.toDoItems);
})

Change Specific Property of the State

I am learning ReactJS and needless to say I am an absolute beginner! I am trying to change a specific property in the array of objects which belongs to state. Every object has two properties: name and active. active values are false by default. When I click on the item, I want to change this item's active value to true.
My array is shown inside of the list element and every list element has onClick={() => props.onChangeSelected(lang.name)} method. onChangeSleceted method goes to handleChangeSelected(name) function, however, I couldn't figure out what to write inside of this function.
class Loading extends React.Component {
constructor(props) {
super(props);
this.state = {
text: 'Loading'
};
}
componentDidMount() {
const stopper = this.state.text + '...';
this.interval = window.setInterval(() => {
this.state.text === stopper
? this.setState(() => ({ text: 'Loading' }))
: this.setState((prevState) => ({ text: prevState.text + '.' }))
}, 300)
}
componentWillUnmount() {
window.clearInterval(this.interval);
}
render() {
return (
<p>
{this.state.text}
</p>
)
}
}
function LanguageList (props) {
return (
<div>
<h3>Choose your favorite:</h3>
<ul>
{props.list.map((lang) => (
<li key={lang.name} onClick={() => props.onChangeSelected(lang.name)}>
<span>{lang.name}</span>
</li>
))}
</ul>
</div>
)
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
languages: [
{
name: 'all',
active: true
},
{
name: 'javascript',
active: false
},
{
name: 'ruby',
active: false
},
{
name: 'python',
active: false
}
]
}
this.handleChangeSelected = this.handleChangeSelected.bind(this)
}
handleChangeSelected(name) {
this.setState((currentState) => {
const lang = currentState.languages.find((lang) => lang.name === name)
return {}
})
}
render() {
return (
<div>
<LanguageList
list={this.state.languages}
onChangeSelected={this.handleChangeSelected}
/>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
)
</script>
You can do it in a number of ways. All you need to make sure is that you aren't mutating the original state array
handleChangeSelected(name) {
this.setState((currentState) => {
return { languages: currentState.languages.map((lang) => {
if(lang.name === name) {
return {...lang, active: true};
}
return lang;
});
})
}
Try this?
handleChangeSelected(name){
// Find matching element in state
var temp = this.state.languages;
for (var i = 0; i < temp.length; i++){
if (temp[i]["name"] === name){
temp[i]["active"] = true;
}
}
this.setState({
languages: temp
});
}
As listed in the React docs, they recommend creating a new object when calling the setState function. This is of course talking about the updater function syntax (this.setState((prevState, props) => {return {...};});), which I assume the same logic is applied to the syntax used above (passing an object into set state)
The first argument [to setState] is an updater function with the signature:
(prevState, props) => stateChange
(prevState, props) => stateChange prevState is a reference to the
previous state. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
prevState and props.

Categories