Sharing Logic Between Components in Different React Renderers
The bottom line is that there are more targets for React and it may still be necessary to reuse logic across multiple renderers for different platforms. Fortunately, building a custom hook provides an easier path to share component logic than the original render props technique did.
Background
Several years ago, I wrote the article Sharing Code Between React Web and React Native Applications. That article
demonstrated how to use render
props to share component logic in a way that a React JS application and a React Native application
could use the same business logic while rendering separate React JS and React Native views.
Since that article was published, there have been enough changes in the React community and
the tools available to justify a re-write. Some of these changes include the shift from class-based to functional components, the introduction and growing importance of hooks, and the growing number of renderers that allow React
developers to output to platforms other than the web and mobile.
The bottom line is that there are more targets for React and it may still be necessary to
reuse logic across multiple renderers for different platforms. Fortunately, building a custom hook provides
an easier path to share component logic than the original render props technique did.
This article discusses how to use a custom hook to share logic between two components; one
component targeting react-dom to render
HTML and another component that uses the react-pdf
renderer primitives for use in creating a larger PDF component.
This article is focused on sharing component logic and does not include a deep dive into React
nor the react-pdf renderer so you should refer to those packages’ documentation if you need familiarization
with them.
Extracting Functionality to A Custom React Hook
Let’s start by using vitejs
to quickly scaffold a simple React project that includes a component suitable for a small demonstration on
how to extract logic into a custom hook.
The component includes a counter that uses the useState hook to track and update
the number of times the button is pressed.
function App() {
const [count, setCount] = useState(0)
All of the logic in the initial component.
If we want to use the same logic in another component we can start by extracting any logic
that component requires into a hook in a separate file. The purpose of this hook is to provide any state and
the functions a component requires to interact with that state.
Our new hook containing all of the component logic.
Now, instead of using useState directly, the App component pulls
logic and state from the “useApp” hook as shown below.
function App() {
const { count, setCount } = useApp();
App now gets its state from the custom useApp hook.
At this point, any component we make can make use of the useApp hook. This is
especially useful for a React Native View that includes the same button pressing logic. Since the user can
still interact with the button and expects to see the count changed.
Using a Custom Hook to in Components Targeting Different Renderers
Now that we have a demonstration of how to extract and build a custom hook, let's look at how
this technique is useful to simplify creating a PDF component to use with react-pdf renderer.
The chatPostThreadInfo component below has multiple pieces of state and several
useEffect to build the JSX that renders HTML.
const ChatPostThreadInfo = ({ post }: { post: Post }) => {
// component state
// ...
const dispatch = useDispatch()
// state items needed by the JSX and useEffects
const [parentPost, setParentPost] = useState()
const [parentPoster, setParentPoster] = useState<bot |="" userprofile="">()
const [parentPostBlurb, setParentPostBlurb] = useState('')
const [isMounted, setIsMounted] = useState(false)
Commented on {parentPoster.username}'s message:
{parentPostBlurb}
)
}
Original component that renders HTML and includes state logic.
In order to use the same state logic to build a PDF component, all the stateful logic can be
extracted to a custom hook that updates and provides state that the presentational components need.
const useChatPostThreadInfo = ({ post }: Props) => {
// other internal state state
// ...
const dispatch = useDispatch()
// state items needed by the JSX and useEffects
const [parentPost, setParentPost] = useState()
const [parentPoster, setParentPoster] = useState<bot |="" userprofile="">()
const [parentPostBlurb, setParentPostBlurb] = useState('')
const [isMounted, setIsMounted] = useState(false)
// return the state that the presentational components need
return { parentPost, parentPoster, parentPostBlurb }
}
export default useChatPostThreadInfo
Custom useChatThreadInfo hook to provide state to presentational
components.
After the extraction, the original component that renders HTML can be updated to retrieve the
necessary state from the custom hook.
const ChatPostThreadInfo = ({ post }: { post: Post }) => {
// other state items…
// obtain state from the new custom hook
const { parentPost, parentPoster, parentPostBlurb } = useChatPostThreadInfo({ post })
// note that this function remains in this HTML component since the PDF component will not need it
const onClickBlurb = async () => { ... }
return (
Commented on {parentPoster.username}'s message:
{parentPostBlurb}
)
}
export default ChatPostThreadInfo
Updated HTML component that now uses the useChatThreadInfo hook.
Next, the custom hook can be used to provide the same data to the component that will render
the PDF version of the component.
const ChatPostThreadInfoPdf = ({ post }: { post: Post }) => {
// obtain state from the new custom hook
const { parentPost, parentPoster, parentPostBlurb } = useChatPostThreadInfo({ post })
return (
{Commented on ${parentPoster.username}'s message: ${parentPostBlurb}}
)
}
export default ChatPostThreadInfoPdf
PDF component that uses the useChatThreadInfo hook.
Conclusion
In the end, we can see that extracting the component logic that needs to be shared into a
custom hook provides a simpler method for sharing logic and state between components than the original
render props technique. This results in logic that can be shared across multiple renderers.
The bottom line is that there are more targets for React and it may still be necessary to reuse logic across multiple renderers for different platforms. Fortunately, building a custom hook provides an easier path to share component logic than the original render props technique did.
The bottom line is that there are more targets for React and it may still be necessary to reuse logic across multiple renderers for different platforms. Fortunately, building a custom hook provides an easier path to share component logic than the original render props technique did.
The bottom line is that there are more targets for React and it may still be necessary to reuse logic across multiple renderers for different platforms. Fortunately, building a custom hook provides an easier path to share component logic than the original render props technique did.