Infinite Loop Fetching - reactjs

The state restaurants changes from an input form in the front and updates a database in the back. Then the idea is to show the RestaurantList component with the new added restaurant from the input. The idea is not to use the useContext hook. I´ve tried using the useEffect hook to render the list everytime the restaurant state changes, but this makes infinit GET requests in my backend.
const RestaurantsList = (props) => {
const [restaurants, setRestaurants] = useState("");
useEffect(async () => {
try {
const response = await fetch("http://localhost:3001/api/v1/restaurants");
const data = await response.json();
setRestaurants(data);
console.log(data);
} catch (err) {
console.log(err);
}
}, [restaurants]);
...
With this code, the front updates OK and shows the new restaurant, but the back keep making get requests. Why is this happening if the restaurants state isnt changing? Any recommendation? How can I avoid this loop?
I've tried one thing that works and is to remove the preventDefault event when I click the add button. In this way, the page reloads and do what I want but i dont know if it is the best practice:
const AddRestaurant = () => {
const [name, setName] = useState("");
const [location, setLocation] = useState("");
const [priceRange, setPriceRange] = useState("");
const handleSubmit = async function (e) {
//e.preventDefault();
try {
await fetch("http://localhost:3001/api/v1/restaurants", {
method: "POST",
made: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name,
location,
price_range: priceRange,
}),
});
} catch (err) {
console.log(err);
}
setName("");
setLocation("");
setPriceRange("");
};
return (
<div className="mb-4">
<form action="" on>
<div className="form-row">
<div className="col">
<input
type="text"
value={name}
className="form-control"
placeholder="Name"
onChange={nameUpdate}
/>
</div>
<div className="col">
<input
className="form-control"
value={location}
type="text"
placeholder="Location"
onChange={locationUpdate}
/>
</div>
<div className="col">
<select
className="custom-select my-1 mr-sm-2"
value={priceRange}
onChange={(e) => setPriceRange(e.target.value)}
>
<option disabled>Price Range</option>
<option value="1">$</option>
<option value="2">$$</option>
<option value="3">$$$</option>
<option value="4">$$$$</option>
<option value="5">$$$$$</option>
</select>
</div>
<button className="btn btn-primary" onClick={handleSubmit}>
Add
</button>
</div>
</form>
</div>
);
};
export default AddRestaurant;

are you saying that RestaurantsList() is throwing the infinite loop? Everything I see here is frontend code. The reason you are getting an infinite loop is because you have a set dependency of [restaurants] in your useEffect hook. Every time it grabs that data it gets updated, causing it call the function again. If you just want it to fetch the data once then leave the dependency array blank.
Try this:
const [restaurants, setRestaurants] = useState("");
useEffect(() => {
const fetchData = async () => {
const response = await fetch('http://localhost:3001/api/v1/restaurants')
const data = await response.json();
console.log(data);
setRestaurants(data);
}
fetchData();
}, []);```

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.

Setting state of a 'Loading' boolean in react doesn't update

I have a page which allows a user to submit a url from which data is scraped. The user is subsequently presented with the filtered data.
Because the scraping takes some time I would like to implement a loader. While the loader class will (hopefully) be relatively straight forward, it's the state for loading which I'm having issues with. The state itself is never updated. Although other state values are such as setFilters.
Body.js
const [searchState, setSearchState] = useState({
searchCriteria: "https://en.wikipedia.org/wiki/2020_Central_Vietnam_floods",
headers:[],
references: []
});
const [filterState, setFilters] = useState({
languageFilter: ""
});
const [loadingState, setLoadingState] = useState({
loading: false
});
The above are all passed into Search with a context
<>
<SearchContext.Provider value={{searchState, setSearchState,filterState, setFilters, loadingState, setLoadingState}} >
<Search />
<DonateButton />
<WikiHeaderGroup />
</SearchContext.Provider>
</>
And then I have a handleSubmit inside the Search component.
Search.js
import React, {useContext} from "react";
import {SearchContext} from "../../contexts/SearchContext"
import "../../App.css"
export function Search (){
const {searchState, setSearchState, filterState, setFilters, loadingState, setLoadingState} = useContext(SearchContext);
const handleSubmit = (event) => {
setFilters({languageFilter:""})
setLoadingState({loading:true})
console.log("Loading State : " + loadingState.loading)
event.preventDefault();
event.persist(); //persists the event object into the function
const fetchReferences = async () => {
fetch('http://127.0.0.1:8080/search/', {
method: 'POST',
body: JSON.stringify({
url: searchState.searchCriteria
}),
headers: {"Content-type": "application/json; charset=UTF-8"}
}).then(response => {
console.log(response)
return response.json()
}).then(json => {
console.log(json)
setSearchState({
headers:json.headers,
references:json.references
})
setLoadingState({loading:false})
console.log("Loading State : " + loadingState.loading)
});}
fetchReferences();
}
return (
<div className="search container">
<div className="input-group input-group-sm mb-3 center">
<div className="input-group-prepend">
<span className="input-group-text" id="inputGroup-sizing-sm">Wikipedia URL:</span>
</div>
<form onSubmit={(event) => handleSubmit(event)}>
<input
type="text"
id="searchBox"
className="form-control center"
aria-label="Sizing example input"
aria-describedby="inputGroup-sizing-sm"
value={searchState.searchCriteria}
onChange={(event) => setSearchState({searchCriteria:event.target.value, resultId:0})}
placeholder="Add a url" />
</form>
</div>
</div>
);
}
export default Search;
don't use object for booleans, just
const [loadingState, setLoadingState] = useState(false);
....
setLoadingState(true)
btw looks like a closure problem. you see loadingState always false cause the closure.
take a look at this Be Aware of Stale Closures when Using React Hooks
A way to solve it is using refs
const loadingStateRef = useRef(loadingState);
//then inside the function u can access
latestValue.current

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.

Issue using React Hooks on an axios call

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.

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