Issue using React Hooks on an axios call - reactjs

I'm making a basic weather app with React, and having an issue getting my setWeather to update weather. I had read that setState doesn't update state the first time it's called, and that seems consistent with the empty object that console.log(weather) returns. cityData returns the full response, as expected, but weather.name and non-nested data (i.e. only strings, not arrays or objects) functions properly, which is unexpected.
I would like to know how to get setWeather to perform as advertised, and why the arrays and objects that the API return are showing as undefined.
import React, { useState } from 'react';
import axios from 'axios';
const Search = () => {
const [query, setQuery] = useState('');
const [weather, setWeather] = useState({});
const findCity = (e) => {
e.preventDefault()
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${query}&units=imperial&appid=${APIKEY}`)
.then(res => {
const cityData = res.data;
console.log(cityData);
setWeather(res.data);
setQuery('');
console.log(weather)
}).catch(err => console.log(err))
}
return(
<React.Fragment>
<h1>App</h1>
<p>Get the weather in your city!</p>
<form onSubmit={findCity}>
<input
type='text'
className='city-search'
placeholder='What city are you looking for?'
name='city-name'
onChange={e => setQuery(e.target.value)}
value={query}
/>
<button
type='submit'>
Get City
</button>
</form>
<h1>{weather.name}</h1>
</React.Fragment>
)
}

You won't be able to do console.log(weather) in the submit handler because the submit handler is still using the old weather (i.e. from current render). Do this instead:
const Search = () => {
const [query, setQuery] = useState('');
const [weather, setWeather] = useState({});
const findCity = (e) => {
e.preventDefault()
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${query}&units=imperial&appid=${APIKEY}`)
.then(res => {
const cityData = res.data;
console.log(cityData);
setWeather(res.data);
setQuery('');
}).catch(err => console.log(err))
}
console.log(weather) //<-- THIS IS THE ONLY THING I'VE CHANGED
return(
<React.Fragment>
<h1>App</h1>
<p>Get the weather in your city!</p>
<form onSubmit={findCity}>
<input
type='text'
className='city-search'
placeholder='What city are you looking for?'
name='city-name'
onChange={e => setQuery(e.target.value)}
value={query}
/>
<button
type='submit'>
Get City
</button>
</form>
<h1>{weather.name}</h1>
</React.Fragment>
)
}

https://api.openweathermap.org/data/2.5/weather?q=${query}&units=imperial&appid=${APIKEY}
Are you passing the query and APIKEY here. If not please add them as well to your axios call. Am assuming that your getting an invalid response. APIKEY has to be provided to get a successful response from the Weather API.

Related

Id invalid in react select, axios post request to API

I'm trying to add a Leader to my DB via a post.
I want to select a branch so the leader is linked to that branch.
but When I select the branch from the select box the Id does not get filled and I get the following error:
.branchId: [,…]
0: "The JSON value could not be converted to System.Guid. Path: $.branchId | LineNumber: 0 | BytePositionInLine: 30."
been stuck for a day now, help is muich appreciated
import React, {useState, useEffect} from 'react'
import axios from 'axios'
const LeadersPost = () => {
const [totem, setTotem] = useState('');
const [branchId, setBranchId] = useState('')
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async() => {
try{
const {data: response} = await axios.get('https://localhost:7070/api/Branches');
setData(response);
} catch (error) {
console.error(error.message);
}
}
fetchData();
}, []);
const onSubmit = async (e) => {
e.preventDefault();
const data = {
totem : totem,
branchId : branchId
}
try{
await axios.post('https://localhost:7070/api/Leaders', data)
.then(res => {
setData(res.data);
setTotem('');
setBranchId('');
})
} catch (error){
console.error(error.message);
}
}
return(
<div className='container mt-2'>
<h2>Leaders Post Request</h2>
<form onSubmit={onSubmit}>
<div className='mb-2 mt-3'>
<input
type={'text'}
placeholder='Leader Totem'
className='form-control'
value={totem}
onChange={e => {
setTotem(e.target.value)
}} />
<select className="form-control" aria-label="Default select example">
<option>Choose a branch</option>
{
data.map(branch =>
<option
onChange={ e => {
setBranchId(e.target.value)
}}>
{branch.id}
</option>)
}
</select>
<button type='submit' className='btn btn-primary'>Create</button>
</div>
</form>
</div>
)
}
export default LeadersPost
I don't believe the option fires an onchange event, I could be wrong but if my memory serves me correctly it doesn't. Also just checked MDN web docs and I may be correct in that.
However, I also believe the value of the option not being set would also cause this to happen. You're just setting a textConext on the event, so you could make it work potentially by that too but you may want to take a look at this answer here for select boxes in react.

