I am new to React so please forgive me if this issue is entry-level to you.
Basically, I want to do the thing as the title goes,
here I have a function designed for this purpose:
function saveAndUpdate() {
// data processing and wrap up the finalized data, lets call it newData
useEffect(() => {
async function loadData() {
try {
await axios.put(API/updateEntry,{objToUpdate: newData}).then(() => {
const { loading, error, data } = axios.get(API/dataForTheTable)
callback(data);
});
} catch (error) {
console.error(error);
}
}
loadData();
}, []);
}
callback is a function in parent component in order to update the state related to the data immediately after the data is updated.
const handleCallback = (data) => {
setInfo(data);
}
And by running this, I get the classic error which I still do not fully understand:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
Any help will be appreciated, thank you!
Seems that what you're doing is creating a hook (saveAndUpdate) and trying to access to It somewhere (for example, inside another hook or function).
Althought, a hook to update data (with axios.put, where you need to pass an argument; the data for the update) is not a good approach, as an UseEffect hook will run when the component is mounted, not when the user clicks on a button or whatever. For example, it would be better if you use this for gathering data (get request) from your backend when the component mounts.
Anyway this is a way you could do what you want to achieve, using the useCallback. I've created a button that fires the function and pass some data (just you can see the expected behaviour):
In the saveAndUpdate hook (name it as useSaveAndUpdate, just a convention for hooks names):
import { useEffect, useState } from "react";
export default function useSaveAndUpdate(dataUpdate) {
const [data, setData] = useState(null);
const [updating, setUpdating] = useState(false);
useEffect(() => {
if (updating) {
const req = function () {
// the put request (dataUpdate will be the data we use for the request)...
// for this i'm just going to set data to the argument we've passed
// the idea is to setData to the returned data from the server
setData(dataUpdate);
setUpdating(false);
// setting updating to false, if there's a re-render it won't do the request again
};
req();
}
}, [updating, dataUpdate]);
return [data, updating, setUpdating];
}
In the parent component:
import { useState, useCallback } from "react";
import useSaveAndUpdate from './useSaveAndUpdate'
const parent = () => {
const [updateTarget,setUpdateTarget = useState(null)
const [data,updating,setUpdating] = useSaveAndUpdate(updateTarget)
const updateFunction = useCallback((updateData) => {
setUpdating(true)
//
// ^ Setting updating to true will fire the request inside useSaveAndUpdate when re-render
//
setUpdateTarget(updateData)
// Setting the data for the update (this data will be used in the put request)
},[setUpdating,setUpdateTarget])
return (
<h3>{loading?'loading...':'not loading'}</h3>
<h3>{data?data.name:'data is null'}</h3>
{/*data will be updated when button clicked, so if it's a table just do data.map()...*/}
<button onClick={() => updateFunction({name:'jhon'})}>Click me to update</button>
)
}
So at first render, It will outpout that It's not loading and data is null.
When click It'll say loading for a sec (the time the request lasts), and the data will be displayed ('jhon').
A small codesandbox so you can see how it works.
Related
i have a react component thats keep re-rendering idk why but i think the reason is the data fetching
data code :
export function KPI_Stock_Utilisation() {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
setKpi_stock_utilisation((existingData) => {
return response.data;
});
});
}, []);
console.log('data get')
return kpi_stock_utilisation;
}
this log displayed many times , and the log in the component too
component code :
import React from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import { useEffect } from "react";
export default function WarehouseUtilisChart(props) {
let kpi_stock_utilisations =KPI_Stock_Utilisation();
let Stock_utilisation = (kpi_stock_utilisations.length / 402) * 100;
console.log('component render')
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
im new with react i tried useEffect inside the componenets but its not working
Calling the react custom hook KPI_Stock_Utilisation several times will for sure render more than once.
in your case I suggest you use useEffect in the same component as I will show you.
import React,{useEffect,useRef} from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import axios from 'axios';
export default function WarehouseUtilisChart(props) {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
const stock_utilisation= useRef(0);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
stock_utilisation.current = (response.data.length / 402) * 100;
setKpi_stock_utilisation(response.data);
});
//this will guarantee that the api will be called only once
}, []);
//you should see this twice, one with the value 0, and another one, the calculated data
console.log('component render',stock_utilisation.current)
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
To note, if you call this component from more than one location, for sure it will render several times - keep that in mind.
On the other hand, all your variables should always start with a lower case and try to name your variables like this: instead of kpi_stock_utilisation change it to kpiStockUtilisation for a better coding practice
You got into infinite loop.
Its hard to explain why it doesn't work as expected, but I can try.
First of all, useEffect with empty array of dependencies works like componentDidMount and fires only after (!) first render.
So you have some value returned from your let kpi_stock_utilisations =KPI_Stock_Utilisation(); then it rendered, after this your useEffect fires a request and set state, setting of state trigger re-render and new value to return, this new value trigger your parent component to return let kpi_stock_utilisations =KPI_Stock_Utilisation(); might run again.
If you are trying to create a custom hook for fetching some info, follow rules of hooks
I hope it helped you
In my code example, my useEffect is subscribed to postId, which is not changing at any point. Yet useEffect is still being run.
Is there a way to prevent this from running and only launch if I change it?
import React, {useState, useEffect} from 'react';
import Vim from './Vim';
import './Main.css';
function Main():JSX.Element {
const [postId,updatePostId] = useState<number|null>(null)
const [content, updateContent] = useState<string>('default text');
const apiUrl = 'http://127.0.0.1:8000/'
useEffect(()=>{
// Detect change in PostID & go download it.
// Ignore if we are coming from null->number
console.log('Why am I running?')
fetch(apiUrl+'get_post/'+postId)
.then(res=>res.json())
.then(result=>console.log(result))
},[postId])
function loadPost(pid:number):string|null{
// fetch from API, load post content
console.log('I can access:'+postId)
return null;
}
function backLinks():JSX.Element{
return(
<div className="backlinks">
</div>
)
}
return (
<div className='main'>
<Vim content={content} />
</div>
)
}
export default Main
In fact in the first run, postId is null since you initialize it in that way. So you need to take care of that as well.
useEffect either accepts an array of dependencies or you could leave it empty. In the later case it just run once since there is no dependency, however in the first case it both listen for dependencies changing and also runs when the component mounts for the first time.
useEffect(()=>{
if (postId == null) return;
...
}, [postId]);
For a more general approach you can have something like below.
const [ready, setReady] = useState(false);
useEffect(() => {
setReady(true);
}, [])
useEffect(()=>{
if (!ready) return;
...
}, [postId, ready]);
The above solution is fair enough in most situations but I suggest you to handle it by creating a ref, assigning it to your DOM element and wait until that ref become available. So you are sure that your component is being rendered for the first time.
const isMounted = useRef(null);
useEffect(()=>{
if (!isMounted.current) return;
// do your fetch stuff here
fetch(apiUrl+'get_post/'+postId)
.then(res=>res.json())
.then(result=>console.log(result))
}, [isMounted]);
<div ref={isMounted}></div>
This way you don't need an extra re-render since updating refs does not lead to a re-render.
I'm making a project using rabbitMQ and react.
I did connect rabbitMQ server and my react app, and I finally success get some data from server.
I can print those data in console (like using console.log()), however I cannot display data in html.
I tried to use async/await but it didn't work.
function ServerTest() {
var a:any;
const client = createClient("admin", "1111");
const queue = v4();
client.onConnect = function () {
console.log("connected to Stomp");
subscribe(client, 'admin', queue, (payload: {}) => {
a = payload;
a = a.UserInfo.userId
console.log(a)
})
publish(client, 'api.user.info', 'admin', queue, {})
}
client.activate();
return (<div>a: {a}</div>)
}
React does not listen for changes happening in any random variable, be it variable a, so the UI will not be updated automatically. To get the desired functionality, useState hook can be used here.
For that, import the useState function.
import {useState} from 'react'
Inside the function, use it like this
function ServerTest() {
const [ valueA, setValueA ] = useState();
An initial value for valueA can also be passed
const [ valueA, setValueA ] = useState(initialState);
valueA will hold the current state, so use it in the return statement
return (<div>a: {valueA}</div>)
Now, when data from the server is available, use setValueA to update the value of valueA
subscribe(client, 'admin', queue, (payload: {}) => {
setValueA(payload.UserInfo.userId);
By doing this, react will automatically update the UI to match the current state of 'valueA'
If this is in your react component, you should use state to show the value from the api.
import React, { useState } from 'react';
const MyComponent = () => {
const [aState, setA] = useState();
const callApi = () => {
//get values from API here
setA(apiValue);
}
return (
<div>{aState}</div>
)
In your code, return the a value you get from the api and then pass it into setA(a). When the aState value changes, the component will automatically rerender and the result will be shown on the page.
check out this article: https://www.robinwieruch.de/react-hooks-fetch-data
It explains how to fetch data and display it with React
I wonder why this gets stuck in infinite renders as soon as I set it's value coming from backend API.
I don't want to stop it using useEffect() as I need to use it to re render after the values have been assigned
CODE :
import React, { useState, useEffect } from 'react';
// import FormCheckLabel from 'react-bootstrap/FormCheckLabel';
import './CheckForms.css';
// import Scrollspy from '../Scrollspy/Scrollspy';
import axios from 'axios';
const CheckForms = () => {
const [menu, setMenu] = useState([]);
const fetchList = async () => {
try {
const res = await axios.get(`http://localhost:5000/api/items`);
const list = res.data.response;
// setMenu(list); // <-- This is causing ulimited renders ! //
} catch (err) {
console.log(err.response);
};
};
fetchList();
console.log("something");
return (
<div>
</div>
)
}
export default CheckForms;
I would really appreciate the help. Thanks.
you cannot place fetchList(); outside useEffect() because,
when component is mounted, fetchList() is called, it sets state and component rerenders.
again fetchList() is executed and again sets state and comoenent again rerenders. it forms an infinite loop.
you must do
useEffect(()=>fetchList(),[])
or execute it on an event
you need useEffect for this. Otherwise fetchList will be called on each re render.
So your infinite loop comes from :
fetchList() => setMenu() => render() => fetchList() => setMenu() and so on ...
I don't want to stop it using useEffect() as I need to use it to re
render after the values have been assigned
useEffect(callback, [triggers]) second parameter is here exactly for that, control when a re render should happen, based on props/state changes
I am using useEffect hook and getting a list of users data with fetch call using function getStoreUsers which dispatches an action on response and stores shopUsers(which is an array) inside the redux store.
In array dependency, I am writing [shopUsers]. I don't know why it is causing infinite rendering.
Here is how I am using useEffect hook:
useEffect(() => {
const { getStoreUsers, shopUsers } = props;
setLoading(true);
getStoreUsers().then(() => {
setLoading(false);
}).catch(() => {
setLoading(false);
});
}, [shopUsers]);
I want to re-render component only when data inside shopUsers array changes.
If I write shopUsers.length inside array dependency. It stops to re-render.
But, let's suppose I have have a page which opens up when the user clicks on a userList and updates user data on next page. After the update I want the user to go back to the same component which is not unmounted previously. So, In this case array length remains the same, but data inside in of array index is updated. So shopUsers.length won't work in that case.
You can make a custom hook to do what you want:
In this example, we replace the last element in the array, and see the output in the console.
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { isEqual } from "lodash";
const usePrevious = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const App = () => {
const [arr, setArr] = useState([2, 4, 5]);
const prevArr = usePrevious(arr);
useEffect(() => {
if (!isEqual(arr, prevArr)) {
console.log(`array changed from ${prevArr} to ${arr}`);
}
}, [prevArr]);
const change = () => {
const temp = [...arr];
temp.pop();
temp.push(6);
setArr(temp);
};
return (
<button onClick={change}>change last array element</button>
)
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Live example here.
Your effect is triggered based on the "shopUsers" prop, which itself triggers a redux action that updates the "shopUsers" prop and thats why it keeps infinitely firing.
I think what you want to optimize is the rendering of your component itself, since you're already using redux, I'm assuming your props/state are immutable, so you can use React.memo to re-render your component only when one of its props change.
Also you should define your state/props variable outside of your hooks since they're used in the scope of the entire function like so.
In your case, if you pass an empty array as a second param to memo, then it will only fire on ComponentDidMount, if you pass null/undefined or dont pass anything, it will be fired on ComponentDidMount + ComponentDidUpdate, if you want to optimise it that even when props change/component updates the hook doesn't fire unless a specific variable changes then you can add some variable as your second argument
React.memo(function(props){
const [isLoading, setLoading] = useState(false);
const { getStoreUsers, shopUsers } = props;
useEffect(() => {
setLoading(true);
getStoreUsers().then(() => {
setLoading(false);
}).catch((err) => {
setLoading(false);
});
}, []);
...
})