DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 966,904 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Max Frolov
Max Frolov

Posted on

Reusable logic with React Render Props

In this post, I want to introduce you RenderProps: 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 props, 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: ({children}) = > {logic... return children(args) }

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

An example of RenderProps usage with "children as prop":

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 children is specified instead of children as ReactNode(s). 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 children, we can pass the function as props with a common name and return the ReactNode as well.

Example of using RenderProps with "custom prop":

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.
More tutorials here.

Feedback is appreciated. Cheers!

Top comments (1)

Collapse
 
nicolasleal570 profile image
Nicolas Leal

You have amazing tips on your profile! And I didn't find out earlier. Thankssss

"I made 10x faster JSON.stringify() functions, even type safe"

☝️ Must read for JS devs