React, unexpected multiple result when using map and fetch - reactjs

I am building Weather App, my idea is to save city name in localStorage, pass a prop to child component, then iterate using map and display each in seperate child of the first child
The problem is that displayed data doubles/triples on render(depending on component when render occurs) so when I have for example city London and add city Berlin it will render:
London,London,Berlin
The problem is not in AddCity component, it's working correctly but in this mix of asynchronous setState/fetching and maping
Please see the code below
App(parent component)
const App = () => {
const [cities, setCities] = useState([]);
const addCity = (newCity)=>{
console.log('adding')
setCities([...cities, newCity]);
let cityId = localStorage.length;
localStorage.setItem(`city${cityId}`, newCity);
}
useEffect(() => {
loadCityFromLocalStore()
}, [])
const loadCityFromLocalStore =()=>{
setCities([...cities, ...Object.values(localStorage)])
}
return (
<div>
<Header />
<AddCity addCity={addCity}/>
<DisplayWeather displayWeather={cities}/>
</div>
)
}
DisplayWeather (first child)
const DisplayWeather = ({displayWeather}) => {
const apiKey = '4c97ef52cb86a6fa1cff027ac4a37671';
const [fetchData, setFetchData] = useState([]);
useEffect(() => {
displayWeather.map(async city=>{
const res =await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData((fetchData=>[...fetchData , data]));
})
}, [displayWeather])
return (
<>
{fetchData.map(data=>(
<ul>
<Weather
data={data}/>
</ul>
))}
</>
)
}
Weather component
const Weather = ({data}) => {
return (
<li>
{data.name}
</li>
)
}

