The goal – I want to set some local form state based on interaction with three checkboxes (see below).
The rule – If two options are selected then the channel state should be set to 'multi'
if one option is selected then the channel state should be the value of the checkbox that is selected (eg eml
, push
, inapp
).
The problem: I am able to successfully update the channel state in my local form state. But, when I lift the state up via props.onGenerateResult(data)
, I find that the channel state in the data object is an empty string.
Heres what I have so far…
State to check status of checkboxes – Initially they are all set to false
const [channel, setChannel] = useState(''); const [channelOptions, setChannelOptions] = useState({ eml: false, push: false, inapp: false });
Handler that updates channelOptions state using computed property values
const channelSelectionChangeHandler = (e) => { setChannelOptions((prevState) => { return { ...prevState, [e.target.value]: e.target.checked }; }); };
JSX pointing to channelSelectionChangeHandler()
to run
<Checkbox value="eml" onChange={channelSelectionChangeHandler}>Email</Checkbox> <Checkbox value="push" onChange={channelSelectionChangeHandler}>Push</Checkbox> <Checkbox value="inapp" onChange={channelSelectionChangeHandler}>Inapp</Checkbox>
Submit handler that fires on form submission – Lifts all my form state up (This includes channel) and where I handle conditional checking to set the channel state.
const onSubmitFormHandler = (e) => { e.preventDefault(); // set channel state here for (const props in channelOptions) { if (channelOptions[props] === true) { selectedChannels.push(props); } } if (selectedChannels.legnth === 1) { setChannel(selectedChannels[0]); } else { setChannel('multi'); } const data = { name, date, channel, content, target, type, frequency, market, city,zone }; props.onGenerateResult(data); };
Why is this? How should I best approach this so that my channel state also gets lifted up? This could be simple if I used select with multiple option but I prefer to use input type=checkboxes. Additionally, I’m thinking of moving the for in loop and if check into my channelSelectionChangeHandler()
. Just keep the submission handler lean.
Thanks in advance and hope this all made sense.
Advertisement
Answer
The comment from @chris-g is correct. Even though your example is using the useState
hook, the behavior is similar to setState
: The value of channel
doesn’t change immediately after you call setChannel
. Instead, react will call your component again and the return value of useState
will contain the updated value. But this is to late for your callback.
To fix your issue, I would recommend two changes:
- Since
channel
is only computed based onchannelOptions
, useuseMemo
forchannel
instead ofuseState
, :
const [channelOptions, setChannelOptions] = useState({ eml: false, push: false, inapp: false }); const channel = useMemo(() => { // Compute the value of channel based on the current value of channelOptions return Object.keys(channelOptions).reduce((channel = '', key) => { if (channelOptions[key] === true) { return channel === '' ? key : 'multi' } return channel; }, ''); // add channelOptions as dependency so this value gets recomputed when they change }, [channelOptions])
- Use
useCallback
foronSubmitFormHandler
and use the newchannel
value returned byuseMemo
:
const onSubmitFormHandler = useCallback((e) => { e.preventDefault(); const data = { name, date, channel, content, target, type, frequency, market, city,zone }; props.onGenerateResult(data); }, [channel, name, date, content, target, type, frequency, market, city, zone, props.onGenerateResult]);