Reputation: 3949
To prevent passing callbacks to child components I am using useReducer
instead. This avoids the issue of child components re-rendering on each parent render but the downside seems to be a tight coupling between parent and child. By tight coupling, I mean that the child needs to be explicitly aware of the shape of the action expected by the reducer which is defined by the parent.
Imagine, for example, a date picker component. At some point this component needs to pass the new date/time value to the calling component so that the data can be saved (or somehow used). With callbacks, we can have a simple prop like saveDate={onSaveDate}
. The date picker defines the contract by saying "I expect these props". Specifically, I expect a saveDate
prop with signature of newDate => {}
. This flow makes sense to me.
With useReducer
, the parent passes dispatch
to the date picker, and the date picker needs to be aware of how to create an action that matches what the reducer expects. This can be solved by defining action creators in a module somewhere and importing them into the date picker but this feels backwards to me. If the date picker will be called from various components in the application, all the components will need to agree on the this interface - the shape of the action. This seems to couple not only one component with the date picker, but all components that use the date picker with each other.
So, what am I overlooking and what strategy exists to deal with this? For what it's worth, I went back to using callbacks where cleaner code makes more sense than the performance concern of re-rendering.
Upvotes: 2
Views: 576
Reputation: 774
I would suggest currying dispatch with the action type in the parent component, like so:
const Parent = () => {
const [state, dispatch] = useReducer(datepickerReducer, initialState)
// might wanna useCallback here if your DatePicker is pure
const changeDate = newDate => dispatch({ type: 'CHANGE_DATE', newDate })
return <DatePicker onChange={changedate} value={state} />
}
This way your component stays isolated from the rest and you can use it as you did before hooks. Although, if you are using the datepickerReducer often, it will be annoying to re-define changeDate every time, so I would have a custom hook do it:
const useDatepicker = init => {
const [date, dispatch] = useReducer(datepickerReducer)
const changeDate = useCallback(newDate => dispatch({ type: 'CHANGE_DATE', newDate }), [])
// I prefer using an object, makes it more convenient to reach values that would have been at the end of the array
return { date, changeDate, /* You could have resetDate here aswell */ }
}
// USAGE
const { date, changeDate } = useDatepicker(new Date())
Upvotes: 1