Setting state in getDetails() occurs infinite loop - javascript

this.state = {
data: [],
details: []
}
componentDidMount() {
this.getDetails()
this.getCountries()
}
getCountries() {
Utils.rest('POST', 'https:///api-spot-get-all', {
country: '',
windProbability: ''
}).then(async (r) => {
const data = await r.json();
this.setState({
data: data.result
})
}).catch(err => {
console.log(err.message);
});
}
`getDetails() {
return new Promise((resolve, reject) => {
let details_list = [];
this.state.data.map(item => {
return (
Utils.rest('POST', 'https:///api-spot-get-details', {
spotId: item.id
})
.then(async (r) => {
const details_item = await r.json()
console.log(`Loaded ${details_list.length} item ...(of ${this.state.data.length})`);
if (details_list.length === this.state.data.length) {
await resolve(details_list)
}
details_list.push(details_item.result);
})
);
})
})
}`
render() {
return (
{
this.state.data.map((item, key) => {
return (
{item.id}
{item.id}
);
})
}
Here is my code. After first call I am receiving id and passing it as input to second call

I think this is happening because you're calling this.getCountries() in the render function. So the function is called in every render, that causes a new request that sets a new state, which will trigger a new render an so on, creating an infinite loop. So, if you delete the function calling from the render function it should work.

This is a basic example:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const myarray=[1,2,3,4,5]
const mytable=myarray.map((item,key)=>{return(
<table key={key}>
<tr><td>id</td><td>{item}</td></tr>
</table>)
})
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{mytable}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can create a const in the render and use after in return so you can tryin your code to do something like that:
render() {
const myComponent= this.state.data.map((item, key) => { return (
<div key={key}>
<span>{item.it}</span>
</div>
) });
return (
{myComponent}
)
}
I used and is just for example you can use what structor you want as in the first example I used table...

Please note that you are calling getDetails method in render. render is not a good place to add methods which modify the internal state. please check the react doc for additional details.

There are a lot of strange things there. First of all, getDetails returns a Promise, but the promise is not resolved anywhere. Its usage should be something like:
getDetails()
.then(data => {
// do something with the data
}, error => {
// manage here the error
}):
Also, this.state.data.map should be this.state.data.forEach and delete the return from inside, because you don't need to return anything outside
On the other hand, there's an issue with getCountries. Its name sais it's a GET, but the API call sends a POST.
After that's clarified, inside getDetails you're using the data retrieved in getCountries, so its call should be inside the request resolving inside getCountries or either change getCountries to a Promise and do something like:
this.getCountries()
.then(data => {
this.getDetails();
});
You don't care when the getDetails call ends, so it doesn't need to return a Promise.
And, in the end, the render function should look more like this:
render() {
return (
<div>
{this.state.data.map((item, key) =>
<div key={key}>{item.id} - {item.id}</div>
)}
</div>
)
}
After this it should work properly, more or less. I have to warn you, though. Probably you would need to do something easier to become familiar with React's flow and how to properly work with state and JS's asynchronous functions.

Related

Promise only resolves correctly on page refresh

I am playing around with an API that gets a list of Pokemon and corresponding data that looks like this.
export function SomePage() {
const [arr, setArray] = useState([]);
useEffect(() => {
fetchSomePokemon();
}, []);
function fetchSomePokemon() {
fetch('https://pokeapi.co/api/v2/pokemon?limit=5')
.then(response => response.json())
.then((pokemonList) => {
const someArray = [];
pokemonList.results.map(async (pokemon: { url: string; }) => {
someArray.push(await fetchData(pokemon))
})
setArray([...arr, someArray]);
})
}
async function fetchData(pokemon: { url: string; }) {
let url = pokemon.url
return await fetch(url).then(async res => await res.json())
}
console.log(arr);
return (
<div>
{arr[0]?.map((pokemon, index) => (
<div
key={index}
>
{pokemon.name}
</div>
))
}
</div>
);
}
The code works(kind of) however on the first render the map will display nothing even though the console.log outputs data. Only once the page has been refreshed will the correct data display. I have a feeling it's something to do with not handling promises correctly. Perhaps someone could help me out.
TIA
Expected output: Data populated on initial render(in this case, pokemon names will display)
The in-build map method on arrays in synchronous in nature. In fetchSomePokemon you need to return a promise from map callback function since you're writing async code in it.
Now items in array returned by pokemonList.results.map are promises. You need to use Promise.all on pokemonList.results.map and await it.
await Promise.all(pokemonList.results.map(async (pokemon: { url: string; }) => {
return fetchData.then(someArray.push(pokemon))
}));
On your first render, you don't have the data yet, so arr[0] doens't exist for you to .map on it, so it crashes. You need to check if the data is already there before mapping.
Using optional chaining, if there's no data it will not throw an error on your first render and it will render correctly when the data arrive and it re-renders.
...
return (
<div>
{arr[0]?.map((pokemon, index) => (
<div key={index}>{pokemon.name}</div>
))}
</div>
);
}
in
useEffect(() => { fetchSomePokemon(); }, []);
[] tells react there is no dependencies for this effect to happen,
read more here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
One way to solve your issues is to await the data fetching in useEffect().
Here's a POC:
export function Page() {
const [pokemon, setPokemon] = useState([]);
// will fetch the pokemon on the first render
useEffect(() => {
async function fetchPokemon() {
// ... logic that fetches the pokemon
}
fetchPokemon();
}, []);
if (!pokemon.length) {
// you can return a spinner here
return null;
}
return (
<div>
{pokemon.map(item => {
// return an element
})}
</div>
);
}

How to render based on AJAX result in react js?

Just want to render movie cards based on results that come from ajax call.
Currently, the movie cards components are rendered based on that hard code array named list. I just want to make it dynamic and replace it with my ajax data.
const getlist = async () => {
const res = await fetch('http://localhost:3001/customize');
const data = await response.json();
getlist();
};
export default function Index() {
const list = ['dexter', 'bb', 'got'];
return (
<>
<main className={parentstyle.main_container}>
<NavBar />
<div className={style.searchbar_container}>
<SearchBar />
</div>
<div className={style.card_container}>
{test.map((element, i) => {
return <MovieCard movieName={element} key={i} />;
})}
</div>
</main>
</>
);
}
Use the useState hook to set up your component state (the list) and fetch data in a useEffect hook...
The Effect Hook lets you perform side effects in function components:
Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.
import { useEffect, useState } from "react"
const getlist = async () => {
const res = await fetch("http://localhost:3001/customize")
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`)
}
return res.json()
}
const Index = () => {
const [ list, setList ] = useState([]) // start with an empty array
useEffect(() => {
getList()
.then(setList)
.catch(console.error)
}, []) // empty dependencies array, this runs only once
return (
// ...
{list.map((element, i) => (
<MovieCard movieName={element} key={i} />
))}
// ...
)
}
export default Index

why wont jsx render firestore data

I'm trying to GET data from firestore and render it, in the most basic way possible. I can console.log the data, but as soon as I try to render it via JSX, it doesn't. Why is this?
import React from 'react'
import { useState, useEffect } from 'react'
import {db} from '../../public/init-firebase'
export default function Todo() {
const [todo, setTodo] = useState()
const myArray = []
//retrive data from firestore and push to empty array
function getData(){
db.collection('Todos')
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id +'pushed to myArray')
myArray.push(doc.id)
})
})
}
useEffect(() => {
getData()
}, [])
return (
<>
<div>
<h1>Data from firestore: </h1>
{myArray.map((doc) => {
<h1>{doc.id}</h1>
console.log('hi')
})}
</div>
</>
)
}
First, change myArray to State like this:
const [myArray, setMyArray] = useState([]);
Every change in myArray will re-render the component.
Then, to push items in the State do this:
function getData(){
db.collection('Todos')
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id +'pushed to myArray')
setMyArray(oldArray => [...oldArray, doc.id])
})
})
}
You're just pushing the ID in myArray, so when to show try like this:
{myArray.map((id) => {
console.log('hi')
return <h1 key={id}>{id}</h1>
})}
If you look closely,
{myArray.map((doc) => {
<h1>{doc.id}</h1>
console.log('hi')
})}
those are curly braces {}, not parenthesis (). This means that although doc exists, nothing is happening since you are just declaring <h1>{doc.id}</h1>. In order for it to render, you have to return something in the map function. Try this instead:
{myArray.map((doc) => {
console.log('hi')
return <h1>{doc.id}</h1>
})}
In order to force a "re-render" you will have to use the hooks that you defined
https://reactjs.org/docs/hooks-intro.html
function getData(){
db.collection('Todos')
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id +'pushed to myArray')
myArray.push(doc.id)
})
})
// in case the request doesn't fail, and the array filled up with correct value using correct keys:
// i'd setState here, which will cause a re-render
setTodo(myArray)
}

Objects are not valid as react child, but I'm using React docs official code [duplicate]

I am trying to render a list of posts by mapping through an array. I've done this many times before but for some reason
renderPosts = async () => {
try {
let res = await axios.get('/posts');
let posts = res.data;
return posts.map((post, i) => {
return (
<li key={i} className="list-group-item">{post.text}</li>
);
});
} catch (err) {
console.log(err);
}
}
render () {
return (
<div>
<ul className="list-group list-group-flush">
{this.renderPosts()}
</ul>
</div>
);
}
All I get is:
Uncaught Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
I've checked the data returned from renderPosts and it is an array with the correct values and no promises. What's going on here?
I also received the same error message when creating an async functional component. Functional components should not be async.
const HelloApp = async (props) => { //<<== removing async here fixed the issue
return (
<div>
<h2>Hello World</h2>
</div>
)
}
ReactDOM.render(<HelloApp />, document.querySelector("#app"))
jsfiddle
this.renderPosts() will return a Promise not the actual data, and AFAIK Reactjs will not resolve Promises implicitly in render.
You need to do it like this
componentDidMount() {
this.renderPosts();
}
renderPosts = async() => {
try {
const res = await axios.get('/posts');
const posts = res.data;
// this will re render the view with new data
this.setState({
Posts: posts
});
} catch (err) {
console.log(err);
}
}
render() {
const posts = this.state.Posts?.map((post, i) => (
<li key={i} className="list-group-item">{post.text}</li>
));
return (
<div>
<ul className="list-group list-group-flush">
{posts}
</ul>
</div>
);
}
Using React Hooks:
UPDATE 2020-08-01: Amended with #ajrussellaudio's suggestion.
import React, {useState, useEffect} from "react"
const ShowPosts = () => {
const [posts, setPosts] = useState([]);
useEffect( () => {
async function fetchData() {
try {
const res = await axios.get('/posts');
setPosts(res.data);
} catch (err) {
console.log(err);
}
}
fetchData();
}, []);
return <div>{posts}</div>
}
Poor me
For anyone using jest test
And trying to call a function children then received this Error
please check:
const children = jest.fn().mockReturnValueOnce(null)
NOT
const children = jest.fn().mockRejectedValue(null);
If you use Next.js you can use this:
Put your codes in {}, if you have some connection or some calculations thats need time, add await to your codes in {}.
import { useEffect } from 'react'
useEffect(async () => {.......},[])

React/Redux - Why this Promise.all continues to execute after dispatch?

I have a React-Native project with redux.
I perform some axios calls, and basically, the app works fine.
But, there is one thing that is not right.
I put a console.log before the dispatch, and even the app loads and renders everything just fine, I see the console.log looping on and on in the console.
I'm not sure why this is happening, but I read the "run to completion" concept in Javascript, which can be the reason. Even though, I couldn't figure it out.
any ideas how can I fix this? thank you very much.
UPDATE: here is the component that invokes the action, in the renderEtiquetas() function. This can be the reason causing this loop, since it runs on every re-render cycle (not sure about this). I tried moving the invoke to componentDidMount() but it didn't seem to run.
I'm new to React so I'm probably doing something dumb.
component.js
class EtiquetasList extends Component {
componentDidMount() {
this.props.FetchEtiquetas();
}
renderEtiquetas() {
if ( this.props.etiquetas.length == 0 ) {
return <ActivityIndicator size="large" color="#00ff00" />
} else {
this.props.FetchGalleries( this.props.etiquetas );
if ( this.props.galleries.length > 0 ) {
return this.props.etiquetas.map(etiqueta =>
<EtiquetaDetail key={etiqueta.id} etiqueta={etiqueta} galleries={ this.props.galleries } />
);
}
}
}
render() {
return (
<ScrollView>
{ this.renderEtiquetas() }
</ScrollView>
);
}
}
const mapStateToProps = (state) => {
return {
etiquetas: state.data.etiquetas,
isMounted: state.data.isMounted,
galleries: state.slides.entries
};
};
export default connect(mapStateToProps, { FetchEtiquetas, FetchGalleries })(EtiquetasList);
actions.js
export function FetchGalleries( etiquetas ) {
return function (dispatch) {
return Promise.all(
etiquetas.map( record =>
axios.get('mydomain.com/?id='+record.id)
)).then(galleries => {
let my_data = [];
let data_json = '';
galleries.map( record => {
record.data.map( subrecord => {
// this is simplified for this example, it works as intended
data_json = data_json + '{ title: "' + subrecord.title+'"}';
});
my_data.push( data_json );
});
console.log( my_data ); // this keeps printing in the console
return dispatch({ type: FETCH_GALLERIES_SUCCESS, payload: my_data });
});
}
}
Aha, FetchGalleries is running inside the render function, it will cause an action->render->action->render infinite loop.
Edit:
How about trying to merge your FetchGalleries and FetchEtiquetas into one action:
export const fetchGalleries = () => {
return function (dispatch) {
return axios.get('/path/to/etiquetas').then(etiquetas => {
dispatch({ type: FETCH_ETIQUETAS_SUCCESS, payload: etiquetas });
// ... your previous promise all code
});
}
}
and only need to call this new fetchGalleries at componentDidMount.
You are close, you just need to return or await Promise.all otherwise it will not be awaited
export function FetchGalleries( etiquetas ) {
return function (dispatch) {
return Promise.all(....
}
// UPDATE: The answer from 李骏骁 is correct

Categories

Resources