One of the many challenges when trying to understand design patterns is making sense of when or where they could be useful.
Most design pattern examples are written in classes. Now, this isn't that hard to understand if you're programming in a OOP based programming language like C++, Java, etc.
But what if were writing Javascript ? More specifically, what if your writing React components ?
Although React has been following a class-based component architecture, it's slowly becoming a thing in the past ever since the release of React 16.8, when functional components became the new way to write components.
So now, we mostly follow a functional programming approach for writing components.
So how could we implement a design pattern in React ? more specifically how can we implement a Decorator Design Pattern ?
Lets see how we could achieve this.
Problem statement.
Suppose we have a confirm password functionality for a register page.
Register Page
// this useEffect compares the values of password and confirm password.
useEffect(() => {
dispatch(SET_REGISTER_CONFIRM_PASSWORD());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formState.password, formState.confirmRegisterPassword]);
// _The rest of the code below is irrelevant for this example.
const hasError = (field: keyof ErrorState) => (errorState?.[field]?.error ? true : false);
const updatedChangePasswordMap = renderConfirmPassword
? registerFieldMap.concat(renderConfirmRegisterPasswordField)
: registerFieldMap;
return (
<Col lg={12}>
<Typography className='text-primary font-primary font-bold mb-3' variant='h2'>
Register
</Typography>
.......
But we also have to do the same for first time googleOauth users.
So the naive / lazy approach would be to duplicate the useEffect like the following..
GoogleRegister.tsx
// duplicated again
useEffect(() => {
dispatch(SET_REGISTER_CONFIRM_PASSWORD());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formState.password, formState.confirmRegisterPassword]);
useEffect(() => {
hasBeenValidated(requiredFields);
}, [requiredFields]);
useEffect(() => {
if (googleId) {
dispatch(SET_INITAL_FORM_AUTH_STATE({ googleId: googleId, email: googleEmail }));
}
checkGoogleAuthenticated();
}, []);
const updatedChangePasswordMap =
formState.password.length >= 6 ? registerFieldMap.concat(renderConfirmRegisterPasswordField) : registerFieldMap;
return (
<Col lg={12}>
<Typography className='text-primary font-primary font-bold mb-3' variant='h4'>
Just need some more information.
</Typography>
...
How can we keep the functionality centralized ? Write a hook right ? sure we can do that, but why don't we apply the decorator design pattern, so we can just wrap the functionality on components who need to use it.
note: side note, you should probably keep this in mind, when writing custom hooks. Custom hooks are useful, but just have to be mindful of the performance factors.
Heres how we can do it...
import { ComponentType, useEffect } from 'react';
import { SET_REGISTER_CONFIRM_PASSWORD, formSelector } from '../redux/store/formSlice';
import { useDispatch, useSelector } from 'react-redux';
/**
*
* @param Component @type { ComponentType}
* @returns
*
* Reusable HOC that confirms password for first time registering users.
*/
export function confirmRegisterPassHoc(Component: ComponentType) {
// we can be more explicit with the props, but for e.g
return (props: any) => {
const dispatch = useDispatch() as any;
const formState = useSelector(formSelector)?.formState;
useEffect(() => {
dispatch(SET_REGISTER_CONFIRM_PASSWORD());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formState.password, formState.confirmRegisterPassword]);
return <Component {...props} />;
};
}
And we can just wrap it around like this.
So essentially, a higher order component is a decorator design pattern.
Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.
The Decorator lets you structure your business logic into layers, create a decorator for each layer and compose objects with various combinations of this logic at runtime. The client code can treat all these objects in the same way, since they all follow a common interface.
source: https://refactoring.guru/design-patterns/decorator
In our example, we didn't need to change anything on the component itself, but just attach new behavior on them without needing to modify the component in any significant way.
Hopefully, you found this use case helpful, cheers 🥳.
Top comments (0)