React, onClick only works after second click, Axios, Async/Await - reactjs

I must be missing something very straight forward.
I am trying to make an axios GET request and then setState in an async/await function, I have also tried fetch().then(). The console.log() returns undefined unless I click getData() twice or put it in a separate onClick() event. I think the state is being set correctly but the console.log() doesn't perform as I expect it. Is there a way to console.log() in the same function as the axios request? As always, any help is greatly appreciated. Thanks!
import React, { useState } from "react";
import axios from "axios";
const SalesGraph = () => {
const [retrievedData, setretrievedData] = useState();
const getData = async () => {
try {
const salesData = await axios.get("http://localhost:5000/sales");
await setretrievedData(salesData);
console.log(retrievedData);
} catch (err) {
console.log(err);
}
};
const showState = () => {
console.log(retrievedData);
};
return (
<div>
<button onClick={getData}>Graph</button>
<button onClick={showState}>showState</button>
</div>
);
};
export default SalesGraph;

setretrievedData is the asynchronous method and you can't get the updated value of retrievedData immediately after setretrievedData.
You need to get it in the useEffect by adding a dependency.
useEffect(() => {
console.log(retrievedData);
}, [ retrievedData ]);

All setters for react useState are async so even if you await your setretrievedData it will go on to the next line even before it is done.
To run any code only after the setter is done you need to add a useEffect and pass the state to watch as the 2nd arg

Related

What's the better way to change state on every render for number of times

Using useEffect I am calling some data from different URLs through axios and trying to change the URL on every render, I am using a state to change URL and whenever the state changes, the URL will also change and run the useeffect again with different URL. But to change the state in the render I am using a button, My question is there any ways to make the state change and run the API call without the button click.
import React,{useState,useEffect} from "react";
import axios from "axios";
export default function TrackLoader(accessToken){
const [number, setNumber] = useState(0);
const [arrayOfPlayLists, setArrayOfPlayLists] = useState([]);
const playListIds = ["3d93aG1WkqLxS2q9GkUgXO", "37i9dQZF1DWX3xqQKu0Sgn", "37i9dQZF1DWU13kKnk03AP", "37i9dQZF1DXdPec7aLTmlC", "37i9dQZF1DX889U0CL85jj", "37i9dQZF1DXbVipT9CLvYD"]
useEffect(() =>{
async function getData(){
const response = await axios.get(`https://api.spotify.com/v1/playlists/${playListIds[number]}`,{
headers:{
Authorization:"Bearer " + accessToken.accessToken,
},
})
let selectedPlayList = response.data
setplayList(selectedPlayList)
arrayOfPlayLists.push(selectedPlayList)
}
getData();
},[number]);
return(
<div>
{number ===5? null: <button onClick={()=> setNumber(number+1)}></button>}
</div>
)
}
And also additionally I am trying to put the data I got from API in an array. Sorry for my English.
You can add another useEffect with setTimeout
useEffect(() => {
if(number <= 5) {
setTimeout(() => {
setNumber(number + 1);
}, 1000);
}
}, [number]);

React JS useEffect hook send infinite no of request if i add an dependency

