Make professional mobile apps with React Native and Typescript — Manage your data. Make Dark mode and theme your app (Chapter 3 — Part 5)

Thinh Tran
6 min readApr 22, 2020

Manage your data. Make Dark mode and theme your app.

Photo by Balázs Kétyi on Unsplash

Most of the time, the data should rest inside components as their states. The typical case is that the root component is a ‘screen' (for example, the weather screen or the setting screen). In this case, you should place the data inside the root component and pass it down to children via props. If your screens having multiple children components that are independent, you may consider moving the related data into child components. However, there are some cases that data needs to be moved outside:

  • data is used in multiple screens/components. As a result, it becomes global (for example dark/light mode, language).
  • data is passed throw many components in the component tree and we want to avoid it.
  • data is persisted so that we can access it the next time we use the app.

There are 2 approaches: use Contexts and custom hooks which are official API from React and use a 3r party library for state management. The most popular one is Redux. In this tutorial, we won't use Redux but another powerful library based on it called Rematch. It gets much fewer stars on Github, but believe me, you won't regret using it instead of Redux because its syntax is much cleaner and shorter.

Contexts & Custom hooks

Let's go with the first approach first. Continue with your code from the previous part. Otherwise, clone the repository.

git clone -b fetch_api https://github.com/thinhtran3588/react-native-starter-kit.git

(Remember to update your weather API key if you clone it, check the previous part).

Go to src/core/interfaces, create a new file mode.ts & update index.ts.

interfaces

Create a new folder src/core/hooks. Then inside it, create index.ts & use_mode.ts.

hooks

We've already created our first custom hook which is served as the global state of mode (dark/light). It’s only a wrapper of the useState hook for now. We need to pass mode & setMode to the context later so we can access it inside child components. However, due to this caveat of Contexts, to avoid re-rendering each time this hook is called, we need to use another hook useMemo so modeState is only changed when mode is changed.

Next, create a new folder src/core/contexts. Then inside it, create index.ts & use_mode.ts. Then update app.tsx.

contexts & app.tsx

Note that ModeContext is created once outside components. We need to wrap the root component with ModeContext.Provider and pass the modeContext state into its value prop. We also use that state to set the dark/light theme of the ApplicationProvider component.

The last step is to update UI. Go to src/core/components. Update index.ts & icon/icon.tsx.

components

Pro tip: the icon component is the sample of how we can extend an existing component, change its default props or even extend its behaviors. Since we only reference it in our other components, we only have to modify the code in one place.

Pro tip: Normally, we should follow all eslint rules we define. However, in some special cases, we have to ignore them because of using 3rd libraries which are not compliant. Another case is when we extend a component, we need to pass all the props into the child component which is normally forbidden with eslint rule react/jsx-props-no-spreading.

Update the Settings screen so we can toggle Dark/Light mode. Go to src/modules/settings/screens/settings_screen. Create new folders & files with the below structures and update settings_screen.tsx.

settings_screen
components
dark_mode
dark_mode.tsx
index.ts
settings_screen.tsx
settings

Note that inside the DarkMode component, we use the useContext hook to access mode’s value and the function setMode to change it. The value passed into the hook is ModeContext, not ModeContext.Provider or ModeContext.Consumer.
Update navigation.tsx & weather_screen.tsx.

navigation & weather_screen

Now, reload your app. You can try toggle between Dark/Light mode in the Settings screen.

screenshots

Look good now! However, the status bar & the bottom of the iPhone emulator are not changed yet. Go to src/core/components. Create a new folder root_layout & add 2 new files root_layout.tsx & root_layout.styles.ts.

root_layout

Add export * from './root_layout/root_layout'; into src/core/components/index.ts.

Then update app.tsx. Update imports

import {Navigation, ApplicationProvider, light, dark, mapping, RootLayout} from '@core/components';

And update the return statement

<ModeContext.Provider value={mode}>  <ApplicationProvider mapping={mapping} theme={{...theme, ...customTheme}}>    <RootLayout>      <Navigation navItems={navItems} />    </RootLayout>  </ApplicationProvider></ModeContext.Provider>

Reload your app and see the result. Now it looks great!

dark/light screenshots

We can toggle between Dark/Light mode now. However, if we re-open/reload the app, it always changes to the default light mode. Now, we’ll use async-storage to persist it. Run

yarn add @react-native-community/async-storage

Then update use_mode.ts.

use_mode.ts

Try toggle the Dark/Light mode & reload your app. It keeps the last mode you select now.

Rematch

Now we'll try the second approach with Rematch and from my experiences, the better one. Run

yarn add @rematch/core @rematch/persist react-redux redux-persist
yarn add --dev @types/react-redux

to install Rematch and its plugins/dependencies. You should take the time to read about Redux here & Rematch here. Basically, we use Rematch as a central store to store global data and/or persisted data.

Add a new file src/store.ts.

store.ts

Let's look into the code. We create a store to store a big object models as an aggregation of multiple child models, each model is a piece of independent data. Each module can have multiple models if necessary. We use persistPlugin to persist whitelisted data into async-storage, which means we only save data that need to be persisted. We set up the throttle to be 1000 so that updates are bundled written into async-storage each second if any.

Create a new folder src/core/models. Inside it, add 2 new files index.ts & settings.ts.

settings model

A model consists of a state (which normally a JSON object) and reducers (which is a group of functions to update that states). We use immer to make updates easier by modifying draftState directly.

Pro top: Rematch also has effects to asynchronously update state (for example, when you need to call an API, get data and update state). But from my experiences, we should keep the same approach as Redux and don't use it. Instead, we should call an asynchronous action in services, get the result and update the store's state via reducers.

Update app.tsx.

app.tsx

We need to wrap the root component inside the Provider component (which we pass the store into). We use the PersistGate component to show the LoadingScreen component instead of rendering the main content while retrieving data from async-storage. We use the useSelector hook to get state from the store and the useDispatch hook (later) to update the state.

Now, let's add the missing components. Go to src/core/components, update index.ts, create a new folder loading_screen and inside it, add 2 new files loading_screen.tsx & loading_screen.styles.ts.

updated components

The last step is updating root_layout.ts, dark_mode.ts, and remove unused contexts & hooks folders.

updated components

Now, reload your app and enjoy the result. Congratulations! That’s all we need to do in this part.

You can clone the sample here:

git clone -b manage_data https://github.com/thinhtran3588/react-native-starter-kit.git

Or with rematch:

git clone -b manage_data_rematch https://github.com/thinhtran3588/react-native-starter-kit.git

Things you have learned in this part:

  1. use Contexts & custom Hooks to create & persist in the global state.
  2. use Rematch to create & persist in the global state.
  3. theme your app with Dark mode.

--

--

Thinh Tran

Software developer. Interested in web/mobile application development with React, React Native, Typescript, Nodejs and AWS.