Promise data when using react and axios - reactjs

in this code whenever I try to direct to /dashboard it wouldn't wait for the response of axios and goes immediately to return part and it use loggedin with it's default value which is undefined here. Well I guess I should use promises but I don't know how...
So I would appreciate it if someone could help.
import axios from 'axios';
import React, { useEffect, useRef, useState } from 'react';
import { Redirect } from 'react-router';
import OverallCondition from "./dashOverall";
import Toolbar from "./dashToolbar";
export default function Dashboard(){
const [loggedin, check] = useState()
axios.get('/loggedin')
.then(response => {
check(response.data)
})
.catch(err => console.error(err));
return <section className='dashboard'>
{loggedin ? <div>
<Toolbar/>
<OverallCondition/>
</div> : <Redirect to='/login'/>}
</section>
}```

You need to use useEffect hook to make the HTTP request.
Making the HTTP request at the top-level inside your function component will trigger a new HTTP request every time your component is re-rendered.
Also, since the axios.get(...) is asynchronous, code below the axios.get(...) will execute before the request is completed.
To handle this situation appropriately, follow the steps mentioned below:
Create a state that represents whether the HTTP request is pending or not. Its initial value should be true
const [isPending, setIsPending] = useState(true);
Use the useEffect hook to make the HTTP request. Making HTTP requests at the top-level inside the function component is NOT the right approach - you don't want to make a request every time your component re-renders
Also don't forget to set isPending to false, otherwise user will continue to see the loading spinner even after the request has completed. You can use the finally() method to call setIsPending(false)
useEffect(() => {
axios.get('/loggedin')
.then(response => setLoggedIn(response.data))
.catch(err => console.error(err))
.finally(() => setIsPending(false));
}, []);
Empty array passes as a second argument to the useEffect hook will ensure that the HTTP request is initiated only once, after the initial render of the component
While the request is pending, show some loading spinner to the user. When the component first renders, as the isPending is true, user will see the loading spinner
if (isPending) {
return <Spinner/>; // you need to create the "Spinner" component
}
Here's how you could implement the above steps in your code:
function Dashboard() {
const [loggedin, setLoggedIn] = useState();
// this state will be used to show a loading spinner
const [isPending, setIsPending] = useState(true);
// for HTTP request, use the "useEffect" hook
useEffect(() => {
axios.get('/loggedin')
.then(response => setLoggedIn(response.data))
.catch(err => console.error(err))
.finally(() => setIsPending(false));
}, []);
// show a spinner to the user while HTTP
// request is pending
if (isPending) {
return <Spinner/>;
}
return (
<section className='dashboard'>
{loggedin ? (
<div>
<Toolbar/>
<OverallCondition/>
</div>
) : <Redirect to='/login'/>
}
</section>
);
}

Issues with the code
Use a better terminology with the useState return
Instead of
const [loggedin, check] = useState()
Use
const [loggedin, setLoggedin] = useState()
Understand the life cycle
Your axios.get is inside the function body, all code there will be executed on every render, it is certainly not what you wanted, for operations that may have side effects, you need to use the useEffect hook https://reactjs.org/docs/hooks-effect.html
The useEffect hook allows you to control better when the code will execute, in your case, you want it to run once
const ShowOnceLoaded = ({ isLoaded, children }) => {
if (!isLoaded)
return <p>Loading...</p>
return <>{children}</>
}
export default function Dashboard(){
const [ isLoaded, setIsLoaded ] = useState(false)
const [loggedin, setLoggedin] = useState(false)
React.useEffect(() => {
axios.get('/loggedin')
.then(response => {
setLoggedin(true)
setIsLoaded(true)
})
.catch(err => console.error(err));
}, [])
return <section className='dashboard'>
<ShowOnceLoaded isLoaded={isLoaded}>
{loggedin ? <div>
<Toolbar/>
<OverallCondition/>
</div> : <Redirect to='/login'/>}
</ShowOnceLoaded>
</section>
}
In addition to what you had, now there is a state set once the request is complete, only then we can decide if the user is logged in or not, otherwise, we will redirect the user even before the request is done, as the state initializes as false by default
The empty array on useEffect is used to run the code only when the component mounts

Related

How can I stop a function mutate from executing in ReactJS?

I am trying to stop this mutate function from executing. ater I call mutate() in List.js when I check the network in my browser I can see that the mutate always executing. I need this to execute once.
This is my List.js which I output the data.
import React, {useEffect} from 'react';
import ListItem from './ListItem'
import FetchData from './FetchData';
function List() {
const {
data,
loading,
mutate,
} = FetchData();
//this needs to stop using interval how?
mutate();
return (
<ul>
{loading && <div>Loading</div>}
{!loading && (
<>
{data.map(item => (<ListItem key={item.id} id={item.id} name={item.name} complete={item.complete} />))}
</>
)}
</ul>
)
}
export default List
And this is the FetchData.js where the mutate came.
import { useEffect, useState} from 'react';
import axios from 'axios';
const FetchData = () => {
const [data, setData] = useState({});
const [loading, setLoading] = useState(true);
const fetchData = async () => {
try {
const { data: response } = await axios.get(
"http://localhost/todolistci/backend/index.php/todos/view",
{ crossDomain: true }
);
setData(response);
} catch (error) {
console.error(error);
}
setLoading(false);
};
const mutate = () => fetchData();
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
mutate,
};
};
export default FetchData;
I cant stop mutate from executing, I need it to stop using interval how?
This is the image of the app. enter image description here
Not sure what you're trying to do with this mutate function, but I can explain the issue:
Because mutate() is just declared inside your component instead of in a useEffect, it's going to get called any time the component renders & re-renders.
So what happens here, in order (kinda), is:
your component renders with data being empty {};
your component has a useEffect() to fetch the data, so that gets fired when it first renders. Which is good.
At the same time, mutate() gets called, which also does the same exact fetch call as in the use effect. Bad.
You now have two concurrent API calls being sent off.
Some time later, one of the two API calls responds first. Doesn't matter which for our purposes.
On that response, we call setData(...) and change the state data value.
Because the data changed, the component rerenders. Which is good.
Because our component rerenders, the mutate() gets called AGAIN. Oh no. we're now fetching data again, will again setData(...), data state value gets changed, and our component rerenders again.... We're stuck in a loop.
On top of all of that, your component has an extra rerender in there when the 2nd of the two fetchData() calls gets returned. Which doesn't matter because it's all broken anyway.
If you get rid of the mutate(), this works fine? I think?
I have no clue what the purpose of the mutate is, other than to cause ridiculous behavior, so I can't advise on how to solve this. Lol.

What's the best practice to wait for fetch call in React?

In my App.js script it looks like this:
import React from "react";
function App() {
const [colorsData, setColorsData] = React.useState();
React.useEffect(() => {
const url = `https://www.thecolorapi.com/scheme?hex=0047AB&rgb=0,71,171&hsl=215,100%,34%&cmyk=100,58,0,33&mode=analogic&count=6`
fetch(url)
.then(res => res.json())
.then(data => setColorsData(data))
}, [])
console.log(colorsData);
return (
<div className="App">
<h1>{colorsData ? colorsData.colors[0].hex.value : "Loading..."}</h1>
<img src={colorsData ? colorsData.colors[0].image.named : "Loading..."} />
</div>
);
}
export default App;
Instead of checking if there's colorsData or not in every element which approach should I use for more clean code? If I don't check if there's colorsData, I get an error for undefined.
The good people at Vercel created a great react hook library called SWR (stale while revalidate)
SWR documentation
The name “SWR” is derived from stale-while-revalidate, a HTTP cache invalidation strategy popularized by HTTP RFC 5861. SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally come with the up-to-date data.
Reasons to use instead of creating your own hook (from their website):
Fast, lightweight and reusable data fetching
Built-in cache and request deduplication
Real-time experience
Transport and protocol agnostic
SSR / ISR / SSG support
TypeScript ready
React Native
Even more reasons (not bulleted to save space):
Fast page navigation, Polling on interval, Data dependency, Revalidation on focus, Revalidation on network recovery, Local mutation (Optimistic UI), Smart error retry, Pagination and scroll position recovery, and React Suspense
Implementation example:
import useSWR from 'swr'
// you can use any fetcher with SWR, this is just a wrapper for the native fetch function
const fetcher = (url) => fetch(url).then((res) => res.json());
function App() {
const url = `https://www.thecolorapi.com/scheme?hex=0047AB&rgb=0,71,171&hsl=215,100%,34%&cmyk=100,58,0,33&mode=analogic&count=6`
// error is optional, if you want to show your user that an error occured
const { data, error } = useSWR(url, fetcher)
return (
<div className="App">
<h1>{colorsData ? data.colors[0].hex.value : "Loading..."}</h1>
<img src={colorsData ? data.colors[0].image.named : "Loading..."} />
</div>
);
}
export default App;
You can use a custom hook like:
import { useEffect, useState } from "react";
function useData(url) {
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((response) => response.json())
.then((data) => {
setData(data);
setLoading(false);
});
}, [url]);
return { data, loading };
}
function App() {
const { data, loading } = useData(
"https://www.thecolorapi.com/scheme?hex=0047AB&rgb=0,71,171&hsl=215,100%,34%&cmyk=100,58,0,33&mode=analogic&count=6"
);
return (
<div className="App">
{loading ? (
"Loading..."
) : (
<>
<h1>{data.colors[0].hex.value}</h1>
<img src={data.colors[0].image.named} />
</>
)}
</div>
);
}
export default App;

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

