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.
Related
I understand that the isPending return in React 18 is used so that lower priority state updates can happen last. I'm struggling to figure out how to make isPending work in a simple REST GET call example. I'm trying to write the code below without having to use a separate isLoading type state.
Is there anything I can do to make this happen? That is, with only isPending render a "loading..." message until the data has been retrieved?
(the results I get from the code below is I see a flash of "loading", then immediately see [] followed by my data. I want to see "loading" until my data actually loads)
import axios from "axios";
import { useEffect, useState, useTransition } from "react";
export default function Test1() {
const [data, setData] = useState([])
const [isPending, startTransition] = useTransition();
useEffect(() => {
async function fetchMyAPI() {
startTransition(async () => {
const results = await axios.get("/api/rest");
setData(results.data);
})
}
fetchMyAPI()
}, [])
if (isPending) return <div>Loading...</div>
return (
<div>{JSON.stringify(data)}</div>
);
}
function Reply({ id, user }) {
const [data, setData] = useState([]);
const [replyText, setReplyText] = useState("");
useEffect(() => {
async function fetchData() {
const response = await _axios.get("/reply/" + id);
setData(response.data);
}
fetchData();
}, [data]); <---- ** problem ** with data(dependency),
infinite request(call) fetchData()
...
}
what's the reason for infinite loop if there's a dependency.
as far as i know, when dependency(data) change, re-render.
but useEffect keep asking for data(axios.get(~~)).
if i leave a comment, i can see normally the latest comments, but the network tab(in develop tools) keeps asking for data(304 Not Modified, under image)
There's an infinite loop because that code says "If data changes, request information from the server and change data." The second half of that changes data, which triggers the first half again.
You're not using data in the callback, so it shouldn't be a dependency. Just remove it:
useEffect(() => {
async function fetchData() {
const response = await _axios.get("/reply/" + id);
setData(response.data);
}
fetchData();
}, []);
// ^^−−−−−−−−−− don't put `data` here
That gives you a blank dependency array, which will run the effect only when the component first mounts. (If you want to run it again after mount, use a different state member for that, or define fetchData outside the effect and use it both in the effect and at the other time you want to fetch data.)
Side note: Nothing in your code is handling rejections from your fetchData function, which will cause "unhandled rejection" errors. You'll want to hook up a rejection handler to report or suppress the error.
You are using setData after the response which causes the data to change and hence the useEffect(() => {<>your code<>} ,[data]) to fire again.
use useEffect(() => {<>your code<>},[]) if you want to execute the AJAX call only once after component mounting
or
use useEffect(() => {<>your code<>}) without the dependency if you want to execute the AJAX call after the component mount and after every update
Dependencies argument of useEffect is useEffect(callback, dependencies)
Let's explore side effects and runs:
Not provided: the side-effect runs after every rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs after EVERY rendering
});
}
An empty array []: the side-effect runs once after the initial rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs ONCE after initial rendering
}, []);
}
Has props or state values [prop1, prop2, ..., state1, state2]: the side-effect runs only when any dependency value changes.
import { useEffect, useState } from 'react';
function MyComponent({ prop }) {
const [state, setState] = useState('');
useEffect(() => {
// Runs ONCE after initial rendering
// and after every rendering ONLY IF `prop` or `state` changes
}, [prop, state]);
}
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
From my parent component, I am passing down the state variable data as a prop to the child component. The data is fetched via fetch/UseEffect. The URL params can be modified by clicking a button, and depending on the param, a completely different object is fetched, which needs to be handled differently in the child component.
export default function ParentComponent() {
const [data, setData] = useState({})
const [param, setParam] = useState('')
const [isLoading, setLoading] = useState(true)
useEffect(() => {
const fetchData = async () => {
const data= await (await fetch(`https://myapi.com/?param=${param}`)).json()
setData(data)
setLoading(false)
}
fetchData()
}, [param])
return (<div>
{ isLoading ?
(<span>Loading...</span>)
:
(<div>
<button onClick={() => setParam('something')}>Click me</button>
<ChildComponent data={ data } />
</div>
</div>)
}
}
My problem is that whenever I click the button (set the state) in the parent, which triggers a rerender, the components rerender twice (causing below console.log to print twice), which I'd dare say is expected behavior, once because of setParam, once when fetchData and setData is completed).
export default function ChildComponent(props) {
const { data } = props
return (
<div>
// Prints twice, first stale data, then when fetchData has completed.
{ console.log(data )}
</div>)
}
My question to you guys, as I have been struggling with this for a couple of hours now, having read ChrisW's post here (Avoid old data when using useEffect to fetch data) and the React.js Hooks FAQ (https://reactjs.org/docs/hooks-faq.html), is how on God's green earth (sorry any atheist!) I only ever get to access the newly fetched, non-stale data prop in the child component, and ignore the stale one? Is it through refs? UseEffect? (I know I can make a UseEffect in the child component with data as a dependency, however, what should I do with it, I am after all trying to control what is being returned?)
Thanks in advance for any answers!
I would suggest to consider useMemo API to solve this issue:
const MemoizedChildComponent = useMemo(({ data }) => <ChildComponent data={data} />, [data]);
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