Why is my component in React being called multiple times? - javascript

I am having an issue where my Listings component is running twice and I only want it to run once. I tried adding a counter below that would only run the code that grabs the data from the backend once but that did not work as you can see below it is still looping through the "grabListings" Function twice. I also tried a while loop and same result I am getting two results instead of one.
I believe my issue has to do with the way I am calling the Listings component on my LandingPage.
How can I run the grabListings component only once?
LandingPage
import NavBar from '../componets/NavBar/NavBar.js'
import Footer from '../componets/Footer/Footer'
import Slide1 from '../pictures/slide-1.jpg'
import Listings from '../componets/Listings'
import '../css/LandingPage.css';
const LandingPage = () => {
return (
<div className='wrapper'>
<NavBar />
<div className='top-img'>
<img src={Slide1} alt='E46 Slide Show Pic 1' />
</div>
<Listings />
<Footer />
</div>
)
}
export default LandingPage
Listings Component
const Listings = () => {
let cars = []
let links = []
let prices = []
let pictures = []
let counter = 0
const grabListings = async () => {
if (counter < 1) {
try {
// console.log("looped thorugh grab listing")
await axios.get('http://localhost:5000/scrape').then(async (res) => {
console.log("looped thorugh grab listing");
console.log(res);
const carsData = await (await axios.get('http://localhost:5000/car')).data;
cars.push(carsData);
const linksData = await (await axios.get('http://localhost:5000/link')).data;
links.push(linksData);
const pricesData = await (await axios.get('http://localhost:5000/price')).data;
prices.push(pricesData);
const picturesData = await (await axios.get('http://localhost:5000/picture')).data;
pictures.push(picturesData);
counter++
});
} catch (err) {
console.log(err);
counter++
};
};
console.log(cars);
console.log(links);
console.log(prices);
console.log(pictures);
};
grabListings();
return (
<>
<h1>{cars[0]}</h1>
</>
)
}
export default Listings
Result

Because React will re-render when state change, if you want stop re-render, put your grabListings() inside useEffect() like this:
useEffect(() => {
grabListings();
},[])

Related

React: How to avoid duplication in a state array

