FlatList performance warning issue when using the useEffect hook - javascript

I get a warning in my FlatList when using the useEffect hook to fetch data.
This is the complete component to reproduce the issue:
import React, { useState, useEffect } from "react";
import {
Text,
View,
FlatList,
ActivityIndicator,
SafeAreaView,
Button
} from "react-native";
const Test = props => {
const [people, setPeople] = useState([]);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(1);
useEffect(() => {
setLoading(true);
fetch(`http://jsonplaceholder.typicode.com/photos?_start=${page}&_limit=20`)
.then(res => res.json())
.then(res => {
//console.log(res);
setPeople(people => [...people, ...res]);
setLoading(false);
});
}, [page]);
const loadMore = () => {
setPage(page + 20);
};
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList
data={people}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<View>
<Text>{item.id}</Text>
<Text>{item.title}</Text>
</View>
)}
ListFooterComponent={
loading ? (
<ActivityIndicator />
) : (
<Button title="Load More" onPress={loadMore} />
)
}
/>
</SafeAreaView>
);
};
export default Test;
This is the warning I'm getting
VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. Object {
"contentLength": 4418,
"dt": 705,
"prevDt": 669,
}
It basically tells me to use PureComponent or shouldComponentUpdate, though, but AFAIK both do not work with either a functional component or the useEffect hook, do they?
Although I did not notice a (huge) performance drop, I'm still wondering if there's a workaround to fix this issue. Any help would be appreciated. Thank you very much.
Edit: Using a PureComponent does not fix the issue:
Created PureComponentTest.js
import React from "react";
import { Text, View } from "react-native";
const PureComponentTest = props => {
return (
<View>
<Text>{props.id}</Text>
<Text>{props.title}</Text>
</View>
);
};
export default PureComponentTest;
And in my Component I updated renderItem={renderItems}:
const renderItems = itemData => {
return (
<PureComponentTest
id={itemData.item.id}
title={itemData.item.title}
/>
);
};

I really don’t see anything wrong with your component. It’s a very simple pure component. It may simply because the fetched data is too big. Try reduce the number of pages fetched each time. say 5 or 10 pages

maybe the warning is for fetch into useEffect, so review the next documentation:
Some rules to keep in mind about the useEffect hook:
You cannot call a hook within a conditional; Hooks must be called in
the exact same order. Putting the useEffect inside of a conditional
could break that cycle; The function you pass the hook cannot be an
async function, because async functions implicitly return promises,
and a useEffect function either returns nothing or a cleanup function.
Consider use it:
fetch(`http://jsonplaceholder.typicode.com/photos?_start=${page}&_limit=20`)
.then(res => res.json())
.then(res => {
//console.log(res);
setPeople(people => [...people, ...res]);
})
.catch(error=> console.error(error))
.finally(() => setLoading(false));

Related

Unable to display data from heroku api to the Dom

I am building an application using React Native, and I want to use data from Heroku api. But when I make an API call and consolog the data I can see the data, but when I try to map them, nothing is coming out of the DOM. Can anyone tell me what I am doing wrong? Thank you so much. Here below are my can and a screenshop.
App.jsx:
import react, { useEffect, useState } from "react";
import { FlatList, useWindowDimensions, View, Text } from "react-native";
import axios from "axios";
const App = () => {
const { height } = useWindowDimensions();
const [places, setPlaces] = useState({});
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
axios
.post(
"https://where2playdk.herokuapp.com/nearest?lat=55.70232019168349&lon=12.561693791177802"
)
.then((response) => console.log(response.data))
.catch((error) => setIsError(error))
.finally(() => setIsLoading(false));
}, []);
return (
<View>
{places.map(({ name, distance }) => (
<View>
<Text>name</Text>
<Text>{name}</Text>
<Text>{distance}</Text>
</View>
))}
</View>
);
};
export default App;
you are not updating the state here, only consoling the data,
useEffect(() => {
axios
.post(
"https://where2playdk.herokuapp.com/nearest?lat=55.70232019168349&lon=12.561693791177802"
)
.then((response) => setPlaces(response.data)) // here is the
//mistake
.catch((error) => setIsError(error))
.finally(() => setIsLoading(false));
}, []);
I've haven't worked with React Native but if it's the same as regular React, then the first problem I see is that each element inside your map should have a key:
{places.map(({ name, distance }) => (
<View key={name}>
{/* ... */}
</View>
))}
You also need to handle the loading state. Because when App first runs, places is undefined, so you are calling undefined.map which will probably throw an error. An early return would suffice.
if (!places) return <Text>Loading...</Text>
And I also don't see the setPlaces() being called, I assume you replaced it with the console log?

