Liskov Substitution Principle in React
Zahid Hasan
Posted on January 13, 2023
What is Liskov Substitution Principle?
The Liskov Substitution Principle (LSP) is a guideline that assists in the effective functioning of the software. It states that if something is of a given kind, you should be able to substitute a different but similar item for it and the software would still function properly.
Consider a toy box filled with various sorts of toys such as vehicles, dolls, and balls. Because the toy box can only contain toys, whatever is placed within it must be a toy. According to the LSP, if you have a toy car, you should be able to replace it with a toy truck and the toy box should still function properly.
According to the LSP, objects of a superclass should be able to be substituted by objects of a subclass without impacting the program’s correctness.
How can you implement Liskov Substitution Principle in React?
Here are some ways to implement the Liskov Substitution Principle in React:
- Make your own hooks to specify the anticipated behavior and features of certain functionality, such as a form input. Any component that makes use of the custom hook should be able to be utilized whenever a form input is anticipated and the application should still function properly.
- Create custom hooks that expect a set of properties, such as an object containing the component’s state and behavior, then return the desired behavior and properties. As long as the items have the same form, this enables for easy substitution of various objects with varying actions.
- Use composition to define the expected behavior and properties of a component. Composing small and focused components together, it allows for easy substitution of related components that conform to the same interface and behavior.
Here’s an example of a scenario where the Liskov Substitution Principle is violated:
Assume you have a custom hook named useAuth
that controls an application’s authentication state, such as the user’s token and login status.
import { useState } from "react";
const useAuth = () => {
const [token, setToken] = useState(null);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const login = (token) => {
setToken(token);
setIsLoggedIn(true);
}
const logout = () => {
setToken(null);
setIsLoggedIn(false);
}
return { token, isLoggedIn, login, logout };
}
Suppose you have a component named Profile
that renders the user’s information while they are signed in and a login button when they are not.
const Profile = () => {
const { token, isLoggedIn, login } = useAuth();
if (!isLoggedIn) return <button onClick={() => login("pass")}>Login</button>;
return <p>Welcome, you are logged in.</p>;
}
Imagine you want to add another component named Settings that allows the user to update their settings while they are logged in and a login button when they are not.
const Settings = () => {
const { token, isLoggedIn, login } = useAuth();
if (!isLoggedIn) return <button onClick={() => login("pass")}>Login</button>;
return (
<div>
<form>
<label>Change your password</label>
<input type="password" placeholder="New password" />
<button>Save</button>
</form>
</div>
);
}
As you can see, both the Profile
and Settings
components utilize the useAuth
hook to manage the application’s authentication state, but because each component has distinct expectations for the hook, the useAuth
hook is unable to handle the various cases. Because the useAuth
hook cannot be used interchangeably in various components, this violates the LSP.
To address this issue, you could construct different hooks for different use cases, or you could develop a more general hook that can handle multiple circumstances by giving more parameters such as anticipated behavior or data.
Following this principle allows you to design a collection of interchangeable hooks, which makes it simple to add new features to your application because you can use the same hooks for other components and the application will still function appropriately.
Here is one solution to fix the issue of the useAuth
hook violating the LSP:
Create a more general useAuth
hook that can handle several cases by giving additional arguments, such as desired behavior or data.
import { useState } from "react";
const useAuth = (initialAuth) =>{
const [auth, setAuth] = useState(initialAuth);
const login = (token) => {
setAuth({ token, isLoggedIn: true });
}
const logout = () => {
setAuth({ token: null, isLoggedIn: false });
}
return { auth, login, logout };
}
Pass the initial auth state as a parameter to the useAuth
hook.
const Profile = () => {
const { auth, login } = useAuth({ token: null, isLoggedIn: false });
if (!auth.isLoggedIn)
return <button onClick={() => login("pass")}>Login</button>;
return <p>Welcome, you are logged in.</p>;
};
const Settings = () => {
const { auth, login } = useAuth({ token: null, isLoggedIn: false });
if (!auth.isLoggedIn)
return <button onClick={() => login("pass")}>Login</button>;
return (
<div>
<form>
<label>Change your password</label>
<input type="password" placeholder="New password" />
<button>Save</button>
</form>
</div>
);
};
The useAuth
hook may handle multiple scenarios and be used interchangeably in other components by giving the initial auth state as a parameter. This enables the components to have varied expectations for the hook while still functioning appropriately, in accordance with the Liskov Substitution Principle.
Posted on January 13, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
May 26, 2024