I'm new in React and I wonder is using ReactElement as useState argument normal?
I try to do it and everything works fine. Is it anti-pattern or it's OK?
Unfortunately, I didn't find any information about it in documentation
const [infoBox, setInfobox] = useState<ReactElement|null>(null);
const catalogLoadedDataEmpty = useSelector(getCatalogLoadedDataEmptySelector);
const catalogHasErrors = useSelector(getCatalogHasErrorsSelector);
...
useEffect(() => {
let infoBoxTitle;
if (catalogLoadedDataEmpty) {
infoBoxTitle = t('pages.Brands.errors.noResults.title');
} else if (catalogHasErrors) {
infoBoxTitle = errorsByErrorCode[EErrorCodes.UNRECOGNIZED_ERROR](t);
} else {
setInfobox(null);
return;
}
setInfobox(<InfoBox
className={catalogInfoBoxClassname}
iconName={EInfoBoxIcon.error}
title={infoBoxTitle}
description={noResultsDescription}
/>);
}, [catalogLoadedDataEmpty, catalogHasErrors]);
You can, but it's easy to create bugs where you expect the page to update, but it doesn't, because you forgot to update the state. It's usually better to save data in state, and then use that data to render fresh elements on each render.
And in your case i'd go one step further: this shouldn't be a state variable at all. The values catalogLoadedDataEmpty and catalogHasErrors are enough to determine the desired output directly. You can thus remove the use effect, and in so doing get rid of the double-render that you currently have:
const catalogLoadedDataEmpty = useSelector(getCatalogLoadedDataEmptySelector);
const catalogHasErrors = useSelector(getCatalogHasErrorsSelector);
let infoBoxTitle;
if (catalogLoadedDataEmpty) {
infoBoxTitle = t('pages.Brands.errors.noResults.title');
} else if (catalogHasErrors) {
infoBoxTitle = errorsByErrorCode[EErrorCodes.UNRECOGNIZED_ERROR](t);
}
const infoBox = infoBoxTitle ? (
<InfoBox
className={catalogInfoBoxClassname}
iconName={EInfoBoxIcon.error}
title={infoBoxTitle}
description={noResultsDescription}
/>
) : null
Related
I'm running into an issue where my hobbies list is not being displayed if it was previously undefined.
The goal is to cycle through a list of hobbies and display them in the UI. If one hobby is passed, only one should be displayed. If none are passed, nothing should be displayed.
Basically, setHobbies(["Cycling"]) followed by setHobbies(undefined) correctly turns off the hobbies render, but then the next setHobbies(["Reading"]) doesn't show up.
Using a debugger I've been able to verify that the relevant code in the useEffect hook in EmployeeInfoWrapper does get triggered, and hobbiesRef.current set accordingly, but it doesn't cause EmployeeInfo to rerender.
Container:
const Container = () => {
const [hobbies, setHobbies] = React.useState<string[]>();
setLabels(["Board games"]); // example of how it's set; in reality this hook is passed down and set in lower components
return (
<SpinnerWrapper hobbies=hobbies otherInfo=otherInfo />
);
};
EmployeeInfoWrapper:
export const EmployeeInfoWrapper = (props) => {
const { hobbies, otherInfo } = props;
const [indx, setIndx] = React.useState<number>(0);
// this doesn't work due to https://github.com/facebook/react/issues/14490, shouldn't matter though because it's handled in the useEffect
const hobbyRef = React.useRef(hobbies?.length ? hobbies[indx] : "");
useEffect(() => {
if (hobbies?.length > 1) { // doubt this is relevant as my bug is with 0 or 1-length hobbies prop
hobbyRef.current = hobbies[indx];
setTimeout(() => setIndx((indx + 1) % hobbies.length), 2000);
}
if (hobbies?.length == 1) {
hobbyRef.current = hobbies[indx]; // can see with debugger this line is hit
}
if (!hobbies?.length) { // covers undefined or empty cases
hobbyRef.current = "";
}
}, [hobbies]);
return (
<EmployeeInfo hobby={hobbyRef.current} otherInfo={otherInfo} />
);
};
I don't know if I am allowed to ask questions like these, but I have a dilemma where I don't know should I use useEffect in the situation I have here:
const handleUrlQuery = () => {
if (params.debouncedValue.length > 0) {
queryName = "name";
queryValue = params.debouncedValue;
return {
queryName,
queryValue,
};
} else if (params.debouncedValue === "") {
queryName = "page";
queryValue = params.page;
return {
queryName,
queryValue,
};
}
};
handleUrlQuery();
const url = `${process.env.REACT_APP_API_URL}?${queryName}=${queryValue}`;
const { data, error } = useFetch(url);
This function is used for changing the query part of the url, now it is supposed to change the queryName and queryValue based on the search value or in this case debounced search value. Now I am confused because I have a feeling that I need to use useEffect, but I am not sure, anyone has any advice on this?
If you really want to optimize this code, which unless its in a super heavy component, I don't see too much of a need you could use useMemo.
const url = useMemo(() => {
if (params.debouncedValue.length > 0) {
queryName = "name";
queryValue = params.debouncedValue;
} else if (params.debouncedValue === "") {
queryName = "page";
queryValue = params.page;
}
return `${process.env.REACT_APP_API_URL}?${queryName}=${queryValue}`;
}, [params.debouncedValue, params.page]);
// don't believe you have to add process.env.REACT_APP_API_URL as a dependency
const { data, error } = useFetch(url);
When you don't call the function handleUrlQuery inside a useEffect, it will be called on every re-render, even if params.debouncedValue didn't change.
Therefore, you need a useEffect if you have other state variables changing, and you only want to call handleUrlQuery when specifically params.debouncedValue changes.
Dummy Codesandbox example
This is probably a basic question but,
I have two useState variables:
const [varOne, setVarOne] = useState(null);
const [varTwo, setVarTwo] = useState(null);
And a third variable that tells me which variables I need to use, varOne or varTwo:
const [whichVar, setWhichVar] = useState(0);
And I have a third variable curVar which will be either varOne or varTwo based on the value of whichVar:
const [curVar, setCurVar] = useState(null);
if (whichVar === 0) {
curVar = varOne;
setCurVar = setVarOne;
} else {
curVar = varTwo;
setCurVar = setVarTwo;
}
I realize this is probably wrong, but in another post I was told I could use useReducer to achieve this, what is the most elegant way to achieve this with useReducer?
Well a simple way would be to use a custom hook that stores all the state/logic and returns the currently active value. For instance:
import React, { useState } from 'react';
const useSwitchValue = () => {
const [varOne, setVarOne] = useState('A');
const [varTwo, setVarTwo] = useState('B');
const [whichVar, setWhichVar] = useState(0);
if (whichVar === 0) return {
value: varOne,
setVarOne,
setVarTwo,
switchVars: () => setWhichVar(whichVar ? 0 : 1)
};
else return {
value: varTwo,
setVarOne,
setVarTwo,
switchVars: () => setWhichVar(whichVar ? 0 : 1)
};
};
export default function App() {
const { value, switchVars, setVarOne, setVarTwo } = useSwitchValue();
return (
<div>
<button onClick={switchVars}>
SWITCH VALUES
</button>
<div>{value}</div>
</div>
);
}
With this hook, you can change the state values and switch between them, but when you want to use the currently selected value, you just refer to {value} without having to do any conditional checking (cos that's done once, inside the custom hook).
Sandbox
Yes useReducer can help achieve similar functionality. useReducer will emit your current state and a dispatch function based on a given reducer and initial state. You then use this dispatch method to dispatch types of actions that are specified in your reducer function to manipulate your state.
Check out the first example in the React docs, they’re very helpful https://reactjs.org/docs/hooks-reference.html#usereducer
I have this setup so that the render is forces when they click by simply updating the state of a hook. Is there a nicer or cleaner way to do this.. here is some code...
const [click, setClick] = useState();
function handle1Click() {
props.UserInfoObject.setWhichPlot(1)
setClick(1000 * 60 * 5)
}
return (
<div>
<button onClick={handle1Click}>5 Minutes</button>
</div>
I came accross this which is another option but I am trying to be as optimal as possible so I am unsure which to use, or if there is another method?
handleClick = () => {
// force a re-render
this.forceUpdate();
};
I only mention this because of the warning that pops up stating this "'click' is assigned a value but never used no-unused-vars
***EDIT
adding the UserInfoObject class for reference
class UserInformation {
constructor(airValue, waterValue){
this.airValue = airValue;
this.waterValue = waterValue;
this.getCalibrationsFlag = false;
this.numberDevices = 0;
this.deviceName = 'defaultName';
this.currentlyChangingName = false;
this.whichPlot = 1;
}
setAirValue(number) {
this.airValue = number;
}
setWaterValue(number) {
this.waterValue = number;
}
setNumberDevices(int){
this.numberDevices = int;
}
setDeviceName(name){
this.deviceName = name;
}
setCurrentlyChangingName(boolean){
this.currentlyChangingName = boolean;
}
setWhichPlot(number){
this.whichPlot = number;
}
}
let UserInfoObject = new UserInformation(10000, -10);
With React, you should generally use pure, functional programming when possible. Mutating objects makes it much, much harder to do things properly.
Create state of the UserInformation instead. When it needs to be changed, instead of mutating the existing object, create a new object. The fact that this object is new will tell React that the component needs to re-render.
const [userInformation, setUserInformation] = useState({
airValue, // this should be in the outer scope
waterValue, // this should be in the outer scope
getCalibrationsFlag: false,
numberDevices: 0,
// ...
});
Do that in the parent component, then pass both userInformation and setUserInformation down as props. In the child, handle1Click can then be changed to:
const handle1Click = () => setUserInformation({
...userInformation,
whichPlot: 1,
});
Neither state nor props should ever be mutated in React.
I have a component that needs to tap into the React Router query params, and I am using the use-react-router hook package to access them.
Here is what I am wanting to do:
import React from "react;
import useReactRouter from "use-react-router";
const Foo = () => {
const { id } = useReactRouter().match.params;
return (
<Bar id={id}/>
)
}
The issue is that this throws the following error in VS Code, and at compile time:
Property 'id' does not exist on type '{}'.ts(2339)
I have found that if I refactor my code like so:
const id = match.params["id"], I do not get the error, but I feel like this is not the correct approach for some reason. If someone could point me in the right direction, I would appreciate it.
I figured it out. The solution was to include angle brackets between the hook's name and the parenthesis, like so:
const { match } = useRouter<{ id: string }>();
const { id } = useRouter<{ id: string }>();
Or if you prefer nested destructuring:
const { match: { params: id } } = useRouter<{ id: string }>();
You can try to give default value to params
const { id } = useReactRouter().match.params || {id: ""};
It may be possible that params to be null at initial level
The code is insufficient.
However, at first glance,
// right way
const { history, location, match } = useReactRouter()
// in your case
const { match: { params : { id } } } = useReactRouter()
// or
const { match } = useReactRouter()
const { id } = match.params
now, try to console the value first.
Also, please try to pass the props to a functional component from it's container, since it's more logical.
From your comment below, i can only assume you solved it. Also, it's recommended to handle possible undefined values when you use it.
{ id && id }
However, the first step should've been consoling whether it has value in it,
console.log('value xyz', useReactRouter())