Showing data from state variable in ReactJS forms infinite loop

I'm trying to show data from an API call. The structure of the application looks like
MainComponent -> RefreshButton (this will fetch the data)
MainComponent -> ShowData (this will show the data that is being fetched)
MainComponent has a state userData that will store the response that was received from the API. Now the issue is, whenever I'm clicking the button, it is getting into an infinite loop of rendering and calls the API infinite times.
This is what the error shows:
Here is my MainComponent -
import React, { useEffect, useState } from "react";
import RefreshButton from "./RefreshButton";
import ShowData from "./ShowData";
const MainComponent = () => {
const [userData, setUserData] = useState();
useEffect(() => {
console.log(userData);
}, [userData]);
return (
<div>
<p style={{ textAlign: "center" }}>Main Component</p>
<RefreshButton setUserData={setUserData} />
{userData && <ShowData userData={userData} />}
</div>
);
};
export default MainComponent;
Here is my RefreshButton component -
import React from "react";
import axios from "axios";
const RefreshButton = ({ setUserData }) => {
const getData = () => {
axios
.get(`https://jsonplaceholder.typicode.com/todos`)
.then((response) => {
if (response.status === 200) setUserData(response.data);
})
.catch((err) => {
console.log(err);
});
};
return (
<div className="button-container">
<button className="fetch-data-button" onClick={() => getData()}>
Fetch new data
</button>
</div>
);
};
export default RefreshButton;
And here is my ShowData component -
import React from "react";
const ShowData = ({ userData }) => {
console.log("Here", userData);
return (
<>
{userData.map((info, idx) => (
<div className="user-data" key={idx}>
{info}
</div>
))}
</>
);
};
export default ShowData;
PS - I'm new to React and couldn't find a potential solution on this, there are several tutorials on how to fetch data from API calls and show it, but I wanted to know what I'm doing wrong here. Thanks in advance!
You might have misunderstood with the infinite loop error
It's actually a render error as being shown here:
To fix your render error, simply put an actual string variable in the {}
Because the response was an array of this object, so you can't simply render the whole object but need to pick an actual string variable inside:
[{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}],
Change to something like this:
const ShowData = ({ userData }) => {
console.log("Here", userData);
return (
<>
{userData.map((info, idx) => (
<div className="user-data" key={idx}>
{info.title} // <-- Put a title here.
</div>
))}
</>
);
};
Remove
useEffect(() => {
console.log(userData);
},[userData])
This will reevaluate component whenever user data changes, which Leeds to call showData infinitely

React native Pull to refresh is not working

