React ternary operation for useState() - reactjs

I am new to React. I have the following setup to initialise my component's state based on a condition, using a ternary operation.
const defaultValue = ['bar']
const myComponent = ({data}) => {
const foo = (data) => {
// ...
}
const [values, setValues] = Object.keys(data).length > 0 ? useState(foo(data)): useState(defaultValue);
return (<Comp state={values} />);
}
export default myComponent
The idea is that the initial state will be dependent on the variable 'data' if it is not empty, otherwise it will be assigned a default value. It is now assumed that 'data' is not empty.
I have it that foo() is being called when I expect it, and the argument, data, is also as I expect. If I log the array that foo returns, it is exactly as I want. Unfortunately, when I log 'state' from inside the Comp component, it is the defaultValue.
If I add a console.log("renders") towards the top of myComponent, I get that printout twice, which I suspect is part of the problem?
If this setup is unconventional please let me know and I'll refactor, but if there is a simple work around that somebody is aware of, please let me know!

It's difficult to give better advice without seeing a bit more code. However, you can use a functional value to initialize the state:
const defaultValue = ['bar'];
const MyComponent = ({data}) => {
const foo = (data) => {
// ...
};
const [values, setValues] = useState(() => Object.keys(data).length > 0 ? foo(data) : defaultValue);
return (<Comp state={values} />);
}
Or just modify the foo closure to fulfill that purpose:
const defaultValue = ['bar'];
const MyComponent = ({data}) => {
const foo = () => {
if (Object.keys(data).length === 0) return defaultValue;
// ...
};
const [values, setValues] = useState(foo);
return (<Comp state={values} />);
}
Using this form has the additional benefit of preventing foo from being invoked needlessly on every render of your component.

The code has a problem because useState is used inside a ternary condition
Object.keys(data).length > 0 ? useState(foo(data)): useState(defaultValue);
We shouldn't useState hook inside any condition check because it'll affect the component rendering as seen in the doc.
To solve the problem
const initialValue = Object.keys(data).length > 0 ? foo(data) : defaultValue;
const [values, setValues] = useState(initialValue);
Ref:
https://beta.reactjs.org/learn/state-a-components-memory#meet-your-first-hook

Related

custom hook create infinite loop