i have made an API in Django for fetching the TODO list tasks in my React JS Application but when i use allTasks of useState in dependency array of useEffect Hook then it starts sending endless get requests. I can't understand how is this happening also if i leave the dependency array empty then it works fine but then i have to refresh the page manually if some data changes in background.
Here below is the code of Tasklist.js Component
import React,{useState, useEffect} from 'react'
import axios from 'axios'
import Task from './Task'
function TaskList() {
async function getAllTasks(url){
let resp = await axios.get(url);
let all_tasks= await resp.data
return all_tasks;
}
const [allTasks, setAllTasks]= useState([])
useEffect(()=>{
async function appendTasks(){
let alltasks= await getAllTasks('http://127.0.0.1:8000/api/retrieve')
setAllTasks(alltasks)
}
appendTasks()
},[allTasks])
return (
<div className="TaskList">
{
allTasks.map(function(task){
return(
<Task key={task.id} task={task} />
)
})
}
</div>
)
}
export default TaskList
You got an infinite loop because you're modifying your useEffect dependency in the useEffect itself.
Remove your dependency to solve your problem :
useEffect(()=>{
async function appendTasks(){
let alltasks= await getAllTasks('http://127.0.0.1:8000/api/retrieve')
setAllTasks(alltasks)
}
appendTasks()
},[])
Remove the dependency. If you want to refresh your data you can, for example, set an interval to call your backend every interval of time.
useEffect(()=>{
async function appendTasks(){
let alltasks= await getAllTasks('http://127.0.0.1:8000/api/retrieve')
setAllTasks(alltasks)
}
const interval = setInterval(() => appendTasks(), 30000) // 30000ms = 30 secondes
return () => clearInterval(interval);
},[])
Here you will refresh your tasks every 30 secondes.
There is of course other options depending of your needs.
1st of all - useEffect is run everytime its dependencies change, in this case its because you specify allTasks as dependency and in useEffect itself you call setAllTasks which changes allTasks so useEffect is called again, so setAllTasks is called again, so allTasks is changed and useEffect called again, and so on... I hope you understand now why you are getting infinite number of calls. Now solution - you don't really need allTasks dependency in this case - setAllTasks is enough - why ? because nowhere in useEffect you use allTasks variable.
Solution:
useEffect(()=>{
async function appendTasks(){
let alltasks= await getAllTasks('http://127.0.0.1:8000/api/retrieve')
setAllTasks(alltasks)
}
appendTasks()
},[setAllTasks]) // notice the change here
2nd - not related to question, but you should include all dependencies in useEffect dependency array - in this case function getAllTasks should be included as well - but this would make infinite number of calls again - i suggest you to read something about useMemo and useCallback, but easiest solution for you should be something like this
useEffect(()=> {
async function getAllTasks(url){
let resp = await axios.get(url);
let all_tasks= await resp.data
return all_tasks;
}
let response = await getAllTasks('http://127.0.0.1:8000/api/retrieve')
setAllTasks(response)
}, [setAllTasks])
The thing you need to replace in your code is useEffect dependency array
"allTasks" with "addTodos"
eg: const [addTodos, setAddTodos]= useState([])
Note: look at the below snippet to get an idea
import {useState } from "react";
import GetTodoList from "./GetTodoList";
export default function AddTodo() {
const [input, setInput]= useState("");
const [addTodos, setAddTodos]= useState([])
const setInputValue =(e)=>{
setInput(e.target.value)
}
const addToLocalStorage=(e)=>{
e.preventDefault();
setAddTodos([...addTodos, {"task": input}])
localStorage.setItem("todo", JSON.stringify(addTodos))
}
return (
<div>
<input value={input} onChange={setInputValue} type="text" />
<button onClick={addToLocalStorage}>Add addTodos</button>
<GetTodoList addTodos={addTodos}/>
</div>
)
}
import { useEffect, useState } from "react";
function GetTodoList({addTodos}) {
const [todoList, setTodoList] = useState([])
const getTodo=()=>{
const result = localStorage.getItem("todo");
setTodoList(JSON.parse(result))
//setTotos(JSON.parse(result))
}
useEffect(() => {
getTodo();
}, [addTodos]) //addTodos as dependency, when the setAddTodos updates then useEffect recalls getTodo() with out hard refresh
return (
<div>
{(todoList)?
todoList.map(todo=><p>{todo.task}</p>) : ""}
</div>
)
}
export default GetTodoList

useState setter not updating state when called in useEffect