I m trying to add pull to refresh to fetch my data from my api but it is not working and i can'y find the problem within the code:
const[refresh,setRefresh]=useState(true)
const onRefresh=()=>{
try{
axios
.get('http://192.168.1.17:8000/File/')
.then((response)=> {
setFile(response.data);
setFilteredFile(response.data)
setEmpty(false)
setRefresh(false);
})}
catch(error){console.log(error)
}
}
useEffect(()=>{
onRefresh()
},[])
<FlatList style={DocumentStyle.flatstyle}
keyExtractor={(item)=>item['id']}
data={filteredfile}
renderItem={renderItem}
onRefresh={()=>onRefresh()}
refreshing={refresh}
/>
never mind me everyone, i haven't set my refresh back to true after the useEffect set it to false
The error is due to not importing useState, but you also need to import useEffect. I also dont see where some of the props your passing to FlatList are being used. But here's a working sample:
import {useState, useEffect} from 'react';
const FlatList = ({file, refreshing, onRefresh}) => {
return (
<div>
<p>{file}</p>
<button onClick={() => onRefresh(2)}>Load another todo</button>
</div>
)
}
export default function App() {
const [refresh, setRefresh] = useState(true);
const [file, setFile] = useState('');
useEffect(() => onRefresh(), []);
const onRefresh = (id=1) => {
try {
fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then(response => response.json())
.then(json => {
console.log(json)
setFile(JSON.stringify(json))
setRefresh(false);
})
}
catch(error) {
console.log(error);
}
}
return <FlatList file={file} refreshing={refresh} onRefresh={onRefresh} />
}

Problem with useState, UseEffect when saving data when getting data

I am new to react native and I want on the welcome screen of my application to click on a request to an api using axios and the data is saved in a variable using useState and then use this data in another class (AllProductCategory .js) without having to make the request back to the api.
I am using React native 0.62 hooks react navigation 5 and axios.
I have the following in the Navigation.js file. A context that has a useMemo as its value, which contains a return so that it returns an array with information that it brings from an application using axios. the class looks like this:
In this class skip certain lines of code that have nothing to do with the problem I am currently having.
export default function Navigation() {
const [allproducts, setAllproducts] = useState([]);
useEffect(() => {
const _loadAllCategories = async () => {
await axiosClient
.get("/service/product_available")
.then(function (response) {
console.log("Data antes de pasarlo al useState ", response.data);
setAllproducts(response.data);
console.log("Los productos son: ", allproducts);
})
.catch(function (error) {
console.log("Error obteniendo el token", error);
});
};
_loadAllCategories();
}, []);
const authContext = useMemo(
() => ({
getAllProducts: () => {
return allproducts;
},
}),
[]
);
return (
<AuthContext.Provider value={authContext}>
{state.isLoading ? (
<SplashStackScreen />
) : state.userToken == null ? (
<PrincipalStackScreen />
) : (
<MyDrawer />
)}
</AuthContext.Provider>
);
}
With this file what I want is for the data that brings all the products to be loaded when the splash screen is loading and so when I want to use this data on another screen, just call the context variable and return the data without having to make another request to the api.
Then in the class I implement the call of this data using the context
const { getAllProducts } = React.useContext(AuthContext);
const allProducts = getAllProducts();
The complete class is like this:
import React, { useState, useEffect } from "react";
import { View, Text, FlatList, StyleSheet, TouchableOpacity, Dimensions, Image } from "react-native";
import { AuthContext } from "../../context";
var { height, width } = Dimensions.get("window");
export default function AllProductCategoryScreen() {
const { getAllProducts } = React.useContext(AuthContext);
const allProducts = getAllProducts();
function Product_Category({ name, image }) {
console.log("name e image", name);
return (
<View>
<TouchableOpacity>
<Image style={styles.imageCategory} source={{ uri: image }} />
<Text>{name}</Text>
</TouchableOpacity>
</View>
);
}
return (
<View>
<Text>Todas las categorias</Text>
<View style={{ alignItems: "center" }}>
<FlatList
scrollEnabled={true}
numColumns={2}
data={allProducts}
renderItem={({ item }) => (
<Product_Category name={item.name} image={item.imagePath} />
)}
keyExtractor={(item, index) => index.toString()}
/>
</View>
</View>
);
}
My app.js is as follows:
import React from 'react';
import Navigation from "./src/components/Navigation/Navigation"
export default function App() {
return <Navigation />
}
The problem that I currently have is that when I start my app, I show that the request is made with axios and it brings the data correctly, however the useState is not filled with the data that the axios responds to me (it prints []). However if I save changes being in the navigation.js class in visual code the variable allproducts of the navigation class is filled with the data correctly and therefore in the other class where I want to display the data, it paints the data correctly.
I need that when my app loads, the data that the api brings is saved and that when using it in the other class, these data remain so that they can be used and illustrate this data on the screen.
When the function passed to useMemo is created, it captures the first value of allproducts and will always have that value. It will always return [].
In order for useMemo to run again and capture a new value, add that value to the second argument, the array. React will call the memo function whenever a value in that array changes, and then getAllProducts will be created again and capture a new value of allproducts.
const authContext = useMemo(
() => ({
getAllProducts: () => {
return allproducts;
},
}),
[allproducts]
);

