I cant update my component state.. Do somebody understand how it fix? - reactjs

I cant understand why my renderMovies() function dont wanna update my component state.data and i cant render component on my screen ?!
Everithing goes ok until renderMovies function.. I think this.setState(newState) in my fetchPostData function is working incorrect... Do somebody know how to fix it? I tried different ways but i cant solve this issue.
class Movies extends React.Component {
constructor(props) {
super(props)
this.state = { data: {}}
this.fetchPostData = this.fetchPostData.bind(this)
this.renderMovies = this.renderMovies.bind(this)
this.populatePageAfterFetch = this.populatePageAfterFetch.bind(this)
}
componentDidMount() {
this.fetchPostData()
}
fetchPostData() {
fetch(`http://localhost/reacttest/wp-json/wp/v2/movies?per_page=100`)
.then(response => response.json())
.then(myJSON => {
let objLength = Object.keys(myJSON).length
let newState = this.state;
for (let i = 0; i < objLength; i++) {
let objKey = Object.values(myJSON)[i].title.rendered;
// console.log(objKey)
let currentMovie = newState.data[objKey];
currentMovie = {};
currentMovie.name = Object.values(myJSON)[i].title.rendered;
currentMovie.description = Object.values(myJSON)[i].content.rendered;
currentMovie.featured_image = Object.values(myJSON)[i]['featured_image_url'];
currentMovie.genre = Object.values(myJSON)[i]['genre'];
}
this.setState(newState)
})
}
renderMovies() {
if(this.state.data) {
const moviesArray = Object.values(this.state.data)
console.log(moviesArray)
return Object.values(moviesArray).map((movie, index) => this.populatePageAfterFetch(movie, index))
}
}
populatePageAfterFetch(movie, index) {
if (this.state.data) {
return (
<div key={index} index={index}>
<h2>{movie.title}</h2>
<h3>{movie.genre}</h3>
<p>{movie.description}</p>
</div>
)
}
}
render() {
return (
<div>
<h1>Movies</h1>
<div>{this.renderMovies()}</div>
</div>
)
}
}
When i try to console.log(moviesArray) it show me:

Issue
You save current state into a variable named newState, never update it, and then save the same object reference back into state. React state never really updates.
let newState = this.state;
for (let i = 0; i < objLength; i++) {
...
}
this.setState(newState);
Additionally you mutate state
let currentMovie = newState.data[objKey];
currentMovie = {};
But this doesn't work either since initial state is an empty object so newState.data[objKey] is aways undefined. (so nothing is ever actually mutated)
Solution
It appears as though you intended to map the myJSON data/values into movie objects to update this.state.data. May I suggest this solution. The key is to always create new object references for any object you update.
fetchPostData() {
fetch(`http://localhost/reacttest/wp-json/wp/v2/movies?per_page=100`)
.then(response => response.json())
.then(myJSON => {
this.setState(prevState => ({
// array::reduce over the JSON values
data: Object.values(myJSON).reduce((movies, movie) => {
// compute movie key
const name = movie.title.rendered;
return {
...movies,
[name]: {
...movies[name], // copy any existing movie properties
// merge in new/updated properties
name,
description: movie.content.rendered,
featured_image: movie.featured_image_url,
genre: movie.genre,
},
}
}, { ...prevState.data }) // use previous state as initial value for reduce
}))
})
}

Related

setState not returned from render when using Axios

