How come my state isn't being filled with response? - javascript

console.log(data) gives me an object with the correct data but, I set rates to that data but when console logging rates I get an empty object {}. Thanks in advance.
const [rates, setRates] = useState({});
useEffect(() => {
search();
}, []);
const search = async () => {
const response = await axios.get('https://api.exchangeratesapi.io/latest');
const data = response.data.rates;
console.log(data);
setRates(data);
console.log(rates);
};

As someone said in the comment, state updates will be reflected in the next render. Also, there are some problems with your code I'll address.
const [rates, setRates] = useState({});
useEffect(() => {
// move this in here unless you plan to call it elsewhere
const search = async () => {
const response = await axios.get('https://api.exchangeratesapi.io/latest');
const data = response.data.rates;
setRates(data);
};
search();
}, [/* no dependencies means it runs once */]);
If you do plan on calling search elsewhere, wrap it in a useCallback hook so you can set it as a dependency of your useEffect (you'll get a lint warning without). useCallback with an empty dependency array will always return the same function reference, so your useEffect will only ever run the one time like it does now.
If you leave search as a normal function in the component, the reference changes each render, so your effect would run every render if you included it as a dependency.
const [rates, setRates] = useState({});
const searchCallback = useCallback(async () => {
const response = await axios.get('https://api.exchangeratesapi.io/latest');
const data = response.data.rates;
setRates(data);
}, [])
useEffect(() => {
// move this in here unless you plan to call it elsewhere
search();
}, [searchCallback]);

Related

useEffect hook runs infinitely when used in a custom hook

Below is my custom hook, I'm trying to handle everything from the hook. I have seen similar questions but none seems to work for my case and I have been made to believe there's a solution for this approach, jus can't figure it out.
const useResource = (baseUrl) => {
const [resources, setResources] = useState([]);
const create = async (resource) => {
const response = await axios.post(baseUrl, resource)
setResources(resources.concat(response.data));
console.log(resources)
return response.data
}
const get = async () => {
const response = await axios.get(baseUrl);
setResources(response.data)
return response.data
}
const service = {
create,
get
}
return [
resources, service
]
}
Here is my approach to use the custom hook, but request keeps looping nonstop, please how do I stop it running after every render?
const App = () => {
const content = useField('text');
const name = useField('text');
const number = useField('text');
const [notes, noteService] = useResource('http://localhost:3005/notes');
const [persons, personService] = useResource('http://localhost:3005/persons');
useEffect(() => {
noteService.get();
}, [noteService])
useEffect(() => {
personService.get();
}, [personService])
const handleNoteSubmit = (event) => {
event.preventDefault();
noteService.create({ content: content.value });
}
const handlePersonSubmit = (event) => {
event.preventDefault();
personService.create({ name: name.value, number: number.value});
}
Edit: I just had to disable ESLint for the dependency line, because I just need it to run once after every render. Works well!
useEffect(() => {
noteService.get();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
personService.get();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
As correctly pointed out in comments, each time the component renders and calls your useResource hook, a new service object is created. If this service object is used as a dependency for any other hooks this will trigger their callbacks.
The solution is to memoize the service object so it's being provided as a stable reference. This can be accomplished via the useMemo hook. Because service will be memoized, the create callback will also be memoized and contain stale resources state. To address this update create to use a functional state update when appending new response data to the existing state.
Example:
import { useEffect, useMemo, useState } from 'react';
const useResource = (baseUrl) => {
const [resources, setResources] = useState([]);
const create = async (resource) => {
const response = await axios.post(baseUrl, resource);
// Functional update to update from previous state
setResources(resources => resources.concat(response.data));
return response.data;
}
const get = async () => {
const response = await axios.get(baseUrl);
setResources(response.data);
return response.data;
}
const service = useMemo(() => ({
create,
get
}), []);
return [resources, service];
};

API call in useEffect returning undefined

I have a simple functional component, which includes a fetch to a JSON file stored locally.
I am trying to bring in the data from the JSON file, and whilst this should be a simple task, I have come across a problem.
My console log is showing two separate logs - one is an empty object, presumably from the useState definition and the second has the data from the fetch inside it.
Therefore, When I try to do anything with the data I'm fetching, undefined is being returned, So I can't use it. I feel like I'm missing something obvious, but I'm not entirely sure what is going on here. I have tried async/await without success.
What am I missing ?
const Landing = () => {
const [api, updateApi] = useState({});
const getData = () => {
fetch('data.json')
.then((response) => response.json())
.then((data) => updateApi({api: {data}}))
}
useEffect(() => {
getData();
}, []);
console.log(api)
return (
<p>Hey!</p>
)
}
All you need to do is to wrap the return within an if/else block, and return (for example a spinning circle) loading indicator. When it re-renders with the data from the api, it returns the desired representation of the data.
Using async/await syntax you can put your fetch requests within the useEffect hook and set your state similar to how you are currently doing.
export default function Landing() {
const [api, updateApi] = useState({});
useEffect(() => {
const getData = async () => {
const response = await fetch("data.json");
const data = await response.json();
updateApi({ api: { data } });
};
getData();
}, []);
return <div>{JSON.stringify(api)}</div>;
}
If you initialise the state with useState({}) then the value won't be undefined. If however you want to check for that before doing something then an if/else statement as suggested in another answer is suitable or if it is within the component return then a common pattern is return <div>{api && JSON.stringify(api)}</div>;.

How to set multiple states using one response from axios API call in React

I invoke a GET API using axios in React. Backend (Python-Flask) is returning data by writing return jsonify(animals=animals, cars=cars) and these (cars and animals) are arrays.
The response has data in the following format:
{
"animals": ["elephant", "lion", "dog"],
"cars": ["range rover", "fortuner", "land cruiser"]
}
I want to update the cars state using the cars array and the animals state using the animals array. I tried the following but only animals state is updating, cars state is remaining empty
App
export default function App() {
const [animals, setAnimals] = useState([])
const [cars, setCars] = useState([])
useEffect(()=> {
axios.get("http://127.0.0.1:5000/animals_cars").then(
response=> setAnimals(response.data.animals),
resp => setCars(resp.data.cars)
);
console.log(cars)
console.log(animals)
}, []);
}
Any help on the responses, that is where I have no idea how can I split the response to update different states.
.then() takes up to two arguments; callback functions for the success and failure cases of the Promise. In this case, your second callback (resp => setCars(resp.data.cars)) is only called if the Promise is rejected.
You want to use the first callback which is called if the Promise is fulfilled. You can update two state variables in one single function like this:
export default function App() {
const [animals, setAnimals] = useState([]);
const [cars, setCars] = useState([]);
useEffect(() => {
axios.get('http://127.0.0.1:5000/animals_cars')
.then((response) => {
setCars(response.data.animals);
setAnimals(response.data.cars);
});
// state updates are asynchronous
// state is not updated yet, you can't log these
// well, you can but chances are it's the default value []
console.log(cars);
console.log(animals);
}, []);
}
Please note though that useState is asynchronous. You can't update the state on one line and assume it's already changed on the next one. You'll likely log the unchanged state.
You need an async function when you are loading data from an API to wait for the completion of the function. To do that in a useEffect you need to define a new function and execute it only when a condition is met. In this case I create a new boolean constant loading, I check against it's value and if it's true I execute the load function. Upon completion of the function I set loading to false and that would prevent the load function from executing again. This has the advantage that if you want to fetch your data again all you have to do is to set loading to true.
Another way to do that is to define a memoised load function with useCallback outside of the useEffect but I don't want to complicate things for you.
A clean way to write that is the following.
export default function App() {
const [loading, setLoading] = useState(true);
const [animals, setAnimals] = useState([])
const [cars, setCars] = useState([])
useEffect(()=> {
const load = async () => {
const responseData = await axios.get("http://127.0.0.1:5000/animals_cars")
.then((response) => {
return response.data;
})
.catch((error) => {
console.log(error);
});
setAnimals(responseData.animals);
setCars(responseData.cars);
setLoading(false);
}
if (loading) {
load();
}
},[loading]);
}
You Can hadle that with function like this
useEffect(() => {
axios.get("http://127.0.0.1:5000/animals_cars")
.then(function (response) {
// handle success
setAnimals(response.data.animals);
setCars(response.data.cars);
});
console.log(cars);
console.log(animals);
}, []);

Updating state in async function

I'm very new to react so apologies if this question has already been answered or should be phrased differently. I have a functional component that fetches a .json file from my public folder in an async function (loadData()). Using the developer tools in my chrome window, I can see that the function gets me exactly what I want, yet the state doesn't seem to want to update when I use setData.
Edit:
So I think I know what the problem is, which is that the first time the component renders, the variable source needs that JSON object which won't be there until the component re-renders. If that's the case, should all the code starting at let source = pickRandomVerb(data) go somewhere outside useEffect()?
function ComposedTextField() {
const classes = useStyles();
const [data, setData] = React.useState([]);
const [displayVerb, setDisplayVerb] = React.useState('');
const pickRandomVerb = (list) => {
var obj_keys = Object.keys(list);
var ran_key = obj_keys[Math.floor(Math.random() * obj_keys.length)];
return list[ran_key];
}
const loadData = async() => {
const response = await fetch('verbs.json');
const json = await response.json();
setData(json);
console.log(json); //json is exactly what I want here
console.log(data); //data is just '[]' here
}
useEffect(() => {
loadData();
console.log(data) //data is also '[]' here
let source = pickRandomVerb(data)
let verbSource = source.infinitive;
let showVerb = verbSource.toString().replaceAll("\"", "");
setDisplayVerb(showVerb)
}, [])
return(
<div>
<Typography className = {classes.form}>
<p>{displayVerb}</p>
</Typography>
</div>
)
}
Can anyone let me know what I'm missing? Thanks in advance.
useState hook is also asynchronous, and will not be reflected immediately and this question already addressed link-1, link-2
changes required (you have to listen the changes of the data in useEffect),
useEffect(() => {
loadData();
console.log(data) //data is also '[]' here
}, []);
useEffect(() => {
let source = pickRandomVerb(data)
let verbSource = source.infinitive;
let showVerb = verbSource.toString().replaceAll("\"", "");
setDisplayVerb(showVerb)
}, [data]);
Maybe you can try something like this
function ComposedTextField() {
const classes = useStyles();
const [data, setData] = React.useState([]);
const [displayVerb, setDisplayVerb] = React.useState('');
const pickRandomVerb = (list) => {
var obj_keys = Object.keys(list);
var ran_key = obj_keys[Math.floor(Math.random() * obj_keys.length)];
return list[ran_key];
}
const loadData = async() => {
const response = await fetch('verbs.json');
const json = await response.json();
setData(json);
console.log(json); //json is exactly what I want here
console.log(data); //data is just '[]' here
}
useEffect(() => {
loadData();
console.log(data) //data is also '[]' here
}, [])
// Move that code outside useEffect and inside condition
if(data!==[]){
let source = pickRandomVerb(data)
let verbSource = source.infinitive;
let showVerb = verbSource.toString().replaceAll("\"", "");
setDisplayVerb(showVerb)
}
return(
<div>
<Typography className = {classes.form}>
<p>{displayVerb}</p>
</Typography>
</div>
)
}
In this code setDisplayVerb() will be called when the data has some value. The flow of the control would be something like this.
Component is mounted with data=[]
usEffect is called which in turns calls loadData()
loadData will set the value of data using setData(json)
When data is set it will cause re-render, since data has value this time, the if
condition is satisfied and the statements inside it will be executed.
setDisplayVerb() will cause re-render of the application.
Since the dependency array of useEffect is empty it will not be called again.
This way you should be able to see the data on your screen.
so to expand on my comment, try this:
const loadData = async() => {
const response = await fetch('verbs.json');
const json = await response.json();
return json; /// <<< note: here you return data
}
useEffect(async () => {
const data = await loadData(); // <<<< note: here you wait for data
console.log(data);
let source = pickRandomVerb(data)
let verbSource = source.infinitive;
let showVerb = verbSource.toString().replaceAll("\"", "");
setDisplayVerb(showVerb)
}, [])
this will only get you the data on initial load.
Just as a side note: to improve a bit on this component, you can move your loadData function outside of the component.
setState is async also then you can't see the data changed in
const loadData = async() => {
const response = await fetch('verbs.json');
const json = await response.json();
setData(json);
console.log(json); //json is exactly what I want here
console.log(data); //data is just '[]' here
}
Your useEffect doesn't define the dependency then it just run once. If you want to see your data changed just add dependency for data to track when data changed.
/// Just load at the first time you render you component
useEffect(()=>{
loadData();
},[])
useEffect(() => {
console.log(data)
let source = pickRandomVerb(data)
let verbSource = source.infinitive;
let showVerb = verbSource.toString().replaceAll("\"", "");
setDisplayVerb(showVerb)
}, [data]) //Just add dependency here for useEffect

React issue with lodash and setInterval

I have an issue with using Lodash + setInterval.
What I want to do:
Retrieve randomly one element of my object every 3 seconds
this is my object:
const [table, setTable]= useState ([]);
so I start with that:
const result = _.sample(table);
console.log(result);
console give => Object { label: "Figue", labelEn: "fig" }
But if a add :
const result = _.sample(table);
console.log(result.label);
console give => TypeError: result is undefined
Beside that I tried to add setInterval and also try with useEffect but or code crash or console give me two numbers every 3 second => 2,6,2,6 ......
Ciao, supposing that table object is correclty filled, you could use lodash and setInterval to get random object of table using useEffect and useRef hooks like:
const interval = useRef(null);
useEffect(() => {
if (!interval.current) {
interval.current = setInterval(() => {
const result = _.sample(table);
console.log(result.label);
}, 3000);
}
}, [table]);
Then to clean setInterval when component will be unmount you could use another useEffect in this way:
useEffect(() => {
return () => {
clearInterval(interval.current);
interval.current = null;
};
}, []);
EDIT
After your explanation, I found the cause of your problem. Before start setInterval you have to wait that table was filled with values (otherwise you get an error).
To solve that it's just necessary to put another condition on first useEffect (table.length > 0), and load data on second useEffect.
So the first useEffect becomes:
const interval = useRef(null);
useEffect(() => {
if (!interval.current && table.length > 0) {
interval.current = setInterval(() => {
const result = _.sample(table);
console.log(result.label);
}, 3000);
}
}, [table]);
And the second one:
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
return () => {
clearInterval(interval.current);
interval.current = null;
};
}, []);
Here the codesandbox updated.
The problem is that you access table before it is loaded.
You should either provide an initial value to that allows a successful do all your operations:
// use an array that has at least one element and a label as initial table value
const [table, setTable] = useState([{label: "default label"}]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
console.log(result.label);
// ...
Or use an if or something alike to handle multiple scenarios:
// use an empty array as initial table value
const [table, setTable] = useState([]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
// account for the initial empty table value by checking the result
// of _.sample which is `undefined` for empty arrays
if (result) {
console.log(result.label);
} else {
console.log("do something else");
}
// ...
If you fetch your data asynchronously you must think about what you want to use while the data is being fetched. The minimal thing to do is tell React to not render the component while the data is missing (being fetch).
const [table, setTable] = useState([]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
if (!result) return null; // <- don't render if there is no result
console.log(result.label);
// ...

Categories

Resources