
React Rendering for Rapidly Changing UIs
This article presents situations where imperative programming can be used to improve performance when updating multiple UI components.
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.
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.
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)
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.
import React, { useState } from "react";
const useApp = () =>; {
const [count, setCount] = useState(0);
return { count, setCount };
};
export default useApp;
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();
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.
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<post>()
const [parentPoster, setParentPoster] = useState<bot |="" userprofile="">()
const [parentPostBlurb, setParentPostBlurb] = useState<string>('')
const [isMounted, setIsMounted] = useState(false)
useEffect(() =>; { … }, [])
useEffect(() =>; { … }, [post.parent_id, posts])
useEffect(() =>; { … }, [parentPost?.message])
useEffect(() =>; { … }, [bots, myUserProfile, parentPost, userProfiles])
const onClickBlurb = async () =>; {... }
return (
<div>
Commented on <span>{parentPoster.username}</span>'s message:
<span onclick="{onClickBlurb}">
{parentPostBlurb}
</span>
</div>
)
}
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<post>()
const [parentPoster, setParentPoster] = useState<bot |="" userprofile="">()
const [parentPostBlurb, setParentPostBlurb] = useState<string>('')
const [isMounted, setIsMounted] = useState(false)
useEffect(() =>; { … }, [])
useEffect(() =>; { … }, [post.parent_id, posts])
useEffect(() =>; { … }, [parentPost?.message])
useEffect(() =>; { … }, [bots, myUserProfile, parentPost, userProfiles])
// return the state that the presentational components need
return { parentPost, parentPoster, parentPostBlurb }
}
export default useChatPostThreadInfo
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 (
<div>
Commented on <span>{parentPoster.username}</span>'s message:
<span onclick="{onClickBlurb}">{parentPostBlurb}</span>
</div>
)
}
export default ChatPostThreadInfo
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 (
<view data-testid="chat-post-thread-info">
<text style="{parentPostMessage}">{`Commented on ${parentPoster.username}'s message: ${parentPostBlurb}`}</text>
</view>
)
}
export default ChatPostThreadInfoPdf
useChatThreadInfo
hook.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.
This article presents situations where imperative programming can be used to improve performance when updating multiple UI components.
An authentic user experience goes far beyond giving customers what they say they want. So we go deeper and research the underlying wants and needs of the end-user. To provide a high-quality user experience, we have to be seamless in our disciplines, which we discuss in detail.
Girls Who Code is changing the game. They're reaching girls worldwide and are on track to close the gender gap in new entry-level tech jobs by 2027.