setState doesn't set immidaltly after a fetch API method - reactjs

I've read in here a lot of the same question and I apply what works for everybody but it not seems to work for me . I try to fetch data from my API but even with the UseEffect hook my setList don't want to set ListVille. This is my code, do you have an idea why ?
import React, {useEffect, useState} from 'react';
import {Navigate} from 'react-router-dom'
import Entete from './MEP/entete'
function Localisation() {
const [CP, setCP]= React.useState('');
const [city, setCity]= React.useState('');
const [ListVille, setList]= React.useState();
const [goToExploit, setGoToExploit] = useState(false)
useEffect ( (CP, ListVille) => {
fetch('http://localhost:3000//api/1.0/getCommunes/'+ CP )
.then( (response) => {
console.log(response)
return response.json()
})
.then(response =>{
setList(response)
console.log(response)
console.log(ListVille)
})
})
function handleSubmit(event) {
event.preventDefault()
setGoToExploit(true)
}
if(goToExploit) {
return <Navigate push to={`/exploitation`} />
}
function handleChange(event) {
var CP = event.target.value
setCP(CP)
}
return (
<div>
<Entete titre="Localisation"/>
<form onSubmit={handleSubmit}>
<div className='titre'> Saisissez votre code postal {CP}</div>
<input
className='input'
value={CP}
onChange={handleChange}
placeholder='Code Postal'
type='text'
required/>
{/*<div className='paragraphe'> Test {CP==='41160' ? ListVille[0].key : 'coucou'}</div>*/}
<div className='centrer'>
<button type='submit' className='validation'> Valider </button>
</div>
</form>
</div>
);
}
export default Localisation;
I don't use the
useEffect ( () => {
...
}, [] )
because I want the useffect to apply everytime the CP changes

First set a state in react is an asynchronous operation, so
setList(response)
console.log(response)
console.log(ListVille)
console will show the old value of ListVille
If you want the current value, you can see it inside the prev value of set state.
setList(response)
console.log(response)
setList(prev => {
console.log(prev)
return prev;
})
Second, using useEffect without an array of dependencies will run on each component render, so if you want to call this function on the component mount, you need to set an empty array of dependencies.

instead of passing empty array in useEffect, pass cp state.
whenever cp value changes useEffect will call that callback function.

Related

input value not updating when mutating state

While creating a little project for learning purposes I have come across an issue with the updating of the input value. This is the component (I have tried to reduce it to a minimum).
function TipSelector({selections, onTipChanged}: {selections: TipSelectorItem[], onTipChanged?:(tipPercent:number)=>void}) {
const [controls, setControls] = useState<any>([]);
const [tip, setTip] = useState<string>("0");
function customTipChanged(percent: string) {
setTip(percent);
}
//Build controls
function buildControls()
{
let controlList: any[] = [];
controlList.push(<input className={styles.input} value={tip.toString()} onChange={(event)=> {customTipChanged(event.target.value)}}></input>);
setControls(controlList);
}
useEffect(()=>{
console.log("TipSelector: useEffect");
buildControls();
return ()=> {
console.log("unmounts");
}
},[])
console.log("TipSelector: Render -> "+tip);
return (
<div className={styles.tipSelector}>
<span className={globalStyles.label}>Select Tip %</span>
<div className={styles.btnContainer}>
{
controls
}
</div>
</div>
);
}
If I move the creation of the input directly into the return() statement the value is updated properly.
I'd move your inputs out of that component, and let them manage their own state out of the TipSelector.
See:
https://codesandbox.io/s/naughty-http-d38w9
e.g.:
import { useState, useEffect } from "react";
import CustomInput from "./Input";
function TipSelector({ selections, onTipChanged }) {
const [controls, setControls] = useState([]);
//Build controls
function buildControls() {
let controlList = [];
controlList.push(<CustomInput />);
controlList.push(<CustomInput />);
setControls(controlList);
}
useEffect(() => {
buildControls();
return () => {
console.log("unmounts");
};
}, []);
return (
<div>
<span>Select Tip %</span>
<div>{controls}</div>
</div>
);
}
export default TipSelector;
import { useState, useEffect } from "react";
function CustomInput() {
const [tip, setTip] = useState("0");
function customTipChanged(percent) {
setTip(percent);
}
return (
<input
value={tip.toString()}
onChange={(event) => {
customTipChanged(event.target.value);
}}
></input>
);
}
export default CustomInput;
You are only calling buildControls once, where the <input ... gets its value only that single time.
Whenever React re-renders your component (because e.g. some state changes), your {controls} will tell React to render that original <input ... with the old value.
I'm not sure why you are storing your controls in a state variable? There's no need for that, and as you noticed, it complicates things a lot. You would basically require a renderControls() function too that you would replace {controls} with.

