React useEffect loop: circular dependency - reactjs

I'm trying to call a function every time that function's parameters change and this has generated a loop that I don't know how to solve. I've tried implementing this in several ways, such as:
const [fromAmount, setFromAmount] = useState(1);
const [fromToken, setFromToken] = useState<keyof typeof tokens>("WBNB");
const [toToken, setToToken] = useState<keyof typeof tokens>("CAKE");
const path = useMemo(
() => [tokens[fromToken].address, tokens[toToken].address],
[fromToken, toToken]
);
const setAmountsOutParams = useCallback(() => {
if (fromAmount > 0) {
getAmountsOut.setParams({
amountIn: ethers.utils.parseUnits(String(fromAmount), 18),
path,
});
}
}, [fromAmount, path]);
useEffect(() => {
setAmountsOutParams()
}, [setAmountsOutParams]);
As context, I have a hook that provides me with the getAmountsOut, and this is the code regarding it:
const [paramsGetAmountsOut, setParamsGetAmountsOut] = useState({
amountIn: ethers.utils.parseUnits('0', 18),
path: ['', ''],
});
const { data: dataGetAmountsOut } = useContractRead({
...swapper,
functionName: 'getAmountsOut',
args: [paramsGetAmountsOut.amountIn, paramsGetAmountsOut.path],
});
const getAmountsOut = {
data: dataGetAmountsOut,
setParams: setParamsGetAmountsOut,
};
How to solve it?

Related

How useStoreState is referring values in react?

I am pretty new to react and easy-peasy, I am stuck with one implementation with useStoreState.
I just want to understand how the useStoreState and useStoreAction work in the below code and how state.AdminCompanyInfo and action.AdminCompanyInfo are getting resolved.
I don't find any simple-to-understand example. Please help.
here is the code
const AdminCompanyInfo = () => {
var userType = isDesignerUser ? 1 : 2;
const [hideInactiveUser, setHideInactiveUser] = useState();
const {
roles: { data: roles },
companyUsers: state,
inactiveUserSetting: { response: orgSetting },
} = useStoreState(state => state.AdminCompanyInfo);
const {
companyUsers: { fetch },
companyUsers: actions,
roles: { fetch: fetchRoles },
inactiveUserSetting: { update: updateOrgSetting },
} = useStoreActions(actions => actions.AdminCompanyInfo);
useEffect(() => {
fetchRoles(userType);
fetch();
}, []);
}

Too many re-renders with useSelector hook closure

Considering this state, I need to select some data from it:
const initialState: PlacesStateT = {
activeTicket: null,
routes: {
departure: {
carriageType: 'idle',
extras: {
wifi_price: 0,
linens_price: 0,
},
},
arrival: {
carriageType: 'idle',
extras: {
wifi_price: 0,
linens_price: 0,
},
},
},
};
so, I came up with two approaches:
first:
const useCoaches = (dir: string) => {
const name = mapDirToRoot(dir);
const carType = useAppSelector((state) => state.places.routes[name].carriageType);
const infoT = useAppSelector((state) => {
return state.places.activeTicket.trainsInfo.find((info) => {
return info.routeName === name;
});
});
const coaches = infoT.trainInfo.seatsTrainInfo.filter((coach) => {
return coach.coach.class_type === carType;
});
return coaches;
};
and second:
const handlerActiveCoaches = (name: string) => (state: RootState) => {
const { carriageType } = state.places.routes[name];
const { activeTicket } = state.places;
const trainInfo = activeTicket.trainsInfo.find((info) => {
return info.routeName === name;
});
return trainInfo.trainInfo.seatsTrainInfo.filter((coach) => {
return coach.coach.class_type === carriageType;
});
};
const useActiveInfo = (dir: string) => {
const routeName = mapDirToRoot(dir);
const selectActiveCoaches = handlerActiveCoaches(routeName);
const coaches = useAppSelector(selectActiveCoaches);
return coaches;
};
Eventually, if the first one works ok then the second one gives a lot of useless re-renders in component. I suspect that there are problems with selectActiveCoaches closure, maybe react considers that this selector is different on every re-render but I am wrong maybe. Could you explain how does it work?
selectActiveCoaches finishes with return seatsTrainInfo.filter(). This always returns a new array reference, and useSelector will force your component to re-render whenever your selector returns a different reference than last time. So, you are forcing your component to re-render after every dispatched action:
https://react-redux.js.org/api/hooks#equality-comparisons-and-updates
One option here would be to rewrite this as a memoized selector with Reselect:
https://redux.js.org/usage/deriving-data-selectors

