I made a function component to return data from API. Purpose is to use it throughout my whole app. The code below will show you the structure only (actual code is with graphQL and contains security credentials)
async function GetData() {
console.log("function called")
try {
return await fetch('https://www.7timer.info/bin/astro.php?lon=113.2&lat=23.1&ac=0&unit=metric&output=json&tzshift=0')
} catch (err)
{ console.log('error fetching data from Source', err) }
}
export default GetData
I call it like the following:
Import:
import { useState} from 'react';
import GetData from "./components/getData";
Inside the component:
const [data, setData]= useState([]);
function pullData(){
GetData().then((result)=>{
setData(result);});
}
pullData()
What this does it get the data but put requests continually (output in console "function called"). I know it's for the react state. but I just need one result at a time (and any update supplied by graphQL), that's all.
Is there any way?
Your function is called continuously because it will run at each re-render and is being called at every re-render.
Consider wrapping your logic in a useEffect hook to control when the function will be called: https://reactjs.org/docs/hooks-effect.html
useEffect(() => {
const pullData = async () => {
GetData().then((result) => {
setData(result);
});
}
pullData()
}, [])
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument.
Related
Hope anyone is able to help me with a custom react hook.
My custom react hook "useFetch" is running 8 times when called.
Can anyone see, why it is running 8 times when the custom "useFetch" hook is called?
I am a bit new to React, but it seems like I am using useEffect method wrong. Or maybe I need to use another method.
UseFetch hook method:
import React, { useState, useEffect } from "react";
export const useFetch = function (
options = {
IsPending: true,
},
data = {}
) {
// load data
const [loadData, setLoadData] = useState(null);
// pending
const [isPending, setIsPending] = useState(false);
// error
const [isError, setIsError] = useState(false);
useEffect(() => {
// method
const fetchData = async function () {
// try
try {
// set pending
setIsPending(true);
// response
const response = await fetch(data.url, data);
// handle errors
if (response.status !== 200 && response.status !== 201) {
// throw new error with returned error messages
throw new Error(`Unable to fetch. ${response.statusText}`);
}
// convert to json
const json = await response.json();
// set load data
setLoadData(json);
// set error
setIsError(false);
// set pending
setIsPending(false);
// catch errors
} catch (err) {
// set error
setIsError(`Error fetching data: ${err.message}`);
// set pending
setIsPending(false);
}
};
// invoke fetch data method
fetchData();
}, []);
// return
return {
loadData,
isPending,
isError,
};
};
export default useFetch;
Everytime you change a state in a hook, the component that has the hook in it will rerender, making it call the function again.
So let's start counting the renders/rerenders by the change of state:
Component mounted
setIsPending(true)
setLoadData(json)
setIsPending(false)
(depending if it's successful or not you might get more state changes, and therefore rerenders, and therefore hook being called again)
So 4 is not 8, so why are you getting 8?
I presume you are using React18, and React18 on development and StrictMode will call your useEffect hooks twice on mount: React Hooks: useEffect() is called twice even if an empty array is used as an argument
What can you do to avoid this?
First of all, check on the network tab how many times you are actually fetching the data, I presume is not more than 2.
But even so you probably don't want to fetch the data 2 times, even though this behaviour won't be on production and will only be on development. For this we can use the useEffect cleanup function + a ref.
const hasDataFetched = useRef(false);
useEffect(() => {
// check if data has been fetched
if (!hasDataFetched.current) {
const fetchData = async function () {
// fetch data logic in here
};
fetchData();
}
// cleanup function
return () => {
// set has data fetched to true
hasDataFetched.current = true;
};
}, []);
Or as you suggested, we can also add data to the dependency array. Adding a variable to a dependency array means the useEffect will only be triggered again, when the value of the variable inside the dependency array has changed.
(Noting that data is the argument you pass to the useFetch hook and not the actual data you get from the fetch, maybe think about renaming this property to something more clear).
useEffect(() => {
// check if data has been fetched
const fetchData = async function () {
// fetch data logic in here
};
fetchData();
}, [data]);
This will make it so, that only if loadData has not been fetched, then it will fetch it. This will make it so that you only have 4 rerenders and 1 fetch.
(There is a good guide on useEffect on the React18 Docs: https://beta.reactjs.org/learn/synchronizing-with-effects)
Every time you change the state within the hook, the parent component that calls the hooks will re-render, which will cause the hook to run again. Now, the empty array in your useEffect dependency should be preventing the logic of the hook from getting called again, but the hook itself will run.
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]);
}
Somewhat new to React and hooks in React. I have a component that calls a communications hook inside of which a call to an API is made with AXIOS and then the JSON response is fed back to the component. The issue I'm having is the component is calling the hook like six times in a row, four of which of course come back with undefined data and then another two times which returns the expected JSON (the same both of those two times).
I did a quick console.log to double check if it was indeed the component calling the hook mulitple times or it was happening inside the hook, and it is the component.
How do I go about only have the hook called only once on demand and not multiple times like it is? Here's the part in question (not including the rest of the code in the widget because it doesn't pertain):
export default function TestWidget() {
//Fetch data from communicator
console.log("called");
const getJSONData = useCommunicatorAPI('https://jsonplaceholder.typicode.com/todos/1');
//Breakdown passed data
const {lastName, alertList, warningList} = getJSONData;
return (
<h1 id="welcomeTitle">Welcome {lastName}!</h1>
);
}
export const useCommunicatorAPI = (requestAPI, requestData) => {
const [{ data, loading, error }, refetch] = useAxios('https://jsonplaceholder.typicode.com/todos/1', []);
console.log("data in Communicator:", data);
return {data};
}
I would use the useEffect hook to do this on mount and whenever any dependencies of the request change (like if the url changed).
Here is what you will want to look at for useEffect
Here is what it might look like:
const [jsonData, setJsonData] = React.useState({})
const url = ...whatver the url is
React.useEffect(() => {
const doFetch = async () => {
const jsonData = await useAxios(url, []);;
setJsonData(jsonData)
}
doFetch();
}, [url])
...use jsonData from the useState
With the above example, the fetch will happen on mount and if the url changes.
Why not just use the hook directly?
export default function TestWidget() {
const [{ data, loading, error }, refetch] =
useAxios('https://jsonplaceholder.typicode.com/todos/1', []);
return (<h1 id="welcomeTitle">Welcome {lastName}!</h1>);
}
the empty array [] makes the hook fire once when called
Try creating a function with async/await where you fetch the data.
Here can you learn about it:
https://javascript.info/async-await
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
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