This hook create an inifite loop. I don't understnd why, since my dependencies array is set.
Error : Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
Custom hook :
export const useListGuessers = () => {
const [list, setList] = useState([]);
const hasMarketing = UserHelper.hasAuthorization(AUTHORIZATION_MARKETING);
const hasTechnical = UserHelper.hasAuthorization(AUTHORIZATION_TECHNICAL);
const dashboardGroups = new DashboardGroups({hasMarketing, hasTechnical});
const guessers = [
...dashboardGroups.appProductGroup(),
...dashboardGroups.articlesGroup(),
...dashboardGroups.mediasGroup(),
...dashboardGroups.productsGroup(),
...dashboardGroups.orderableProductsGroup(),
...dashboardGroups.typesGroup(),
...dashboardGroups.usersGroup(),
...dashboardGroups.othersGroup(),
...dashboardGroups.userManagementGroup(),
];
const filteredGuesser = guessers
.filter(({canShow}) => canShow)
.map((guesser: any) => {
return {
label: guesser.label ?? guesser.value.options.label,
link: (guesser.operation && `user-management/${guesser.operation}`) ?? guesser.value.name,
};
})
.sort((a, b) => a.label.localeCompare(b.label));
useEffect(() => {
filteredGuesser && setList(filteredGuesser);
}, [filteredGuesser]);
return list;
};
The class :
export class DashboardGroups {
authorizations: {hasMarketing: boolean; hasTechnical: boolean};
constructor(authorizations: {hasMarketing: boolean; hasTechnical: boolean}) {
this.authorizations = authorizations;
}
// [all groups comes here...]
getGroups = () => {
// return an object for each groups with labels, and the group as "children"
};
}
Since filteredGuesser calculates on each re-render, which triggers useEffect(..., [filterGuesser] which causes re-render... so it loops.
The easiest straighforward solution is to ensure reference equality for filteredGuesser with useMemo. Then it will be referentially the same until guessers is changed:
const filteredGuesser = useMemo(() =>
guessers
.filter(({canShow}) => canShow)
.map((guesser: any) => {
label: guesser.label ?? guesser.value.options.label,
link: (guesser.operation && `user-management/${guesser.operation}`) ?? guesser.value.name,
})
.sort((a, b) => a.label.localeCompare(b.label))
, [guessers]);
However, I think the better solution would be reconsider need in
useEffect(() =>
...
setList(filteredGuesser)
This storing ready for use calculation into state does not seem reasonable to me. I think you better use filteredGuesser directly, instead of storing it into list state
Beta docs for useMemo
Referential equality aka strict equality on MDN

How to use useReducer to determine which useState variable to use?

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

useState() bug - state value different from initial value

I have a component that uses useState() to handle the state of its floating label, like this:
const FloatingLabelInput = props => {
const {
value = ''
} = props
const [floatingLabel, toggleFloatingLabel] = useState(value !== '')
I have a series of those components and you'd expect initialFloatingLabel and floatingLabel to always be the same, but they're not for some of them! I can see by logging the values:
const initialFloatingLabel = value !== ''
console.log(initialFloatingLabel) // false
const [floatingLabel, toggleFloatingLabel] = useState(initialFloatingLabel)
console.log(floatingLabel) // true???
And it's a consistent result. How is that possible?
How come value can be different from initialValue in the following example? Is it a sort of race condition?
const [value, setValue] = useState(initialValue)
More details here
UPDATE
This (as suggested) fixes the problem:
useEffect(() => setFloatingLabel(initialFloatingLabel), [initialFloatingLabel])
...but it creates another one: if I focus on a field, type something and then delete it until the value is an empty string, it will "unfloat" the label, like this (the label should be floating):
I didn't intend to update the floatingLabel state according to the input value at all times; the value of initialFloatingLabel was only meant to dictate the initial value of the toggle, and I'd toggle it on handleBlur and handleChange events, like this:
const handleFocus = e => {
toggleFloatingLabel(true)
}
const handleBlur = e => {
if (value === '') {
toggleFloatingLabel(false)
}
}
Is this approach wrong?
UPDATE
I keep finding new solutions to this but there's always a persisting problem and I'd say it's an issue with Formik - it seems to initially render all my input component from its render props function before the values are entirely computed from Formik's initialValues.
For example:
I added another local state which I update on the handleFocus and handleBlur:
const [isFocused, setFocused] = useState(false)
so I can then do this to prevent unfloating the label when the input is empty but focused:
useEffect(() => {
const shouldFloat = value !== '' && !isFocused
setFloatLabel(shouldFloat)
}, [value])
I'd still do this to prevent pre-populated fields from having an animation on the label from non-floating to floating (I'm using react-spring for that):
const [floatLabel, setFloatLabel] = useState(value !== '')
But I'd still get an animation on the label (from "floating" to "non-floating") on those specific fields I pointed out in the beginning of this thread, which aren't pre-populated.
Following the suggestion from the comments, I ditched the floatingLabel local state entirely and just kept the isFocused local state. That's great, I don't really need that, and I can only have this for the label animation:
const animatedProps = useSpring({
transform: isFocused || value !== '' ? 'translate3d(0,-13px,0) scale(0.66)' : 'translate3d(0,0px,0) scale(1)',
config: {
tension: 350,
},
})
The code looks cleaner now but I still have the an animation on the label when there shouldn't be (for those same specific values I mentioned at the start), because value !== '' equals to true for some obscure reason at a first render and then to false again.
Am I doing something wrong with Formik when setting the initial values for the fields?
You have the use useEffect to update your state when initialFloatingLabel change.
const initialFloatingLabel = value !== ''
const [floatingLabel, setFloatingLabel] = useState(initialFloatingLabel)
// calling the callback when initialFloatingLabel change
useEffect(() => setFloatingLabel(initialFloatingLabel), [initialFloatingLabel])
...
Your problem look like prop drilling issue. Perhaps you should store floatingLabel in a context.
// floatingLabelContext.js
import { createContext } from 'react'
export default createContext({})
// top three component
...
import { Provider as FloatingLabelProvider } from '../foo/bar/floatingLabelContext'
const Container = () => {
const [floatingLabel, setFloatingLabel] = useState(false)
return (
<FloatingLabelProvider value={{ setFloatingLabel, floatingLabel }}>
<SomeChild />
</FloatingLabel>
)
}
// FloatingLabelInput.js
import FloatingLabelContext from '../foo/bar/floatingLabelContext'
const FloatingLabelInput = () => {
const { setFloatingLabel, floatingLabel } = useContext(FloatingLabelContext)
...
}
This way you just have to use the context to change or read the floatingLabel value where you want in your components three.

What is the correct way to use react hook useState update function?

Considering the following declaration:
const [stateObject, setObjectState] = useState({
firstKey: '',
secondKey: '',
});
Are the following snippets both corrects ?
A)
setObjectState((prevState) => ({
...prevState,
secondKey: 'value',
}));
B)
setObjectState({
...stateObject,
secondKey: 'value',
}));
I am sure that A) is correct, but is it necessary ? B) seems ok, but as setObjectState is an asynchronous function, stateObject might not have the most recent value.
One useful thing about case of A that I have found is that you can use this method to update state from child components while only passing down a single prop for setObjectState. For example, say you have parent component with state you would like to update from the child component.
Parent Component:
import React, {useState} from 'react';
import ChildComponent from './ChildComponent';
export const ParentComponent = () => {
const [parentState, setParentState] = useState({
otherValue: null,
pressed: false,
});
return (
<ChildComponent setParentState={setParentState} />
)
}
Child Component:
import React from 'react';
export const ChildComponent = (props) => {
const callback = () => {
props.setParentState((prevState) => ({
...prevState
pressed: true,
}))
}
return (
<button onClick={callback}>test button<button>
)
}
When the button is pressed, you should expect to see that the state has been updated while also keeping its initial values. As for the difference between the two, there isn't much as they both accomplish the same thing.
A will always give you the updated value. B could be correct but might not. Let me give an example:
const Example = props => {
const [counter, setCounter] = useState(0);
useEffect(() => {
// 0 + 1
// In this first case the passed value would be the same as using the callback.
// This is because in this cycle nothing has updated counter before this point.
setCounter(counter + 1);
// 1 + 1
// Thanks to the callback we can get the current value
// which after the previous iexample is 1.
setCounter(latest_value => latest_value + 1);
// 0 + 1
// In this case the value will be undesired as it is using the initial
// counter value which was 0.
setCounter(counter + 1);
}, []);
return null;
};
When the new value depends on the updated one use the callback, otherwise you can simply pass the new value.
const Example = props => {
const [hero, setHero] = useState('Spiderman');
useEffect(() => {
// Fine to set the value directly as
// the new value does not depend on the previous one.
setHero('Batman');
// Using the callback here is not necessary.
setHero(previous_hero => 'Superman');
}, []);
return null;
};
Also in the example you are giving it would probably be better to use two different states:
const [firstKey, setFirstKey] = useState("");
const [secondKey, setSecondKey] = useState("");