Strange bugs with localStorage with useState react

I tried to save values of the input with localStorage and i have a strange bugs, it doens't load data from localStorage.
1- I set the data of multiple input with setItem in useEffect in the InputFile.js
useEffect(() => {
let validateInputs = {
fullNameValidate: enteredFullNameIsValid,
phoneValidate: enteredPhoneIsValid,
};
setValidation(validateInputs);
const allData = { fullNameEntered, phoneEntered };
localStorage.setItem("data", JSON.stringify(allData));
}, [
enteredFullNameIsValid,
enteredPhoneIsValid,
setValidation,
fullNameEntered,
phoneEntered,
]);
2 - i update the value of the handler in the custom hooks (use-input.js) :
const [enteredValue, setEnteredValue] = useState({
enterValidate: "",
});
const valueChangeHandler = (event) => {
setEnteredValue({
...enteredValue,
enterValidate: event.target.value,
});
};
3- I tried to take the values saved with (in custom hooks, use-input.js):
useEffect(() => {
const saved = localStorage.getItem("data");
const { fullNameEntered, phoneEntered } = JSON.parse(saved);
setEnteredValue((prevState) => {
return { ...prevState, fullNameEntered, phoneEntered };
});
}, []);
But it doesn't works!
UPDATE:
here there are the 2 complete files:
1- custom hooks for the inputs
const useInput = (validateValue) => {
const [enteredValue, setEnteredValue] = useState({
enterValidate: "",
});
const [isTouched, setIsTouched] = useState(false);
const [clickClasses, setClickClasses] = useState(false);
const valueIsValid = validateValue(enteredValue.enterValidate);
const hasError = !valueIsValid && isTouched;
const valueChangeHandler = (event) => {
setEnteredValue({
...enteredValue,
enterValidate: event.target.value,
});
};
const inputBlurHandler = () => {
setIsTouched(true);
setClickClasses(false);
};
const inputClickHandler = () => {
setClickClasses(!clickClasses);
};
const reset = () => {
setEnteredValue("");
setIsTouched(false);
};
////TAKE STORED DATA////// BUT IT DOESN'T WORK
useEffect(() => {
const saved = localStorage.getItem("data");
const { fullNameEntered, phoneEntered } = JSON.parse(saved);
setEnteredValue((prevState) => {
return { ...prevState, fullNameEntered, phoneEntered };
});
}, []);
console.log(enteredValue);
return {
value: enteredValue.enterValidate,
isValid: valueIsValid,
hasError,
valueChangeHandler,
inputBlurHandler,
inputClickHandler,
click: clickClasses,
reset,
};
};
export default useInput;
2- Input files
import React, { useEffect } from "react";
import useInput from "../../../../../hooks/use-input";
import validateInput from "../../../../../utils/validateInput";
import {
WrapperNamePhone,
LabelNamePhone,
SpanInputDescription,
InputStyle,
} from "../ContactFormInput.style";
export default function FullNamePhoneInput({ setValidation }) {
//FullName Input
const {
value: fullNameEntered,
isValid: enteredFullNameIsValid,
hasError: fullNameHasError,
valueChangeHandler: fullNameChangeHandler,
inputBlurHandler: fullNameBlurHandler,
inputClickHandler: fullNameClickHandler,
click: fullNameClickClasses,
} = useInput((value) => {
const inputValidFullName = {
value: value,
maxLength: 20,
whiteSpace: true,
allowNumber: false,
allowStrings: true,
};
return validateInput(inputValidFullName);
});
//Phone numbers input
const {
value: phoneEntered,
isValid: enteredPhoneIsValid,
hasError: phoneHasError,
valueChangeHandler: phoneChangeHandler,
inputBlurHandler: phoneBlurHandler,
} = useInput((value) => {
const inputValidPhone = {
value: value,
maxLength: 15,
whiteSpace: true,
allowNumber: true,
allowStrings: false,
};
return validateInput(inputValidPhone);
});
useEffect(() => {
let validateInputs = {
fullNameValidate: enteredFullNameIsValid,
phoneValidate: enteredPhoneIsValid,
};
setValidation(validateInputs);
//STORE DATA////
const allData = { fullNameEntered, phoneEntered };
localStorage.setItem("data", JSON.stringify(allData));
}, [
enteredFullNameIsValid,
enteredPhoneIsValid,
setValidation,
fullNameEntered,
phoneEntered,
]);
//FUll NAME
const borderColorFullName = fullNameHasError ? `rgb(245, 2, 2)` : `#d5d9dc`;
const clickedColor = fullNameClickClasses ? "#2696e8" : "#a4aeb4";
//PHONE NUMBERS
const borderColorPhone = phoneHasError ? `rgb(245, 2, 2)` : `#d5d9dc`;
return (
<WrapperNamePhone>
<LabelNamePhone htmlFor="full-name">
<SpanInputDescription clickedColor={clickedColor}>
Full name
</SpanInputDescription>
<InputStyle
type="text"
name="full-name"
id="full-name"
borderColor={borderColorFullName}
value={fullNameEntered}
onChange={fullNameChangeHandler}
onBlur={fullNameBlurHandler}
onClick={fullNameClickHandler}
/>
{fullNameHasError && <p> - Enter a valid Full Name</p>}
</LabelNamePhone>
<LabelNamePhone htmlFor="phoneNumber">
<InputStyle
placeholder="Enter a valid phone number"
type="text"
name="phoneNumber"
borderColor={borderColorPhone}
value={phoneEntered}
onChange={phoneChangeHandler}
onBlur={phoneBlurHandler}
/>
{phoneHasError && <p> - Enter a valid Phone Number</p>}
</LabelNamePhone>
</WrapperNamePhone>
);
}
///////////////////////////
//////////////////////////
Final Update, i don't like this solution but it works!
I've done like this:
-1 I deleted the setKeys from the Input files.
-2 I update the setKeys dinamically in the use-input hooks:
-3 then with useState i update the getItem!
const [enteredValue, setEnteredValue] = useState(() => {
return {
validateInput: "",
fullName: JSON.parse(localStorage.getItem("fullName")) || "",
phoneNumber: JSON.parse(localStorage.getItem("phoneNumber")) || "",
email: JSON.parse(localStorage.getItem("email")) || "",
country: JSON.parse(localStorage.getItem("country")) || "",
From: JSON.parse(localStorage.getItem("From")) || "",
To: JSON.parse(localStorage.getItem("To")) || "",
};
});
const valueChangeHandler = (event) => {
setEnteredValue({
[event.target.name]: event.target.value,
validateInput: event.target.value,
});
localStorage.setItem(event.target.name, JSON.stringify(event.target.value));
};
*/ Other code for validation, useless in this example */
return {
value: enteredValue.validateInput,
valueName: enteredValue.fullName,
valuePhone: enteredValue.phoneNumber,
valueEmail: enteredValue.email,
valueCountry: enteredValue.country,
valueFrom: enteredValue.From,
valueTo: enteredValue.To,
isValid: valueIsValid,
hasError,
valueChangeHandler,
inputBlurHandler,
inputClickHandler,
click: clickClasses,
reset,
};
I m not sure i have completely understood your problem, however instead of doing all that in useState you could use an effect and set its value based on any dependency or just on component mount like so:
const [enteredValue, setEnteredValue] = useState({});
useEffect(()=>{
const saved = localStorage.getItem("data");
const { fullNameEntered, phoneEntered } = JSON.parse(saved);
setEnteredValue({
...enteredValue,
fullname:fullNameEntered,
phone:phoneEntered
})
},[])
you can change to add some conditions to ensure its not null before being set etc. but this approach should work in general instead of using a callback in useState.
You can then use the values in the component via the state i.e enteredValue?.fullname etc. (?. is optional chaining to prevent undefined errors)

