I have a reducer with a nextAction state that works as sort of a callback. The NextAction is typed like so:
type NextActionSave = {type: 'save'};
type NextActionSaveAndSet = {type: 'saveAndSet', name: TextFieldName, value: string};
type NextAction = NextActionSave | NextActionSaveAndSet;
interface EditRowState {
...
nextAction: NextAction | null;
}
In another file, I have a switch statement on state.nextAction.type
import React from 'react';
import {EditRowState, EditRowAction, NextAction} from './reducer';
interface Props {
save: () => Promise<void>;
state: EditRowState;
dispatch: React.Dispatch<EditRowAction>;
}
const useNextAction = (props: Props) => {
React.useEffect(() => {
executeNextAction(props.state.nextAction);
}, [props.state.nextAction]);
const executeNextAction = (nextAction: NextAction | null) => {
if (nextAction === null) return;
switch(nextAction.type) {
case 'save':
props.save();
break;
case 'saveAndSet':
props.save().then(() => {
const {name, value} = nextAction;
props.dispatch({type: 'set', name, value, advance: true});
});
default: return;
}
};
};
This works. Here is a TypeScript Playground of the executeNextAction showing the executeNextAction function.
If I move the body of executeNextAction inside the useEffect, so that nextAction comes directly from the state, it says, "Property 'name' does not exist on type 'NextAction'". I get the same error for the value property. My code without a separate executeNextAction function is below. Here is a simplified version of my code below in a TypeScript Playground showing the error.
import React from 'react';
import {EditRowState, EditRowAction} from './reducer';
interface Props {
save: () => Promise<void>;
state: EditRowState;
dispatch: React.Dispatch<EditRowAction>;
}
const useNextAction = (props: Props) => {
React.useEffect(() => {
if (props.state.nextAction === null) return;
switch(props.state.nextAction.type) {
case 'save':
props.save();
break;
case 'saveAndSet':
props.save().then(() => {
const {name, value} = props.state.nextAction;
props.dispatch({type: 'set', name, value, advance: true});
});
default: return;
}
}, [props.state.nextAction]);
};
It works if I typecast props.state.nextAction using as NextActionSaveAndSet
. It looks like the switch statement acts as a literal type guard, so I shouldn't need the type cast. I'm guessing this is why it works in the version where I moved the code into a executeNextAction function. What's the difference between using nextAction as a function parameter or accessing it on the state? Would I be able to use it on the state if I added an as const
somewhere?
I am not able to properly explain why this works, but I have two different solutions which don't involve assertion.
this.props.nextAction
to a variable.const useNextAction = (props: Props) => {
React.useEffect(() => {
const {nextAction} = props.state;
if (nextAction === null) return;
switch(nextAction.type) {
case 'save':
props.save();
break;
case 'saveAndSet':
props.save().then(() => {
const {name, value} = nextAction;
props.dispatch({type: 'set', name, value, advance: true});
});
default: return;
}
}, [props.state.nextAction]);
};
name
and value
outside of the callbackconst useNextAction = (props: Props) => {
React.useEffect(() => {
if (props.state.nextAction === null) return;
switch(props.state.nextAction.type) {
case 'save':
props.save();
break;
case 'saveAndSet':
const {name, value} = props.state.nextAction;
props.save().then(() => {
props.dispatch({type: 'set', name, value, advance: true});
});
default: return;
}
}, [props.state.nextAction]);
};