Introducing The Problem
I am beginner ReactJS learner developing a simple weather app using OpenWeather API. The app is designed to fetch data from two components: one that returns the current weather of the user input and another one that returns the weather forecast for the next 5 days.
When the city name is typed down into the input field, the following message appears on the console:
GET https://api.openweathermap.org/data/2.5/weather?q=undefined&units=metric&appid=${Api.key} 400 (Bad Request)
I do not know how to pass the data from Search Component into App Component. Seriously, I have tried a lot of alternatives but they have been unsuccessful. There are commented lines of code to show my last try so far.
(ignore ForecastWeather because this component is empty)
I know that all of you are quite busy folks, but I would appreciate the help in a respectful way. Even suggestions about what I have to study (e.g. callBack) are welcome. I've tried this already:
https://stackoverflow.com/questions/56943427/whether-to-save-form-input-to-state-in-onchange-or-onsubmit-in-react
https://sebhastian.com/react-onchange/
The code is forward below:
App.js
import React, { useState } from "react";
import { Api } from "./Api";
import {
Search,
CurrentWeather,
ForecastWeather,
Footer,
} from "./components/index";
import "./App.css";
function App() {
const [getCity, setGetCity] = useState();
const [weatherData, setWeatherData] = useState(null);
const [forecastData, setForecastData] = useState(null);
const handleSearchLocation = (dataSearch) => {
const weatherDataFetch = fetch(
`${Api.url}/weather?q=${getCity}&units=metric&appid=${Api.key}`
);
const forecastDataFetch = fetch(
`${Api.url}/forecast?q=${getCity}&units=metric&appid=${Api.key}`
);
Promise.all([weatherDataFetch, forecastDataFetch])
.then(async (response) => {
const weatherResponse = await response[0].json();
const forecastResponse = await response[1].json();
setGetCity(dataSearch);
setWeatherData(weatherResponse);
setForecastData(forecastResponse);
})
.catch(console.log);
};
return (
<div className="App">
<Search
searchResultData={handleSearchLocation}
textPlaceholder="Search for a place..."
/>
{weatherData && <CurrentWeather resultData={weatherData} />}
<ForecastWeather resultData={forecastData} />
<Footer />
</div>
);
}
export default App;
Search.jsx
import React, { useState } from "react";
function Search({ textPlaceholder, searchResultData }) {
const [searchCity, setSearchCity] = useState("");
//const handlerOnChange = ( event, dataSearch ) => {
//setSearchCity(event.target.value);
//setSearchCity(dataSearch);
//searchResultData(dataSearch);
//};
return (
<div className="componentsBoxLayout">
<input
value={searchCity}
//onChange={handlerOnChange}
onChange={(event) => setSearchCity(event.target.value)}
onKeyDown={(event) => event.key === "Enter" && searchResultData(event)}
placeholder={textPlaceholder}
/>
</div>
);
}
export default Search;
CurrentWeather.jsx
import React from "react";
function CurrentWeather({ resultData }) {
return (
<div className="componentsBoxLayout">
<p>{resultData.name}</p>
</div>
);
}
export default CurrentWeather;
ForecastWeather.jsx (empty)
import React from 'react';
function ForecastWeather() {
return (
<div className="componentsBoxLayout">ForecastWeather</div>
)
}
export default ForecastWeather;
Api.js
const Api = {
url: "https://api.openweathermap.org/data/2.5",
key: "etcetc",
img: "https://openweathermap.org/img/wn",
};
export { Api };
Yippee-ki-yay
You can not use getCity in this function:
const handleSearchLocation = (dataSearch) => {
const weatherDataFetch = fetch(
`${Api.url}/weather?q=${getCity}&units=metric&appid=${Api.key}`
);
const forecastDataFetch = fetch(
`${Api.url}/forecast?q=${getCity}&units=metric&appid=${Api.key}`
);
Promise.all([weatherDataFetch, forecastDataFetch])
.then(async (response) => {
const weatherResponse = await response[0].json();
const forecastResponse = await response[1].json();
setGetCity(dataSearch);
setWeatherData(weatherResponse);
setForecastData(forecastResponse);
})
.catch(console.log);
};
getCity is defined on that function so it does not exist when you try to use it, unless you need getCity later for another component I would delete it becuase is redundant and do this:
const handleSearchLocation = (dataSearch) => {
const weatherDataFetch = fetch(
`${Api.url}/weather?q=${dataSearch}&units=metric&appid=${Api.key}`
);
const forecastDataFetch = fetch(
`${Api.url}/forecast?q=${dataSearch}&units=metric&appid=${Api.key}`
);
Promise.all([weatherDataFetch, forecastDataFetch])
.then(async (response) => {
const weatherResponse = await response[0].json();
const forecastResponse = await response[1].json();
setWeatherData(weatherResponse);
setForecastData(forecastResponse);
})
.catch(console.log);
};
When you run searchResultData on the search component you send the city you are looking for. Remember that useState will trigger a re-render but a function that is already running before that will never get the new value of the state if the state changes
Related
I want my React.js page to reload after hitting the submit button. This is because I put new entries into my database and once submitting was successful, I want to request new data from the database and display them.
import React, {useEffect, useState} from 'react';
import axios from "axios";
const Questionnaire = () => {
const [questions, setQuestions] = useState({questions: []});
const [updated, setUpdated] = useState(false); // approach 1
// let updated = false // approach 2
const onSubmit = async (answeredQuestions) => {
let data = Object.values(answeredQuestions)
axios.put('http://localhost:8000/questionnaire/', data)
.then(response => {
// setUpdated(false); // approach 1
// updated = !updated; // approach 2
}
);
};
useEffect( () => {
axios.get('http://localhost:8000/questionnaire/', {})
.then((response) => {
setQuestions({"questions": response.data});
setUpdated(true); // approach 1
});
}, [updated]);
return (
<>
<Questions questions={questions} onSubmit={onSubmit} />
</>
);
}
export default Questionnaire;
I want the useEffect() to be executed immediately after getting the response from axios.put() so that the new questions can be requested and displayed to the user.
I tried out two approaches, but either axios.get() was executed twice or the re-render didn't work properly.
I appreciate your support!
Use location.reload(); after put/post request is finished as below
import React, {useEffect, useState} from 'react';
import axios from "axios";
const Questionnaire = () => {
const [questions, setQuestions] = useState({questions: []});
const [updated, setUpdated] = useState(false); // approach 1
// let updated = false // approach 2
const onSubmit = async (answeredQuestions) => {
let data = Object.values(answeredQuestions)
axios.put('http://localhost:8000/questionnaire/', data)
.then(response => {
//The below line will force browser refresh
location.reload();
}
);
};
....
//the rest of the codes
Updated answer.
It seems that your logic for approach 1 is not completely correct. Try this instead
import React, {useEffect, useState} from 'react';
import axios from "axios";
const Questionnaire = () => {
const [questions, setQuestions] = useState({questions: []});
const [updated, setUpdated] = useState(true); // Set to true to trigger get on first render
const onSubmit = async (answeredQuestions) => {
let data = Object.values(answeredQuestions)
axios.put('http://localhost:8000/questionnaire/', data)
.then(response => {
setUpdated(true); // approach 1
}
);
};
useEffect( () => {
if (updated) {
axios.get('http://localhost:8000/questionnaire/', {})
.then((response) => {
setQuestions({"questions": response.data});
setUpdated(false); // approach 1
});
}
}, [updated]);
return (
<>
<Questions questions={questions} onSubmit={onSubmit} />
</>
);
}
export default Questionnaire;
Old answer:
Is there any reason why you need to get data in a useEffect? Or could you simply do the get-request whenever the put is resolved?
import React, {useEffect, useState} from 'react';
import axios from "axios";
const Questionnaire = () => {
const [questions, setQuestions] = useState({questions: []});
const onSubmit = async (answeredQuestions) => {
let data = Object.values(answeredQuestions)
axios.put('http://localhost:8000/questionnaire/', data)
.then(response => {
axios.get('http://localhost:8000/questionnaire/', {})
.then((response) => {
setQuestions({"questions": response.data});
});
}
);
};
return (
<>
<Questions questions={questions} onSubmit={onSubmit} />
</>
);
}
export default Questionnaire;
(Or do it with async/await since you are declaring onSubmit to be an async function)
i have setup a strapi API, and i am using react to consume that API (with Axios).
here's what the code look like inside App.js
import axios from "axios";
import React, {useEffect, useState} from "react";
import LineCard from "./components/Linecard"
function App() {
// don't mind the URL i will fix them later
const root = "http://localhost:1337"
const URL = 'http://localhost:1337/pick-up-lines'
// this is the "messed up" data from strapi
const [APIdata, setAPIdata] = useState([])
//this is the clean data
const [lines, setLines] = useState([])
// the array that i will be using later to "setLines" state
const linesFromApi = APIdata.map((line, index) => {
const profileImage = root + line.users_permissions_user.profilePicture.formats.thumbnail.url
const userName = line.users_permissions_user.username
const title = line.title
const lineBody = line.line
const rating = line.rating
const categories = line.categories.map((category, index) => category.categoryName)
return {
profileImage,
userName,
title,
lineBody,
rating,
categories
}
})
useEffect(() => {
// calling the API with get method to fetch the data and store it inside APIdata state
axios.get(URL).then((res) => {
setAPIdata(res.data)
})
setLines(linesFromApi)
}, [URL, linesFromApi])
return (
<div>
// mapping through the lines list and rendering a card for each element
{lines.map((line, index) => <LineCard line={line} />)}
</div >
);
}
export default App;
i know for sure that this is causing the problem
return (
<div>
{lines.map((line, index) => <LineCard line={line} />)}
</div >
);
my problem is that react keeps sending GET requests constantly, and i want it to stop after the first time it has the list.
how can i do that!
Try adding a check in your hook so that it restricts the api call if the value is already set.
Something like this
useEffect(() => {
if(lines.length === 0){
axios.get(URL).then((res) => {
setAPIdata(res.data)
})
setLines(linesFromApi)
}
}, [URL, linesFromApi])
You need to add the key property to the element in a map.
<div>
{lines.map((line, index) => <LineCard key={index} line={line} />)}
</div>
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()))
}
I am currently working on a chat application and for some reason every time I pass in my array of messages as a prop to another component it passes in a number to the component instead of the message object. I have tried a lot of different methods of passing it in regarding using multiple components etc but it seems to still be passing in the number of elements for some reason. Any help is appreciated... code is below
Component receiving the props
import React, { useEffect } from 'react'
import Message from '../../Message/Message'
function Messages({ messages }) {
useEffect(() => {
console.log(messages)
}, [messages])
return (
<div>
test
</div>
)
}
export default Messages
// Import React dependencies.
import React, { useEffect, useState, } from "react";
// Import React dependencies.
import io from 'socket.io-client'
import axios from 'axios'
import Messages from './Messages/Messages'
import uuid from 'react-uuid'
import { Redirect } from 'react-router-dom'
// Import the Slate components and React plugin.
const ENDPOINT = 'http://localhost:5000/'
export const socket = io.connect(ENDPOINT)
const LiveChatFunction = ({ group_id }) => {
// Add the initial value when setting up our state.
const [message, setValue] = useState("")
const [user, setUser] = useState("")
const [groupId, setGroup] = useState('')
const [messages, setMessages] = useState([])
const [toLogin, userAuth] = useState(false)
useEffect(() => {
setGroup(group_id)
axios.post('http://localhost:5000/api/users/refresh_token', null, { withCredentials: true }).then(data => {
if (!data.data.accessToken) {
userAuth(true)
}
})
axios.get('http://localhost:5000/api/users/userInfo', { withCredentials: true }).then(data => {
setUser(data.data.user)
})
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
axios.get(`http://localhost:5000/live/${group_id}`).then(x => {
console.log(x.data)
})
}, [group_id, messages])
function setClick() {
const data = {
messageId: uuid(),
user,
groupId,
message
}
socket.emit('message', data)
}
if (toLogin) {
return (
<Redirect to="/login" />
)
}
return (
<div>
<input placeholder="message" type="text" onChange={value => {
setValue(value.target.value)
socket.emit('typing-message', { username: user, time: new Date() })
}} />
<button onClick={setClick}>Submit</button>
<Messages messages={messages} />
</div>
)
}
export default LiveChatFunction;
I have added some comments of what I think you can change:
useEffect(() => {
const recieveFunction = (data) => {
//using callback so no dependency on messages
setMessages((messages) => messages.push(data));
};
async function init() {
//next line is pointless, this runs when group_id
// has changed so something must have set it
// setGroup(group_id);
await axios //not sure if this should be done before listening to socket
.post(
'http://localhost:5000/api/users/refresh_token',
null,
{ withCredentials: true }
)
.then((data) => {
if (!data.data.accessToken) {
userAuth(true);
}
});
await axios
.get('http://localhost:5000/api/users/userInfo', {
withCredentials: true,
})
.then((data) => {
setUser(data.data.user);
});
//start listening to socket after user info is set
socket.on(`message-${group_id}`, recieveFunction);
axios
.get(`http://localhost:5000/live/${group_id}`)
.then((x) => {
console.log(x.data);
});
}
init();
//returning cleanup function, guessing socket.off exists
return () =>
socket.off(`message-${group_id}`, recieveFunction);
}, [group_id]); //no messages dependencies
console.log('messages are now:',messages);
If messages is still not set correctly then can you log it
So I think I found your problem:
In your useEffect hook, you're setting messages to the wrong thing.
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
An example:
const m = [].push();
console.log(m);
// m === 0
const n = [].push({});
console.log(n);
// n === 1
As you can see this is the index.
So what you need is:
socket.on(`message-${group_id}`, data => {
messages.push(data);
setMessages(messages);
});
This will set messages to the array of messages.
I am new to React and I want to run API based search. I have written a sample code with the search functionality but it is not working as per requirement. I want to search from the list but it is always giving the same array of as the whole list when I am writing anything in the search box.
Please help me out and let me know where the code in wrong.
Here is my code:
TestEntry.js
import React , {useState,useEffect} from 'react'
import {Table} from 'reactstrap'
import {Navbar,Nav,NavDropdown,Form,FormControl,Button} from 'react-bootstrap'
//import axios from 'axios'
import Loading from './loading.gif';
const CoinGecko = require('coingecko-api');
const CoinGeckoClient = new CoinGecko();
function TestEntry(){
const[item,SearchData]=useState([]);
const[cryptos,setCryptos]=useState([]);
useEffect(()=>{
fetchItems()
},[])
const fetchItems=async()=>{
const url="https://api.coingecko.com/api/v3/coins/list";
const response= await fetch(url);
const info=await response.json();
console.log(info);
setCryptos(info);
}
const Search=(key)=>{
console.log(key);
fetch("https://api.coingecko.com/api/v3/coins/list?q="+key)
.then((data)=>{
data.json().then((resp)=>{
console.warn("resp:",resp)
SearchData(resp)
})
})
}
const cryptoJsx=cryptos.map(crypto=>(
<div key={crypto.id}>
{crypto.id}
</div>
));
return(
<div>
Search:
<input type="text" onChange={(event)=>Search(event.target.value)}/>
<div>
{
{item} ?
<div>
{
item.map((items)=>
<div key={items.id}>{items.name}</div>
)
}
</div>
: ""
}
</div>
{cryptoJsx}
</div>
)
}
export default TestEntry
The search api seems not working. When I tried api-search for a text separately in browser, it returned full results.
Anyway....
You can do search locally i.e. filter the cryptos array. It will cause re-render and only filtered results are shown.
Note:
Maintain a copy of the cryptos and always filter based on original and only mutate the cryptos. This way search works (both typing a char and deleting a char) and search results are re-rendered automatically
The downside of filtering state data is that new data from server is only obtained in client upon page refresh
If you really want to use api docs and use correct endpoint. Also consider using debouncing. Perform debounce in React.js
I have checked this and search is working fine.
import React, { useState, useEffect } from "react";
function TestEntry() {
const [item, SearchData] = useState([]);
const [cryptos, setCryptos] = useState([]);
const [origCryptosCount, setOrigCryptosCount] = useState([]);
useEffect(() => {
fetchItems();
}, []);
const fetchItems = async () => {
const url = "https://api.coingecko.com/api/v3/coins/list";
const response = await fetch(url);
const info = await response.json();
setCryptos(info);
setOrigCryptosCount(info);
};
// const Search_Old = key => {
// console.log(key);
// fetch("https://api.coingecko.com/api/v3/coins/list?q=" + key).then(data => {
// data.json().then(resp => {
// SearchData(resp);
// });
// });
// };
//
const Search = key => {
const newResults = origCryptosCount.filter(crypto => crypto.name.includes(key));
console.log('newResults', newResults);
setCryptos(newResults);
};
const cryptoJsx = cryptos.map(crypto => (
<div key={crypto.id}>{crypto.id}</div>
));
return (
<div>
Search:
<input type="text" onChange={event => Search(event.target.value)} />
{cryptoJsx}
</div>
);
}
export default TestEntry;