HOC - Functional Component
I agree with siraj, strictly speaking the example in the accepted answer is not a true HOC. The distinguishing feature of a HOC is that it returns a component, whereas the PrivateRoute
component in the accepted answer is a component itself. So while it accomplishes what it set out to do just fine, I don't think it is a great example of a HOC.
In the functional component world, the most basic HOC would look like this:
const withNothing = Component => ({ ...props }) => ( <Component {...props} />);
Calling withNothing
returns another component (not an instance, that's the main difference), which can then be used just like a regular component:
const ComponentWithNothing = withNothing(Component);const instance = <ComponentWithNothing someProp="test" />;
One way to use this is if you want to use ad-hoc (no pun intended lol) context providers.
Let's say my application has multiple points where a user can login. I don't want to copy the login logic (API calls and success/error messages) across all these points, so I'd like a reusable <Login />
component. However, in my case all these points of login differ significantly visually, so a reusable component is not an option. What I need is a reusable <WithLogin />
component, which would provide its children with all the necessary functionality - the API call and success/error messages. Here's one way to do this:
// This context will only hold the `login` method.// Calling this method will invoke all the required logic.const LoginContext = React.createContext();LoginContext.displayName = "Login";// This "HOC" (not a true HOC yet) should take care of// all the reusable logic - API calls and messages.// This will allow me to pass different layouts as children.const WithLogin = ({ children }) => { const [popup, setPopup] = useState(null); const doLogin = useCallback( (email, password) => callLoginAPI(email, password).then( () => { setPopup({ message: "Success" }); }, () => { setPopup({ error: true, message: "Failure" }); } ), [setPopup] ); return ( <LoginContext.Provider value={doLogin}> {children} {popup ? ( <Modal error={popup.error} message={popup.message} onClose={() => setPopup(null)} /> ) : null} </LoginContext.Provider> );};// This is my main component. It is very neat and simple// because all the technical bits are inside WithLogin.const MyComponent = () => { const login = useContext(LoginContext); const doLogin = useCallback(() => { login("a@b.c", "password"); }, [login]); return ( <WithLogin> <button type="button" onClick={doLogin}> Login! </button> </WithLogin> );};
Unfortunately, this does not work because LoginContext.Provider
is instantiated inside MyComponent
, and so useContext(LoginContext)
returns nothing.
HOC to the rescue! What if I added a tiny middleman:
const withLogin = Component => ({ ...props }) => ( <WithLogin> <Component {...props} /> </WithLogin>);
And then:
const MyComponent = () => { const login = useContext(LoginContext); const doLogin = useCallback(() => { login("a@b.c", "password"); }, [login]); return ( <button type="button" onClick={doLogin}> Login! </button> );};const MyComponentWithLogin = withLogin(MyComponent);
Bam! MyComponentWithLogin
will now work as expected.
This may well not be the best way to approach this particular situation, but I kinda like it.
And yes, it really is just an extra function call, nothing more! According to the official guide:
HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature.
Definitely you can create a functional stateless component that accepts component as an input and return some other component as an output, for example;
- You can create a PrivateRoute component that accepts a Component as a prop value and returns some other Component depending on if user is authenticated or not.
- If user is not authenticated(read it from context store) then you redirect user to login page with
<Redirect to='/login'/>
else you return the component passed as a prop and send other props to that component<Component {...props} />
App.js
const App = () => { return ( <Switch> <PrivateRoute exact path='/' component={Home} /> <Route exact path='/about' component={About} /> <Route exact path='/login' component={Login} /> <Route exact path='/register' component={Register} /> </Switch> );}export default App;
PrivateRoute.jsx
import React, { useContext , useEffect} from 'react';import { Route, Redirect } from 'react-router-dom'import AuthContext from '../../context/auth/authContext'const PrivateRoute = ({ component: Component, ...rest }) => { const authContext = useContext(AuthContext) const { loadUser, isAuthenticated } = authContext useEffect(() => { loadUser() // eslint-disable-next-line }, []) if(isAuthenticated === null){ return <></> } return ( <Route {...rest} render={props => !isAuthenticated ? ( <Redirect to='/login'/> ) : ( <Component {...props} /> ) } /> );};export default PrivateRoute;
Higher Order Components does not have to be class components, their purpose is to take a Component as an input and return a component as an output according to some logic.
The following is an over simplified example of using HOC with functional components.
The functional component to be "wrapped":
import React from 'react'import withClasses from '../withClasses'const ToBeWrappedByHOC = () => {return ( <div> <p>I'm wrapped by a higher order component</p> </div> )}export default withClasses(ToBeWrappedByHOC, "myClassName");
The Higher Order Component:
import React from 'react'const withClasses = (WrappedComponent, classes) => {return (props) => ( <div className={classes}> <WrappedComponent {...props} /> </div> );};export default withClasses;
The component can be used in a different component like so.
<ToBeWrappedByHOC/>