State outside of my useCallback is different to the one that is inside

console.log('outside', currentPageNumber); // 0 then 3.
const fetchMoreItems = useCallback(
page => {
const { from, to } = dateModifier(selectedMonth);
const params = {
from,
to,
limit: ITEMS_PER_PAGE,
page: 3,
};
console.log('inside', currentPageNumber); // 0
if (selectedTab[ISSUES]) dispatchUserSuggestions({ ...params, type: 'issue' });
if (selectedTab[SUGGESTIONS]) dispatchUserSuggestions({ ...params, type: 'suggestion' });
},
[dispatchUserSuggestions, selectedTab, selectedMonth],
);
I need the currentPageNumber to be the new version of the state rather than the old one. I've tried adding it as a dependency to the useCallback but this puts me into an infinite loop.
Don't suppose anyone can tell what is going on?
Ideally you should add the currentPageNumber as a dependency, and solve the infinite loop. The code that causes the loop doesn't appear in your example.
If you can't, you can use a ref as an escape hutch:
const currentRef = useRef();
console.log('outside', currentPageNumber);
useEffect(() => {
currentRef.current = currentPageNumber;
}, [currentPageNumber]);
const fetchMoreItems = useCallback(
page => {
const { from, to } = dateModifier(selectedMonth);
const params = {
from,
to,
limit: ITEMS_PER_PAGE,
page: 3,
};
console.log('inside', currentRef.current);
if (selectedTab[ISSUES]) dispatchUserSuggestions({ ...params, type: 'issue' });
if (selectedTab[SUGGESTIONS]) dispatchUserSuggestions({ ...params, type: 'suggestion' });
},
[dispatchUserSuggestions, selectedTab, selectedMonth],
);

