I'm new to programming and have been learning React for a few weeks. I decided to create a weather app to practice what I've learned so far.
I created a Button.js file, where I can enter a zip code. The zip code is then used to setZip in Weather.js.
I am using the zip to fetch lat/long, which I would like to use (the lat/long) in another fetch. But when the 2nd fetch runs, it uses the previous lat/long, not the newly set lat/long.
For example:
If I enter 10001, the app uses lat/long from 90210 (which is what state started with). Then if I enter 99501, the app uses the lat/long from 10001.
It's like the 2nd fetch is always one step behind.
But even the console.log I have right below the 1st fetch shows the previous lat/long.
Thank you for your help.
Weather.js
import React, {useState, useEffect} from "react"
import Button from "./Button"
function Weather() {
const [loading, setLoading] = useState(false)
const [maxTemp, setMaxTemp] = useState([])
const [lat, setLat] = useState("34")
const [long, setLong] = useState("-118")
const [zip, setZip] = useState("90210")
useEffect(()=>{
setLoading(true)
fetch("https://api.openweathermap.org/geo/1.0/zip?zip="+ zip +",US&appid={api}")
.then(res => res.json())
.then((result) => {
setLat(result.lat)
setLong(result.lon)
console.log(lat)
console.log(long)
console.log(zip)
console.log(result)
return fetch("https://api.openweathermap.org/data/2.5/onecall?lat="+ lat +"&lon="+ long +"&units=imperial&exclude=current,minutely,hourly,alerts&appid={api}")
})
.then(res => res.json())
.then((data) => {
setLoading(false)
setMaxTemp(data.daily[0].temp.max)
console.log(lat)
console.log(long)
console.log(data)
})
}, [zip])
if(loading === true){
return <div>Loading...</div>
} else return(
<div>
<Button zip={setZip} /> <br />
High: {Math.round(maxTemp)}
</div>
)
}
export default Weather
Button.js
import React, {useState} from "react"
function Button(props) {
const [zip, setZip] = useState([])
const handleSubmit = (event) => {
console.log(zip)
props.zip(`${zip}`)
event.preventDefault();
}
return(
<div>
<input placeholder="Zip Code" type="number" min="0" max="99999" value={zip} onChange={(e) => setZip(e.target.value)} />
<br />
<button onClick={handleSubmit}>Submit</button>
</div>
)
}
export default Button
As the state is being set and immediately available, you'll need to use the value returned from the first API call.
import React, {useState, useEffect} from "react"
import Button from "./Button"
function Weather() {
const [loading, setLoading] = useState(false)
const [maxTemp, setMaxTemp] = useState([])
const [lat, setLat] = useState("34")
const [long, setLong] = useState("-118")
const [zip, setZip] = useState("90210")
useEffect(() => {
setLoading(true)
fetch("https://api.openweathermap.org/geo/1.0/zip?zip=" + zip + ",US&appid={api}")
.then(res => res.json())
.then(result => {
const {lat, lon} = result;
setLat(lat)
setLong(lon)
console.log(lat)
console.log(long)
console.log(zip)
console.log(result)
return fetch("https://api.openweathermap.org/data/2.5/onecall?lat=" + lat + "&lon=" + long + "&units=imperial&exclude=current,minutely,hourly,alerts&appid={api}")
})
.then(res => res.json())
.then((data) => {
setLoading(false)
setMaxTemp(data.daily[0].temp.max)
console.log(lat)
console.log(long)
console.log(data)
})
}, [zip])
if (loading === true) {
return <div>Loading...</div>
} else return (
<div>
<Button zip={setZip}/> <br/>
High: {Math.round(maxTemp)}
</div>
)
}
export default Weather
If you want to use the updated state, you'll need to do that in a separate useEffect hook with lat and long in the dependency array.
Related
import React, { useState, useEffect } from "react";
import Nav from "./nav.jsx";
import axios from "axios";
import Mensbuttons from "./mensbuttons.jsx";
export default function Standort() {
const [radius, setRadius] = useState(10);
const [latitude, setLatitude] = React.useState("");
const [longitude, setLongitude] = React.useState("");
React.useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setLatitude(position.coords.latitude);
setLongitude(position.coords.longitude);
});
}, []);
function handleClick() {
setLongitude()
}
const [posts, setPosts] = useState([]);
useEffect(() => {
axios
.get(
"https://openmensa.org/api/v2/canteens?near[lat]=" +
latitude +
"&near[lng]=" +
longitude +
"&near[dist]=" +
radius
)
.then((res) => {
setPosts(res.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<>
<Nav />
<div class="header">
<h1>Mensen in der Nähe</h1>
</div>
<button onClick={handleClick(2)}>2</button>
{posts.map((list) => {
return <Mensbuttons name={list.name} id={list.id} />;
})}
</>
);
}
Hello guys so I have a method where I want to show nearby canteens. I do this by getting the current koordinates and passing them as a varible into the Url of the api. The Problem is the buttons of the canteens only show up when I change something at the code. So I probably have to rerender it. But if I try to do it with a button method. Nothing shows up probably because there too many rerenders.(I think that because this shows in the console)
I hope you can help me
Your onClick handler should be a function, not a function call.
Instead of
<button onClick={handleClick(2)}>2</button>
use
<button onClick={() => handleClick(2)}>2</button>
I believe you haven't defined the useEffects correctly.
I reduced longitude and latitude states to a single coordinates state. which is initialized as null.
const [coordinates, setCoordinates] = React.useState(null);
Which in the very first render is set to true coordinates:
React.useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setCoordinates(position.coords);
});
}, []);
Now make the second useEffect run only after the coordinates has changed:
useEffect(() => {
if (coordinates) { // Don't let it make get request if it is null.
const { latitude, longitude } = coordinates;
axios
.get(
"https://openmensa.org/api/v2/canteens?near[lat]=" +
latitude +
"&near[lng]=" +
longitude +
"&near[dist]=" +
radius
)
.then((res) => {
setPosts(res.data);
})
.catch((err) => {
console.log(err);
});
}
}, [coordinates]);
And, I don't think this button is needed anymore:
<button onClick={handleClick(2)}>2</button>
Final Code:
import React, { useState, useEffect } from "react";
import Nav from "./nav.jsx";
import axios from "axios";
import Mensbuttons from "./mensbuttons.jsx";
export default function Standort() {
const [radius, setRadius] = useState(10);
const [coordinates, setCoordinates] = React.useState(null);
React.useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setCoordinates(position.coords);
});
}, []);
const [posts, setPosts] = useState([]);
useEffect(() => {
if (coordinates) {
// Don't let it make get request if it is null.
const { latitude, longitude } = coordinates;
axios
.get(
"https://openmensa.org/api/v2/canteens?near[lat]=" +
latitude +
"&near[lng]=" +
longitude +
"&near[dist]=" +
radius
)
.then((res) => {
setPosts(res.data);
})
.catch((err) => {
console.log(err);
});
}
}, [coordinates]);
return (
<>
<Nav />
<div class="header">
<h1>Mensen in der Nähe</h1>
</div>
{posts.map((list) => {
return <Mensbuttons name={list.name} id={list.id} />;
})}
</>
);
}
I want to do a movie search with the oMdb api using React Hooks.
The result is not as expected. I seem to break some React Hooks rule that I don't understand.
Here is the code.
HOOK TO SEARCH
The Hook inside of a store.
(If I use searchMovies('star wars') in a console.log I can see the result of star wars movies and series.)
import React, { useState, useEffect } from "react";
const useSearchMovies = (searchValue) => {
const API_KEY = "731e41f";
const URL = `http://www.omdbapi.com/?&apikey=${API_KEY}&s=${searchValue}`
// Manejador del estado
const [searchMovies, setSearchMovies] = useState([])
//Llamar y escuchar a la api
useEffect(() => {
fetch(URL)
.then(response => response.json())
.then(data => setSearchMovies(data.Search))
.catch((error) => {
console.Console.toString('Error', error)
})
}, []);
return searchMovies;
};
THE INPUT ON A SANDBOX
Here i have the input to search with a console log to see the result.
import React, { useState } from "react";
import searchMovies from "../store/hooks/useSearchMovies";
const Sandbox = () => {
const [search, setSearch] = useState('')
const onChangeHandler = e =>{
setSearch(e.target.value)
console.log('Search result', searchMovies(search))
}
const handleInput =()=> {
console.log('valor del input', search)
}
return (
<div>
<h1>Sandbox</h1>
<div>
<input type="text" value={search} onChange={onChangeHandler}/>
<button onClick={handleInput()}>search</button>
</div>
</div>
)
}
export default Sandbox;
Issue
You are breaking the rules of hooks by conditionally calling your hook in a nested function, i.e. a callback handler.
import searchMovies from "../store/hooks/useSearchMovies";
...
const onChangeHandler = e => {
setSearch(e.target.value);
console.log('Search result', searchMovies(search)); // <-- calling hook in callback
}
Rules of Hooks
Only call hooks at the top level - Don’t call Hooks inside loops,
conditions, or nested functions.
Solution
If I understand your code and your use case you want to fetch/search only when the search button is clicked. For this I suggest a refactor of your useSearchMovies hook to instead return a search function with the appropriate parameters enclosed.
Example:
const useSearchMovies = () => {
const API_KEY = "XXXXXXX";
const searchMovies = (searchValue) => {
const URL = `https://www.omdbapi.com/?apikey=${API_KEY}&s=${searchValue}`;
return fetch(URL)
.then((response) => response.json())
.then((data) => data.Search)
.catch((error) => {
console.error("Error", error);
throw error;
});
};
return { searchMovies };
};
Usage:
import React, { useState } from "react";
import useSearchMovies from "../store/hooks/useSearchMovies";
const Sandbox = () => {
const [search, setSearch] = useState('');
const [movies, setMovies] = useState([]);
const { searchMovies } = useSearchMovies();
const onChangeHandler = e => {
setSearch(e.target.value)
};
const handleInput = async () => {
console.log('valor del input', search);
try {
const movies = await searchMovies(search);
setMovies(movies);
} catch (error) {
// handle error/set any error state/etc...
}
}
return (
<div>
<h1>Sandbox</h1>
<div>
<input type="text" value={search} onChange={onChangeHandler}/>
<button onClick={handleInput}>search</button>
</div>
<ul>
{movies.map(({ Title }) => (
<li key={Title}>{Title}</li>
))}
</ul>
</div>
);
};
export default Sandbox;
I am preparing a game in which the player, based on the presented card, has to decide whether the next one will be higher or lower than the previous one. Every round, data(card value and image) is pulled from the API https://deckofcardsapi.com/.
I have a problem, how can I compare the old and new condition if the data is downloaded from the API.
import { useEffect, useState, useCallback } from 'react';
import Card from './components/Card/Card';
import { Fragment } from 'react';
import HistoryTable from './components/HistoryTable/HistoryTable';
import './App.scss';
import Buttons from './components/Buttons/Buttons';
import Stats from './components/Stats/Stats';
function App() {
const [card, setCard] = useState({});
const [isLoading, setIsLoading] = useState(true);
const [httpError, setHttpError] = useState();
const [roundNumber, setRoundNumber] = useState(1);
const [score, setScore] = useState(0);
function handleHigherClick(e) {
e.preventDefault();
setRoundNumber((prevState) => ++prevState);
//How can I compare new state with previous?
}
function handleLowerClick(e) {
e.preventDefault();
setRoundNumber((prevState) => ++prevState);
//How can I compare new state with previous?
}
const fetchCard = useCallback(async () => {
setIsLoading(true);
setHttpError(null);
try {
const response = await fetch(
'https://deckofcardsapi.com/api/deck/new/draw/?count=1'
);
if (!response.ok) {
throw new Error('Something went wrong!');
}
const responseData = await response.json();
const data = responseData.cards[0];
setCard({
value: data.value,
image: data.image,
});
} catch (error) {
setHttpError(error.message);
}
setIsLoading(false);
}, []);
useEffect(() => {
fetchCard();
}, [roundNumber, fetchCard]);
return (
<Fragment>
<Header />
<main>
<Stats round={roundNumber} score={score} />
<Card
value={card.value}
image={card.image}
loading={isLoading}
error={httpError}
/>
{roundNumber !== 30 ? (
<Buttons higher={handleHigherClick} lower={handleLowerClick} />
) : (
<Buttons />
)}
<HistoryTable />
</main>
</Fragment>
);
}
export default App;
It seems to me that you will have to do this manually, storing the result of each fetch in a structure that allows you to do this comparison.
A two-position array seems to me the most ideal form.
Maybe you can manipulate this structure using just .pop() and .shift()
I try to training in react and want to make a form who call the api marvel when submitted with the current input and display the name + description of the character search.
The Api call is ok but when i submit the form nothing show any advice?
import React, { Component, useEffect, useState } from 'react'
import axios from 'axios'
const SearchEngine = React.forwardRef((props, ref) => {
const [asked, setAsked] = useState([]);
const [characterInfos, setCharacterInfos] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const [loading, setLoading] = useState(true);
const [inputs, setInputs] = useState('');
const handleChange = (event) => {
setInputs(event.target.value);
console.log(inputs);
}
const getCharacters = (inputs) => {
setSearchTerm(inputs)
axios
.get(`https://gateway.marvel.com:443/v1/public/characters?name=${searchTerm}&apikey=XXX`)
.then(response => {
console.log(searchTerm)
console.log(response)
setCharacterInfos(response.data.data.results[0]);
setLoading(false);
console.log(response.data.data.results[0].name)
response.data.data.results.map((item) => {
return characterInfos.push(item.name)
})
localStorage.setItem(characterInfos, JSON.stringify(response.data))
if (!localStorage.getItem('marvelStorageDate')) {
localStorage.setItem('marvelStorageDate', Date.now());
}
})
.catch(error => {
console.log(error);
})
}
return (
<div className="search-container">
<h1>Character Infos</h1>
<form onSubmit={getCharacters}>
<input
type="text"
placeholder="Search"
value={inputs}
onChange={handleChange}
/>
<input type="submit" value="Envoyer" />
</form>
<ul>
<li>{characterInfos.name}</li>
</ul>
</div>
)
})
export default React.memo(SearchEngine)
Thanks for your help. Any to advice to show a list of all the character and make a search filter who work with minimum 3 characters?
getCharacters is fired with form submit event as param. You are assuming that is getting inputs from the state wrongly:
const getCharacters = event => {
event.preventDefault() // Prevent browser making undesired form native requests
// setSearchTerm(inputs); // Not sure what are you trying here but, again, inputs is a form submit event
axios
.get( // use searchValue as query string in the url
`https://gateway.marvel.com:443/v1/public/characters?name=${searchValue}&apikey=XXX`
)
.then(response => {
console.log(searchTerm);
console.log(response);
setCharacterInfos(response.data.data.results[0]);
setLoading(false);
console.log(response.data.data.results[0].name);
response.data.data.results.map(item => {
return characterInfos.push(item.name);
});
localStorage.setItem(characterInfos, JSON.stringify(response.data));
if (!localStorage.getItem("marvelStorageDate")) {
localStorage.setItem("marvelStorageDate", Date.now());
}
})
.catch(error => {
console.log(error);
});
};
I am needing to fetch data from the MovieDB API and I have my code setup to where I just want to return some data after I hit the search button. But when I hit the search button I get back NetworkError when attempting to fetch resource
My code so far consists of this
import React, {useEffect, useState} from 'react';
import './App.css';
const App = () => {
const API_KEY = '664e565dee7eaa6ef924c41133a22b63';
const [movies, setMovies] = useState([]);
const [query, setQuery] = useState("");
useEffect(() => {
async function getMovies(){
const response = await fetch(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}`)
const data = await response.json()
console.log(data.results)
setMovies(data.results)
}
if(query !== "") getMovies();
}, [query])
return (
<div>
<form>
<button onClick={() => setQuery("Avengers")}type="submit">Search</button>
<p>{JSON.stringify(movies)}</p>
</form>
</div>
);
}
export default App;
If use (and query ='Avengers'):
${query}`
in API URL, you get this (Every record is corelated with Avengers movie)
Try this - It's not include more advanced functions, which you need.
But it's good fundamental for bulding next features:
import React, { useEffect, useState } from 'react';
const App2 = () => {
const API_KEY = '664e565dee7eaa6ef924c41133a22b63';
const [movies, setMovies] = useState([]);
const [query, setQuery] = useState('Avengers');
useEffect(() => {
async function getMovies(query) {
await fetch(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=$query`)
.then(data => data.json())
.then(data => {
console.log(data.results)
const result = data.results.map(obj => ({ popularity: obj.popularity, id: obj.id }));
console.log(result)
setMovies(result)
console.log(movies)
})
}
getMovies()
}, [query])
return (
<div>
{movies.map((movie, key) => (
<div key={key}>
<h1> {movie.popularity}</h1>
<h1>{movie.id}</h1>
</div>
))}
</div>
);
}
export default App2;
Here is your schema from API (only 1 object in array) (I used only id & popularity) - it's possible to use what you wish: