React design patterns offer two significant benefits to software developers. Firstly, they provide a handy approach to tackling software development issues with proven solutions. Secondly, they facilitate the development of highly cohesive modules with minimal coupling. This article delves into the most important React-specific design patterns and best practices, and explores the applicability of general design patterns for various React use cases.
>> Read more:
- Top 6 Best React Component Libraries for Your Projects
- Top 10 Best Open Source React & React Native IDEs
- Tailwind CSS for React UI Components: Practical Code Examples
- Top 9 Best React Animation Libraries for Web Developers
4 Key React Design Patterns
While general design patterns can be applied in React, React developers stand to benefit most from React-specific design patterns. Let’s take a closer look at 4 fundamentals: higher-order components, providers, compound components, and hooks.
Higher-order Components (HOC)
Higher-order components (HOC) offer reusable logic to components via props. A HOC is utilized when we require the functionality of an existing component but with a new user interface.
A component is merged with a HOC to achieve the desired outcome: a component that possesses more functionality than the original component.
In terms of code, a component is encapsulated within a HOC, which then returns our desired component:
// React Welcome HOC.
const Welcome = ({ name, ...remainingProps }) => <div {...remainingProps}>Hello {name}!</div>;
const welcomeWithName = (PrimaryComponent) => (props) => (
<PrimaryComponent {...props} name='Recruitment Specialist' />
);
const Advanced = welcomeWithName(Welcome)
Provider Design Pattern
The Provider Design Pattern is a powerful tool in React that helps us avoid the issue of prop drilling, or the process of passing props down to nested components in a tree. This pattern can be implemented using React Context API:
import React, { createContext, useContext } from 'react';
// Creating a context for Magazine
export const MagazineContext = createContext();
// Main function
export default function MainApp() {
return (
// Providing the context value
<MagazineContext.Provider value="french-article">
<Magazine />
</MagazineContext.Provider>
)
}
// Function to display the Magazine
function Magazine() {
const magazineValue = useContext(MagazineContext);
return <h1>{magazineValue}</h1>;
}
This code snippet illustrates the provider pattern, showing how we can pass props directly to a new object using context. The context includes both a state provider and a state consumer; in this case, our provider is an app component and our consumer is a book component that uses BookContext. Here’s a visual representation of this concept:
The use of the provider design pattern is implied when props are directly passed from component A to component D. In the absence of this pattern, prop drilling would take place, with components B and C serving as intermediaries.
Compound Components
Compound components are a set of interconnected parts that work in harmony. A simple illustration of this design pattern is a card component with its various elements
The card component consists of its image, actions, and content, which together provide its functionality:
import React from 'react';
// Define a reusable Card component
const Panel = ({ content }) => {
return <div className="panel-container">{content}</div>;
};
// Define a CardImage component to display images
const PanelImage = ({ source, description }) => {
return <img src={source} alt={description} className="panel-img" />;
};
// Define a CardContent component for the body of the card
const PanelContent = ({ content }) => {
return <div className="panel-body">{content}</div>;
};
// Define a CardActions component for actions like buttons
const PanelActions = ({ content }) => {
return <div className="panel-footer">{content}</div>;
};
// Define a CompoundCard component that uses the above components
const CompoundPanel = () => {
return (
<Panel>
<PanelImage source="https://shorturl.at/hqCDJ" description="Sample Image" />
<PanelContent>
<h2>Panel Title</h2>
<p>
Panel Content
</p>
</PanelContent>
<PanelActions>
<button>Like</button>
<button>Share</button>
</PanelActions>
</Panel>
);
};
export default CompoundPanel;
The API for compound components provides an efficient way to express relationships between components.
Hooks
React hooks provide a way to handle a component’s state and lifecycle events. They were first introduced in early 2019, with additional hooks added in React version 16.8. These hooks include state, effect, and custom hooks.
The state hook in React (useState
) consists of two parts: the current value and a function that updates this value as required by the state:
const [data, setData] = React.useState(initialData);
Here’s a more in-depth example of the state hook:
// Import useState from React
import React, { useState } from "react";
// Define a state variable 'info' and a function 'setInfo' to update it
const [info, setInfo] = React.useState(initialData);
// Define a functional component 'StateField'
export default function StateField() {
// Define a state variable 'field' and a function 'setField' to update it
const [field, setField] = useState("");
// Define a function 'fieldHandler' to handle changes in the input field
const fieldHandler = (e) => {
setField(e.target.value)
}
// Return an input field that uses the above state and handler
return (
<input
onChange={fieldHandler}
value={field}
placeholder="Placeholder..."
/>
);
}
A state is declared with an empty current value (""
), and its value can be updated using the onChange
handler.
Effect hooks (useEffect
) are also found in class-based components. The functionalities of the useEffect
hook are akin to the lifecycle methods previously used in React: componentDidMount
, componentWillMount
, and componentDidUpdate
.
While proficient React developers are likely well-versed in hooks, HOCs, providers, and compound components, the most skilled engineers also understand general design patterns like proxies and singletons, and know when to apply them in React.
General Design Patterns in React
Design patterns are versatile and can be applied to any language or framework, irrespective of system requirements. This versatility simplifies the understanding and maintenance of the entire system. Moreover, employing design patterns enhances communication between designers. When discussing system design, software professionals can mention the pattern used to address a specific problem, enabling their colleagues to quickly grasp the high-level design.
Design patterns are primarily categorized into three types:
- Creational
- Structural
- Behavioral
While these patterns are beneficial in the context of React, they are also used in general JavaScript programming, making this knowledge easily transferable.
Creational Design Patterns in React
Creational design patterns are designed to construct objects that can be used in a variety of scenarios, enhancing flexibility and promoting reuse.
The Builder Design Pattern:
The builder design pattern streamlines the process of creating objects by outlining a sequence of steps to follow, and then delivering the outcome of these combined steps:
import React, { useState, useEffect } from 'react';
const ConstructingBuilding = ({additionalProps}) => {
const [buildStructure, setBuildStructure] = useState({});
const finalizeArchitecturalPlan = () => {
setBuildStructure(prevState => ({ ...prevState, architecturalPlan: 'completed' }));
};
const finalizeConcreteStructure = () => {
setBuildStructure(prevState => ({ ...prevState, concreteStructure: 'completed' }));
};
const finalizeInteriorDecoration = () => {
setBuildStructure(prevState => ({ ...prevState, interiorDecoration: 'completed' }));
};
const finalizeExteriorWork = () => {
setBuildStructure(prevState => ({ ...prevState, exteriorWork: 'completed' }));
};
useEffect(() => {
finalizeArchitecturalPlan();
finalizeConcreteStructure();
finalizeInteriorDecoration();
finalizeExteriorWork();
}, []);
// All updated states are returned in one state object buildStructure.
// This is then passed as props to the child component.
return (
<ConstructBuildingLand buildStructure={buildStructure} {...additionalProps} />
);
}
export default ConstructingBuilding;
completingArchitectureWork
,completingGrayStructure
,completingInteriorDesign
, andcompletingFinishingWork
: These are functions that simulate different stages of house construction. Each function updates theconstructHouse
state to indicate the completion of a specific stage.useEffect
: This is another React Hook that performs side effects in function components. In this case, it’s used to call the house construction functions when the component mounts. The empty array[]
as the second argument means the effect runs once after the initial render.
This pattern distinguishes the creation of a complex object from its representation, enabling the creation of different representations using the same construction process.
The Singleton Design Pattern:
The singleton design pattern is a technique that restricts a class to instantiate only one object. For instance, a singleton can be used to ensure that a single authentication instance is created when a user selects from various login methods:
Consider an AuthComponent
with its singleton method authInstance
that handles the types and triggers a state change based on the type. We could have an authInstance
for three components that dictate whether we should render Google, Apple, or Facebook authentication components:
function AuthenticationComponent({ authMethod }) {
const [activeAuth, setActiveAuth] = useState();
const authHandler = () => {
if (authMethod === 'google') {
setActiveAuth('google-verification')
} else if (authMethod === 'apple') {
setActiveAuth('apple-verification')
} else if (authMethod === 'facebook') {
setActiveAuth('facebook-verification')
} else {
// Execute additional logic here.
}
}
useEffect(()=>{
authHandler()
},[authMethod])
return (
<div>
{activeAuth === 'google-verification' ? <GoogleAuth /> :
activeAuth === 'apple-verification' ? <AppleAuth /> :
activeAuth === 'facebook-verification' ? <FacebookAuth /> :
null}
</div>
)
}
function AuthHandlerUsage() {
return <AuthenticationComponent authMethod='apple' />
}
AuthenticationComponent
: This is a functional component in React that takesauthMethod
as a prop.authMethod
is expected to be a string that could be ‘google’, ‘apple’, or ‘facebook’.useState
: This is a React Hook that lets you add state to functional components. In this case, it’s used to create a state variableactiveAuth
and a functionsetActiveAuth
to update it.authHandler
: This is a function that checks theauthMethod
and sets theactiveAuth
state accordingly. IfauthMethod
is ‘google’, it setsactiveAuth
to ‘google-verification’. IfauthMethod
is ‘apple’, it setsactiveAuth
to ‘apple-verification’. IfauthMethod
is ‘facebook’, it setsactiveAuth
to ‘facebook-verification’. IfauthMethod
is none of these, it doesn’t changeactiveAuth
.useEffect
: This is another React Hook that performs side effects in function components. In this case, it’s used to call theauthHandler
function wheneverauthMethod
changes.
The Singleton Design Pattern is demonstrated by the authHandler
function. No matter how many times authHandler
is called, it ensures that only one authentication instance (either ‘google-verification’, ‘apple-verification’, or ‘facebook-verification’) is active at a time. This is similar to how a singleton class ensures that only one object can be instantiated from it. The authHandler
function acts as the single global entry point to manage the authentication instance. Please let me know if you need further assistance.
A class should have a single instance and a single global access point. Singletons should be used only when the following three conditions are met:
- It’s not feasible to assign logical ownership of a single instance.
- Lazy initialization of an object is contemplated.
- There’s no need for global access to any instance.
Lazy initialization, or postponing the initialization of an object, is a technique to enhance performance by delaying the creation of an object until it’s actually needed.
Factory Design Pattern:
The factory design pattern is used when we have a superclass with multiple subclasses and need to return one of the subclasses based on input. This pattern transfers responsibility for class instantiation from the client program to the factory class.
Let’s say we have different types of buttons and we want to create a button based on the type provided:
// Define a basic Clickable component
const Clickable = ({ onActivate, label }) => (
<button onClick={onActivate}>{label}</button>
);
// Define different styles of clickables
const MainClickable = (props) => <Clickable {...props} style={{ background: 'blue', color: 'white' }} />;
const AlternateClickable = (props) => <Clickable {...props} style={{ background: 'grey', color: 'black' }} />;
// Factory function to create clickables
const ClickableFactory = (variant, props) => {
switch (variant) {
case 'main':
return <MainClickable {...props} />;
case 'alternate':
return <AlternateClickable {...props} />;
default:
return <Clickable {...props} />;
}
};
// Usage
function Application() {
return (
<div>
{ClickableFactory('main', { onActivate: () => alert('Activated!'), label: 'Main Clickable' })}
{ClickableFactory('alternate', { onActivate: () => alert('Activated!'), label: 'Alternate Clickable' })}
</div>
);
}
export default Application;
In this example, ButtonFactory
is a factory function that creates different types of buttons based on the type
argument. This is a simple example, but the Factory Design Pattern can be very powerful in more complex applications. It promotes loose coupling, improves code modularity, and can make your code easier to read and maintain.
Structural Design Patterns in React
Structural design patterns are instrumental for React developers in establishing the connections between different components. They facilitate the grouping of components and streamline complex structures.
Facade Design Pattern:
The facade design pattern is designed to simplify the interaction with numerous components by introducing a unified API. By hiding the underlying interactions, the code becomes more readable. The facade pattern is beneficial in bundling general functionalities into a more specific context, making it particularly useful for intricate systems with interaction patterns.
Consider a support department with a variety of tasks, such as confirming whether an item was billed, a support ticket was acknowledged, or an order was made.
Let’s assume we have an API that includes get
, post
, and delete
methods:
import axios from 'axios';
class FacadeAPI {
constructor() {
this.baseURL = 'https://your-api-url.com';
}
get(endpoint) {
return axios.get(`${this.baseURL}/${endpoint}`);
}
post(endpoint, data) {
return axios.post(`${this.baseURL}/${endpoint}`, data);
}
delete(endpoint, id) {
return axios.delete(`${this.baseURL}/${endpoint}/${id}`);
}
}
Now we’ll finish implementing this facade pattern example:
const service = new InterfaceAPI();
import { useState, useEffect } from 'react';
const Interface = () => {
const [elements, setElements] = useState([]);
useEffect(()=>{
// Retrieve elements from API.
service.get('getElements').then(response => {
setElements(response.data);
});
}, [])
// Adding elements.
const appendElement = (newElement) => {
service.post('addElement', newElement).then(() => {
setElements([...elements, newElement]);
});
}
// Using delete API.
const discardElement = (elementId) => {
service.delete('discardElement', elementId).then(() => {
setElements(elements.filter(item => item.id !== elementId));
});
}
return (
<div>
<button onClick={() => appendElement({id: 'newElement', value: 'New Element'})}>Add Element</button>
{elements.map(item=>{
return (
<>
<h2 key={item.id}>{item.id}</h2>
<button onClick={() => discardElement(item.id)}>Remove Element</button>
</>
);
})}
</div>
);
};
export default Interface;
In this code, FacadeAPI
is a class that provides a simplified interface (get
, post
, delete
) to interact with the backend API. These methods hide the complexity of the underlying network requests. The Facade
component uses the FacadeAPI
to interact with the backend. It uses the get
method to fetch data, the post
method to add data, and the delete
method to remove data. These methods are used in the useEffect
, addData
, and removeData
functions respectively.
Please replace '<https://your-api-url.com>'
, 'getData'
, 'addData'
, and 'removeData'
with your actual API URL and endpoints. Also, you might need to adjust the addData
and removeData
functions according to your actual data structure. This is a basic example and might need to be adjusted based on your specific use case.
Remember to handle errors appropriately in your actual code. This sample does not include error handling for simplicity. You might want to use try/catch
blocks or .catch
method for promise-based error handling.
This way, the Facade
component doesn’t need to know the details of how the network requests are made. It just needs to call the appropriate method on the FacadeAPI
class. This is the essence of the Facade Design Pattern: providing a simplified interface to a complex subsystem.
Decorator Design Pattern:
The decorator design pattern involves using layered, wrapper objects to enhance the behavior of existing objects without altering their core functionality. This approach allows a component to be layered or wrapped by an unlimited number of components; all exterior components can instantly modify their behavior, but the base component’s behavior remains unchanged. The base component is a pure function that simply produces a new component without any side effects.
A Higher-Order Component (HOC) is an example of this pattern. (The most suitable use case for decorator design patterns is memo
, but it is not discussed here as there are numerous excellent examples available online.)
Let’s delve into decorator patterns in React:
// This is our enhancer function
function canSwim(target) {
// We're adding a new property 'swim' to the target
target.prototype.swim = function() {
return `${this.constructor.name} can swim`;
};
}
// Example 1: Using the enhancer with a class
@canSwim
class Dolphin {
constructor() {
this.species = 'Dolphin';
this.velocity = '37 km/h';
}
}
// Now we can create an instance of Dolphin and call the 'swim' method
let myDolphin = new Dolphin();
console.log(myDolphin.swim()); // Outputs: "Dolphin can swim"
// Example 2: Using the enhancer with a function inside a functional component
const DolphinComponent = () => {
@canSwim
function Dolphin() {
this.species = 'Dolphin';
this.velocity = '37 km/h';
}
let myDolphin = new Dolphin();
console.log(myDolphin.swim()); // Outputs: "Dolphin can swim"
}
In this illustration, canSwim
is a universally applicable method that operates without causing any side effects. Decorators can be established atop any class component, or they can be applied to functions that are declared within class or functional components.
Decorators represent a potent code design pattern that facilitates the creation of neater and more maintainable React components. However, I still favor Higher-Order Components (HOCs) over class decorators. Given that decorators are currently an ECMAScript proposal, their specifications may evolve over time, so it’s advisable to use them judiciously.
Bridge Design Pattern:
The bridge design pattern is a potent tool in any front-end application due to its ability to decouple an abstraction from its implementation, allowing both to evolve separately.
The bridge design pattern comes into play when we aim for runtime binding implementations, face an explosion of classes due to a tied interface and multiple implementations, desire to share an implementation across various objects, or need to map orthogonal class hierarchies.
Let’s examine the functionality of the bridge pattern through this example:
// Define the Television interface
class Television {
constructor(make) {
this.make = make;
}
powerOn() {
console.log(`${this.make} Television is on`);
}
powerOff() {
console.log(`${this.make} Television is off`);
}
}
// Define the Controller interface
class Controller {
constructor(television) {
this.television = television;
}
switchPower() {
console.log(`Using ${this.television.make} controller to switch power`);
this.television.powerOn();
this.television.powerOff();
}
}
// Implement LGTelevision and LGController
class LGTelevision extends Television {
constructor() {
super('LG');
}
}
class LGController extends Controller {
constructor(television) {
super(television);
}
}
// Implement PanasonicTelevision and PanasonicController
class PanasonicTelevision extends Television {
constructor() {
super('Panasonic');
}
}
class PanasonicController extends Controller {
constructor(television) {
super(television);
}
}
// Usage
const lgTelevision = new LGTelevision();
const lgController = new LGController(lgTelevision);
lgController.switchPower();
const panasonicTelevision = new PanasonicTelevision();
const panasonicController = new PanasonicController(panasonicTelevision);
panasonicController.switchPower();
This code maintains the same structure as your original code, but with unique variable names and comments. The TV
class is now Television
, the Remote
class is now Controller
, and the methods and variables within these classes have been renamed for uniqueness. Additionally, SamsungTV
and SamsungRemote
have been replaced with LGTelevision
and LGController
, and SonyTV
and SonyRemote
have been replaced with PanasonicTelevision
and PanasonicController
. The functionality of the code remains the same.
In the bridge design pattern, it’s crucial to ensure that the reference is accurate and mirrors the appropriate modification.
Proxy Design Pattern:
The proxy design pattern employs a proxy as a stand-in or substitute for accessing an object. A common real-world example of a proxy is a credit card, which symbolizes tangible cash or funds in a bank account.
Let’s apply this pattern and construct a similar example where we execute a money transfer, and a payment application verifies the existing balance in our bank account:
// This function simulates an external API call.
const externalAPI = (userId) => {
// This function would typically make a network request to get the user balance.
// For simplicity, let's assume it returns a promise that resolves to the user balance.
return new Promise(resolve => resolve(100)); // Assume the user balance is 100.
}
// This is the intermediary function.
const verifyBalance = userId => {
return new Promise(resolve => {
// Here, we're using the externalAPI function to verify the user balance.
externalAPI(userId).then((funds) => {
// We can add some conditions here. For example, we might want to check if the funds are adequate.
if (funds >= 50) {
resolve(true);
} else {
resolve(false);
}
});
});
}
// This function simulates a money transfer.
const moveMoney = () => {
return new Promise(resolve => {
// This function would typically make a network request to move money.
// For simplicity, let's assume it returns a promise that resolves to the user ID.
resolve('uniqueUserId');
});
}
// Test run on intermediary function.
moveMoney().then(uniqueUserId => {
// Using intermediary before moving money.
verifyBalance(uniqueUserId).then(isAdequate => {
if (isAdequate) {
console.log('Transfer successful');
} else {
console.log('Insufficient funds');
}
});
}).catch(error => console.log('Transfer failed', error));
In our code, the payment app’s verification of the account’s balance serves as the proxy.
Behavioral Design Patterns in React
Behavioral design patterns are centered around the interaction between different components. This makes them particularly suitable for React, which is built on a component-based architecture.
State Design Pattern:
The state design pattern is frequently employed to introduce fundamental encapsulation units (states) in component-based programming. A television controlled by a remote serves as an illustration of the state pattern:
The television’s behavior is altered based on the remote button’s state (on or off). Similarly, in React, a component’s state can be modified depending on its props or other factors.
The behavior of an object is altered when its state changes:
import React, { Component } from 'react';
// Component without any state.
class StatelessComponent extends Component {
render() {
// Accessing the rest of the properties here...
return <div>This is a stateless component</div>;
}
}
// Component with a state property.
class StatefulComponent extends Component {
constructor(props) {
super(props);
this.state = this.props.initialState; // Setting the state here...
}
render() {
// Accessing the rest of the properties and state here...
return <div>This is a stateful component</div>;
}
}
// How to use these components
const additionalProps = {};
const initialState = {};
<StatelessComponent additionalProps={additionalProps} state={null}/>
<StatefulComponent additionalProps={additionalProps} initialState={initialState}/>
This code creates two components: StatelessComponent
and StatefulComponent
. The StatelessComponent
does not use any state, while the StatefulComponent
does. The state for StatefulComponent
is passed in as a prop named initialState
and then set in the constructor. Please modify this code according to your requirements. Remember to replace the render
methods with your own implementation.
Command Design Pattern:
The command design pattern is a highly effective pattern for creating systems that are clean and loosely coupled. This pattern enables us to carry out a segment of business logic at a future point in time. I want to concentrate specifically on the command pattern as I consider it to be the foundational pattern of Redux. Let’s explore how the command pattern can be applied in conjunction with a Redux reducer:
// Initial state of the application
const defaultState = {
category: 'DISPLAY_ALL',
list: []
}
// Function to handle different actions
function actionHandler(currentState = defaultState, operation) {
switch (operation.kind) {
case 'CHANGE_CATEGORY': { /* ... */ }
case 'INSERT_ITEM': { /* ... */ }
case 'MODIFY_ITEM': { /* ... */ }
default:
// Return the current state if no matching action is found
return currentState
}
}
In this instance, the Redux reducer comprises several cases—each activated by distinct circumstances—that yield varying behaviors.
Observer Design Pattern:
The observer design pattern is a mechanism that enables objects to follow changes in the state of another object. When the state changes, the observing objects are automatically notified. This pattern separates the observing objects from the observed object, enhancing modularity and flexibility.
In the Model-View-Controller (MVC) architecture, the observer pattern is frequently employed to disseminate changes from the model to the views. This allows the views to monitor and present the model’s updated state without needing direct access to the model’s internal data.
const Watcher = () => {
useEffect(() => {
const actionHandler = () => { ... }
// Bind event handler.
documentListener('EVENT_TYPE', actionHandler)
return () => {
// Unbind event handler.
documentListener('EVENT_TYPE', actionHandler)
}
}, [])
}
The Watcher object facilitates communication by establishing “watcher” and “subject” objects. In contrast, other patterns such as the mediator encapsulate communication within objects. It’s simpler to create reusable watchers compared to reusable mediators. However, a mediator can employ a watcher to dynamically enlist colleagues and interact with them. This matches the variable names used in the code above.
Strategy Design Pattern:
The strategy design pattern is a method that allows for the modification of certain behaviors from the outside without altering the core component. It outlines a set of algorithms, encapsulates each, and makes them interchangeable. This strategy enables the parent component to evolve independently of the child that utilizes it. The abstraction can be placed in an interface, with the implementation specifics concealed in subclasses.
// Define the Plan component
const Plan = ({ elements }) => {
return <div>{elements}</div>;
};
// Define the ComponentA
const ComponentA = () => {
return <div>ComponentA</div>;
};
// Define another component, ComponentB
const ComponentB = () => {
return <div>ComponentB</div>;
};
// Use the Plan component with ComponentA
<Plan elements={<ComponentA />} />;
// Use the Plan component with ComponentB
<Plan elements={<ComponentB />} />;
The open-closed principle is a key approach in object-oriented design. Utilizing the strategy design pattern allows adherence to OOP principles while maintaining flexibility during runtime.
Memento Design Pattern:
The memento design pattern is a method that records and externalizes an object’s internal state, allowing it to be restored later without violating encapsulation. The roles in the memento design pattern are as follows:
- The originator is the object capable of saving its own state.
- The caretaker understands when the originator needs to save and restore its state.
- The mementos, or memories, are stored in a secure container managed by the caretaker and accessed by the originator.
To understand this better, let’s look at a code example. The memento pattern employs the chrome.storage
API (implementation details omitted for brevity) to save and retrieve data. In the conceptual example below, we use the setState
function to store data and the getState
function to retrieve it:
class Memento {
// Loads the data.
loadGameState(){ ... }
// Stores the data.
saveGameState() { ... }
}
But the actual use case in React is as follows:
const gameController = () => ({
gamePlayer: () => {
return fetchGameState(); // This is the Source.
},
gameEngine: (gameAction, actionType) => {
return actionType === "STORE" && gameAction === "ADVANCE"
? {
action: "ADVANCE",
state: fetchGameState().STORE,
}
: {
action: "END_GAME",
state: fetchGameState().STORE,
};
},
saveState: (gameStatus) => {
const gameData = {};
// Logic to update gameData based on gameStatus.
// Send gameStatus as well to store the state based on.
// Game actions for gameEngine function.
storeGameState({ gameStatus, ...gameData }); // These are the Snapshots.
},
});
gamePlayer
is the Source that can store its own state.gameEngine
is the Caretaker that knows when to store and retrieve the game data.saveState
represents the Snapshots where the game data is stored.
>> Read more:
- An In-Depth Guide for Front-End Development with React
- Top 12 Best Javascript Frameworks for Your Projects
- React vs Angular: A Comprehensive Side-by-Side Comparision
- Guide for React Native End-To-End Testing with Detox
- An Ultimate Guide to Build Interactive Charts with React Chart.js
The Importance of React Design Patterns
While patterns provide proven solutions to common problems, it’s crucial for software engineers to understand the pros and cons of each design pattern before implementing it.
Frequently, engineers utilize design patterns such as React’s state, hooks, custom hooks, and the Context API. However, gaining a deeper understanding and application of the React design patterns discussed will enhance a React developer’s fundamental technical abilities and be beneficial across various programming languages. These universal patterns enable React engineers to articulate the architectural behavior of code, rather than merely employing a specific pattern to fulfill requirements or tackle a single problem.
>>> Follow and Contact Relia Software for more information!
- development
- coding