It looks like the problem comes from calling setFetchData for cities that you already added previously.
One easy way to fix it would be to store fetch data as an object instead of a dictionary so that you just override the data for the city in case it already exists (or maybe even skip the fetch as you already have the data).
For example:
const [fetchData, setFetchData] = useState({});
useEffect(() => {
displayWeather.map(async city=>{
const res = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData((fetchData=> ({...fetchData, [city]: data})));
})
}, [displayWeather])
Then, to map over fetch data you can just use Object.values:
return (
<>
{Object.values(fetchData).map(data=>(
<ul>
<Weather
data={data}/>
</ul>
))}
</>
)
If you want to skip already fetched cities you can do something like this instead:
useEffect(() => {
displayWeather.map(async city=>{
if (!fetchData[city]) {
const res = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData((fetchData=> ({...fetchData, [city]: data})));
}
})

Related

How to display an array in JSX from a function in React?

I implemented a function where I fetch all Docs from a Firebase collection on a click.
Now I want to display each doc I fetched in a <div> container in JSX. When I try to take the array and display it, I´m getting the error that the array is not found.
This is my code:
async function getAllDivs(){
const querySnapshot = await getDocs(collection(db, "Div"))
const allDivs = [];
querySnapshot.forEach(doc => {
allDivs.push(doc.data().DivContent);
});
}
You would have to return the array from the function, because of the "scope".
Example:
//your current function
async function getAllDivs(){
const querySnapshot = await getDocs(collection(db, "Div"));
return querySnapshot.map((doc) => doc.data().DivContent);
}
//your component
let divs = getAllDivs(); //you can now use "divs" in this scope
return (
<>
divs.map((current_div) => { <div>{current_div}</div> })
</>
)
Also, I suggest against pushing data to an array labeled as const, as it could be confusing for someone else reading your code.
I think you could use something like this:
const MyComponent = () => {
const [docs, setDocs] = useState();
const onClickHandler = async () => {
const docs = await getDocs(collection(db, "Div"));
setDocs(docs);
}
return (
<>
<button onClick={onClickHandler}>Get docs</button>
{docs && docs.map(doc => (
<div>{doc.data().DivContent}</div>
))}
</>
)
}
If DivContent contains HTML you can use dangerouslySetInnerHTML.

When Url Typed I need to fetch the single data in react js while calling api

enter image description here
When I typed this http://localhost:3000/hell/:2 the page loads but will not fetch a single item that has id 2
But When I clicked the button then the page shows the single item that has id 2
I need to show the data when I entered http://localhost:3000/hell/:2 as URL
""""""""""CODE Gets RUN but it was showing in a paragraph format so I had edit and made code easier to understand """""""""
the code is --->>
At App.js-->
<div className="App">
<div className='body'>
<Router history={History}>
<Switch>
<Route path="/hell/:id"><Hell/></Route>
</Switch>
</Router>
</div>
</div>
At Hello.js-->
let {id} = useParams();
let di = id;
const [loading,setloading] = useState([false]);
const [posts,setposts] = useState([]);
const [search,setsearch] = useState("");
const [message,setmessage] = useState("");
const history = useHistory();
useEffect(()=>{
const getload = async ()=>{
setloading(true);
const response = await axios.get(`http://127.0.0.1:8000/list/`);
const message = "Error";
setposts(response.data);
setmessage(message);
setloading(false);
}
},[]);
console.log({di});
function inputhandler(a){
id = a;
history.push(generatePath("/hell/:id", { id }));
setsearch(a);
}
return (
<div>
<h1>Find : {id}</h1>
{
posts.map((curElem) =>{
return(
<div>
<Link key={curElem.id} to={curElem.id} onClick={() => inputhandler(curElem.id)}>{curElem.title}</Link>
</div>
)
})
}
{
loading ?(<h4>Loading...{message}</h4>):(
(posts.filter((value)=>{
if(value.id===(search)){
return message;
}
})
.map(item =><Help key={item.id} title={id}></Help>)))
}
</div>
)
}
You've accessed the id route match param but then never used it.
You do not form the link targets correctly. Once you are linking correctly then there is no need for the extraneous onClick handler to set any search state with the item.id value since you can consume the linked id right from the params.
Remember to also call getload so the posts state is updated.
Hello.js
const history = useHistory();
const { id } = useParams(); // <-- current `id` from "/hell/:id"
const [loading,setloading] = useState([false]);
const [posts,setposts] = useState([]);
const [message,setmessage] = useState("");
useEffect(()=>{
const getload = async ()=>{
setloading(true);
const response = await axios.get(`http://127.0.0.1:8000/list/`);
const message = "Error";
setposts(response.data);
setmessage(message);
setloading(false);
}
getload(); // <-- need to call to make the GET request
},[]);
return (
<div>
<h1>Find : {id}</h1>
{posts.map((curElem) => {
return(
<div key={curElem.id}> // <-- React key on outermost element
<Link
// generate target path here
to={generatePath("/hell/:id", { id: curElem.id })}
>
{curElem.title}
</Link>
</div>
)
})}
{loading
? <h4>Loading...{message}</h4>
: posts.filter((value) => value.id === id) // <-- filter by id
.map(item => <Help key={item.id} title={id}></Help>)
}
</div>
)

Why my hook state doesn´t update correctly?

i don't know how make this guys, i can't update my state with the api array, and if i put it in useEffect i have an error cause i am not sending any data, help me please is my first time using stackoverflow
import React, { useEffect, useState } from "react";
import getTeam from "../Helpers/getTeam";
const selectTeams = [
"Barcelona",
"Real Madrid",
"Juventus",
"Milan",
"Liverpool",
"Arsenal",
];
const Select = () => {
const [team, setTeam] = useState(null);
const [loading, setLoading] = useState(null);
const handleOption = async (e) => {
setLoading(true);
let teamsJson = await getTeam(e.target.value);
let arr = [];
Object.keys(teamsJson).map((teamjs, i) => {
return arr.push(teamsJson[teamjs]);
});
console.log(arr);
console.log(team);
setTeam(arr);
setLoading(false);
};
return (
<div
style={{ background: "skyblue", textAlign: "center", padding: "20px" }}
>
<h1>Equipos Disponibles</h1>
<div>
<select onChange={handleOption}>
<option>Elige tu equipo</option>
{selectTeams.map((selectTeam, i) => {
return <option key={i}>{selectTeam}</option>;
})}
</select>
</div>
{loading ? <h1>suave</h1> : (
team !== null ? (
team.map((newTeam, i) => {
return (
<div>
the items are here
</div>
)
})
) : null
)}
</div>
);
};
export default Select;
i let you my api file down
const getTeam = async (teamName) => {
const url = `https://www.thesportsdb.com/api/v1/json/1/searchteams.php?t=${teamName}`;
const res = await fetch(url);
const team = await res.json();
return team;
};
export default getTeam;
i wanna update my const team with the response of my api call, but it doesn't update it, i dont know what do, please help me
The teamsJson value is an object with a single key and value of some array
{ teams: [...] }
So you are updating your state with a nested array when you push the value into another array.
let arr = [];
Object.keys(teamsJson).map((teamjs, i) => {
return arr.push(teamsJson[teamjs]);
});
Based upon how you want to map your team state array I assume you just want the raw inner array from teamJson.
const { teams } = await getTeam(e.target.value);
setTeam(teams);
Then when you are mapping you can access any of the properties you need.
team.map((newTeam, i) => {
return <div key={i}>{newTeam.idTeam}</div>;
})
I've just tested it & it seems to works just fine.
The only 2 issues seem to be that:
You don't use team anywhere (apart from a console.log statement).
At the moment when you console.log(team); the constant team will (yet) be null for the first time (because it still keeps the initial state).
Here's what I see in React dev tools after picking a random team in the <select>:

React: Passing up data from child component into parent does not update values in parent

// Edit --
This may help:
Project Hatchways
Link to issue -
Issue
As the codes stands right now, the results from the tags still aren't rendering results.
I have a component App.js that renders some children. One of them is 2 search bars. The second search bar TagSearch is supposed to render results from tag creation. What I'm trying to do is pass data from Student where the tags live, and pass them up to the App component in order to inject them into my Fuse instance in order for them to be searched. I have tried to create a function update in App.js and then pass it down to Student.js in order for the tags to update in the parent when a user searches the tags. For some reason, I'm getting a TypeError that states update is not a function.
I put in console logs to track where the tags appear. The tags appear perfectly fine in Student.js, but when I console log them in App.js, the tags just appear as an empty array which tells me they aren't being properly passed up the component tree from Student.js to App.js.
// App.js
import axios from "axios";
import Fuse from "fuse.js";
import Student from "./components/Student";
import Search from "./components/Search";
import TagSearch from "./components/TagSearch";
import "./App.css";
function App() {
const [loading, setLoading] = useState(true);
const [students, setStudents] = useState([]);
const [query, updateQuery] = useState("");
const [tags, setTags] = useState([]);
const [tagQuery, setTagQuery] = useState("");
console.log("tags from app: ", tags);
const getStudents = async () => {
setLoading(true);
try {
const url = `private url for assignment`;
const response = await axios.get(url);
setStudents(response.data.students);
setLoading(false);
} catch (err) {
console.log("Error: ", err);
}
};
const fuse = new Fuse(students, {
keys: ["firstName", "lastName"],
includeMatches: true,
minMatchCharLength: 2,
});
const tagFuse = new Fuse(tags, {
keys: ["text", "id"],
includesMatches: true,
minMatchCharLength: 2,
});
function handleChange(e) {
updateQuery(e.target.value);
}
function handleTags(e) {
setTagQuery(e.target.value);
}
const results = fuse.search(query);
const studentResults = query ? results.map((s) => s.item) : students;
const tagResults = tagFuse.search(tagQuery);
const taggedResults = tagQuery ? tagResults.map((s) => s.item) : tags;
const update = (t) => {
t = tags; // changed this to make sure t is tags from this component's state
setTags(t);
};
useEffect(() => {
getStudents();
}, []);
if (loading) return "Loading ...";
return (
<div className="App">
<main>
<Search query={query} handleChange={handleChange} />
<TagSearch query={tagQuery} handleTags={handleTags} />
{studentResults &&
studentResults.map((s, key) => <Student key={key} students={s} update={update} />)}
{taggedResults &&
taggedResults.map((s, key) => (
<Student key={key} students={s} update={update} />
))}
</main>
</div>
);
}
export default App;
// Student.js
import Collapsible from "../components/Collapsible";
import findAverage from "../helpers/findAverage";
import Styles from "../styles/StudentStyles";
const KeyCodes = {
comma: 188,
enter: 13,
};
const delimiters = [KeyCodes.comma, KeyCodes.enter];
const Student = ({ students, update }) => {
const [isOpened, setIsOpened] = useState(false);
const [tags, setTags] = useState([]);
const collapse = () => {
setIsOpened(!isOpened);
};
const handleDelete = (i) => {
const deleted = tags.filter((tag, index) => index !== i);
setTags(deleted);
};
const handleAddition = (tag, i) => {
setTags([...tags, tag]);
};
useEffect(() => {
update(tags);
}, []);
return (
<Styles>
<div className="student-container">
<img src={students.pic} alt={students.firstName} />
<div className="student-details">
<h1>
{students.firstName} {students.lastName}
</h1>
<p>Email: {students.email}</p>
<p>Company: {students.company}</p>
<p>Skill: {students.skill}</p>
<p>Average: {findAverage(students.grades)}</p>
<Collapsible
students={students}
delimiters={delimiters}
handleDelete={handleDelete}
handleAddition={handleAddition}
isOpened={isOpened}
tags={tags}
/>
</div>
</div>
<button onClick={collapse}>+</button>
</Styles>
);
};
export default Student;
Ciao, try to call update function every time you update tags in Student. Something like this:
const handleDelete = (i) => {
const deleted = tags.filter((tag, index) => index !== i);
setTags(deleted);
update(deleted);
};
const handleAddition = (tag, i) => {
let result = tags;
result.push(tag);
setTags(result);
update(result);
};
In this way, every time you change tags in Student, you will update App state.
An alternative could be use useEffect deps list. In Student, modify useEffect like this:
useEffect(() => {
update(tags);
}, [tags]);
This means that, every time tags will update, useEffect will be triggered and update function will be called.

Reactjs How is the weather state in <Weather /> component is undefined even though 'promise' is fullfilled?

I am trying to create a search filter for countries. I search a country and display their information and weather of country's capital using a weather api. I am fetching the data of a country using axios but the response.data is undefined and hence its cause error.
I know the code is async. So how do I fetch data from url before I setWeather(response.data) .
const Weather = ({capital}) => {
const [weather, setWeather] = useState([])
const key = 'mykey'
const url = `http://api.weatherstack.com/current?access_key=${key}&query=${capital}`
axios.get(url)
.then(response => {
console.log('promise fullfilled')
setWeather(response.data)
})
return(
<div>
<h1>Weather in {weather.location.name}</h1>
<h2>temperature: {weather.current.temperature} </h2>
<img src = {weather.current.weather_icons} />
<h2>wind: {weather.current.wind_speed} kph direction {weather.current.wind_dir}</h2>
</div>
)
}
const PrintLanguages = ({lang}) =>{
return(
lang.map(l => <li key={l}>{l}</li>)
)
}
const View = ({country}) =>{
const lang = country.languages.map(lang => lang.name)
return(
<div>
<h1>{country.name}</h1>
<p>capital {country.capital}</p>
<p>population {country.population}</p>
<h2>languages</h2>
<ul><PrintLanguages lang={lang}/></ul>
<img src={country.flag} alt="flag photo" height="100" width="100"/>
<Weather capital={country.capital}/>
</div>
)
}
I expected this result but instead I am getting this Type Error
Please guide me on how to fix this ??
You can use the effect hook here:
const Weather = ({capital}) => {
const [weather, setWeather] = useState({location:{}, current: {}});
const key = 'mykey'
const url = `http://api.weatherstack.com/current?access_key=${key}&query=${capital}`
useEffect(() => {
axios.get(url)
.then(response => {
console.log('promise fullfilled')
setWeather(response.data)
})
}, [capital]) // Fetch the data when capital changes
return(
<div>
<h1>Weather in {weather.location.name}</h1>
<h2>temperature: {weather.current.temperature} </h2>
<img src = {weather.current.weather_icons} />
<h2>wind: {weather.current.wind_speed} kph direction {weather.current.wind_dir}</h2>
</div>
)
}
This will call the api method after the component is mounted.
Also make sure your initial state structure is the same as the one for the rendered state. In your case you set it to an empty array but when rendering it expects an object.
One more way is to use a loading state, during which you can show a loading indicator before the data fetches:
const Weather = ({capital}) => {
const [weather, setWeather] = useState({location:{}, current: {}});
const [loading, setLoading] = useState(true);
const key = 'mykey'
const url = `http://api.weatherstack.com/current?access_key=${key}&query=${capital}`
useEffect(() => {
axios.get(url)
.then(response => {
console.log('promise fullfilled')
setLoading(false);
setWeather(response.data)
})
}, [])
return loading ? <p>Loading...</p> : (
<div>
<h1>Weather in {weather.location.name}</h1>
<h2>temperature: {weather.current.temperature} </h2>
<img src = {weather.current.weather_icons} />
<h2>wind: {weather.current.wind_speed} kph direction {weather.current.wind_dir}</h2>
</div>
)
}

Resources