I'm using axios to get data from an endpoint. I'm trying to store this data inside the state of my React component, but I keep getting this error:
Error: Results(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I've struggled with many approaches: arrow functions etc., but without luck.
export default class Map extends Component {
constructor() {
super();
this.state = {
fillColor: {},
selectedCounty: "",
dbResponse: null,
};
}
getCounty(e) {
axios.get("/getWeatherData?county=" + e.target.id)
.then((response) => {
this.setState(prevState => {
let fillColor = {...prevState.fillColor};
fillColor[prevState.selectedCounty] = '#81AC8B';
fillColor[e.target.id] = '#425957';
const selectedCounty = e.target.id;
const dbResponse = response.data;
return { dbResponse, selectedCounty, fillColor };
})
}).catch((error) => {
console.log('Could not connect to the backend');
console.log(error)
});
}
render() {
return (
<div id="map">
<svg>big svg file</svg>
{this.state.selectedCounty ? <Results/> : null}
</div>
)
}
I need to set the state using prevState in order to update the fillColor dictionary.
Should this be expected? Is there a workaround?

Modify a property of an element that is inside of react state

I have this program that brings an article from my data base in componentDidMount(), fragmentedArticle() grabs each word and put it in this.state.fragmented and each word is put it in a span tag in this.state.fragmentedTags
I print the article in grey color text, but I want to change the style color property of the text (with a setTimeout every 1000 milliseconds) but I don't know if it's posible to changed a property of a tag that is save it in the react state.
import React, { Component } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
export default class ArticleDetails extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
title: '',
article: '',
date: new Date(),
lenguages: [],
articles: [],
fragmented: [],
fragmentedTags: [],
showSpans: false,
counterSpaces: 0,
}
this.deleteArticle = this.deleteArticle.bind(this);
this.fragmentedArticle = this.fragmentedArticle.bind(this);
this.coloredArticle = this.coloredArticle.bind(this);
}
componentDidMount() {
this.setState({
id: this.props.match.params.id
})
// get individual exercise.
axios.get('http://localhost:5000/articles/'+ this.props.match.params.id)
.then(response => {
this.setState({
title: response.data.title,
article: response.data.article,
duration: response.data.duration,
date: new Date(response.data.date)
})
})
.catch(function (error) {
console.log(error);
})
// get all lenguages.
axios.get('http://localhost:5000/lenguages/')
.then(response => {
if (response.data.length > 0) {
this.setState({
lenguages: response.data.map(lenguage => lenguage.lenguage),
})
}
}).catch( error => console.log(error) )
}
deleteArticle( id ) {
axios.delete( 'http://localhost:5000/articles/' + id )
.then( res => console.log( res.data ) );
this.setState({
articles: this.state.articles.filter( el => el._id !== id )
}
)
}
fragmentedArticle = () => {
let length = this.state.article.length;
let word = [];
let fragmentedArticle = [];
let counter = 0;
let p1, p2 = 0;
for (let x = 0; x <= length; x++) {
word[x] = this.state.article[x];
if( this.state.article[x] === ' ' || this.state.article[x] === "\n" ){
p2 = x;
fragmentedArticle[counter] = word.join('').substr(p1,p2);
p1 = p2
p2 = 0;
counter++;
}
}
// we save each word
this.setState({
fragmented: fragmentedArticle,
counterSpaces: counter,
showSpans: !this.state.showSpans,
})
// we save each word wrapped in a span tag with a property of color grey.
this.setState( prevState => ({
fragmentedTags: prevState.fragmented.map( (name, index) =>
<span key={ index } style={{color:'grey'}} >{name}</span>
)
}))
}
coloredArticle = () => {
console.log(this.state.fragmentedTags[0].props.style.color);
// I see the actual value color style property of the span tag (grey) but I want to change it on green from the this.state.fragmentedTags[0] to the last word within a x period of time with the setTimeout js method.
// this code bellow change the color but not one by one.
this.setState( prevState => ({
fragmentedTags:
// map all the elements
prevState.fragmented.map( (name, index) =>
// with a delay of 1500 milliseconds
setTimeout(() => {
<span key={ index } style={{color:'green'}} >{name}</span>
}, 1500)
)
})
)
}
render(props) {
const displaySpan = this.state.showSpans ? 'inline-block' : 'none';
const {fragmentedTags} = this.state
return (
<div>
<h6>{ this.state.title }</h6>
{/* this show/hide the article text */}
<p onClick={ this.fragmentedArticle }>Show</p>
{/* I want to changed the text color one by one within a period of time (velocity, setTimeout) */}
<p onClick={ this.coloredArticle }>Play</p>
{/* Show us the full article (each word wrapped in a span with its property) */}
<div style={{ display:displaySpan }}>
{ fragmentedTags }
</div>
</div>
)
}
}
You shouldn't be transforming state like that. It gets very difficult to debug your application and makes it much more difficult to do simple things.
Download your articles and save them into state but if you need to make any other changes save it into a new part of state rather than overwriting current state. Most likely you do not need to save transformations into state though.
To answer your question, I would set a timestamp for each article and once its downloaded set a timer that will rerender the article with the new changes if sufficient time has passed.

How to use a method in render reactjs?

i have a method set_data which is used to set data based on id. I know it could be easy to call this set_data in componentdidupdate when id changes. However in doing so it doesnt set some state variables in the parent component.
To get rid of that want to call set_data method in render . However since this set_data method sets state of data it enters into an infinite loop in render . Also cannot provide a condition (like prevprops.id!== this.props.id) to execute set_data method.
To prevent it thought of using this set_data method not to set state at all. and can call this set_data method in render.
Below is the code,
export default class child extends React.Component {
state = {
query: '',
data: null,
};
empty_id = 0xffffffff;
componentDidMount() {
this.set_open_data();
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.set_data();
}
}
set_data = () => {
if (!this.props.info) {
return;
}
if (this.props.id === this.empty_id) {
this.setState({data: null});
return;
}
let data = {
info: [],
values: [],
};
const info = this.props.info;
for (let i=0, ii=info.length; i < ii; i++) {
if (info[i].meshes.includes(this.props.id)) {
const info = info[i].info;
const values = info[i].values;
data = {
info: typeof info === 'string' ? info.split('\r\n') : [],
values: values ? values : [],
};
break;
}
}
this.setState({data: this.filter_data(data, this.state.query)});
};
render = () => {
const shown_data= this.state.data;
/* i want to call set_data method here*/};}
Could someone help me solve this. Thanks.
You can't call setData there, because that would be anti-pattern. It will trigger a loop that will continuously render as well as keeps setting state.
You can probably rewrite the component this way:
export default class child extends React.Component {
state = {
query: ''
};
empty_id = 0xffffffff;
componentDidMount() {
this.set_open_data();
}
set_data = () => {
let data = {};
if (!this.props.info) {
return data;
}
if (this.props.id === this.empty_id) {
return data;
}
let data = {
info: [],
values: [],
};
const info = this.props.info;
for (let i=0, ii=info.length; i < ii; i++) {
if (info[i].meshes.includes(this.props.id)) {
const info = info[i].info;
const values = info[i].values;
data = {
info: typeof info === 'string' ? info.split('\r\n') : [],
values: values ? values : [],
};
break;
}
}
data = this.filter_data(data, this.state.query);
return data;
};
render = () => {
const shown_data= this.state.data;
const data = this.set_data();
/* i want to call set_data method here*/};}
In this, we are not setting data in the state. For every new ID, it will get new data and will compute it from render thereby avoiding antipattern. I have also removed componentDidMount, since we are doing computation in render. Note: This solution means taking away data from the state, if you are not using data anywhere before render, this will work.
Let me know if this helps.

