Use SWR to fetch multiple times to populate an array - reactjs

I have a component in React in need of an array of users. I'm able to fetch one single user at a time with useUwr like so:
export function Hello(id: number) {
const { data } = useSWR('/api/user/{id}', fetcher)
return <div>hello {data.name}!</div>
}
What I need now is an array of users, so basically:
var users = [];
for(var i = 0; i < 100; i++) {
const { data } = useSWR('/api/user/{i}', fetcher);
users.push(data);
}
Issue with this approach is Error: Rendered more hooks than during the previous render.
Was thinking that there must be a smarter way to fetch the data of 100 users. Thanks for any suggestion.

Try this:
function arrayFetcher(...urlArr) {
const f = (u) => fetch(u).then((r) => r.json());
return Promise.all(urlArr.map(f));
}
export function Hello(id: number) {
let urlArray = [];
for(let i = 0; i < 100; i++) {
urlArray.push(`/api/user/${i}`);
}
const { data } = useSWR(urlArray, arrayFetcher);
return (
<ul>{data && data.map(x => <li key={x.name}>x.name</li>)}</ul>
)
}

function multiFetcher(...urls) {
return Promise.all(urls.map(url => fetcher(url))
}
const { data: [data1, data2, data3] } = useSWR([url1, url2, url3], multiFetcher)

add urlArray in array
const { data } = useSWR([urlArray], arrayFetcher);

Related

How to apply a filter through the pages of an API and that all the results are stored in a state?

I need it to filter on each api page and have those results stored in a state.
I try this but only the result of the last page is being stored.
const [collections] = useState(["wedding", "outdoors", "portraits", "travel", "pets", "christmas", "products", "halloween"]);
const [resulPhotos, setResultPhotos] = useState([]);
const [pages, setPages] = useState(1);
useEffect(() => {
for (let j = 0; j < 7; j++) {
for (let i = 0; i < 7; i++) {
const url = `https://api.unsplash.com/search/photos?page=${i}&query=${collections[j]}&client_id=${accessKey}`
axios.get(url).then((response) => {
const photos = response.data.results.filter((item) => (
item.user.name === "Nathan Dumlao"
)).map(item => item)
setResultPhotos(photos)
});
}
}
}, []);
You are overwriting the state every time you call setResultPhotos(photos). So the ResultPhotos will always be the last ones at the end of the loop.
You can add each filtered output by doing the following:
setResultPhotos(prev=>[...prev,...photos])
This will keep populating your ResultPhotos with new photos
Full useEffect would look like :
useEffect(() => {
for (let j = 0; j < 7; j++) {
for (let i = 0; i < 7; i++) {
const url = `https://api.unsplash.com/search/photos?page=${i}&query=${collections[j]}&client_id=${accessKey}`;
axios.get(url).then((response) => {
const photos = response.data.results
.filter((item) => item.user.name === "Nathan Dumlao")
.map((item) => item);
setResultPhotos(prev=>[...prev,...photos]);
});
}
}
}, []);
Side guess: I think you want ResultPhotos to be empty each time that useEffect fires. If that's the case, just reset the state back to an empty array before the loop starts
Here is an example : https://codesandbox.io/s/happy-stitch-dd8s76?file=/src/App.js

Why does my function still return a promisse in react native

My function which gets the values is returning a promise which I don't know how to wait for resolve. My failed code:
const [rednderSaves, setRenderSaves] = React.useState([])
const saveRenders = async () => {
var data = await JSON.parse(await AsyncStorage.getItem("connections"))
for (var i = 0; i < data.length; i++) {
data[i].data = await JSON.parse(await data[i].data)
}
setRenderSaves(data)
}
{saveRenders().then(rednderSaves.map(save => {
return <Text key={Date.now()}>{save.name}</Text>
}))}
This overcomplicated thing STILL RETURNS A PROMISE. I have been scratching my head for the last 4 hours, please someone finally help. Thanks in advance
chained then always returns another promise. the proper way would to load your data at useEffect lifecycle correctly.
const [renderSaves, setRenderSaves] = React.useState([])
React.useEffect(() => {
const saveRenders = async () => {
const data = JSON.parse(await AsyncStorage.getItem("connections"))
for (let i = 0; i < data.length; i++) {
data[i].data = JSON.parse(data[i].data)
}
setRenderSaves(data)
}
saveRenders()
}, [])
return (
<>
// ... content
{ renderSaves.map(save => <Text key={Date.now()}>{save.name}</Text>) }
</>
)
try this
const [rednderSaves, setRenderSaves] = React.useState([])
React.useEffect(()=>{
saveRenders()
},[])
const saveRenders = async () => {
var data = await JSON.parse(await AsyncStorage.getItem("connections"))
for (var i = 0; i < data.length; i++) {
data[i].data = await JSON.parse(await data[i].data)
}
setRenderSaves(data)
}
return (
<>
{
rednderSaves.length?rednderSaves.map(save => (
<Text key={Date.now()}>{save.name}</Text>
)):
null
}
</>
)

React update or add to an array of objects in useState when a new object is received

Occasionally a newItem is received from a WebSocket and gets saved to useState with saveNewItem
this then kicks off the useEffect block as expected.
Update. If there is an object in the closeArray with the same openTime as the newItem I want to replace that object in closeArray with the newItem because it will have a new close
Add. If there isn't an object in the closeArray with the same open time as newItem I want to push the new item into the array.
Remove. And finally, if the array gets longer than 39 objects I want to remove of the first item.
If I add closeArray to the array of useEffect dependencies I'm going to create a nasty loop, if I don't add it closeArray isn't going to get updated.
I want usEffect to only fire off when newItem changes and not if closeArray changes, but I still want to get and set data to closeArray in useEffect
interface CloseInterface {
openTime: number;
closeTime: number;
close: number;
}
function App() {
const [newItem, saveNewItem] = useState<CloseInterface>();
const [closeArray, saveCloseArray] = useState<CloseInterface[]>([]);
useEffect(() => {
if (newItem) {
let found = false;
let arr = [];
for (let i = 0; i < closeArray.length; i++) {
if (closeArray[i].openTime === newItem.openTime) {
found = true;
arr.push(newItem);
} else {
arr.push(closeArray[i]);
}
}
if (found === false) {
arr.push(newItem)
}
if (arr.length === 39) arr.shift();
saveCloseArray(arr);
}
}, [newItem]); // <--- I need to add closeArray but it will make a yucky loop
If I do add closeArray to the useEffect dependancy array I get the error...
index.js:1 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
in App (at src/index.tsx:9)
in StrictMode (at src/index.tsx:8)
if I don't add closeArray to the useEffect dependancy array I get this error...
React Hook useEffect has a missing dependency: 'closeArray'. Either include it or remove the dependency array react-hooks/exhaustive-deps
the second useEffect block gets the initial data for closeArray and listens to a WebSocket that updates newItem as it arrives.
useEffect(() => {
const getDetails = async () => {
const params = new window.URLSearchParams({
symbol: symbol.toUpperCase(),
interval
});
const url = `https://api.binance.com/api/v3/klines?${params}&limit=39`;
const response = await fetch(url, { method: "GET" });
const data = await response.json();
if (data) {
const arrayLength = data.length;
let newcloseArray = [];
for (var i = 0; i < arrayLength; i++) {
const openTime = data[i][0];
const closeTime = data[i][6];
const close = data[i][4];
newcloseArray.push({ openTime, closeTime, close });
}
saveCloseArray(newcloseArray);
const ws = new WebSocket("wss://stream.binance.com:9443/ws");
ws.onopen = () =>
ws.send(
JSON.stringify({
method: "SUBSCRIBE",
params: [`${symbol}#kline_${interval}`],
id: 1
})
);
ws.onmessage = e => {
const data = JSON.parse(e.data);
const value = data.k;
if (value) {
const openTime = value.t;
const closeTime = value.T;
const close = value.c;
saveNewItem({ openTime, closeTime, close });
}
};
}
};
getDetails();
}, [symbol, interval]);
In order to better write your code, you can make use of state updater callback method, so that even if you don't pass closeArray to the useEffect and it will sstill have updated values on each run of useEffect
function App() {
const [newItem, saveNewItem] = useState<CloseInterface>();
const [closeArray, saveCloseArray] = useState<CloseInterface[]>([]);
useEffect(() => {
if (newItem) {
let found = false;
saveCloseArray(prevCloseArray => {
let arr = [];
for (let i = 0; i < prevCloseArray.length; i++) {
if (prevCloseArray[i].openTime === newItem.openTime) {
found = true;
arr.push(newItem);
} else {
arr.push(prevCloseArray[i]);
}
}
if (found === false) {
arr.push(newItem)
}
if (arr.length === 39) arr.shift();
return arr;
})
}
}, [newItem]);
You want to use a useCallback to save your new array with the updated item, like so:
const [closeArray, saveCloseArray] = useState<CloseInterface[]>([]);
const updateEntry = useCallback(newItem => {
saveCloseArray(oldCloseArray => oldCloseArray.reduce((acc, item) => {
acc.push(item.openTime === newItem.openTime ? newItem : item);
return acc;
}, []));
}, []);
You'd then apply the callback function to your button or div or whatever component is being generated, EG
return (
[1, 2, 3, 4, 5].map(item => <button key={`${item}`} onClick={() => updateEntry(item)}>Click me</button>)
);
If the only reason you have newItem is to update closeArray I would consider moving that functionality into the useEffect that utilizes WebSocket. You can still use newItem if you need to do something in addition to just updating closeArray, like showing an alert or a popup, for instance. Here's what I mean:
interface CloseInterface {
openTime: number;
closeTime: number;
close: number;
}
function App() {
const [newItem, saveNewItem] = useState<CloseInterface>();
const [closeArray, saveCloseArray] = useState<CloseInterface[]>([]);
useEffect(() => {
// Do something when newItem changes, e.g. show alert
if (newItem) {
}
}, [newItem]);
useEffect(() => {
// Work with the new item
const precessNewItem = (item = {}) => {
let found = false;
let arr = [];
for (let i = 0; i < closeArray.length; i++) {
if (closeArray[i].openTime === item.openTime) {
found = true;
arr.push(item);
} else {
arr.push(closeArray[i]);
}
}
if (found === false) {
arr.push(item)
}
if (arr.length === 39) arr.shift();
saveCloseArray(arr);
// save new item
saveNewItem(item);
};
const getDetails = async () => {
const params = new window.URLSearchParams({
symbol: symbol.toUpperCase(),
interval
});
const url = `https://api.binance.com/api/v3/klines?${params}&limit=39`;
const response = await fetch(url, { method: "GET" });
const data = await response.json();
if (data) {
const arrayLength = data.length;
let newcloseArray = [];
for (var i = 0; i < arrayLength; i++) {
const openTime = data[i][0];
const closeTime = data[i][6];
const close = data[i][4];
newcloseArray.push({ openTime, closeTime, close });
}
saveCloseArray(newcloseArray);
const ws = new WebSocket("wss://stream.binance.com:9443/ws");
ws.onopen = () =>
ws.send(
JSON.stringify({
method: "SUBSCRIBE",
params: [`${symbol}#kline_${interval}`],
id: 1
})
);
ws.onmessage = e => {
const data = JSON.parse(e.data);
const value = data.k;
if (value) {
const openTime = value.t;
const closeTime = value.T;
const close = value.c;
// process new item
processNewItem({ openTime, closeTime, close });
}
};
}
};
getDetails();
}, [symbol, interval, closeArray]); // add closeArray here
}

cannot update during an existing state transition react native

in my app, I am able to get the values, distance and names of location from an array by using the fuctions below. However, I am unable to dispatch the values obtained from them in my redux store using the mapDispatchToProps,
which is for example
handleNavigation() {
this.props.navigation.navigate('LocationLists');
this.props.totalDistanceChange(this.totalDistance()).
}
<Button
onPress={this.handleNavigation.bind(this)}
/>
const mapDispatchToProps = (dispatch) => ({
totalDistanceChange: totalDistance=> {
dispatch(totalDistanceChange(totalDistance));
}
});
I keep getting cannot update during an existing state transition.
below are just my functions as I wanted to keep it as simple as possible, kindly correct where appropriate.
totalDistance = () => {
const { selectedItemObjects } = this.state;
const total = selectedItemObjects.reduce((result, { Distance }) => result += Distance, 0);
return total.toFixed(1);
}
totalValue = () => {
const { selectedItemObjects } = this.state;
const total = selectedItemObjects.reduce((result, { Value }) => result += Value, 0);
return total;
}
renderLocationText = () => {
const { selectedItemObjects } = this.state;
return selectedItemObjects.length ?
`${selectedItemObjects.map((item, i) => {
let label = `${item.name}, `;
if (i === selectedItemObjects.length - 2) label = `${item.name} and `;
if (i === selectedItemObjects.length - 1) label = `${item.name}.`;
return label;
}).join('')}`
:
null;
}
my question is how can i pass the values obtained to my redux store
fixed it by converting it to a string
handleNavigation() {
this.props.navigation.navigate('LocationLists');
this.props.totalDistanceChange(this.totalDistance().toString()).
}

Cannot access array inside of map function to return props to different component

I am fetching data from an API. I am building an array of 5 objects using the API call. What I am trying to do is iterate over the array, use the data inside each array index to build a component and pass along the props to another component.
I've tried accessing the element the same way I normally would by doing:
img={pokemon.name} but it keeps returning undefined. When I type in
console.log(pokemon) I get the individual pokemon stored within the array of objects.
import React, { Component } from "react";
import Pokecard from "./Pokecard";
async function getPokemon() {
const randomID = Math.floor(Math.random() * 151) + 1;
const pokeRes = await fetch(`https://pokeapi.co/api/v2/pokemon/${randomID}/`);
const pokemonJSON = await pokeRes.json();
return pokemonJSON;
}
function buildPokemon() {
let pokemonArr = [];
let builtPokemon = {};
getPokemon()
.then(data => {
builtPokemon.name = data.forms[0].name;
builtPokemon.exp = data.base_experience;
builtPokemon.img = data.sprites.front_default;
builtPokemon.type = data.types[0].type.name;
pokemonArr.push(builtPokemon);
})
.catch(err => console.log(err));
return pokemonArr;
}
class Pokedex extends Component {
constructor(props) {
super(props);
this.state = { pokemonArr: [] };
}
componentDidMount() {
const pokemonArr = [];
for (let i = 0; i < 5; i++) {
pokemonArr.push(buildPokemon());
}
this.setState({ pokemonArr });
}
render() {
console.log(this.state.pokemonArr);
return (
<div className="Pokedex">
{this.state.pokemonArr.map(pokemon => console.log(pokemon))}
</div>
);
}
}
export default Pokedex;
What should happen is that when I map the pokemonArr I want to create 5 separate pokemon by doing
this.state.pokemonArr.map(pokemon => <Pokecard name={pokemon.name} but I keep getting undefined whenever I check this.props in the Pokecard component.
I think my buildPokemon() function is working because when I call it in the componentDidMount() and then I console.log this.state.pokemonArr in the render() function, I actually get an array returned with 5 different pokemon with the proper fields filled out.
And also when I map out this.state.pokemonArr.map(pokemon => clg(pokemon)), it actually displays each individual pokemon. When I pass the pokemon item into a component like this
<Pokecard name={pokemon}/>, I see all the pokemon data.
when I type <Pokecard name={pokemon.name} I get undefined
There are several problems with your approach but the main one is that getPokemon() is asynchronous.
Return the getPokemon() promise from buildPokemon() and return the object from it's then()
In your for() loop create an array of these promises and use Promise.all() to set state once they have all resolved
function buildPokemon() {
let builtPokemon = {};
// return the promise
return getPokemon()
.then(data => {
builtPokemon.name = data.forms[0].name;
builtPokemon.exp = data.base_experience;
builtPokemon.img = data.sprites.front_default;
builtPokemon.type = data.types[0].type.name;
// return the object
return builtPokemon
});
}
componentDidMount() {
const pokemonPromises = [];
for (let i = 0; i < 5; i++) {
pokemonPromises.push(buildPokemon());
}
Promise.all(pokemonPromises).then(pokemonArr => this.setState({ pokemonArr }));
}
componentDidMount executes after first render, initially your state is pokemonArr: [] (whch is empty) so you are getting an error. You need to conditionally render like,
{this.state.pokemonArr.length > 0 && this.state.pokemonArr.map(pokemon => console.log(pokemon))}
Side Note:
In buildPokemon function you are returning an array, and again in componentDidMount you are storing it in array which creates array of array's, you just need to return object from buildPokemon function.
The problem is mainly how the Promise should be resolved.
The data isn't available right away so the state (pokemonArr) should only be set once data is available.
Here's the refactored component:
class Pokedex extends Component {
constructor(props) {
super(props);
this.state = { pokemonArr: [] };
}
componentDidMount() {
for (let i = 0; i < 5; i++) {
this.getPokemon()
.then((pokemon) => this.buildPokemon(pokemon));
}
}
async getPokemon() {
const randomID = Math.floor(Math.random() * 151) + 1;
const pokeRes = await fetch(`https://pokeapi.co/api/v2/pokemon/${randomID}/`);
return pokeRes.json();
}
setPokemon(pokemon) {
this.setState({
pokemonArr: [
...this.state.pokemonArr, pokemon
],
});
}
buildPokemon(data) {
let builtPokemon = {};
builtPokemon.name = data.forms[0].name;
builtPokemon.exp = data.base_experience;
builtPokemon.img = data.sprites.front_default;
builtPokemon.type = data.types[0].type.name;
this.setPokemon(builtPokemon);
}
render() {
return (
<div className="Pokedex">
{this.state.pokemonArr.map(pokemon => console.log(pokemon))}
</div>
);
}
}

Resources