How to update React state once useMutation is done?

I add an user to an api with react-query's useMutation hook. It works. Now, I need to add the new user to my array of users, which is in my state.
I know I'm supposed to query all the users with useQuery and then use onSuccess inside useMutation to modify the cache. But in certain cases, I don't fetch the users with useQuery, so I need to update a local state as I would do with a normal promise.
For the moment, I simply check if the prop "success" is true and if so, I update the array. But it only works on the second click. Why and how to fix this?
It seems the success condition inside onAddUser() is only reached on a second click.
export default function App() {
const { updateUser } = userService();
const { update, loading, error, data, success } = updateUser();
const [users, setUsers] = useState(allUsers);
const onAddUser = async () => {
await update(newUser);
if (success) {
return setUsers((users) => [...users, data]);
}
};
return (
<>
<div>
{users.map((user) => (
<div key={user.id}>
{user.name} - {user.job}
</div>
))}
</div>
{loading && <div>sending...</div>}
{error && <div>error</div>}
<button onClick={() => onAddUser()}>add user</button>
</>
);
}
Here is also a sandbox: https://codesandbox.io/s/usemutation-test-co7e9?file=/src/App.tsx:283-995
The success prop returned from updateUser (I assume this is somehow the result returned by useMutation) will only update on the next render cycle. Functions will still keep the reference to whatever they closured over, even if you have an async await in there. This is nothing react-query specific, this is just how react works.
I would suggest to use the onSuccess callback of the mutation function, or use mutateAsync and check the result, though you have to keep in mind to catch errors manually if you use mutateAsync. You can read about mutateAsync here, I'm gonna show you a mutate example:
const { mutate, loading } = useMutation(() => ...);
const onAddUser = () =>
mutate(newUser, {
onSuccess: (newData) => setUsers((users) => [...users, data]);
});
};
also, please don't violate the rules of hooks. You can only call hooks from functional components or other hooks (=functions that start with use), but in your codesandbox, you call useMutation from the updateUser function ...

