Technical Documentation: callstack/react-native-paper
βΉοΈ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference
Overview (`README.md`)
Features
- Follows material design guidelines
- Works on both iOS and Android following platform adaptation guidelines
- Full theming support
Currently supported React Native version: >= 0.50.3
Try it out
Run the example app with Expo to see it in action.
The source code for the examples are under the /example folder.
Getting Started
Refer to the getting started guide for instructions.
Documentation
Check the components and their usage in our documentation.
Contributing
Read the contribution guidelines before contributing.
Figma and Sketch component kits
Use official component kits provided by Material Design.
Made with β€οΈ at Callstack
react-native-paper is an open source project and will always remain free to use. If you think it's cool, please star it π. Callstack is a group of React and React Native geeks, contact us at [email protected] if you need any help with these or just want to say hi!
Contributors
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
Overview (`example/README.md`)
Example App for React Native Paper
React Native App
You can run the React Native app with this Snack. Snack allows you to make changes to the example app directly in the online editor and see changes on your phone instantly using the Expo app without having to install or setup anything on your computer. You can also "Export" it to download as a standalone Expo app to run locally.
If you want to run the example from the repo,
- Clone the repository and run
yarnin the root - Run
yarn example startto start the packager - Follow the instructions to open it with the Expo app
Web App
You can also run the example app as a web app using react-native-web,
- Clone the repository and run
yarnin the root - Run
yarn example webto start the webpack server and open the app in your browser
Pages 1.Getting Started (`docs/pages/1.getting-started.md`)
title: Getting Started
Getting Started
Installation
Open a Terminal in your project's folder and run:
yarn add react-native-paperor
npm install react-native-paperIf you're on a vanilla React Native project, you also need to install and link react-native-vector-icons.
Specifically MaterialCommunityIcons icon pack needs to be included in the project, because some components use those internally (e.g. AppBar.BackAction on Android).
yarn add react-native-vector-icons
react-native link react-native-vector-iconsIf you don't want to install vector icons, you can use babel-plugin-optional-require to opt-out.
If you use Expo, you don't need to install vector icons. But if you have a babel.config.js or .babelrc file, make sure that it includes babel-preset-expo.
To get smaller bundle size by excluding modules you don't use, you can use our optional babel plugin. The plugin automatically rewrites the import statements so that only the modules you use are imported instead of the whole library. Add react-native-paper/babel to the plugins section in your babel.config.js for production environment. It should look like this:
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
env: {
production: {
plugins: ['react-native-paper/babel'],
},
},
};If you created your project using Expo, it'll look something like this:
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
env: {
production: {
plugins: ['react-native-paper/babel'],
},
},
};
};The plugin only works if you are importing the library using ES2015 import statements and not with require.
Note: The above examples are for the latest react-native using Babel 7. If you have react-native <= 0.55, you'll have a .babelrc file instead of a babel.config.js file and the content of the file will be different.
If you're using Flow for typechecking your code, you need to add the following under the [options] section in your .flowconfig:
module.file_ext=.js
module.file_ext=.native.js
module.file_ext=.android.js
module.file_ext=.ios.jsUsage
Wrap your root component in Provider from react-native-paper. If you have a vanilla React Native project, it's a good idea to add it in the component which is passed to AppRegistry.registerComponent. This will usually be in the index.js file. If you have an Expo project, you can do this inside the exported component in the App.js file.
Example:
import * as React from 'react';
import { AppRegistry } from 'react-native';
import { Provider as PaperProvider } from 'react-native-paper';
import { name as appName } from './app.json';
import App from './src/App';
export default function Main() {
return (
<PaperProvider>
<App />
</PaperProvider>
);
}
AppRegistry.registerComponent(appName, () => Main);The PaperProvider component provides the theme to all the components in the framework. It also acts as a portal to components which need to be rendered at the top level.
If you have another provider (such as Redux), wrap it outside PaperProvider so that the context is available to components rendered inside a Modal from the library:
import * as React from 'react';
import { Provider as PaperProvider } from 'react-native-paper';
import { Provider as StoreProvider } from 'react-redux';
import App from './src/App';
import store from './store';
export default function Main() {
return (
<StoreProvider store={store}>
<PaperProvider>
<App />
</PaperProvider>
</StoreProvider>
);
}Customization
You can provide a custom theme to customize the colors, fonts etc. with the Provider component. Check the default theme to see what customization options are supported.
Example:
import * as React from 'react';
import { DefaultTheme, Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';
const theme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
primary: 'tomato',
accent: 'yellow',
},
};
export default function Main() {
return (
<PaperProvider theme={theme}>
<App />
</PaperProvider>
);
}Package.Json (`docs/package.json`)
{
"name": "react-native-paper-docs",
"version": "0.0.1",
"description": "Documentation for React Native Paper",
"private": true,
"author": "",
"license": "MIT",
"scripts": {
"start": "component-docs serve",
"build": "component-docs build",
"clean": "del dist/",
"prestart": "yarn clean"
},
"dependencies": {
"color": "^3.1.2",
"component-docs": "^0.23.0",
"linaria": "^1.2.4",
"react-native-vector-icons": "^6.6.0"
},
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/node": "^7.6.3",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"@babel/preset-flow": "^7.8.3",
"@babel/preset-react": "^7.8.3",
"@babel/preset-typescript": "^7.8.3",
"del-cli": "^3.0.0",
"file-loader": "^4.2.0",
"metro-react-native-babel-preset": "^0.56.0",
"url-loader": "^4.1.0"
}
}
Pages 2.Theming (`docs/pages/2.theming.md`)
title: Theming
Theming
Applying a theme to the whole app
To support custom themes, paper exports a Provider component. You need to wrap your root component with the provider to be able to support themes:
import * as React from 'react';
import { Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';
export default function Main() {
return (
<PaperProvider>
<App />
</PaperProvider>
);
}If no prop is specified, this will apply the default theme to the components. You can also provide a theme prop with a theme object with same properties as the default theme:
import * as React from 'react';
import { DefaultTheme, Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';
const theme = {
...DefaultTheme,
roundness: 2,
colors: {
...DefaultTheme.colors,
primary: '#3498db',
accent: '#f1c40f',
},
};
export default function Main() {
return (
<PaperProvider theme={theme}>
<App />
</PaperProvider>
);
}You can change the theme prop dynamically and all the components will automatically update to reflect the new theme.
A theme usually contains the following properties:
dark(boolean): whether this is a dark theme or light theme.mode('adaptive' | 'exact'): color mode for dark theme (See Dark Theme).roundness(number): roundness of common elements, such as buttons.colors(object): various colors used throughout different elements.primary- primary color for your app, usually your brand color.accent- secondary color for your app which complements the primary color.background- background color for pages, such as lists.surface- background color for elements containing content, such as cards.text- text color for content.disabled- color for disabled elements.placeholder- color for placeholder text, such as input placeholder.backdrop- color for backdrops of various components such as modals.
fonts(object): various fonts used throughout different elements.regularmediumlightthin
animation(object)scale- scale for all animations
When creating a custom theme, you will need to provide all of these properties.
If you don't use a custom theme, Paper will automatically turn animations on/off, depending on device settings.
Otherwise, your custom theme will need to handle it manually, using React Native's AccessibilityInfo API.
Extending the theme
Keeping your own properties in the theme is fully supported by our library:
import * as React from 'react';
import { DefaultTheme, Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';
const theme = {
...DefaultTheme,
// Specify custom property
myOwnProperty: true,
// Specify custom property in nested object
colors: {
myOwnColor: '#BADA55',
}
};
export default function Main() {
return (
<PaperProvider theme={theme}>
<App />
</PaperProvider>
);
}TypeScript
By default it won't work well with TypeScript, but we can take advantage of global augmentations and specify the new properties that we added to the theme:
import * as React from 'react';
import { DefaultTheme, Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';
declare global {
namespace ReactNativePaper {
interface ThemeColors {
myOwnColor: string;
}
interface Theme {
myOwnProperty: boolean;
}
}
}
const theme = {
...DefaultTheme,
// Specify custom property
myOwnProperty: true,
// Specify custom property in nested object
colors: {
myOwnColor: '#BADA55',
}
};
export default function Main() {
return (
<PaperProvider theme={theme}>
<App />
</PaperProvider>
);
}As you can see, custom properties e.g. myOwnColor defined in nested object e.g. colors needs to be declared in its own interface. You'll find more information in our example app where we have it implemented.
Applying a theme to a paper component
If you want to change the theme for a certain component from the library, you can directly pass the theme prop to the component. The theme passed as the prop is merged with the theme from the Provider:
import * as React from 'react';
import { Button } from 'react-native-paper';
export default function ButtonExample() {
return (
<Button raised theme={{ roundness: 3 }}>
Press me
</Button>
);
}Using the theme in your own components
To access the theme in your own components, you can use the withTheme HOC exported from the library. If you wrap your component with the HOC, you'll receive the theme as a prop:
import * as React from 'react';
import { withTheme } from 'react-native-paper';
function MyComponent(props) {
const { colors } = props.theme;
return <Text style={{ color: colors.primary }}>Yo!</Text>;
}
export default withTheme(MyComponent);Components wrapped with withTheme support the theme from the Provider as well as from the theme prop.
You can also use the useTheme hook:
import * as React from 'react';
import { useTheme } from 'react-native-paper';
function MyComponent(props) {
const { colors } = useTheme();
return <Text style={{ color: colors.primary }}>Yo!</Text>;
}Customizing all instances of a component
Sometimes you want to style a component in a different way everywhere but don't want to change the properties in the theme so that other components are not affected. For example, say you want to change the font for all your buttons, but don't want to change theme.fonts.medium because it affects other components.
We don't have an API to do this, because you can already do it with components:
import * as React from 'react';
import { Button } from 'react-native-paper';
export default function FancyButton(props) {
return <Button theme={{ fonts: { medium: 'Open Sans' } }} {...props} />;
}Now you can use your FancyButton component everywhere instead of using Button from Paper.
Dark Theme
Since 3.0 we adapt dark theme to follow Material design guidelines.
In contrast to light theme, dark theme by default uses surface colour instead of primary on large components like AppBar or BottomNavigation.
The dark theme adds a white overlay with opacity depending on elevation of surfaces. It uses it for the better accentuation of surface elevation. Using only shadow is highly imperceptible on dark surfaces.
We are aware that users often use dark theme in their own ways and may not want to use the default dark theme features from the guidelines.
That's why if you are using dark theme you can switch between two dark theme modes:
exactwhere everything is like it was before.AppbarandBottomNavigationwill still use primary colour by default.adaptivewhere we follow Material design guidelines, the surface will use white overlay with opacity to show elevation,AppbarandBottomNavigationwill use surface colour as a background.
If you don't use a custom theme, Paper will automatically change between the default theme and the default dark theme, depending on device settings.
Otherwise, your custom theme will need to handle it manually, using React Native's Appearance API.
Gotchas
The Provider exposes the theme to the components via React's context API, which means that the component must be in the same tree as the Provider. Some React Native components will render a different tree such as a Modal, in which case the components inside the Modal won't be able to access the theme. The work around is to get the theme using the withTheme HOC and pass it down to the components as props, or expose it again with the exported ThemeProvider component.
The Modal component from the library already handles this edge case, so you won't need to do anything.
Pages 3.Icons (`docs/pages/3.icons.mdx`)
export const meta = {
title: "Icons"
}
import IconsList from './src/components/IconsList.js'
Icons
Configuring icons
Many of the components require the react-native-vector-icons library to render correctly. If you're using Expo, you don't need to do anything extra, but if it's a vanilla React Native project, you need link the library as described in the getting started guide.
If you opted out of vector icons support using babel-plugin-optional-require, you won't be able to use icon names for the icon prop. Some components may not look correct without vector icons and might need extra configuration.
Using the `icon` prop
Many components such as Button accept an icon prop which is used to display an icon. The icon prop supports the following types of values:
1. An icon name
You can pass the name of an icon from MaterialCommunityIcons. This will use the react-native-vector-icons library to display the icon.
Example:
<Button icon="camera">
Press me
</Button>See the list of supported icons
2. An image source
You can pass an image source, such as an object of shape { uri: 'https://path.to' } or a local image: require('../path/to/image.png') to use as an icon. The image might be rendered with a different color than the one provided depending on the component. If don't want this behavior, see the next example to pass an Image element.
Remote image:
<Button icon={{ uri: 'https://avatars0.githubusercontent.com/u/17571969?v=3&s=400' }}>
Press me
</Button>Local image:
<Button icon={require('../assets/chameleon.jpg')}>
Press me
</Button>3. A render function
You can pass a function which returns a react element to be used an icon. The function receives an object with size and color properties as its arguments. The element is used as is without any modification. However, it might get clipped if the provided element's size is bigger than what the component renders. It's up to you to make sure that the size of the element is correct.
Example:
<Button
icon={({ size, color }) => (
<Image
source={require('../assets/chameleon.jpg')}
style={{ width: size, height: size, tintColor: color }}
/>
)}
>
Press me
</Button>4. Use custom icons
If you want to use icons other than MaterialCommunityIcons you need to import the icons and pass it to the settings prop within PaperProvider.
Example:
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
// ...
<PaperProvider
settings={{
icon: props => <AwesomeIcon {...props} />,
}}
theme={this.state.theme}
>
// ...
</PaperProvider>RTL support
If you want your icon to behave properly in a RTL environment, you can pass an object to the icon prop with shape: { source: { uri: 'https://path.to' }, direction : 'rtl' }. source can be any of the values that the icon prop accepts in option 1 and option 2. For direction you have a few options:
auto- uses the device language to determine if icon should be displayed from rtl. Uses theI18nManagermodule to get this info.rtl- flips the icon so that it is rtl, this is regardless of the device language.ltr- displays from ltr, even if in an rtl environment.
Example for using an image source:
<Button icon={{ source: { uri: 'https://avatars0.githubusercontent.com/u/17571969?v=3&s=400' }, direction: 'rtl' }}>
Press me
</Button>Example for using an icon name:
<Button icon={{ source: "add-a-photo", direction: 'rtl' }}>
Press me
</Button>You can also use a render function. Along with size and color, you have access to direction which will either be 'rtl' or 'ltr'. You can then decide how to render your icon component accordingly.
Example of using a render function:
<Button
icon={({ size, color, direction }) => (
<Image
source={require('../assets/chameleon.jpg')}
style={[
{
transform: [{ scaleX: direction === 'rtl' ? -1 : 1 }],
},
{
width: size,
height: size,
tintColor: color
}
]}
/>
)}
>
Press me
</Button>Pages 4.Fonts (`docs/pages/4.fonts.md`)
title: Fonts
Fonts
Installing custom fonts
The easiest way to install custom fonts to your RN project is do as follows:
- Define path to assets directory with fonts in project:
Example:
// React Native < 0.60 package.json
...
"rnpm": {
"assets": [
"fonts"
]
},
...
// React Native >= 0.60 react-native.config.js
module.exports = {
...
"assets": [
"fonts"
],
...Note: fonts is a folder with .ttf files
- Place your font files in your assets folder.
- Link font files using '
react-native link' command. - Restart your project to refresh changes.
Now, you are able to use fontFamily from font files.
Configuring fonts in ThemeProvider
To create a custom font, prepare a fontConfig object where fonts are divided by platforms. After that, you have to pass the fontConfig into configureFonts method in a custom theme.
The fontConfig object accepts ios, android, macos, windows, web, and native. Use these to override fonts on particular platforms.
Note: At a minimum, you need to explicitly pass fonts for android, ios, and web.
Check the default theme to see what customization options are supported.
import * as React from 'react';
import { configureFonts, DefaultTheme, Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';
const fontConfig = {
web: {
regular: {
fontFamily: 'sans-serif',
fontWeight: 'normal',
},
medium: {
fontFamily: 'sans-serif-medium',
fontWeight: 'normal',
},
light: {
fontFamily: 'sans-serif-light',
fontWeight: 'normal',
},
thin: {
fontFamily: 'sans-serif-thin',
fontWeight: 'normal',
},
},
ios: {
regular: {
fontFamily: 'sans-serif',
fontWeight: 'normal',
},
medium: {
fontFamily: 'sans-serif-medium',
fontWeight: 'normal',
},
light: {
fontFamily: 'sans-serif-light',
fontWeight: 'normal',
},
thin: {
fontFamily: 'sans-serif-thin',
fontWeight: 'normal',
},
},
android: {
regular: {
fontFamily: 'sans-serif',
fontWeight: 'normal',
},
medium: {
fontFamily: 'sans-serif-medium',
fontWeight: 'normal',
},
light: {
fontFamily: 'sans-serif-light',
fontWeight: 'normal',
},
thin: {
fontFamily: 'sans-serif-thin',
fontWeight: 'normal',
},
}
};
const theme = {
...DefaultTheme,
fonts: configureFonts(fontConfig),
};
export default function Main() {
return (
<PaperProvider theme={theme}>
<App />
</PaperProvider>
);
}Pages 5.React Native Web (`docs/pages/5.react-native-web.md`)
title: Using on the Web
Using on the Web
Pre-requisites
Make sure that you have followed the getting started guide and have react-native-paper installed and configured before following this guide.
We're going to use react-native-web and webpack to use React Native Paper on the web, so let's install them as well.
To install react-native-web, run:
yarn add react-native-web react-dom react-artUsing CRA ([Create React App](https://github.com/facebook/create-react-app))
Install react-app-rewired to override webpack configuration:
yarn add --dev react-app-rewiredConfigure babel-loader using a new file called config-overrides.js:
module.exports = function override(config, env) {
config.module.rules.push({
test: /\.js$/,
exclude: /node_modules[/\\](?!react-native-vector-icons)/,
use: {
loader: "babel-loader",
options: {
// Disable reading babel configuration
babelrc: false,
configFile: false,
// The configuration for compilation
presets: [
["@babel/preset-env", { useBuiltIns: "usage" }],
"@babel/preset-react",
"@babel/preset-flow",
"@babel/preset-typescript"
],
plugins: [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread"
]
}
}
});
return config;
};Change your script in package.json:
/* package.json */
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test --env=jsdom",
+ "test": "react-app-rewired test --env=jsdom"
}Custom webpack setup
To install webpack, run:
yarn add --dev webpack webpack-cli webpack-dev-serverIf you don't have a webpack config in your project, copy the following to webpack.config.js get started:
const path = require('path');
module.exports = {
mode: 'development',
// Path to the entry file, change it according to the path you have
entry: path.join(__dirname, 'App.js'),
// Path for the output files
output: {
path: path.join(__dirname, 'dist'),
filename: 'app.bundle.js',
},
// Enable source map support
devtool: 'source-map',
// Loaders and resolver config
module: {
rules: [
],
},
resolve: {
},
// Development server config
devServer: {
contentBase: [path.join(__dirname, 'public')],
historyApiFallback: true,
},
};Also create a folder named public and add the following file named index.html:
<!doctype html>
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1" />
<title>App</title>
<style>
html, body, #root {
height: 100%;
}
#root {
display: flex;
flex-direction: column;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="app.bundle.js"></script>
</body>Now we're ready to start configuring the project.
Configure webpack
1. Alias `react-native` to `react-native-web`
First, we have to tell webpack to use react-native-web instead of react-native. Add the following alias in your webpack config under resolve:
alias: {
'react-native$': require.resolve('react-native-web'),
}2. Configure `babel-loader`
Next, we want to tell babel-loader to compile react-native-paper and react-native-vector-icons. We would also want to disable reading the babel configuration files to prevent any conflicts.
First install the required dependencies:
yarn add --dev babel-loader @babel/preset-env @babel/preset-react @babel/preset-flow @babel/preset-typescript @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spreadNow, add the following in the module.rules array in your webpack config:
{
test: /\.js$/,
exclude: /node_modules[/\\](?!react-native-vector-icons)/,
use: {
loader: 'babel-loader',
options: {
// Disable reading babel configuration
babelrc: false,
configFile: false,
// The configuration for compilation
presets: [
['@babel/preset-env', { useBuiltIns: 'usage' }],
'@babel/preset-react',
'@babel/preset-flow',
"@babel/preset-typescript"
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-object-rest-spread'
],
},
},
},3. Configure `file-loader`
To be able to import images and other assets using require, we need to configure file-loader. Let's install it:
yarn add --dev file-loaderTo configure it, add the following in the module.rules array in your webpack config:
{
test: /\.(jpg|png|woff|woff2|eot|ttf|svg)$/,
loader: 'file-loader',
}Load the Material Community Icons
If you followed the getting started guide, you should have the following code in your root component:
<PaperProvider>
<App />
</PaperProvider>Now we need tweak this section to load the Material Community Icons from the react-native-vector-icons library:
<PaperProvider>
<React.Fragment>
{Platform.OS === 'web' ? (
<style type="text/css">{`
@font-face {
font-family: 'MaterialCommunityIcons';
src: url(${require('react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf')}) format('truetype');
}
`}</style>
) : null}
<App />
</React.Fragment>
</PaperProvider>Remember to import Platform from react-native at the top:
import { Platform } from 'react-native';You can also load these fonts using css-loader if you prefer.
Load the Roboto fonts (optional)
The default theme in React Native Paper uses the Roboto font. You can add them to your project following the instructions on its Google Fonts page.
We're done!
You can run webpack-dev-server to run the webpack server and open your project in the browser. You can add the following script in your package.json under the "scripts" section to make it easier:
"web": "webpack-dev-server --open"Now you can run yarn web to run the project on web.
Pages 6.Recommended Libraries (`docs/pages/6.recommended-libraries.md`)
title: Recommended Libraries
Recommended Libraries
Our mission is to provide a full suite of well-integrated components built with Material Design in mind. However, many components already have better well maintained implementations.
Here are some of the libraries we recommend:
Tabs
react-native-community/react-native-tab-view
Material Design themed swipeable tabs, maintained by @satya164 and @mosdnk.
Bottom sheet
An implementation bottom sheet behaviour, maintained by @mosdnk.
Date Picker
web-ridge/react-native-paper-dates
react-native-community/react-native-datetimepicker
Time Picker
web-ridge/react-native-paper-dates
Pages 7.Contributing (`docs/pages/7.contributing.md`)
title: Contributing
/../../CONTRIBUTING.md
Pages 8.Theming With React Navigation (`docs/pages/8.theming-with-react-navigation.md`)
title: Theming with React Navigation
Theming with React Navigation
In this guide we will look into how to apply theming for an application using React Native Paper and React Navigation at the same time.
Offering different theme options, especially dark/light ones, becomes increasingly a standard requirement of the modern mobile application. Fortunately, both React Navigation and React Native Paper support configurable theming out-of-the-box.
But how to make them work together?
Combining theme objects
Both libraries require a wrapper to be used at the entry point of the application.
React Navigation exposes NavigationContainer which ensures that navigation works correctly, but also accepts theme as an optional property. Read more about setting up navigation here.
For React Native Paper theme to work, we need to use PaperProvider also at application's entry point.
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { TouchableOpacity } from 'react-native';
import {
Card,
Title,
Paragraph,
List,
Provider as PaperProvider,
} from 'react-native-paper';
const Stack = createStackNavigator();
const HomeScreen = ({ navigation }) => (
<TouchableOpacity
onPress={() =>
navigation?.push('Details', {
title,
content,
})
}
>
<Card>
<Card.Content>
<Title>{title}</Title>
<Paragraph>{content}</Paragraph>
</Card.Content>
</Card>
</TouchableOpacity>
);
const DetailsScreen = (props) => {
const { title, content } = props?.route?.params;
return (
<List.Section>
<List.Subheader>{title}</List.Subheader>
<List.Item title={content} />
</List.Section>
);
};
export default function App() {
return (
<PaperProvider>
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
</PaperProvider>
);
}Fortunately, both React Navigation and React Native Paper offer very similar API when it comes to theming. It's possible to import default themes in light and dark variants from both.
React Navigation and React Native Paper use the same name for default themes - DefaultTheme and DarkTheme, so we need to alias them at the imports.
Our goal here is to combine those two themes, so that we could control the theme for the entire application from a single place.
To make things easier we can use deepmerge package. With yarn we can install it like this
yarn add deepmergeimport {
NavigationContainer,
DarkTheme as NavigationDarkTheme,
DefaultTheme as NavigationDefaultTheme,
} from '@react-navigation/native';
import {
DarkTheme as PaperDarkTheme,
DefaultTheme as PaperDefaultTheme,
Provider as PaperProvider,
} from 'react-native-paper';
import merge from 'deepmerge';
const CombinedDefaultTheme = merge(PaperDefaultTheme, NavigationDefaultTheme);
const CombinedDarkTheme = merge(PaperDarkTheme, NavigationDarkTheme);Alternatively, we could merge those themes using vanilla JavaScript
const CombinedDefaultTheme = {
...PaperDefaultTheme,
...NavigationDefaultTheme,
colors: {
...PaperDefaultTheme.colors,
...NavigationDefaultTheme.colors,
},
};
const CombinedDarkTheme = {
...PaperDarkTheme,
...NavigationDarkTheme,
colors: {
...PaperDarkTheme.colors,
...NavigationDarkTheme.colors,
},
};Passing theme with Providers
After combining the themes, we will be able to control theming in both libraries from a single source, which will come in handy later.
Next, we need to pass merged themes into the Providers. For this part, we use the dark one - CombinedDarkTheme.
const CombinedDefaultTheme = merge(PaperDefaultTheme, NavigationDefaultTheme);
const CombinedDarkTheme = merge(PaperDarkTheme, NavigationDarkTheme);
const Stack = createStackNavigator();
export default function App() {
return (
<PaperProvider theme={CombinedDarkTheme}>
<NavigationContainer theme={CombinedDarkTheme}>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
</PaperProvider>
);
}Customizing theme
We don't need to limit ourselves to the themes offered by the libraries in default. Both packages allow for custom themes to be applied.
You can learn all about it their documentations:
React Context for theme customization
Now, we wouldn't want to stay forever with dark theme being on, which is why we need to gain the ability to control theme dynamically. A bit of state management is needed for this purpose.
React Context proves itself very useful in handling cross-cutting concerns like global theme handling, so we will use just that.
Creating Context
First, we define our Context.
import React from 'react';
export const PreferencesContext = React.createContext({
toggleTheme: () => {},
isThemeDark: false,
});Using Context
Context Provider should be imported also at the entry point, as we want it to wrap the whole app, for the theme values to be accessible at every component that we have.
import React from 'react';
import { PreferencesContext } from './PreferencesContext';
const Stack = createStackNavigator();
const CombinedDefaultTheme = merge(PaperDefaultTheme, NavigationDefaultTheme);
const CombinedDarkTheme = merge(PaperDarkTheme, NavigationDarkTheme);
export default function App() {
const [isThemeDark, setIsThemeDark] = React.useState(false);
let theme = isThemeDark ? CombinedDarkTheme : CombinedDefaultTheme;
const toggleTheme = React.useCallback(() => {
return setIsThemeDark(!isThemeDark);
}, [isThemeDark]);
const preferences = React.useMemo(
() => ({
toggleTheme,
isThemeDark,
}),
[toggleTheme, isThemeDark]
);
return (
// Context is wired into the local state of our main component, so that its values could be propagated throughout the entire application
<PreferencesContext.Provider value={preferences}>
<PaperProvider theme={theme}>
<NavigationContainer theme={theme}>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
</PaperProvider>
</PreferencesContext.Provider>
);
}Now that the Context is available at every component, all we need to do is import it. Next thing is to provide the user with some UI element to control changing the theme. We will use Paper's Switch for this purpose.
import React from 'react';
import { useTheme, Appbar, TouchableRipple, Switch } from 'react-native-paper';
import { PreferencesContext } from './PreferencesContext';
const Header = ({ scene }) => {
const theme = useTheme();
const { toggleTheme, isThemeDark } = React.useContext(PreferencesContext);
return (
<Appbar.Header
theme={{
colors: {
primary: theme?.colors.surface,
},
}}
>
<Appbar.Content title={scene.route?.name} />
<TouchableRipple onPress={() => toggleTheme()}>
<Switch
style={[{ backgroundColor: theme.colors.accent }]}
color={'red'}
value={isThemeDark}
/>
</TouchableRipple>
</Appbar.Header>
);
};And now you can switch between light and dark theme!
Thanks to the linking of themes that we did earlier, switching themes can be controlled with only one piece of state.
React Native Paper components will automatically use provided theme thanks to the PaperProvider that is wrapped around the entry point of our application, but we can also access theme values manually with useTheme hook,
exposed by the library. You can see how it's done in the Header component code above.
If light/dark themes are not enough for your use case, you can learn more about creating Material Design themes here.
On master branch of the example app, you will find implemented Menu component, which allows to choose a few custom themes. Inspecting code in utils and Header may give you some idea how to use your own themes with Paper, in addition to dedicated docs.
Read more about integrating Paper with React Navigation in a brilliant article by @trensik
Pages 9.React Navigation (`docs/pages/9.react-navigation.md`)
title: Integrate AppBar with react-navigation
Integrate AppBar with react-navigation
Prerequisites
react-native-paperreact-navigation
We assume that you have already installed the mentioned libraries above, otherwise please check out the guides below.
React Native Paper - Getting Started
React Navigation - Getting Started
Stack Navigator
We will start with react-navigation by creating a basic navigation stack. Stack navigator gives us a possibility of transition between screens in our app and manage navigation's history. In a simple scenario where there is only one stack navigator present in the app, it resembles a navigation state in a browser.
Screens are pushed and popped from the stack while the user navigates to a new screen or go back to the previous one.
Let's create two screens. A main screen named Home and details screen named Details.
import 'react-native-gesture-handler';
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}At the moment our navigation stack contains two screens and will render HomeScreen or DetailsScreen components according to the current navigation state. We have not implemented those components yet, so let's do this now:
import React from 'react';
import {View, Text, Button, StyleSheet} from 'react-native';
function HomeScreen() {
return (
<View style={style.container}>
<Text>Home Screen</Text>
</View>
);
}
function DetailsScreen() {
return (
<View style={style.container}>
<Text>Details Screen</Text>
</View>
);
}
const style = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});Once we have finished implementing the components, we can run the app and check how Stack looks like.
To navigate from HomeScreen to DetailsScreen we can use the navigation object provided by Stack.Screen component. Every component rendered by Stack.Screen has an access to the navigation object via props. Let's modify our HomeScreen component:
function HomeScreen({ navigation }) {
return (
<View style={style.container}>
<Text>Home Screen</Text>
<Button
title="Go to details"
onPress={() => navigation.navigate('Details')}
/>
</View>
);
}Our result:
As you can see, we can already navigate between two screens. In the next steps, we will show you how to use Paper's AppBar instead of the default header.
Adding AppBar
We can customize Stack's header by passing custom component:
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
header: CustomNavigationBar,
}}>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}Now we will implement CustomNavigationBar using AppBar component:
import { Appbar } from 'react-native-paper';
function CustomNavigationBar() {
return (
<Appbar.Header>
<Appbar.Content title="My awesome app" />
</Appbar.Header>
);
}Current implementation of the CustomNavigationBar is simple - we just render a title inside of it. You may notice there is no way to go back to the previous screen, because the back button is not visible in the header. Let's add it now and let's make sure it's visible on all Stack's screens except Home screen.
Firstly, pass navigation props to CustomNavigationBar:
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
header: (props) => <CustomNavigationBar {...props} />,
}}>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}Secondly, we check if navigation has previous state. If it has, it means there is another screen on the stack beneath the current screen and we should render the back arrow button in such a case.
function CustomNavigationBar({ navigation, previous }) {
return (
<Appbar.Header>
{previous ? <Appbar.BackAction onPress={navigation.goBack} /> : null}
<Appbar.Content title="My awesome app" />
</Appbar.Header>
);
}Another interesting pattern that can be implemented with react-native-paper and react-navigation is a "hamburger menu". Thanks to the Menu component we can add a nice looking pop-up to our Appbar. To implement this feature we need to make a couple of changes in CustomNavigationBar:
- Render a
Menucomponent - Pass
Appbar.Actionto the anchor prop - Add a state to control
Menuvisibility
We also want the menu to appear only on HomeScreen, which means we will render it conditionally based on the previous prop.
function CustomNavigationBar({ navigation, previous }) {
const [visible, setVisible] = React.useState(false);
const openMenu = () => setVisible(true);
const closeMenu = () => setVisible(false);
return (
<Appbar.Header>
{previous ? <Appbar.BackAction onPress={navigation.goBack} /> : null}
<Appbar.Content title="My awesome app" />
{!previous ? (
<Menu
visible={visible}
onDismiss={closeMenu}
anchor={
<Appbar.Action icon="menu" color="white" onPress={openMenu} />
}>
<Menu.Item onPress={() => {console.log('Option 1 was pressed')}} title="Option 1" />
<Menu.Item onPress={() => {console.log('Option 2 was pressed')}} title="Option 2" />
<Menu.Item onPress={() => {console.log('Option 3 was pressed')}} title="Option 3" disabled />
</Menu>
) : null}
</Appbar.Header>
);
}Final result:
That's all we need! We have app bar that contains everything we need to navigate through screens and access an additional menu on the main screen. As you can see, with Material design Appbar provided by react-native-paper used together with react-navigation we can easily create an app that looks and works great.
--- METRICS ---
- Files Extracted: 12
- Estimated Token Budget: ~14401 tokens
- Canonical Reference: https://codewiki.google/github.com/callstack/react-native-paper