How to display a list of movies from an API using ReactJS

I'm trying to display a list of movies title based on the year the user searches for. I dont need to loop all the pages, the results of the first page is enough. I really don't know what am I doing wrong that when I click search the app crashes.
import React, { useState } from "react";
const Movies = () => {
const [search, setSearch] = useState(false);
const [message, setMessage] = useState(null);
const [year, setYear] = useState("");
const [movies, setMovies] = useState([]);
const searchMovies = async (e) => {
e.preventDefault();
setSearch(true);
const url = `https://jsonmock.hackerrank.com/api/movies?Year=${year}`;
try {
const response = await fetch(url);
const data = await response.json();
setMessage(null);
setMovies(data); // needs check
setSearch(false);
} catch (err) {
setMessage("Unexpected Error happened");
setSearch(false);
}
};
return (
<div>
<form action="" onSubmit={searchMovies}>
<input
type="text"
placeholder="Enter a year"
name="year"
value={year}
onChange={(e) => {
setYear(e.target.value);
}}
/>
<button type="submit">Searc</button>
</form>
<div className="movies-container">
{search && !message ? (
<span>Loading...</span>
) : message ? (
<div className="message"> {message} </div>
) : (
movies.map((movie) => <li key={movie.imdbID}> Title: {movie.Title}</li>)
)}
</div>
</div>
);
};
export default Movies;
You need to make sure movies is an array,
Check the value of movies by adding a console log
const data = await response.json();
console.log("data ", data);
setMovies(data);
And i saw the json of your API think you need to put setMovies(data.data); instead of setMovies(data);

How to cancel all subscriptions and asynchronous tasks in a useEffect cleanup function?

