I am new to React, I dont really know how React hook works, here I encounter a problem. I tried to draw a chart using Chart.js and re draw it in specific interval using data from API, the problem is the chart does not read the data. This is the get data function:
const getData = async ()=>{
const response = await axiosJWT.get('http://localhost:5000/in-total-assets', {
headers:{
Authorization: `Bearer ${token}`
}
})
var formated = [];
formated.push = response.data[0].Total_OR_Weight;
formated.push = response.data[0].Total_IN_Weight;
setData(formated)
}
And this is the chart renderer function:
async function getChart(){
await getData();
let config = {
type: "doughnut",
data: {
labels: [
"Organic",
"Inorganic",
],
datasets: [
{
label: 'Dataset',
backgroundColor: ["#ed64a6", "#4c51bf"],
data: data,
hoverOffset: 5
},
],
}
};
let ctx = document.getElementById("doughnut-chart").getContext("2d");
window.myBar = new Chart(ctx, config);
}
And here is the useEffect function:
React.useEffect(() => {
refreshToken();
getChart();
const id = setInterval(getChart, 10000)
return ()=> clearInterval(id);
}, []);
The result is the chart show undefined as the value of the chart, any idea how to solve it?
Your html file in the part where you are going to draw the chart should look something like this:
<div class="canvasContainer" style="margin-top:20px; margin-bottom:20px;">
<canvas id="doughnut-chart"></canvas>
</div>
The first think that come to my mind is if there is any data at all coming from your getData function, if data is flowing in then my next guess is that setData is not updating it in time by the time you decide to use the variable data in your code.
I found this on a google search
State updates are asynchronous. This was true in class-based components. It's true with functions/Hooks
And
Even though they are asynchronous, the useState and setState functions do not return promises. Therefore we cannot attach a then handler to it or use async/await to get the updated state values
So, there it lays your problem. You need to redefine the part where you fetch the data and the part where you set your chart up, since the await part is not functioning like you think it is for hook functions. Something like this.
useEffect(() => {
getData() // it handles the setting up the data variable part
}, []); // 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.
useEffect(() => {
refreshToken();
getChart(); // it only sets the chart up, nothing else
}, [data]); // our component re-renders with every change on the var data
You can have as many useEffect blocks as you like and you can get rid off the part getContext("2d") from your ctx.
Related
When the page loads, I am making an API call, displaying a table with appointments. After the API call, I set a state for hasData to true, and the data is inserted in another setState. The issue is when the API returns the data from the async call, the component does not show the data. Please see code below.
const [recentAppointmentData, setRecentAppointmentData] = useState([])
const [hasAppointmentData, setHasAppointmentData] = useState(false)
const getAppointments = useCallback(() => {
const getAppointmentDataService = new GetAppointmentsService();
getAppointmentDataService.getDataFromService("263749804").then((results) => {
console.log("APPOINTMENT DATA ", results);
results.recentAppointments.map((result) => {
var recentAppointments = {
appointmentObject: {
serviceCategory: [],
serviceId: "",
appointmentDate: "",
groomer: "",
resourceId: "",
visitId: "",
},
};
if (result["services"] !== undefined) {
console.log("SERVICESS", result["services"]);
result["services"].map((service) => {
recentAppointments.appointmentObject.serviceCategory.push(
service["serviceCategory"]
);
recentAppointments.appointmentObject.serviceId = service["serviceId"];
});
}
recentAppointments.appointmentObject.appointmentDate = moment(
result["appointmentDateTime"]
).format("MM/DD/YY");
recentAppointments.appointmentObject.groomer = result["groomer"];
recentAppointments.appointmentObject.resourceId = result["resourceId"];
recentAppointments.appointmentObject.visitId = result["visitId"];
appointments.push(recentAppointments.appointmentObject);
Here I am setting the has Appointment data to true after the async function has been completed.
if (!hasAppointmentData) {
setHasAppointmentData(true);
}
});
Here I am storing the data in another state.
if (!hasAppointmentData) {
console.log("APPOINTMEN", appointments);
setRecentAppointmentData(appointments);
}
});
}, [hasAppointmentData]);
I am calling the function in the useEffect.
useEffect(() => {
getAppointments();
renderTabs();
}, [getAppointments, renderTabs]);
Can someone guide me on what I am doing wrong? Thanks
The problem is that you're using the useEffect hook wrong.
useEffect runs every time one of its dependencies change, or runs just once when the component mounts if you don't pass in any dependency to it. The dependencies are usually state variables within the component that useEffect runs in.
You want your getAppointments() to run only once, since it calls an external API to get the data. And you want to call renderTabs() (which I assume is responsible for displaying the data in the UI) only when the data is available. So you need to put them into two separate useEffect hooks.
useEffect(() => {
getAppointments();
}, []); // Runs just once when the component is mounted
useEffect(() => {
if (hasAppointmentData) {
renderTabs();
}
}, [hasAppointmentData]); // Runs every time the value of hasAppointmentData changes
But you'll need to watch out for a problem here, when using hasAppointmentData as the dependency. You're calling setHasAppointmentData first, and then following it up with setRecentAppointmentData. The second useEffect hook would run right after you set the boolean to true. By the time renderTabs() tries to fetch the data from recentAppointmentData, the data may not have been updated.
To me, hasAppointmentData is pretty much useless here. Checking for recentAppointmentData.length would serve you just as well, and is guaranteed to work reliably every time. So my second hook would look like this:
useEffect(() => {
if (recentAppointmentData.length) {
renderTabs();
}
}, [recentAppointmentData.length]);
I have a handleRating function which sets some state as so:
const handleRating = (value) => {
setCompanyClone({
...companyClone,
prevRating: [...companyClone.prevRating, { user, rating: value }]
});
setTimeout(() => {
handleClickOpen();
}, 600);
};
I think also have a function which patches a server with the new companyClone values as such:
const updateServer = async () => {
const res = await axios.put(
`http://localhost:3000/companies/${companyClone.id}`,
companyClone
);
console.log("RES", res.data);
};
my updateServer function gets called in a useEffect. But I only want the function to run after the state has been updated. I am seeing my res.data console.log when I load my page. Which i dont want to be making reqs to my server until the comapanyClone.prevRating array updates.
my useEffect :
useEffect(() => {
updateServer();
}, [companyClone.prevRating]);
how can I not run this function on pageload. but only when companyClone.prevRating updates?
For preventing function call on first render, you can use useRef hook, which persists data through rerender.
Note: useEffect does not provide the leverage to check the current updated data with the previous data like didComponentMount do, so used this way
Here is the code example.
https://codesandbox.io/s/strange-matan-k5i3c?file=/src/App.js
Hi I'm trying to make a twitter clone app. I am using React on the client side and Express on the server side and PostgreSQL as my database. So here's the problem, I'm trying to use the useEffect like this:
const [tweets, setTweets] = useState([]);
const getTweets = async () => {
const res = await api.get("/posts", {
headers: { token: localStorage.token },
});
setTweets(res.data);
};
useEffect(() => {
getTweets();
}, [tweets]);
I have no idea why it's looping infinite times, am I using it correctly though? I want the tweets to be updated every time I post a tweet. It's working fine but it's running infinite times. I just want it to re-render if a tweet got posted.
Here's my server code for getting all the posts:
async all(request: Request, response: Response, next: NextFunction) {
return this.postRepository.find({
relations: ["user"],
order: {
createdAt: "DESC",
},
});
}
The problem is every time you change the tweets it executes useEffect and changes the tweets and so long and so forth, so it's natural that it loops infinitely, the solution is to add a trigger that you set to true when a tweet gets posted, so the solution would be like this
const [tweets, setTweets] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const getTweets = async () => {
const res = await api.get("/posts", {
headers: { token: localStorage.token },
});
setTweets(res.data);
};
useEffect(() => {
getTweets();
setIsFetching(false);
}, [isFetching]);
and set some logic to use setIsFetching(true) in order to execute the useEffect
PS: if you use an empty array in useEffect, it would execute only when the component is mounted (at the start)
useEffect(() => {
getTweets();
}, [tweets]); // [tweets means that hook works every time 'tweets' state changes]
so your getTweets function set tweets => as tweets are changed hook works again => call getTweets => ... = infinite loop
if you want to download tweets, use empty array instead - hook will work once then
Pass empty array as a second arg for calling it once otherwise for changing it on every tweet change it will re-trigger, so whenever state will change only then it will be re-rendered like Tarukami explained. One thing you can do is check the length like mentioned below so not to compare the whole object but just the length
useEffect(() => {
getTweets();
}, [tweets.length]);
This might raise an error react-hooks/exhaustive-deps lint error (that's a bypass you can use it).
But if you want more tighter check you can compare the ids on each re-render (create a hash/key/id from all element in the array and compare them on each render) like so [tweet id here]) // Only re-subscribe if id changes
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
I have a large JSON blob stored inside my Context that I can then make references to using jsonpath (https://www.npmjs.com/package/jsonpath)
How would I go about being able to access the context from inside useEffect() without having to add my context variable as a dependency (the context is updated at other places in the application)?
export default function JsonRpc({ task, dispatch }) {
const { data } = useContext(DataContext);
const [fetchData, setFetchData] = useState(null);
useEffect(() => {
task.keys.forEach(key => {
let val = jp.query(data, key.key)[0];
jp.value(task.payload, key.result_key, val);
});
let newPayload = {
jsonrpc: "2.0",
method: "call",
params: task.payload,
id: "1"
};
const domain = process.env.REACT_APP_WF_SERVER;
let params = {};
if (task.method === "GET") {
params = newPayload;
}
const domain_params =
JSON.parse(localStorage.getItem("domain_params")) || [];
domain_params.forEach(e => {
if (e.domain === domain) {
params[e.param] = e.value;
}
});
setFetchData({ ...task, payload: newPayload, params: params });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [task]);
}
I'm gonna need to post an answer because of code, but I'm not 100% sure about what you need, so I'll build a correct answer with your feedback :)
So, my first idea is: can't you split your effects in two React.useEffect? Something like this:
export default function JsonRpc({ task, dispatch }) {
...
useEffect(() => {
...
setFetchData(...);
}, [task]);
useEffect(() => {
...
}, [data]);
..
}
Now, if my understanding are correct, this is an example of events timeline:
Due to the update on task you will trigger the first useEffect, which can setFetchData();
Due to the update on fetchData, and AXIOS call is made, which updates data (property in the context);
At this, you enter the second useEffect, where you have the updated data, but NO call to setFetchData(), thus no loop;
Then, if you wanted (but couldn't) put data in the dependencies array of your useEffect, I can imagine the two useEffect I wrote have some shared code: you can write a common method called by both useEffects, BUT it's important that the setFetchData() call is outside this common method.
Let me know if you need more elaboration.
thanks for your reply #Jolly! I found a work around:
I moved the data lookup to a state initial calculation:
const [fetchData] = useState(processFetchData(task, data));
then im just making sure i clear the component after the axios call has been made by executing a complete function passed to the component from its parent.
This works for now, but if you have any other suggestions id love to hear them!