React Hooks - Retry Failed Async Requests Inside of useEffect that is Dependent on Props

Does anybody know what patterns are considered best practice when it comes to retrying async network requests that fail within a useEffect hook? I have the following component:
import React, {useEffect, useState, useCallback} from 'react'
import styles from './_styles.module.scss'
type Props = {
itemIds: string[];
}
function MyComponent({itemIds}: Props): JSX.Element {
const [loading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<boolean>(true)
const [itemsData, setItemsData] = useState<Record<string, Item>>({})
useEffect(() => {
async function fetchItemsData() {
setError(false)
const {data, error: fetchError} = await getDataForItems(itemIds) // uses fetch API under the hood
if (fetchError) {
setError(true)
setLoading(false)
return
}
setItemsData(data)
setLoading(false)
}
if (loading) {
void fetchItemsData()
}
}, [itemIds, loading])
const retry = useCallback(() => {
setLoading(true)
}, [])
return (
<div>
{loading && (
<div className={styles.loadingSpinner} />
)}
{(!loading && error) && (
<div>
<div>Some error message with a try again button</div>
<button onClick={retry}>Try Again</div>
</div>
)}
{(!loading && !error) && (
<div>Success</div>
)}
</div>
)
}
The intent is for this component to be re-usable, and I have the useEffect hook configured to trigger in the event the itemIds prop changes or if the loading state changes. I have that loading state to help display loading-based visuals but also to serve as the retry request mechanism. The problem with this, however, is I have to guard the execution of the fetchItemsData function using that flag to prevent the effect from running that request again after every setLoading invocation.
Furthermore, in the event the initial request succeeds (loading will be set to false), when the itemIds prop updates to a new set of ids, the effect will not fire off because loading is false which prevents the execution of the fetchItemsData function.
Does anyone have a good pattern for handling request retries like this in React Hooks?

Resources