React is such a powerful library, that everyone with knowledge of the basics can build a really good application. Managing state in react is built out of the box with React own state management APIs.
But as your app gets more complex, it becomes harder to track, get a better hold of your state and understand what is going on. In order to improve the understanding of your code at such a moment, React has made available techniques and APIs that helps us build components that seamlessly work.
Some of those techniques and API are:
HOC is an advanced technique to React to reusing component logic. Just like a Higher Order Function, which receives a function as an argument and returns a function, a HOC takes a component as an argument and returns a new component.
Let's take this code for example:
import React from 'react'
function Students() {
const students = [
{ name: "John", score: "A-" },
{ name: "Samuel", score: "B-" },
{ name: "Smith", score: "A+" },
{ name: "Mark", score: "A-" },
{ name: "Mike", score: "B-" },
{ name: "John", score: "B+" },
];
return (
<div>
{students.map((student) => (
<p>
{student.name} - {student.score}
</p>
))}
</div>
);
}
From the snippet of code above, we could tell that the list of students and their grade is tied to the
Students
component. What happens when another component needs to make use of that same list? We do not want to copy and paste the same list across all components. But what we want is a reusable component that could be used by other components. This is where HOC shines, it allows us to create a Wrapper Component that provides other components with the data they need. import React from "react"
function Students(props) {
return (
<div>
{props.students.map((student) => (
<p>
{student.name} - {student.score}
</p>
))}
</div>
);
}
const withStudents = (Component) => {
const students = [
{ name: "John", score: "A-" },
{ name: "Samuel", score: "B-" },
{ name: "Smith", score: "A+" },
{ name: "Mark", score: "A-" },
{ name: "Mike", score: "B-" },
{ name: "John", score: "B+" },
];
return () => <Component {...students}></Component>;
};
const ComponentWithStudents = withStudents(Students);
export default ComponentWithStudents;
We create a
withStudents
component which accepts any component as argument and supplies data to it in the form of props
. The wrapper component withStudents
returns the supplied component by wrapping it in a container component, it does not alter the argument component in any way. HOC are pure functions with no side-effects. The syntax above will look familiar to you if you have worked with redux before.We could pass extra parameters to our wrapper component by doing the following:
const withStudents = (count) => (Component) => {
const students = [
{ name: "John", score: "A-" },
{ name: "Samuel", score: "B-" },
{ name: "Smith", score: "A+" },
{ name: "Mark", score: "A-" },
{ name: "Mike", score: "B-" },
{ name: "John", score: "B+" },
];
const listStudentsLimited = students.slice(0, count);
return () => <Component students={listStudentsLimited}></Component>;
};
const maxStudentCount = 3;
export default withStudents(maxStudentCount)(App);
Our
Students
component remains the same while the withStudents
wrapper now returns a function that wraps what was previously returned, making it a true Higher Order Function :).Next, we will look at how we could use Render Props to do similar data sharing.
The second way by which we can share data among components is with Render Props. From the react.js , it defines render props to be
A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.
So using our previous example, we create a render props component that surrounds the rendering part of the original
Student
component. The Render Props in turn returns the component as its child and passes any data to it.import React from "react";
function Students() {
return (
<StudentWithRenderProps>
{({ students }) => (
<div>
<h1>Students with grades</h1>
{students.map((student) => (
<p>
{student.name} - {student.score}
</p>
))}
</div>
)}
</StudentWithRenderProps>
);
}
const StudentWithRenderProps = (props) => {
const students = [
{ name: "John", score: "A-" },
{ name: "Samuel", score: "B-" },
{ name: "Smith", score: "A+" },
{ name: "Mark", score: "A-" },
{ name: "Mike", score: "B-" },
{ name: "John", score: "B+" },
];
return props.children({
students,
});
};
export default Students;
From the React.js website, it defines context as follow,
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
This is one way to solve the component drilling issue, where you have to pass data through several components to share data with children located down in the components. Using Context makes it easier to share data among many components within an application; data such as user session, theme or language.
In our example, we are going to use a context to share user-session information among components that need it.
export const AuthContext = React.createContext({});
export default function App() {
const userInfo = {
name: "John Smith",
email: "[email protected]"
};
return (
<AuthContext.Provider value={userInfo}>
<Profile></Profile>
</AuthContext.Provider>
);
}
First, we create the context
React.createContext({})
and assign it to a variable. This will be used to wrap any consuming component with the help of the Provider component that is made available by the context. The Provider accepts a value
prop that contains the data to share among any nested components. In our case, we want to share the userInfo
. Next, for any component to access the data being shared by a context, we need to get a reference of the context and pass it to the
useContext
hook made available by React.import { useContext } from "react";
import { AuthContext } from "./App";
export default function Profile() {
const auth = useContext(AuthContext);
console.log(auth);
return (
<div>
User is
<span style={{ color: "red" }}>
{Object.keys(auth).length > 0 ? "Logged in" : "Logged out"}
</span>
</div>
);
}
Now, the Profile component has access to the
userInfo
from the AuthContext.Both HOC and Render Props works almost in the same way. HOC works in a way that feels like it works behind the scene, while Render Props are more frontend centered. They are less code-intensive as compared to Context. Context on the other hand allows us to give all consuming components access to the data passed to the
Provider
.Also published at https://dev.to/edemagbenyo/build-better-components-with-react-3kha/edit