Lifecycle hooks - Where to set state?

I am trying to add sorting to my movie app, I had a code that was working fine but there was too much code repetition, I would like to take a different approach and keep my code DRY. Anyways, I am confused as on which method should I set the state when I make my AJAX call and update it with a click event.
This is a module to get the data that I need for my app.
export const moviesData = {
popular_movies: [],
top_movies: [],
theaters_movies: []
};
export const queries = {
popular:
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=",
top_rated:
"https://api.themoviedb.org/3/movie/top_rated?api_key=###&page=",
theaters:
"https://api.themoviedb.org/3/movie/now_playing?api_key=###&page="
};
export const key = "68f7e49d39fd0c0a1dd9bd094d9a8c75";
export function getData(arr, str) {
for (let i = 1; i < 11; i++) {
moviesData[arr].push(str + i);
}
}
The stateful component:
class App extends Component {
state = {
movies = [],
sortMovies: "popular_movies",
query: queries.popular,
sortValue: "Popularity"
}
}
// Here I am making the http request, documentation says
// this is a good place to load data from an end point
async componentDidMount() {
const { sortMovies, query } = this.state;
getData(sortMovies, query);
const data = await Promise.all(
moviesData[sortMovies].map(async movie => await axios.get(movie))
);
const movies = [].concat.apply([], data.map(movie => movie.data.results));
this.setState({ movies });
}
In my app I have a dropdown menu where you can sort movies by popularity, rating, etc. I have a method that when I select one of the options from the dropwdown, I update some of the states properties:
handleSortValue = value => {
let { sortMovies, query } = this.state;
if (value === "Top Rated") {
sortMovies = "top_movies";
query = queries.top_rated;
} else if (value === "Now Playing") {
sortMovies = "theaters_movies";
query = queries.theaters;
} else {
sortMovies = "popular_movies";
query = queries.popular;
}
this.setState({ sortMovies, query, sortValue: value });
};
Now, this method works and it is changing the properties in the state, but my components are not re-rendering. I still see the movies sorted by popularity since that is the original setup in the state (sortMovies), nothing is updating.
I know this is happening because I set the state of movies in the componentDidMount method, but I need data to be Initialized by default, so I don't know where else I should do this if not in this method.
I hope that I made myself clear of what I am trying to do here, if not please ask, I'm stuck here and any help is greatly appreciated. Thanks in advance.
The best lifecycle method for fetching data is componentDidMount(). According to React docs:
Where in the component lifecycle should I make an AJAX call?
You should populate data with AJAX calls in the componentDidMount() lifecycle method. This is so you can use setState() to update your component when the data is retrieved.
Example code from the docs:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
Bonus: setState() inside componentDidMount() is considered an anti-pattern. Only use this pattern when fetching data/measuring DOM nodes.
Further reading:
HashNode discussion
StackOverflow question

How to update one part of state with setState

i'm trying to use setState to update one property of a sub object of the state. What is the correct way to do this? I want to access the state and define which part I want to update, as opposed to update the entire state with a new state. Hope that makes sense...
class BooksApp extends React.Component {
state = {
books: []
}
componentDidMount() {
BooksAPI.getAll().then((books) => {
this.setState({books})
})
}
selectStateUpdate = (book,shelf) => {
this.updateShelf(book, shelf);
}
updateShelf = (book, shelf) => {
BooksAPI.update(book, shelf)
.then(() => {
for (var i=0; this.state.length < i; i++) {
if (this.state.title === book.title) {
this.setState({
books[i].shelf: book.shelf
})
}
}
})
}
Try to change your state changing part to:
this.setState({
books: this.state.books.map((item, index) =>
index === i ? {...item, shelf: book.shelf} : item
)
})

Resources