Welcome to react-native-template
π, the go-to template for building mobile applications with Expo and React Native. This template is designed to kickstart your project, utilizing the Expo router, TypeScript, and Nativewind 4.0 (TailwindCSS for React Native) for a smooth and type-safe development experience.
- Quick Start
- Project Structure
- Configuration Files
- Features and Benefits
- Included Packages and Their Benefits
- Customizable Components
- Using the Template Effectively
- Finish Line
To create a new project using this template, run:
git clone https://github.com/zerodays/react-native-template.git my-app
cd my-app
yarn
This template provides a well-organized directory structure with a set of pre-configured files to streamline your development process. Here's an overview of the essential components:
.github
: Contains GitHub Actions workflows for CI/CD.app
: Contains the screens (utilizing expo-router file naming).assets
: Stores static files like images, fonts, and videos.components
: Houses reusable components that can be shared across multiple screens.locales
: Contains localization files for internationalization.utils
: A place for utility functions, hooks, stores and types that can be shared across your application.
Each configuration file is set up to ensure that your development experience is as smooth as possible:
.eslintrc.js
: Configures ESLint for identifying and reporting on patterns in JavaScript, helping you to write clean code..eslintignore
: Lists the files and directories that ESLint should ignore..gitignore
: Specifies intentionally untracked files to ignore by Git, likenode_modules
..prettierrc
: Configuration for Prettier, a code formatter that enforces a consistent style.app.json
: Contains metadata about your app that Expo and EAS Build use to configure your app correctly.babel.config.js
: Configuration file for Babel, a tool that is used to convert ECMAScript 2015+ code into a backwards-compatible version of JavaScript.global.css
: A global stylesheet that imports tailwindcss styles (needed for Nativewind to work).index.js
: The entry point of your React Native application (should NOT be modified).metro.config.js
: Configuration for Metro, the JavaScript bundler for React Native.nativewind-env.d.ts
: TypeScript declaration file for Nativewind, providing autocompletion for Tailwind classes.package.json
: Lists the dependencies of the project and defines build and start (and other) scripts.tailwind.config.js
: Configuration file for Tailwind CSS, where you can define custom styles, themes, and responsive breakpoints.tsconfig.json
: The TypeScript compiler configuration file that specifies the root files and the compiler options required to compile the project.yarn.lock
: Auto-generated file that ensures consistent installation of node modules across environments.
This template comes equipped with a robust set of features and solutions to enhance your development workflow:
Leverage full linting and auto-formatting with ESLint and Prettier, configured to help maintain a clean and consistent codebase. Auto-sorting of imports and Tailwind classes on save, alongside the enforcement of good practices, keeps your project tidy.
For linting and formatting your code, use these commands in package.json
:
"scripts": {
"lint": "eslint .",
"format-check": "prettier --check . --ignore-path .gitignore",
"format-fix": "prettier --write . --ignore-path .gitignore"
}
Clean up your imports with straightforward path aliases in tsconfig.json
, promoting a more organized code structure.
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"paths": {
"@assets/*": ["assets/*"],
"@components/*": ["components/*"],
"@features/*": ["features/*"],
"@utils/*": ["utils/*"]
}
}
Hit the ground running with Nativewind's setup. Utilize the theme
object for consistent styling and the cn
function to conditionally apply classes.
import theme from '@utils/theme';
import { cn } from '@utils/cn';
<Text
className={cn('text-blue-600', value > 0 && 'bg-red-600')}
style={{
backgroundColor: value > 5 ? 'red' : theme.primary[500],
}}>
Example Text
</Text>
Achieve effortless internationalization with react-i18next
and i18next
. Translation keys are typesafe and localisation files are neatly organized.
import { useTranslation } from 'react-i18next';
const { t, i18n } = useTranslation('common');
i18n.changeLanguage('en'); // Switch languages
<Text>{t('helloWorld')}</Text>; // Typesafe keys
Pre-configured routing structure for authenticated and guest users. Routes are typed for hassle-free navigation.
import { Href } from 'expo-router';
type RouteConstructor = <T>(href: Href<T>) => Href<T>;
const Route: RouteConstructor = (href) => href;
const Routes = {
guest: {
index: Route('/(guest)/'),
},
auth: {
index: Route('/(authenticated)/'),
},
} as const;
export default Routes;
// Example usage:
// const router = useRouter();
// router.push(Routes.auth.index);
// router.push(Routes.artists.artist('1').songs.song('2'));
A pre-configured Zodius API client with Tenstack Query for managing API calls. The ./api
folder includes a fully set up example for GET and POST requests, complete with schemas, definitions, and global error handling through a custom Zodius plugin.
import { Zodios } from '@zodios/core';
import { ZodiosHooks } from '@zodios/react';
import apiErrorPlugin from './api-error-plugin';
import exampleApi from './example';
const API_URL = process.env.EXPO_PUBLIC_API_URL || '';
// Zodios API client
const apiClient = new Zodios(API_URL, [...exampleApi]);
// Apply global error handling
apiClient.use(apiErrorPlugin);
// Zodios hooks for react
const api = new ZodiosHooks('exampleApi', apiClient);
export { api, apiClient };
Two custom hooks are provided for enhanced functionality:
useCustomFonts
: Loads custom fonts and manages splash screen visibility.
const [fontsLoaded, fontError] = useCustomFonts({
callback: async () => {
await SplashScreen.hideAsync();
},
});
useIsOnline
: Checks if the device has network connectivity.
const isOnline = useIsOnline();
Effortlessly manage your application state with Zustand, which includes async persistent storage support.
const { value, increment, decrement } = useExampleStore();
Automate your development processes with pre-defined GitHub Actions workflows located in the .github
folder:
build.yml
: For continuous integration builds.lint.yml
: For code linting checks.- A pull request template to standardize contributions.
Incorporate environment variables securely using the Infisical service with a custom script infisical.sh
, which is run to inject variables into your build process.
Integrate Sentry for error monitoring and tracking in your application. The template includes a pre-configured setup for Sentry, allowing you to easily track and resolve issues in your app.
import * as Sentry from 'sentry-expo';
Sentry.init({
debug: true,
dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
environment: process.env.EXPO_PUBLIC_SENTRY_ENV,
...
});
These environment variables are injected into the build process using the Infisical service, ensuring that sensitive information is kept secure.
The react-native-template
includes several packages that extend its capabilities and enrich the development experience. Hereβs a brief overview of these packages and what they offer:
Zod is a TypeScript-first schema declaration and validation library. It allows you to build schemas using TypeScript syntax, ensuring that data conforms to the specified shapes and types at runtime. Zod is particularly useful for validating data received from external sources, such as APIs or user input, and helps enforce type safety throughout the application.
Lottie-React-Native is a mobile library for React Native that parses Adobe After Effects animations exported as json with Bodymovin and renders them natively on mobile. This package enables developers to add high-quality animations to their React Native applications easily. Lottie animations are highly performant and can drastically enhance the user interface by providing fluid, eye-catching animations that can be controlled programmatically.
Lucide-React-Native is a fork of the Feather Icons project, specifically tailored for React Native applications. It provides a collection of beautifully crafted, customizable icons which are easy to use in UI development. Using Lucide icons helps maintain consistency and clarity in the appβs design, making the interface more intuitive and visually appealing.
React Hook Form is a flexible and efficient library for managing forms in React applications. It embraces uncontrolled components and native HTML inputs, utilizing hooks to optimize re-renders and improve performance. React Hook Form reduces the amount of boilerplate code needed to build complex forms while increasing performance compared to traditional form state management practices.
This template includes a set of customizable components that you can use to build your application:
A highly customizable button component that adopts the variants pattern inspired by shadcn/ui
, allowing for a consistent yet adaptable design throughout your application.
The Button
component leverages the power of Tailwind CSS with React Native through Nativewind to offer a range of pre-set button styles, termed 'variants'. You can easily extend or customize these styles to fit your design requirements.
Here's a quick rundown on how it works:
-
Variants: Define the look of your buttons through a
variants
object. Each variant contains styles for the container, text, and optional icon. Variants are applied using thecn
function fromutils/cn.ts
which ensures proper sorting of Tailwind classes. -
Animation: The component uses
react-native-reanimated
to provide feedback when the button is pressed, with a gentle fade-in and move-up effect. -
Icons: You can include left and right icons within the button by passing
LucideIcon
components. -
Feedback Text: Optionally, you can display a feedback text upon button press, which uses the same animation as the button text.
-
Loading State: When the
loading
prop is true, the button displays anActivityIndicator
.
Simply import and use the Button
component in your screens or components, selecting the variant you need and passing any required icons or handlers:
import Button from '@components/Button';
import { HeartIcon } from 'lucide-react-native';
// ...
<Button
variant="filled"
onPress={() => console.log('Button pressed')}
iconLeft={HeartIcon}>
Like
</Button>;
To create or customize variants, edit the variants
object within the button component file. Add your styles using the cn
function for auto-sorting of Tailwind classes:
const variants = {
...,
custom: {
container: cn('rounded-lg bg-custom px-4 py-2'),
text: cn('font-semibold text-custom-dark'),
icon: cn('text-custom-dark'),
},
...
};
To use your custom variant, just set the variant
prop on the Button
component:
<Button variant="custom" onPress={...}>
Custom Button
</Button>
By modifying the Button
component's variants or adding new ones, you can cater to all your button design needs across the application with ease.
A pre-styled Dialog
component, using a context-based approach to manage its visibility. This component provides an easy way to create and control modal dialogs within your application.
Dialog
: Acts as the provider which holds the state and logic for showing and hiding the dialog.DialogContent
: The actual modal view that is displayed. It's customizable and can be dismissable.DialogTrigger
: A button that toggles the dialog's visibility.DialogClose
: A discreet close button usually displayed at the top-right corner of the dialog.DialogHeader
: A styled header for your dialog that can hold a title.DialogFooter
: A footer for the dialog which can contain buttons for actions like 'cancel' or 'confirm'.DialogDismissFooter
: A pre-styled footer with a single button that closes the dialog.
- Initialization: The
Dialog
is set up to manage its state using a context provider, allowing its children to toggle the dialog's visibility without prop drilling.
<Dialog>{/* ... */}</Dialog>
- Displaying Content: The
DialogContent
wraps the content you want to display inside the modal. It's equipped with a fade-in animation andKeyboardAvoidingView
to ensure that input fields are always in view.
<DialogContent>{/* Your content here */}</DialogContent>
- Trigger: Use
DialogTrigger
anywhere within theDialog
context to provide a button that can open the dialog.
<DialogTrigger variant="default">Open Dialog</DialogTrigger>
- Closing:
DialogClose
provides a clickable icon that closes the dialog when pressed.
<DialogClose />
You can customize the dialog by editing the Dialog
's variants or adding new ones directly in the component's file, just like the Button component:
const DialogVariants = {
//... define your custom styles
};
Here's an example of how to use the Dialog
in a component:
<Dialog>
<DialogTrigger variant="filled">Show Terms of Service</DialogTrigger>
<DialogContent>
<DialogHeader>Terms of Service</DialogHeader>
{/* ... Your terms content */}
<DialogFooter>
<DialogClose />
{/* ... Other action buttons */}
</DialogFooter>
</DialogContent>
</Dialog>
This overview should provide you with a good understanding of how to integrate and utilize the Dialog
component in your application for a variety of modal needs.
Loading
component that provides visual feedback to users during loading states. It's flexible, allowing the choice between a native activity indicator and a more visually rich Lottie animation.
- Customizable Message: Display an optional message below the loader to inform users what's happening.
- Choice of Loader: Choose between using the native
ActivityIndicator
for a simple loading experience or a Lottie animation for something more engaging.
The Loading
component checks the nativeLoader
prop to determine which type of loader to display. It will show a Lottie animation by default. If a message
is provided, it will be displayed below the loader.
<Loading message="Loading your dashboard..." />
- Lottie Animation: You can change the Lottie animation by replacing the
LottieLoader
import with another Lottie file. - Message Styling: Customize the message style by adding Tailwind CSS classes to the
<Text>
component.
To implement the Loading
component, import it into your screen or component and add it where you handle your loading logic.
import { Loading } from '@components/Loading';
// ...
return isLoading ? (
<Loading message="Fetching data, please wait..." />
) : (
// Your content
);
With the Loading
component, you provide a seamless and informative loading experience to your application's users.
The FormTextInput
component in the template is a versatile input field designed to work seamlessly with react-hook-form
. It incorporates features like form context integration, custom icons, and support for different input types including text, number, and multiline text.
- Integration with
react-hook-form
: The component is built to automatically hook into theFormProvider
context, making form state management effortless. - Custom Icons: It supports the inclusion of icons within the text input for a polished look.
- Multiline Support: Easily switch between single-line and multiline inputs based on the provided
params
. - Display Mode: A display mode that disables editing, suitable for presenting values in a read-only format.
The FormTextInput
uses a Controller
from react-hook-form
to manage the text input's state and validations. It receives a name
prop which corresponds to the form field it controls.
To use this component, wrap your form in a FormProvider
and then include FormTextInput
for each field you need:
import { FormProvider } from 'react-hook-form';
import FormTextInput from '@components/FormTextInput';
const formMethods = useForm(); // Initialize react-hook-form methods
// ...
<FormProvider {...formMethods}>
<FormTextInput
name="username"
label="Username"
icon={UserIcon} // Replace with an actual icon component
params={{ answerType: 'text' }}
/>
</FormProvider>;
To customize the text input, pass additional props like style
for styling and params
for defining the input's behavior:
<FormTextInput
name="description"
label="Description"
params={{ answerType: 'multiline-text' }}
style={{ ... }}
/>
Pass your desired icon component through the icon
prop:
import { MailIcon } from 'lucide-react-native';
<FormTextInput name="email" label="Email Address" icon={MailIcon} />;
By using FormTextInput
, form inputs are handled elegantly, providing a streamlined user experience for form interactions.
The ValidationError
component in the template is a dedicated UI element for displaying form validation errors. It integrates with react-hook-form
and uses internationalization for error messages.
- Seamless Integration: Works in conjunction with
react-hook-form
to display validation messages. - Internationalization Support: Utilizes
react-i18next
for translating error messages, making the component ready for multilingual applications. - Flexibility: Offers a
tiny
prop to display a smaller-sized error message, suitable for inline errors or limited space scenarios.
The ValidationError
component taps into the FormProvider
context to access the form's error state. It checks if there are any errors associated with the given name
prop and renders the error message if present.
Wrap your form controls within a FormProvider
and position the ValidationError
component where you want to display the error message:
import { FormProvider } from 'react-hook-form';
import FormTextInput from '@components/FormTextInput';
import ValidationError from '@components/ValidationError';
const formMethods = useForm(); // Initialize react-hook-form methods
// ...
<FormProvider {...formMethods}>
<FormTextInput
name="username"
label="Username"
// other props
/>
<ValidationError name="username" />
</FormProvider>;
To show a smaller error message, use the tiny
prop:
<ValidationError name="username" tiny />
For convenience, you can wrap any form control with WithValidationError
to include both the input and its validation message:
import { WithValidationError } from '@components/ValidationError';
// ...
<WithValidationError name="password">
<FormTextInput
name="password"
label="Password"
secureTextEntry
// other props
/>
</WithValidationError>;
The ValidationError
and WithValidationError
components help maintain a clean UI by only showing error messages when necessary, enhancing the user experience with clear feedback.
The Toaster
component is a dynamic and interactive toast notification system designed to provide immediate feedback to users. It's connected to a store for global state management and comes with an API plugin for automatic display on API events.
- Gesture Support: Users can dismiss the toast by dragging it down, thanks to the integrated gesture handler.
- Animated Visibility: Uses
react-native-reanimated
for smooth show and hide animations. - Safe Area Handling: Accounts for device safe areas, ensuring the toast is always visible and accessible.
- Custom Icons: Displays icons for error, success, or information based on the toast type.
The Toaster
component listens to the toast state from useToastStore
. When a toast is set, it animates into view. It can be dismissed with a drag gesture or by pressing the 'Dismiss' button.
The Toaster
component does not need to be manually managed; it works by setting the toast state through the useToastStore
actions:
useToastStore.getState().setToast({
type: 'success',
message: 'Your changes have been saved!',
});
While the Toaster
itself does not require props, you can customize the animations and styles directly within the component's file if needed.
apiToastPlugin
is set up to automatically display toasts in response to API calls, making use of the ZodiosPlugin
system. It provides feedback for errors and successes, skipping certain URLs or GET requests as configured.
Simply add the apiToastPlugin
to your Zodios API client configuration:
const apiClient = new Zodios(API_URL, [
/* ...endpoints */
]);
apiClient.use(apiToastPlugin);
The Toaster
provides a smooth, user-friendly notification mechanism that enhances the interactivity of the application, keeping users informed with minimal disruption.
Stay tuned for more components and features that will be added to the template in the future. We're committed to providing a comprehensive set of tools and solutions to help you build your mobile applications with ease.
To get the most out of the react-native-template
, it's crucial to understand and utilize the recommended folder structure. This structure is meticulously designed to guide best practices in maintaining a clean, scalable, and maintainable codebase, enabling you to build applications quickly and efficiently.
The folder structure serves as a blueprint for organizing your project's files:
/app
: Contains all your screens and pages, adhering to the structure required by Expo Router./api
: Houses Zod schemas, API endpoint definitions, and any inferred types./utils
: For reusable logic across the application./locales
: To manage all localization files./assets
: For all static files like images, fonts, and Lottie animations./components
: Split into two distinct subfolders:/ui
: For generic, reusable UI components./[component-name]
: For large, complex, or specific components like/sidebar
,/complex-navbar
, or/custom-calendar
.
When developing with this template, use the following decision process:
- New Screens: Start by writing code directly in the screen/page file within the
/app
directory. - Component Extraction: If a piece of UI repeats multiple times within the same screen, extract it as a new component within the same file.
- Component Generalization: Once a component is needed across multiple screens, generalize it and place it in the appropriate
/components
subfolder.
- Readability: Keeping simple, screen-specific components within the screen file makes the code easier to follow.
- Efficiency: Components are modularized only when necessary, avoiding premature refactoring.
- Clarity: The structure clarifies where each piece of code should reside.
- Continuous Refinement: Regularly transitioning components from local to global as needed helps to reduce technical debt.
By following this development flow, you can maintain a clean and organized codebase that is easy to navigate and scale as your application grows.
Now go build something amazing with react-native-template
! We hope this template provides you with the tools and structure you need to create high-quality mobile applications efficiently. If you have any questions, feedback, or suggestions, feel free to reach out to us. Happy coding! π