Pretty much what it says on the title. When I console.log(repos) it returns an empty array. Why is the repos state not updating?
import React, { useEffect, useState } from "react";
import axios from "axios";
export default () => {
const [repos, setRepos] = useState([]);
useEffect(() => {
(async () => {
try {
let repo_lists = await axios.get(
"https://api.github.com/users/Coddielam/repos"
// { params: { sort: "created" } }
);
setRepos(repo_lists.data.slice(1).slice(-10));
console.log(repo_lists.data.slice(1).slice(-10));
console.log(repos);
} catch (err) {
setRepos([
"Something went wrong while fetching GitHub Api./nPlease use the link below to view my git repos instead ",
]);
}
})();
}, []);
return (
<div className="content">
<h2>View my recent git repos:</h2>
<ul>
...
</ul>
</div>
);
};
Answer is very simple. Your useState is updating .. believe me. The reason why you don't see it when you console.log() is because SetRespos is an asynchronous function.
Basically when you declare a function to update you useState value, react will use it as an async function
EXAMPLE
const [example, setExample] = useState('');
useEffect(() => {
setExample('Hello');
console.log('I'm coming first'); // This will be executed first
console.log(example); // This will come after this
}, [])
The output will be :
I'm coming first
// Blank Line
But still your useState will update after this. If you want to see that do this :
useEffect(() => {
console.log(respose); // This will give you the value
}, [respos])
I'm using a separate useEffect to console.log() the value. In the [] (dependency array) we pass respos which simply means that the useEffect will run every time the value of respos changes.
Read more about useStates and useEffects in react's documentation
State updates are async. You will only see them reflected on the next render. If you console log the state immediately after calling setState it will always log the current state, not the future state.
You can log the state in an effect every time it changes and you will see it changing:
useEffect(() => console.log(repos), [repos]);
This effect will be called after the state update has been applied.

How do I correctly implement setState() within useEffect()?

If this useEffect() runs once (using [] as the second parameter), setTicket(response.data) does not update the value of ticket with data. If I run useEffect() with [ticket] as the parameter, it updates the value of ticket with data, but useEffect becomes an infinite loop.
I need it to run once and update the ticket data. I don't think I understand useEffect() and its second parameter.
What do I do to get the expected result?
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
const EditTicket = (props) => {
const [ticket, setTicket] = useState("");
useEffect(() => {
axios
.get("http://localhost:4000/tickets/" + props.match.params.id)
.then((response) => {
setTicket(response.data);
console.log({ ticket });
})
.catch(function (error) {
console.log(error);
});
}, []);
return <div>edit</div>;
};
export default EditTicket;
ticket is a local const. It will never change, and that's not what setTicket is trying to do. The purpose of setTicket is to tell the component to rerender. On that next render, a new local variable will be created, with the new value.
Your code is already written the way it should be written, except that your log statement is not providing you with any useful information. If you want to see that it rerenders with the new value you could move the log statement to the body of the component.
const EditTicket = (props) => {
const [ticket, setTicket] = useState("");
console.log('rendering', ticket);
useEffect(() => {
// same as before

React Hooks , function fetchData is not a react component?

I'm playing around with react hooks and I ran into a weird issue trying to fetch some data, when i'm creating a file to fetch data using hooks and axios
this works
import axios from 'axios';
const useResources = (resource) => {
const [resources, setResources ] = useState([]);
useEffect(
() => {
(async resource => {
const response = await axios.get(`randomapi.com/${resource}`);
setResources(response.data);
})(resource);
},
[resource]
);
return resources;
};
export default useResources;
but this doesn't
import { useState, useEffect } from 'react';
import Axios from 'axios';
const fetchData = () => {
const [data, setData] = useState('');
useEffect( async () => {
const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
setData(response.data);
});
return data;
};
export default fetchData;
'React Hook useEffect contains a call to 'setData'. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass [] as a second argument to the useEffect Hook.'
Aren't they the same?
On first glance, they are similar, but they still have differences.
Let's check them:
useEffect(
() => {
// we create a function, that works like a "black box" for useEffect
(async resource => {
const response = await axios.get(`randomapi.com/${resource}`);
// we don't use `setResources` in dependencies array, as it's used in wrapped function
setResources(response.data);
// We call this function with the definite argument
})(resource);
// this callback doesn't return anything, it returns `undefined`
},
// our function depends on this argument, if resource is changed, `useEffect` will be invoked
[resource]
);
useEffect hook should receive a function, which can return another function to dispose of all dynamic data, listeners (e.g. remove event listeners, clear timeout callbacks, etc.)
Next example:
// this function returns a promise, but actually useEffect needs a function,
// which will be called to dispose of resources.
// To fix it, it's better to wrap this function
// (how it was implemented in the first example)
useEffect( async () => {
const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
setData(response.data);
// we don't set up an array with dependencies.
// It means this `useEffect` will be called each time after the component gets rerendered.
// To fix it, it's better to define dependencies
});
So, we have 2 major errors:
1) Our useEffect callback returns a Promise instead of function, which implements dispose pattern
2) We missed dependencies array. By this reason component will call useEffect callback after each update.
Let's fix them:
useEffect(() => {// we wrap async function:
(async () => {
// we define resource directly in callback, so we don't have dependencies:
const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
setData(response.data);
})()
}, []);
Finally, we have fixed our second example :)
I even made an example with custom fetchData hook and final version of useEffect: https://codesandbox.io/s/autumn-thunder-1i3ti
More about dependencies and refactoring hints for useEffect you can check here: https://github.com/facebook/react/issues/14920

Resources