Should I use useMemo in hooks?

I created useBanner hooks
const useBanner = (array, yardage) => {
const [bannArr, setBannArr] = useState(array.slice(0, yardage));
const [bannListIndex, setBannIndex] = useState(1);
return {
....
};
};
Am I doing the right thing and the props throw in useState.
It’s permissible to use useBanner.
const Banner= ({
array,
yardage
}) => {
const { bannForth, bannBeck, bannArr } = useBanner(array, yardage);
return (
...
);
};
when props will change here.
Will change the state in useBanner.
or is it considered anti-patterns I have to write all this in useMemo
const useBanner = (array, yardage) => {
const [bannArr, setBannArr] = useState([]);
const [bannListIndex, setBannIndex] = useState(1);
useMemo(() => {
setBannArr(array.slice(0, yardage));
setBannIndex(1);
}, [array, yardage]);
return {
....
};
};
Yes, custom hooks are possible in React. Here is separate document discussing custom hooks.
But exactly you sample may require additional code depending on what is your final goal.
If you want initialize state only once, when component Banner is first created, you can just do as in your first sample
const Banner= ({
array,
yardage
}) => {
const { bannForth, bannBeck, bannArr } = useBanner(array, yardage);
return (
...
);
};
This will work perfectly. But if props array and yardage will change, this will not be reflected in component. So props will be used only once as initial values and then will not be used in useBanner even if changed (And it doesn't matter whether you'll use useBanner or useState directly). This answer highlight this.
If you want to update inital values on each props change, you can go with useEffect like below
const Banner= ({
array,
yardage
}) => {
const { bannForth, bannBeck, bannArr, setBannArr } = useBanner(array, yardage);
useEffect (() => {
// setBannArr should also be returned from useBanner. Or bannArr should be changed with any other suitable function returned from useBanner.
setBannArr(array.slice(0, yardage));
}, [array, yardage, setBannArr])
return (
...
);
};
In this case Banner component can control state itself and when parent component change props, state in Banner component will be reset to new props.
Here is small sample to showcase second option.

Resources