Today I extracted those error handling functionalities into a custom hook that can be used across different other hooks and components without re-writing the core logic behind it.
So here are our custom hooks:
function useControlledSwitchWarning(
controlPropValue,
controlPropName,
componentName,
) {
const isControlled = controlPropValue != null
const {current: wasControlled} = React.useRef(isControlled)
React.useEffect(() => {
if(isControlled && !wasControlled){
console.error(`\`useToggle\` is changing from uncontrolled to be controlled. Components should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled \`useToggle\` for the lifetime of the component. Check the \`on\` prop.`)
}
if(!isControlled && wasControlled){
console.error(`\`useToggle\` is changing from controlled to be uncontrolled. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled \`useToggle\` for the lifetime of the component. Check the \`on\` prop.`)
}
}, [componentName, controlPropName, isControlled, wasControlled])
}
function useOnChangeReadOnlyWarning(
controlPropValue,
controlPropName,
componentName,
hasOnChange,
readOnly,
readOnlyProp,
initialValueProp,
onChangeProp,
) {
const isControlled = controlPropValue != null
React.useEffect(() => {
if(!hasOnChange && isControlled && !readOnly){
console.error(`An \`on\` prop was provided to useToggle without an \`onChange\` handler. This will render a read-only toggle. If you want it to be mutable, use \`initialOn\`. Otherwise, set either \`onChange\` or \`readOnly\`.`);
}
}, [
componentName,
controlPropName,
isControlled,
hasOnChange,
readOnly,
onChangeProp,
initialValueProp,
readOnlyProp,
])
}
Now, we can utilize these hooks just by passing arguments to them. One thing to note here is that we don’t need these warnings when our app is in production mode, these warnings are just there to alert the developer using them in order to avoid any anti-pattern and misuse of this hook.
Since we extracted our core logic into custom hooks, this gives us the opportunity to implement Dead Code Elimination.
Since there is no significant value in displaying an error for misuse of our hook to the end-user, we can simply avoid these checks, and by avoiding these checks(using dead code elimination) we can leverage the following benefits:
- It shrinks program size
- It eliminates unnecessary computation
- End User will not be able to see these warnings, which is often the intended behavior. (because these errors are only meant for developers utilizing these hooks in their components)
Take a look at the following implementation to see the above in action:
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
useControlledSwitchWarning(controlledOn, 'on', 'useToggle')
// eslint-disable-next-line react-hooks/rules-of-hooks
useOnChangeReadOnlyWarning(
controlledOn,
'on',
'useToggle',
Boolean(onChange),
readOnly,
'readOnly',
'initialOn',
'onChange',
)
}