optional question mark and object destruction - reactjs

const { isSaved } = props?.options;
does this code make sense? what's the purpose of using ? here?
is it better if I do
const { isSaved = null } = props.options;

The first code does not make sense. With optional chaining, when any part of the chain fails, the whole expression will evaluate to undefined. But you can't access properties of undefined, so if the chain fails, the engine will throw:
const props = undefined;
const { isSaved } = props?.options;
But in React, props, if you're referring to what's normally referred to as props, will always be truthy. At worst, it'll be an empty object.
Your second code makes more sense, but it will still throw if options happens to not be a prop:
const props = {};
const { isSaved = null } = props.options;
Alternate with the empty object instead:
const props = {};
const { isSaved = null } = props.options || {};
console.log(isSaved);

Related

Destructure using rest parameters

I am trying to destructure a React context based on rest parameters passed to the hook.
Say I am passing an array of enums and want to return only the ones that are passed into the hook.
Here's my interface context type
enum ConfigItem {
SomeItem = 'SomeItem',
AnotherItem = 'AnotherItem',
}
type Config = Record<ConfigItem, boolean>;
type ConfigState = {
config: Config;
}
and the hook itself
const useConfig = (...configArgs: ConfigItem) => {
const configContext = useContext(ConfigContext);
const { config } = configContext;
const { ...configArgs } = config; // Duplicate identifier 'configArgs'.
return configArgs;
}
I would like to use it like so
const config = useConfig(ConfigItem.SomeItem, ConfigItem.AnotherItem);
and that would return an object with relevant properties. I might want to pass a single arg but potentially many.
The above const would return this (true/false is whatever would be in the context but that is not in scope of the question)
{
SomeItem: true/false,
AnotherItem: true/false,
}
but if I would only pass one of them I expect to see a single property.
By their nature, rest parameters are arrays, so you need an array type:
const useConfig = (...configArgs: ConfigItem[]) => {
// ^^−−−−−−−−−−−−−−−−−−−−−−−−−
// ...
If you pass just one argument, the array will have one element, but it will still be an array.
In a comment you've said:
My problem is how to return only the provided args properties but thanks for the correction.
If you mean you're having trouble creating the object you want to return, you can do that in a few ways; I'd probably lean toward Object.fromEntries (you'll need to polyfill if targeting pre-ES2019 environments):
const useConfig = (...configArgs: ConfigItem[]) => {
const configContext = useContext(ConfigContext);
const { config } = configContext;
return Object.fromEntries(
configArgs.map(itemKey => [itemKey, config[itemKey]])
) as Record<ConfigItem, boolean>;
};
You may need a type guard to avoid

Should I use useEffect in this situation?

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

Can I use ReactElement as useState argument?

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

react.js useState hook is assigned a value but not used, but that is ok

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.

Ensure that nested fields where the path is defined by a variable trigger React.useMemo update when added to the dependency array

I use TypeScript 4 and React 17
I had this code in a custom hook:
const myValue = useMemo((): string[] => {
if (someCondition) {
return (myMap?.field && myMap.field.[subfieldId]) || null
} else {
...
}
}, [myMap.field.[subfieldId]])
ESlint complained that myMap.field.[subfieldId] had to be moved in a variable to be checked progammatically.
So I changed to this:
const currentData: string[] | null = (myMap?.field && myMap.field.[subfieldId]) || null
const myValue = useMemo((): string[] => {
if (someCondition) {
return currentData
} else {
...
}
}, [currentData])
I'm not sure that letting the variable in the upper scope, being recalculated at each rendering is a good practice, I'm even worried that an infinite loop could be created.
ESLInt suggest me to to this instead, that is much more simple:
const myValue = useMemo((): string[] => {
if (someCondition) {
return (myMap?.field && myMap.field.[subfieldId]) || null
} else {
...
}
}, [myMap.field])
Will myMap.field be enought for React to find out that myMap.field[subfieldId] change? I guess it shall be. Indeed, React will not know what changed if I only put myMap, because the object reference itself does not change, but when comparing the previous and the new myMap.field, React check all the sub-fields. Does it?
According the react hook linter:
React Hook React.useMemo has an unnecessary dependency: 'myMap'. Either exclude it or remove the dependency array. Outer scope values like 'myMap' aren't valid dependencies because mutating them doesn't re-render the component. (react-hooks/exhaustive-deps)
So you should be fine with this:
const myValue = useMemo((): string[] => {
if (someCondition) {
return myMap?.field[subfieldId] || null
} else {
// ...
}
}, [subfieldId]);

Resources