I am making MERN social media app.
I want to show all the friends of the current user in a list in SideBar.jsx .
Home.jsx (parent of Sidebar.jsx)
import React, { Component } from "react";
import { Person } from "#material-ui/icons";
import Topbar from "../../components/topbar/Topbar";
import Sidebar from "../../components/sidebar/Sidebar";
import Feed from "../../components/feed/Feed";
import Rightbar from "../../components/rightbar/Rightbar";
import "./home.css";
export default function Home() {
return (
<>
<Topbar />
<div className="homeContainer">
<Sidebar />
<Feed />
<Rightbar />
</div>
</>
);
}
SideBar.jsx
import "./sidebar.css";
import React, { Component, useContext, useState } from "react";
...
import { axiosInstance } from "../../config";
export default function Sidebar() {
const { user } = useContext(AuthContext);
const [followings, setFollowings] = useState([]);
const followingsList = user.followings;
useEffect(() => {
const fetchFollowings = async () => {
followingsList.map(async (id) => {
try {
const theUser = await axiosInstance.get(`/users?userId=${id}`);
if (followings.includes(theUser.data)) {
} else {
setFollowings((prev) => [...prev, theUser.data]);
}
} catch (error) {
console.log(error);
}
});
};
fetchFollowings();
}, [user]);
return (
<div className="sidebar">
.....
<ul className="sidebarFriendList">
{followings.map((u) => (
<CloseFriend key={u._id} user={u} />
))}
</ul>
</div>
</div>
);
}
For example, in this case, in the state "followings", there are 2 user objects.
So, the line
followings.map((u) => (...
should only show 2 entries.
However, the result is below.
As you can see, it is showing each friend twice.
I tired to check if a user object already exists in followings by doing
if (followings.includes(theUser.data)) {
} else {
setFollowings((prev) => [...prev, theUser.data]);
}
But this is not working.
How can I make sure that it only shows each user once?
I want it to be like this
Any help would be greatly appreciated. thank you
This is happening because it seems that your useEffect method is being fired two times (probably because you are using React.StrictMode) and you are setting the state inside the .map method (that is not good because you trigger a new render each time you call the setState).
What I would recommend you to do, is to remove the setState from the .map method and just set the new state after you format your data. So it would be something like this:
const newFollowings = followingsList.map(async (id) => {
try {
const theUser = await axiosInstance.get(`/users?userId=${id}`);
return theUser.data;
} catch (error) {
console.log(error);
}
});
setFollowings(newFollowings);
Probably you would have to add a filtering to the array in case there are some errors (because on errors the mapped value would be undefined):
.filter(data => data);
When you are using the .map function with async/await Promise.all usually always does the trick. Instead of pushing the state on every iteration you collect the followers list and set the state when all your fetching is done. I did not test it yet, but I hope it works.
const followingsList = user.followings;
useEffect(() => {
const fetchFollowings = async () => {
const list = await Promise.all(followingsList.map(async (id) => (
await axios.get('/user?userId=' + id);
)));
setFollowers(list);
};
fetchFollowings();
}, [user]);
Note: let me know if it works, if not I'll do a little sandbox on my own

React useState array empty on initial load but after editing code while app is running array fills?

This is going to be really hard to explain, but here goes. I am building a React card grid with a filter. The data is pulled from an MySQL AWS API I built. The .tags property is JSON with an array that stores each tag associated with the card. I have written Javascript in App.jsx to turn this JSON into an object, and then store every unique tag in a piece of state. See code below:
//App.jsx
import { useEffect, useState } from 'react';
import '../assets/css/App.css';
import Card from './Card';
import Filter from './Filter'
import {motion, AnimatePresence} from 'framer-motion'
function App() {
const [cards, setCards] = useState([]);
const [filter, setFilter] = useState([]);
const [activeFilter, setActiveFilter] = useState("all");
const [tags,setTags] = useState([]);
useEffect(() => {
fetchData();
}, []);
/*useEffect(() => {
console.log(tags);
console.log(activeFilter);
}, [activeFilter,tags]);
*/
const getTags = () => {
let tags = [];
cards.forEach((card) => {
let obj = JSON.parse(card.tags);
obj.forEach((tag) => {
if (!tags.includes(tag)) {
tags.push(tag);
}
});
});
setTags(tags);
}
const fetchData = async () => {
const data = await fetch("<<api>>");
const cards = await data.json();
setCards(cards);
setFilter((cards));
getTags();
}
return (
<div className="App">
<Filter
cards={cards}
setFilter={setFilter}
activeFilter={activeFilter}
setActiveFilter={setActiveFilter}
/>
<motion.div layout className="Cards">
<AnimatePresence>
{filter.map((card) => {
return <Card key={card.id} card={card}/>;
})}
</AnimatePresence>
</motion.div>
</div>
);
}
export default App;
The problem that I am having is that when I run the app initially, the tags state is empty when inspecting from React Dev tools. However, when I keep the app running, and then add something like a console.log(tags); before setTags(tags) is called in the getTags() function, the data suddenly appears in the state. If someone could explain why the state seems to be empty even though I am updating it on the initial render that would be really appreciated.
You are running getTags on empty array. setCards doesn't set the const variable instantly. New values will be present in the next render cycle.
Try adding cards param
const getTags = (cards) => {
let tags = [];
cards.forEach((card) => {
let obj = JSON.parse(card.tags);
obj.forEach((tag) => {
if (!tags.includes(tag)) {
tags.push(tag);
}
});
});
setTags(tags);
}
And use it like this:
const fetchData = async () => {
const data = await fetch("API url");
const cards = await data.json();
setCards(cards);
setFilter((cards));
getTags(cards);
}

Asynchronously update state hook in react

I am trying to set a state generated by useState in an async function, but I found if I do like this, react would render my component infinitely.
This is a demo I made
export const App = () => {
const [nodes, setNodes] = useState([])
// some async refresh code, like http request, like axios.get("/list-nodes").then ...
const refresh = async () => {
let arr = []
for (let i = 0; i < 10; i++) {
arr.push(Math.random())
}
setNodes(arr)
}
refresh();
return (
<div>
{
nodes.map(v =>
<div>
value: {v}
</div>)
}
</div>
)
}
In the code, the rendering is continuously happening and the numbers are keeping changing.
May I ask how I am able to set a state in an async function correctly?
You need to use a useEffect hook to fetch data on the first render only. If not, the component fetches at every render, which happens every time the state is updated, which render the component ...
export const App = () => {
const [nodes, setNodes] = useState([]);
useEffect(()=>{
//Self calling async function
//Be carefull to add a ; at the end of the last line
(async () => {
let data = await fetch(url)
let json = await data.json()
setNodes(data)
})()
},[])
return (
<div>
{ nodes.map(node => <div>Value: {node}</div>) }
</div>
)
}
You can use useEffect
export default function App() {
const [nodes, setNodes] = useState([]);
const refresh = async () => {
let arr = [];
for (let i = 0; i < 10; i++) {
arr.push(Math.random());
}
setNodes(arr);
};
useEffect(() => {refresh(); }, []);
return (
<div className="App">
{nodes.map((v) => (
<div>value: {v}</div>
))}
</div>
);}

logging the data but not rendering p tag , why?

I am using firebase firestore and i fetched the data , everything is working fine but when i am passing it to some component only one item gets passed but log shows all the elements correctly.
I have just started learning react , any help is appreciated.
import React, { useEffect, useState } from 'react'
import { auth, provider, db } from './firebase';
import DataCard from './DataCard'
function Explore() {
const [equipmentList, setEquipments] = useState([]);
const fetchData = async () => {
const res = db.collection('Available');
const data = await res.get();
data.docs.forEach(item => {
setEquipments([...equipmentList, item.data()]);
})
}
useEffect(() => {
fetchData();
}, [])
equipmentList.forEach(item => {
//console.log(item.description);
})
const dataJSX =
<>
{
equipmentList.map(eq => (
<div key={eq.uid}>
{console.log(eq.equipment)}
<p>{eq.equipment}</p>
</div>
))
}
</>
return (
<>
{dataJSX}
</>
)
}
export default Explore
You have problems with setting fetched data into the state.
You need to call setEquipments once when data is prepared because you always erase it with an initial array plus an item from forEach.
The right code for setting equipment is
const fetchData = async () => {
const res = db.collection('Available');
const data = await res.get();
setEquipments(data.docs.map(item => item.data()))
}

How to create a function in Reactjs that is not called by an event Listener?

I'm a beginner in reactjs and am trying to create a simple quiz app. What I want to do can be seen in the selectionQues() function.
import React,{useState,useEffect} from 'react';
import './App.css';
import Question from './components/Question';
function App() {
const [ques, setques] = useState([]);
const [currentQues, setcurrentQues] = useState([]);
//importing ques using api
useEffect(() =>{
fetch('https://opentdb.com/api.php?amount=20&category=18&difficulty=medium&type=multiple')
.then((res)=> res.json())
.then((question) => {
setques(question.results);
});
},[setques])
//selecting 5 ques at random for our quiz
const selectingQues = () => {
let curr=[];
let qlen=ques.length;
for(let i=0;i<5;i++){
let selector= Math.floor(Math.random()*qlen);
curr[i]=ques[selector];
}
setcurrentQues(curr);
// console.log(ques);
}
return (
<div className="App">
<Question currentQues={currentQues}/>
</div>
);
}
export default App;
Now what I want to do is call this SelectingQues() without explicitly using a onClick listener or anything of the like. Is this possible using useEffect? But i want it to be executed after the first useEffect has been called.
Question component is not attached since rn it does nothing more than just displaying the ques.
IDEA: Main Idea is to get our Questions array in our API call and make sure to get random 5 questions there itself. How I do this is, as soon as I am receiving my data from my API call, I do a promise chaining and handle all my code for get 5 random questions in this another then() block. As I receive my 5 random questions, I saved in the state currentQues.
Codesandbox Demo
import React, { useState, useEffect } from "react";
import "./App.css";
function App() {
const [currentQues, setcurrentQues] = useState([]);
//importing ques using api
useEffect(() => {
selectingQues();
}, []);
// selecting 5 ques at random for our quiz
const selectingQues = async () => {
const response = await fetch(
"https://opentdb.com/api.php?amount=20&category=18&difficulty=medium&type=multiple"
);
const data = await response.json();
console.log(data);
const initialQuestions = data.results;
let curr = [];
// console.log(initialQuestions.length);
let length = initialQuestions.length;
for (let i = 0; i < 5; i++) {
let selector = Math.floor(Math.random() * length);
curr[i] = initialQuestions[selector];
}
setcurrentQues(curr);
};
return (
<div className="App">
{currentQues.length > 0 && <Question currentQuestions={currentQues} />}
</div>
);
}
export default App;
const Question = ({ currentQuestions }) => {
// const { question, correct_answer } = question;
console.log(currentQuestions);
return (
<>
{currentQuestions.map((question) => (
<div key={question.question}>
<p>
<strong>Question:</strong> {question.question}
</p>
<p>
<strong>Answer:</strong> {question["correct_answer"]}
</p>
</div>
))}
</>
);
};

Categories

Resources