React hooks useeffect

I am having an issue with my app in that it re renders a new joke twice when I click the new button function. Here is my code:
import React, { useState, useEffect } from "react";
import { Typography, Button } from "#material-ui/core";
import Navigation from "../Navigation";
export default function RandomJoke() {
const [isLoaded, setLoaded] = useState(false);
const [jokeData, setJokeData] = useState({});
const [loadNewJoke, setLoadNewJoke] = useState(false);
function useFetch() {
async function fetchMyAPI() {
let response = await fetch("https://icanhazdadjoke.com/slack");
response = await response.json();
setJokeData(response);
setLoaded(true);
}
useEffect(() => {
fetchMyAPI();
if (loadNewJoke) setLoadNewJoke(false);
}, [loadNewJoke]);
}
useFetch();
function reloadJoke() {
setLoaded(false);
setLoadNewJoke(true);
}
return (
<>
<Navigation mainpage="RandomJoke" />
<Typography variant="h6">Random Dad Joke</Typography>
{isLoaded && <div>{jokeData.attachments[0].text}</div>}
{!isLoaded && <div>loading...</div>}
{isLoaded && (
<Button variant="contained" onClick={() => reloadJoke()}>
New one
</Button>
)}
</>
);
}
I tried adding a newjoke state hook but still couldn't work it out. Thank you
That useEffect fires whenever the value of loadNewJoke changes, right? Not just when loadNewJoke is set to true. Look closely at the calls made after a button press, and how many times setLoadNewJoke is called.
Try to move:
if (loadNewJoke) setLoadNewJoke(false);
In your fetchMyApi function. I'm guessing when you hit the button, you trigger the effect cuz u change you deps value in this case to true. Then before effect is over you change it again to false which will triggeer re-run on your effect.
But why you dont just trigger fetchApi in your callback on button click, this way you can remove 1 state [loadNewJoke, setLoadNewJoke], will also remove the useEffect and make code cleaner over all
You're using useEffect wrong, i suggest u take a look at the Rules of Hooks
Don’t call Hooks inside loops, conditions, or nested functions.
I followed what Andom Miltev said about triggering the async function directly in my callback and it now works smoothly - thank you everyone for the help :)
import React, { useState, useEffect } from "react";
import { Typography, Button } from "#material-ui/core";
import Navigation from "../Navigation";
export default function RandomJoke() {
const [isLoaded, setLoaded] = useState(false);
const [jokeData, setJokeData] = useState({});
async function fetchMyAPI() {
setLoaded(false);
let response = await fetch("https://icanhazdadjoke.com/slack");
response = await response.json();
setJokeData(response);
setLoaded(true);
console.log("fired 1");
}
useEffect(() => {
fetchMyAPI();
}, []);
return (
<>
<Navigation mainpage="RandomJoke" />
<Typography variant="h6">Random Dad Joke</Typography>
{isLoaded && <div>{jokeData.attachments[0].text}</div>}
{!isLoaded && <div>loading...</div>}
{isLoaded && (
<Button variant="contained" onClick={() => fetchMyAPI()}>
New one
</Button>
)}
</>
);
}

Categories

Resources