map not a function using react hooks

i'm trying to populate a select bar with a name from an API call. I Have created my hook, also useEffect for its side effects, and passed the data down the return. its giving me map is not a function error. my variable is an empty array but the setter of the variable is not assigning the value to my variable. How can i clear the map not a function error ? i have attached my snippet. Thanks.
import React, { useEffect, useState } from "react";
import axios from "axios";
const Sidebar = () => {
const [ingredients, setIngredients] = useState([]);
useEffect(() => {
const fetchIngredients = async (url) => {
try {
let res = await axios.get(url);
setIngredients(res.data);
} catch (error) {
setIngredients([]);
console.log(error);
}
};
fetchIngredients(
"https://www.thecocktaildb.com/api/json/v2/1/search.php?i=vodka"
);
}, []);
const displayIngredients = ingredients.map((ingredient) => {
setIngredients(ingredient.name);
return <option key={ingredient.name}>{ingredients}</option>;
});
return (
<div className="sidebar">
<label>
By ingredient:
<select>{displayIngredients}</select>
</label>
</div>
);
};
export default Sidebar
First, here
setIngredients(res.data);
change res.data to res.ingredients (the response object doesn't have data property). Then you'll face another bug,
const displayIngredients = ingredients.map((ingredient) => {
setIngredients(ingredient.name);
//...
First, ingredient.name is undefined, and second, it probably would be a string if it existed. Just ditch the setIngredients call here.
You are declaring displayIngredients as a variable typeof array (By directly affecting the array.map() result). You need it to be a function that return an array as follow :
const displayIngredients = () => ingredients.map((ingredient) => {
// Do not erase your previous values here
setIngredients(previousState => [...previousState, ingredient.name]);
// Changed it here as well, seems more logic to me
return <option key={ingredient.name}>{ingredient.name}</option>;
});
You should also wait for the API call to end before to display your select to prevent a blank result while your data load (If there is a lot). The easiest way to do that is returning a loader while the API call is running :
if(!ingredients.length) {
return <Loader />; // Or whatever you want
}
return (
<div className="sidebar">
<label>
By ingredient:
<select>{displayIngredients}</select>
</label>
</div>
);

Incorrect use of useEffect() when filtering an array

I have this React app that's is getting data from a file showing in cards. I have an input to filter the cards to show. The problem I have is that after I filter once, then it doesn't go back to all the cards. I guess that I'm using useEffect wrong. How can I fix this?
import { data } from './data';
const SearchBox = ({ onSearchChange }) => {
return (
<div>
<input
type='search'
placeholder='search'
onChange={(e) => {
onSearchChange(e.target.value);
}}
/>
</div>
);
};
function App() {
const [cards, setCards] = useState(data);
const [searchField, setSearchField] = useState('');
useEffect(() => {
const filteredCards = cards.filter((card) => {
return card.name.toLowerCase().includes(searchField.toLowerCase());
});
setCards(filteredCards);
}, [searchField]);
return (
<div>
<SearchBox onSearchChange={setSearchField} />
<CardList cards={cards} />
</div>
);
}
you should Include both of your state "Card", "searchedField" as dependincies to useEffect method.once any change happens of anyone of them, your component will re-render to keep your data up to date,
useEffect(() => { // your code }, [searchField, cards]);
cards original state will be forever lost unless you filter over original data like const filteredCards = data.filter().
though, in a real project it's not interesting to modify your cards state based on your filter. instead you can remove useEffect and create a filter function wrapped at useCallback:
const filteredCards = useCallback(() => cards.filter(card => {
return card.name.toLowerCase().includes(searchField.toLowerCase());
}), [JSON.stringify(cards), searchField])
return (
<div>
<SearchBox onSearchChange={setSearchField} />
<CardList cards={filteredCards()} />
</div>
);
working example
about array as dependency (cards)
adding an object, or array as dependency at useEffect may crash your app (it will throw Maximum update depth exceeded). it will rerun useEffect forever since its object reference will change everytime. one approach to avoid that is to pass your dependency stringified [JSON.stringify(cards)]

How to fire React Hooks after event

I am quite new to React and how to use hooks. I am aware that the following code doesn't work, but I wrote it to display what I would like to achieve. Basically I want to use useQuery after something changed in an input box, which is not allowed (to use a hook in a hook or event).
So how do I correctly implement this use case with react hooks? I want to load data from GraphQL when the user gives an input.
import React, { useState, useQuery } from "react";
import { myGraphQLQuery } from "../../api/query/myGraphQLQuery";
// functional component
const HooksForm = props => {
// create state property 'name' and initialize it
const [name, setName] = useState("Peanut");
const handleNameChange = e => {
const [loading, error, data] = useQuery(myGraphQLQuery)
};
return (
<div>
<form>
<label>
Name:
<input
type="text"
name="name"
value={name}
onChange={handleNameChange}
/>
</label>
</form>
</div>
);
};
export default HooksForm;
You have to use useLazyQuery (https://www.apollographql.com/docs/react/api/react-hooks/#uselazyquery) if you wan't to control when the request gets fired, like so:
import React, { useState } from "react";
import { useLazyQuery } from "#apollo/client";
import { myGraphQLQuery } from "../../api/query/myGraphQLQuery";
// functional component
const HooksForm = props => {
// create state property 'name' and initialize it
const [name, setName] = useState("Peanut");
const [doRequest, { called, loading, data }] = useLazyQuery(myGraphQLQuery)
const handleNameChange = e => {
setName(e.target.value);
doRequest();
};
return (
<div>
<form>
<label>
Name:
<input
type="text"
name="name"
value={name}
onChange={handleNameChange}
/>
</label>
</form>
</div>
);
};
export default HooksForm;
I think you could call the function inside the useEffect hook, whenever the name changes. You could debounce it so it doesn't get executed at every letter typing, but something like this should work:
handleNameChange = (e) => setName(e.target.value);
useEffect(() => {
const ... = useQuery(...);
}, [name])
So whenever the name changes, you want to fire the query? I think you want useEffect.
const handleNameChange = e => setName(e.target.value);
useEffect(() => {
// I'm assuming you'll also want to pass name as a variable here somehow
const [loading, error, data] = useQuery(myGraphQLQuery);
}, [name]);

Wait for state to update when using hooks

How do I wait for state to update using Hooks. When I submit my form I need to check if termsValidation is false before running some additional code. If the state has just changed it doesn't pick up on this.
import React, { useState } from 'react';
export default function Signup() {
const [terms, setTerms] = useState('');
const [termsValidation, setTermsValidation] = useState(false);
function handleSubmit(e) {
e.preventDefault();
if (!terms) {
setTermsValidation(true);
} else {
setTermsValidation(false);
}
if (!termsValidation) {
console.log('run something here');
}
}
return (
<div>
<form>
<input type="checkbox" id="terms" name="terms" checked={terms} />
<button type="submit" onClick={handleSubmit}>
Sign up
</button>
</form>
</div>
);
}
The useState hook is asynchronous but it doesn't have a callback api like setState does. If you want to wait for a state update you need a useEffect hook:
import React, { useState, useEffect } from 'react';
export default function Signup() {
const [terms, setTerms] = useState('');
const [termsValidation, setTermsValidation] = useState(false);
useEffect(() => {
if (!termsValidation) {
console.log('run something here');
}
}, [termsValidation]);
function handleSubmit(e) {
e.preventDefault();
if (!terms) {
setTermsValidation(true);
} else {
setTermsValidation(false);
}
}
return (
<div>
<form>
<input type="checkbox" id="terms" name="terms" checked={terms} />
<button type="submit" onClick={handleSubmit}>
Sign up
</button>
</form>
</div>
);
}
Changing state like setTermsValidation is asynchronous action which means it's not immediate and the program does not wait for it. It fires and forgets. Hence, when you call setTermsValidation(true) the program will continue run the next block instead of waiting termValidation to change to be true. That's why termsValidation will still have the old value.
You can just do this
function handleSubmit(e) {
e.preventDefault();
if (!terms) {
setTermsValidation(true);
} else {
setTermsValidation(false);
// assuming you want to run something when termsvalidation turn to false
console.log('run something here');
}
}
Or ideally use hooks useEffect()
useEffect(() => {
if (!termsValidation) {
console.log('run something here');
}
}, [termsValidation]);
However, be careful because useEffect also runs on initial render.
Don't forget useRef as a possibility in situations like this - useState and useEffect have their place of course, but your logic to track and manage the state can be a bit of a pain, as well as causing probable unnecessary re-renders of your component (when that state doesn't form part of the render output). As an example from the OP:
import React, { useState, useRef } from 'react';
export default function Signup() {
const [terms, setTerms] = useState('');
const termsValidation = useRef(false);
function handleSubmit(e) {
e.preventDefault();
if (!terms && !termsValidation.current) {
termsValidation.current = true;
console.log('run something here');
termsValidation.current = false; // when its finished running
}
}
return (
<div> etc
);
}

Resources