I can't update the state when I am calling a second API inside a react functional component. The first API call is inside useEffect, and the second API call is done when the user clicks the button. When the second API call is done react throws this error "Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function."
And the state is not updating, I want to set the state after the second API call. How to fix this?
My code:
const AddNewProduct = () => {
const [productName, setProductName] = useState("");
const [originalPrice, setOriginalPrice] = useState("");
const [newPrice, setNewPrice] = useState("");
const [category, setCategory] = useState("");
const [description, setDescription] = useState("");
const [categoriesArray, setCategoriesArray] = useState([]);
const [isLogin, setIsLogin] = useState([]);
const [id, setId] = useState("");
useEffect(() => {
const getCategoriesData = async () => {
const Data = await fetchCategoriesApi();
setIsLogin(Data.data.login);
setCategoriesArray(Data.data.data);
console.log(Data);
};
getCategoriesData();
}, []);
const handleCategoryClick = (id) => {
setCategory(id);
console.log(id);
};
const handleNextClick = async () => {
const postApi = "https://fliqapp.xyz/api/seller/products";
try {
const post = await axios
.post(
postApi,
{
product_name: productName,
product_desc: description,
product_price: originalPrice,
product_cat: category,
},
{
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}
)
.then((response) => {
setId(response.data.data.product_id);
console.log(id);
console.log(response);
});
} catch (error) {
return error;
}
console.log("clicked");
};
return (
<>
<div className={styles.container}>
<div className={styles.blank}></div>
<input
type="text"
className={styles.input_field}
placeholder="Product name*"
onChange={(e) => setProductName(e.target.value)}
/>
<input
type="text"
className={styles.input_field}
placeholder="original price*"
onChange={(e) => setOriginalPrice(e.target.value)}
/>
<input
type="text"
className={styles.input_field}
placeholder="new price"
onChange={(e) => setNewPrice(e.target.value)}
/>
<select
name="parent category"
id="parentcategory"
className={styles.dropdown}
defaultValue={"DEFAULT"}
onChange={(e) => handleCategoryClick(e.target.value)}
>
<option value="DEFAULT" disabled>
select category
</option>
{isLogin &&
categoriesArray.map((item, index) => (
<option value={item.id} key={index}>
{item.cat_name}
</option>
))}
</select>
<textarea
type="textarea"
className={styles.input_field}
placeholder="Description"
rows="4"
onChange={(e) => setDescription(e.target.value)}
/>
<Link
to={{
pathname: `/add_image/${id}`,
}}
className={styles.btn}
onClick={handleNextClick}
disabled
>
Next
</Link>
<div className={styles.header}>
<h1 className={styles.heading_normal}>Add new product</h1>
</div>
</div>
</>
);
};
You need to change your Link to Button and manually navigate to other route because id used in route /add_image/${id} is coming from second Api call.
Reason : because when you click on Link it will fire axios request and change route of your app, thus current component is unmounted and new route component is mounted, after this happens your axios response comeback and try to setState on unmounted component.
// import
import { useHistory } from 'react-router-dom';
// inside component
const history = useHistory();
// click handler
const handleNextClick = async () => {
// ...axiosrequest
.then((response) => {
setId(response.data.data.product_id); // may be not needed now
const id = response.data.data.product_id;
history.push(`/add_image/${id}`);
}
}
// button
<button
className={styles.btn}
onClick={handleNextClick}
>
Next
</button>
In this way you change route only once after you get proper response from server and based on response ID you update your route.
For better user experience you can show loading meanwhile you perform axios ajax request.
if any doubt please comment.

Why is the API response not being rendered by useState?

I am trying to render data fetched from an API using axios but nothing renders on screen. Note that data is actually available as indicated on the console log. Here is the code and what I have tried.
import React, { useState, useEffect } from "react";
import axios from "axios";
function Test() {
const [movie, setMovie] = useState([]);
const [query, setQuery] = useState("pulp fiction");
const [queryFromButtonClick, setQueryFromButtonClick] = useState(
"pulp fiction"
);
const handleClick = () => {
setQueryFromButtonClick(query);
};
useEffect(() => {
axios
.get(`http://www.omdbapi.com/?apikey=fd010aa6&s=${queryFromButtonClick}`)
.then(({ data }) => {
console.log(data);
setMovie(data.Search);
});
}, [queryFromButtonClick]);
return (
<div>
<input
type="text"
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
/>
<button onClick={handleClick}>Fetch movies</button>
<div>{movie.Title}</div>
</div>
);
}
export default Test;
Why are the search query results not being rendered on screen and how can I go on about that?
Movie is defined as an array and apparently data.Search is also an array.
You need to iterate over movie array to get the data about each movie.
Like this:
return (
<div>
<input
type="text"
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
/>
<button onClick={handleClick}>Fetch movies</button>
<div>{movie.map((el)=>el.Title)}</div>
</div>
);

I don`t understand how to render objects(react)

I am getting a object from "api" and set it to match but when i try do loop through or render it i get a error.
I have tryed Objct.keys maybe my syntax is wrong im not sure im still learning thx for any help.
const [match, setMatch] = useState();
const [search, setSearch] = useState('');
const [query, setQuery] = useState(4749875544)
useEffect(() => {
getData();
}, [query]);
const getData = async () => {
const response = await
fetch(`https://api.opendota.com/api/matches/${query}`)
const result = await response.json();
setMatch(result);
}
}
return (
<div className="App" >
<form onSubmit={getSearch}
className="search-form">
<input className="search-bar"
type="text"
value={search}
onChange={searchInput}
/>
<Button as="input"
type="submit"
value="Submit" />
</form>
<li>
{
Object.keys(match).map((oneKey,i)=>{
return (
<li key={i}>{match[oneKey]}</li>
)})
}
</li>
</div>
)}
First I would default the state to an Object. It is always good to default your state to the data types you will use. So at the top useState({}).
React can’t render an object. You have to render each key separately. In your map when you return the list item do it with match[oneKey].title or whatever key is actially valid.

Resources