useState does not update the state

I'm trying to update a state using useState hook, however the state won't update. I've checked how to fix it but really have no idea about it what cause this point. This is the whole code I didnt include the urls and import files...
When onchange method trigger ilceZoom function event has value so ı can get it evt.value example values is "1234" but I can not set it using useState future
const ilceUrl = 'URL';
const AddressSearchMaks = (props) => {
useEffect(() => {
ilceLoad();
}, []);
const [ ilceler, setIlceler ] = useState([]);
const [ selectedIlce, setSelectedIlce ] = useState(null);
let queryTask;
let query;
let sfs;
let lineSymbol;
let polygon;
let polyline;
let graphic;
let extent;
let point;
let wMercatorUtils;
let rfConverter;
loadModules([
'esri/tasks/query',
'esri/tasks/QueryTask',
'esri/symbols/SimpleFillSymbol',
'esri/symbols/SimpleLineSymbol',
'esri/geometry/Polygon',
'esri/geometry/Polyline',
'esri/geometry/webMercatorUtils',
'esri/geometry/Extent',
'esri/geometry/Point',
'esri/graphic',
'esri/Color',
'libs/ReferenceConverter'
]).then(
(
[
Query,
QueryTask,
SimpleFillSymbol,
SimpleLineSymbol,
Polygon,
Polyline,
webMercatorUtils,
Extent,
Point,
Graphic,
Color,
referenceConverter
]
) => {
queryTask = QueryTask;
query = Query;
polygon = Polygon;
polyline = Polyline;
graphic = Graphic;
extent = Extent;
point = Point;
wMercatorUtils = webMercatorUtils;
rfConverter = referenceConverter;
sfs = new SimpleFillSymbol(
SimpleFillSymbol.STYLE_SOLID,
new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([ 0, 255, 255 ]), 4),
new Color([ 140, 140, 140, 0.25 ])
);
lineSymbol = new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([ 0, 255, 255 ]), 4).setWidth(4);
}
);
const getAdres = async (url) => {
let response = await fetch(url);
let data = await response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
list.push({
label: item.ADI,
value: item.ID,
lat: item.LAT,
lon: item.LON
});
});
return list;
};
async function ilceLoad() {
let ilceList = await getAdres(ilceUrl);
setIlceler(ilceList);
}
const convertExtent = (lat, lon) => {
let p;
let ext;
const sr = props.map.spatialReference;
if (sr.wkid == 102100) {
const _p = wMercatorUtils.lngLatToXY(lon, lat);
ext = extent({
xmin: _p[0],
ymin: _p[1],
xmax: _p[0],
ymax: _p[1],
spatialReference: props.map.spatialReference
});
} else {
const res = rfConverter.WgsToItrf(lat, lon);
ext = extent({
xmin: res.x,
ymin: res.y,
xmax: res.x,
ymax: res.y,
spatialReference: props.map.spatialReference
});
p = point(res.x, res.y);
}
p.spatialReference = sr;
return ext;
};
const ilceZoom = (evt) => {
setSelectedIlce(evt.value);
console.log('selectedIlce', selectedIlce);
setError(false);
console.log('error', error);
const qTask = queryTask(maksIlce);
const q = query();
q.returnGeometry = true;
q.outFields = [ '*' ];
q.outSpatialReference = { wkid: 5254 };
q.where = `KIMLIKNO=${evt.value}`;
qTask.execute(q, (evt) => {
const polyGon = polygon({
rings: evt.features[0].geometry.rings
});
props.map.graphics.add(graphic(polyGon, sfs));
});
const extent = convertExtent(evt.lat, evt.lon);
props.map.setExtent(extent);
mahalleLoad();
};
return (
<Select name='adresSelect' options={ilceler} onChange={(e) => ilceZoom(e)} placeholder='İlçe Seçiniz' />
);
};
const mapStateToProps = (state) => ({
map: state.map.map
});
export default connect(mapStateToProps, null)(AddressSearchMaks);
It can be related for some environment binding issue. Try to use the the setState as function:
useEffect(() => {
ilceLoad();
}, []);
const [ ilceler, setIlceler ] = useState([]);
const [ selectedIlce, setSelectedIlce ] = useState(null);
async function ilceLoad() {
let ilceList = await getAdres(ilceUrl);
setIlceler(ilceList); // update the state, it works here
}
const ilceZoom = (evt) => {
setSelectedIlce(prev => {
console.log("prev: ", prev);
console.log("evt.value: ", evt.value);
return evt.value;
});
const qTask = queryTask(url);
const q = query();
q.returnGeometry = true;
q.outFields = [ '*' ];
q.outSpatialReference = { wkid: 5254 };
q.where = `VARIABLE NAME=${evt.value}`;
qTask.execute(q, (evt) => {
const polyGon = polygon({
rings: evt.features[0].geometry.rings
});
props.map.graphics.add(graphic(polyGon, sfs));
});
const extent = convertExtent(evt.lat, evt.lon);
props.map.setExtent(extent);
};
Can you try like this. Because, in your code, you setting the data in selectedIlce, but before it re-render, your trying to checking the value in the console, so better use your console outside the event function, so that when it get updated, it will reflect in the console.
console.log('selectedIlce', selectedIlce);
const ilceZoom = (evt) => {
setSelectedIlce(evt.value);
....
}

Resources