Create A WebView-free Blog App with React Native Render HTML, Part II
This article is the part II of the Create a WebView-free Blog App with React Native Render HTML serie. See also Part I and Part III.
tip
The source code of this case study is available in the main
branch of this
repo: jsamr/rnrh-blog
. The enhanced
branch contains a few more features beyond this tutorial, such as a refined UI,
dark mode, caching with react-queries...
etc. You can try out the enhanced version right now with expo, see the
project page for instructions.
tip
If you have any question or remarks regarding this tutorial, you're welcome in our Discord channel.
#
The Home ScreenLet's get back to our HomeScreen.tsx
file. Below are the steps to get to a functional component:
- Fetch the RSS feed items. We'll do this in the
useRssFeed
hook. - Render a
FlatList
filled with the fetched items. - Render individual items in a
FeedItemDisplay
component.
useRssFeed
Hook#
The First of all, we'll define the FeedItem
TypeScript interface in
shared-types.ts
. This is the shape of items parsed by the RSS parser.
Then, let's define the hook in the hooks/useRssFeed.ts
file.
This hook uses an effect to load the feed, and store the retrieved items in a
local state (items
). A few remarks:
- We provide a
refresh
function to trigger a new fetch along with aisRefreshing
state. - The effect callback returns a cleanup function to avoid setting state on unmounted components. Not doing this is considered an antipattern, see this guide for a deep dive.
Last but not least, if you are using TypeScript, you will need to add module
definitions for rss-parser. Put this file in the hooks
folder:
FeedItemDisplay
Component#
The We are going to define this component in the components
directory, like so:
This component barely displays a FeedItem
in a Card
component from
react-native-paper
. Besides, it allows navigation to the "Article" route when
pressed.
#
The List ComponentSince we have the data consumable with a hook, and individual items, let's
rewrite the default export of screens/HomeScreen.tsx
:
Great! Thus you should be able to see the list on your app. Press a card and
you'll see an empty Article screen showing up. Note that we are using a
Separator
component for consistent spacing above, below and between items.
tip
If you are unfamiliar with the FlatList
component, check out the official guide.
note
You can drag-to-refresh the list to fetch the RSS feed again. This is thanks to
onRefresh
and refreshing
props of the FlatList
component.
Now it's time to refine the Article screen!
#
The Article ScreenTo render the article, we'll need to follow the below steps:
- Fetch the HTML from the given URL;
- Parse the HTML to build a DOM;
- Extract headings from the DOM;
- Render the headings in a Drawer and the DOM in a
ScrollView
withRenderHTMLSource
.
One important note is that we must use the explicit composite rendering
architecture
because we want access to the DOM object from the controlling component to
easily extract headings, which is more tedious when using the
implicit composite architecture (e.g., with the RenderHTML
component).
#
Setting up the Composite Rendering ArchitectureExplicit composite rendering implies that we will replace RenderHTML
with RenderHTMLSource
, which will have two ascendants in the render tree: a TRenderEngineProvider
and a RenderHTMLConfigProvider
. Those
parents will respectively share an engine instance and configuration with any
​RenderHTMLSource
descendant.
A good place to put those providers is the very root of the application. For that end, we will
create a components/WebEngine.tsx
and load it from App.tsx
:
A few remarks on different props used here:
TRenderEngineConfig.ignoreDomTags
prop to ignore irrelevant tags;TRenderEngineConfig.selectDomRoot
prop to select the first DOM element to render;RenderHTMLConfig.enableExperimentalMarginCollapsing
prop to collapse vertical margins.
We will go back to this component later to refine the appearance. For the time
being, we'll focus on features.
A final step is to import the WebEngine
from the root component:
#
Rendering the ArticleArticleBody
Component#
The Let's implement an ArticleBody
component which sole purpose is to display
the rendered DOM when it's ready, and a loading indicator when it's not:
Note that the RenderHTMLSourceProps.source
prop can take a dom
, uri
or html
field. Just as a reminder, we need to
use the dom
source variant because we will have to query
headings displayed in a Drawer menu.
ArticleScreen
#
Back to the We need to produce a dom
object to feed the ArticleBody
component we have
just defined. We propose two hooks to produce this object:
useFetchHtml(url: string)
to fetch the HTML;useArticleDom(url: string)
to create a DOM.
Add this new file in hooks/useArticleDom.ts
:
The important stuff is happening in the useArticleDom
hook. We're using
​useAmbientTRenderEngine
hook to get the
instance of the Transient Render Engine provided by TRenderEngineProvider
,
which in turns offers the parseDocument
method to
transform an HTML string into a DOM. Moreover, note that because we passed selectDomRoot
prop to select the first <article>
met, the returned dom
object will be an
<article>
element. Everything else such as <header>
, <footer>
and other
elements will be ignored.
Finally, let's consume this hook from the ArticleScreen
component and render an ArticleBody
:
Fantastic! It's now rendering the whole article. It's very ugly though, and significantly divergent from the webpage layout, but we'll address that later:
#
The Drawer LayoutWe want to display a menu on the right. For this purpose, we will use the
DrawerLayout
component from react-native-gesture-handler
.
Let's include this component in the ArticleScreen
component:
#
Extracting headingsNow, let's get back to useArticleDom
hook and use an effect to extract headings from the DOM:
The effect is pretty straightforwards. It uses findAll
from domutils
to
extract all h2
and h3
tags, and finally update the headings
state.
We are now ready to define a new TOC
component to render those headings in the drawer.
TOC
Component#
The Let's start by defining a TOCEntry
component in components/TOCEntry.tsx
:
The TOCEntry
renders a Pressable
which label is styled depending of the
tagName
(h2 or h3) and whether it's active (e.g. selected).
Now we're ready to define the TOC
component in components/TOC.tsx
:
Finally, we must render the TOC
component in the ArticleScreen
:
Now, you should have a drawable TOC!
However, pressing an entry won't do anything. It is hence time to tackle the implementation of the tap-to-scroll feature! Let's jump to Part III.