Reusable logic with React Render Props
Tags: webdev, tutorial, react, javascript
Published: 2020-05-29 11:41:14

In this post, I want to introduce you <code>RenderProps</code>: what is it and how to use it.

RenderProps is a pattern, as well as HOC (higher order component) it's designed to pack logic into a component for further reuse where required. Simply said, it is a component that takes <code>props</code>, one of which must be a function. Calling this function, we can pass the data by arguments that will be available in the component from which the function was passed.

The main advantage of RenderProps versus HOC:
>You can pass data from the involving component to the RenderProps component via props. In a HOC case, we can only pass static data as function arguments.

Short description: <code>({children}) = > {logic... return children(args) }</code>

Imagine we have two pages where each needs to fetch user data. The logic for each page is repeating so we will create a <code>ProvideAuth</code> component which provides the user profile and <code>loading</code> state

An example of RenderProps usage with "children as prop":
```jsx
const ProvideAuth = ({ children }) => {
   // state
   const [userProfile, setUserProfile] = React.useState({ isAuthorized: false, data: {} })
   const [isUserLoading, setUserLoadingState] = React.useState(false)
  
   const handleSetUserLoading = value => {
     setUserLoadingState(value)
   }
  
   React.useEffect(() => {
     handleGetUser()
   }, [])
  
   const handleGetUser = async () => {
     try {
       handleSetUserLoading(true)
  
       const response = await getUser()
  
       setUserProfile({ isAuthorized: true, data: response.data })
     } catch (error) {
       console.log('Error while User preload:', error)
     } finally {
       handleSetUserLoading(false)
     }
   }
  
   if (!userProfile.isAuthorized && !isUserLoading) {
     return <div>U're not authorized</div>
   }
  
   return (
     <>
       {isUserLoading ? (
         <div>Loading...</div>
       ) : (
         <>
           {/* call children function and provide data */}
           {children({ userProfile, isUserLoading })}
         </>
       )}
     </>
   )
 }
  
 const PageFirst = () => (
   <ProvideAuth>
     {/* provide a function as children and return ReactNode */}
     {({ userProfile }) => (
       <>
         <div>Your First Name: {userProfile.data.firstName}</div>
         <div>Your Last Name: {userProfile.data.lastName}</div>
         <div>Is Authorized: {userProfile.isAuthorized ? 'Yes' : 'No'}</div>
       </>
     )}
   </ProvideAuth>
 )
  
 const PageSecond = () => (
   <ProvideAuth>
     {/* provide a function as children and return ReactNode */}
     {({ userProfile }) => (
       <div>
         Your Full Name: {userProfile.data.firstName} {userProfile.data.lastName}
       </div>
     )}
   </ProvideAuth>
 )
```
If RenderProps (ProvideAuth) wraps elements within render, a function that returns <code>children</code> is specified instead of children as <code>ReactNode(s)</code>. The data passed from the ProvideAuth are the arguments for this function. Thus, unlike a standard container where children can be ReactNode(s), we pass a function, once called, returns a ReactNode. That 's all the magic RenderProps.

Instead of <code>children</code>, we can pass the function as props with a common name and return the ReactNode as well.

Example of using RenderProps with "custom prop":
```jsx
const ProvideAuth = ({ renderAuth }) => {
   // state
   const [userProfile, setUserProfile] = React.useState({ isAuthorized: false, data: {} })
   const [isUserLoading, setUserLoadingState] = React.useState(false)
  
   const handleSetUserLoading = value => {
     setUserLoadingState(value)
   }
  
   React.useEffect(() => {
     handleGetUser()
   }, [])
  
   const handleGetUser = async () => {
     try {
       handleSetUserLoading(true)
  
       const response = await getUser()
  
       setUserProfile({ isAuthorized: true, data: response.data })
     } catch (error) {
       console.log('Error while User preload:', error)
     } finally {
       handleSetUserLoading(false)
     }
   }
  
   if (!userProfile.isAuthorized && !isUserLoading) {
     return <div>U're not authorized</div>
   }
  
   return (
     <>
       {isUserLoading ? (
         <div>Loading...</div>
       ) : (
         <>
           {/* call renderAuth prop function and provide data */}
           {renderAuth({ userProfile, isUserLoading })}
         </>
       )}
     </>
   )
 }
  
 const PageFirst = () => (
   <ProvideAuth
     // provide prop renderAuth function and return ReactNode
     renderAuth={({ userProfile }) => (
       <>
         <div>Your First Name: {userProfile.data.firstName}</div>
         <div>Your Last Name: {userProfile.data.lastName}</div>
         <div>Is Authorized: {userProfile.isAuthorized ? 'Yes' : 'No'}</div>
       </>
     )}
   />
 )
  
 const PageSecond = () => (
   <ProvideAuth
     // provide prop renderAuth function and return ReactNode
     renderAuth={({ userProfile }) => (
       <div>
         Your Full Name: {userProfile.data.firstName} {userProfile.data.lastName}
       </div>
     )}
   />
 )
```
As a matter of experience, I can say RenderProps is ideal for creating UI modules that can be reused in different projects. They can be easily adapted to the needs of each project where applicable. That is very important for development acceleration.

More tips and best practices on my [Twitter](https://twitter.com/max_frolov_).
More tutorials [here](https://dev.to/max_frolov_).

Feedback is appreciated. Cheers!