Mediator Pattern
— OOP, Design Patterns, JavaScript, TypeScript, React
Introduction
Mediator is a behavioral design pattern, which defines how a set of objects interact.
This pattern allows keeping loose relations between objects by managing the communication between them in one object instead of keeping explicit relations.
The benefit of this pattern is reusable, maintainable and testable code. This pattern reflects the SOLID rule.
The pattern assumptions:
- Define the mediator object which encapsulates the interaction between a set of objects.
- The objects delegates their interaction to the mediator object instead of interacting with each other directly.
Mediator is one of the most popular pattern using in the modern web application architecture. The components tree itself implements a mediator pattern.
To present this design pattern, let's consider the following use case.
Use Case
We would like to add a feature to the React App that allows to upload photos and preview them.
It doesn't sound good to keep a logic being responsible for uploading the photos and previewing them in one place. In this case, our component would have too much responsibility. Additionally, our component wouldn't be reusable and would be hard to test.
At this moment we know that the logic of this component should be separated. We have to also think about how to handle communication between these two components.
If we put the Preview component inside the Upload component it will cause both components coupled closely.
To solve this problem we can use the Mediator pattern. We provide one more component which will be the broker between these two components and it will be responsible for handling communication between them.
Components Structure:
1└── Upload.tsx2 ├── UploadInput.tsx3 └── UploadPreview.tsx
Example
You can find the full example here
1import React, { FC, useState } from 'react'2import { UploadInput } from './UploadInput'3import { UploadPreview } from './UploadPreview'45interface UploadState {6 errors: string[],7 files: string[],8 isLoading: boolean9}1011export const Upload: FC = () => {12 const [uploadState, setUploadState] = useState<UploadState>({ 13 errors: [], 14 files: [],15 isLoading: false 16 })17 18 const onUploadStart = () => {19 setUploadState({ ...uploadState, isLoading: true })20 }2122 const onUploadDone = (errors: string[], files: string[]) => {23 setUploadState({ 24 errors, 25 files,26 isLoading: false 27 })28 }2930 return (31 <React.Fragment>32 <UploadInput 33 onUploadStart={onUploadStart} 34 onUploadDone={onUploadDone} 35 />36 <UploadPreview 37 isLoading={uploadState.isLoading} 38 files={uploadState.files} 39 errors={uploadState.errors}40 />41 </React.Fragment>42 )43}
1import React, { FC, ChangeEvent } from 'react'2import { readFile } from '../../utils'34export const UploadInput: FC<any> = ({ onUploadStart, onUploadDone }) => {5 const onChange = async (event: ChangeEvent<HTMLInputElement>) => {6 if (!event.target.files?.length) return7 await readFile(8 event.target.files,9 onUploadStart,10 onUploadDone 11 )12 }13 14 return (15 <div className="custom-file">16 <label htmlFor="file-upload" className="custom-file-label">Select files</label>17 <input 18 id="file-upload" 19 className="custom-file-input"20 name="file-upload" 21 type="file" 22 accept="image/*" 23 multiple24 onChange={onChange}25 >26 </input>27 </div>28 )29}
1import React, { FC } from 'react'23interface UploadPreviewProps {4 files: string[],5 errors: string[],6 isLoading: boolean7}89export const UploadPreview: FC<UploadPreviewProps> = ({ isLoading, files, errors }) => {10 if (isLoading) {11 return (12 <div className="spinner-border mt-5" role="status">13 <span className="sr-only">Loading...</span>14 </div> 15 )16 }17 18 return (19 <div className="container mt-3">20 <div className="row">21 <div className="col-12 p-0">22 <p>Preview</p>23 </div>24 {files.map((file, index) => {25 return <img className="img-fluid rounded" src={file} key={index} alt="..." />26 })}27 {errors.map((error, index) => {28 return (29 <div className="alert alert-primary" role="alert" key={index}>30 {error}31 </div>32 )33 })} 34 </div>35 </div>36 )37}