diff --git a/404.html b/404.html index 32b7d262..9adbb6d4 100644 --- a/404.html +++ b/404.html @@ -10,8 +10,8 @@ - - + +
Skip to main content

Authentication with Supabase

Overview

+

Authentication with Supabase

Overview

Many applications require an external service to authenticate the user. Setting up authentication for your application can feel daunting. Where do I start? What data do I need from my users? What service(s) should or could I use? What are the signup, signin, and other user flows that I'll need?

This recipe is going to use Supabase as the backend. We'll build some primitives that will allow you to customize the authentication to your needs or existing backend service as well.

Requirements

diff --git a/docs/recipes/CircleCIRNSetup/index.html b/docs/recipes/CircleCIRNSetup/index.html index 41b5b7f3..de904bc1 100644 --- a/docs/recipes/CircleCIRNSetup/index.html +++ b/docs/recipes/CircleCIRNSetup/index.html @@ -10,8 +10,8 @@ - - + +

CircleCI CD Setup - React Native

This document shows the steps necessary to set up CircleCI automatic continuous integration testing and automatic Fastlane beta builds upon successfully merging a pull request.

+

CircleCI CD Setup - React Native

This document shows the steps necessary to set up CircleCI automatic continuous integration testing and automatic Fastlane beta builds upon successfully merging a pull request.

First Things First

  1. Write Tests
  2. diff --git a/docs/recipes/CreatingGreateExperienceForScreenReaders/index.html b/docs/recipes/CreatingGreateExperienceForScreenReaders/index.html index c5dbfeef..035208e0 100644 --- a/docs/recipes/CreatingGreateExperienceForScreenReaders/index.html +++ b/docs/recipes/CreatingGreateExperienceForScreenReaders/index.html @@ -10,8 +10,8 @@ - - + +

    Creating a Good Experience for Screen Readers

    UI Patterns

    +

    Creating a Good Experience for Screen Readers

    UI Patterns

    Screens

    Titles All screen should ideally have unique titles, to make it easier to know quickly which screen you're on source.

    diff --git a/docs/recipes/DetoxIntro/index.html b/docs/recipes/DetoxIntro/index.html index 6b27ccca..b3e0fad4 100644 --- a/docs/recipes/DetoxIntro/index.html +++ b/docs/recipes/DetoxIntro/index.html @@ -10,8 +10,8 @@ - - + +

    Detox Intro

    Detox is a library for end-to-end testing of React Native apps. This wiki provides information on how to use Detox effectively.

    +

    Detox Intro

    Detox is a library for end-to-end testing of React Native apps. This wiki provides information on how to use Detox effectively.

    Installation

    Detox's documentation for installation.

    What's unique about Detox

    diff --git a/docs/recipes/DistributingAuthTokenToAPI/index.html b/docs/recipes/DistributingAuthTokenToAPI/index.html index 6adaee02..30fb7f02 100644 --- a/docs/recipes/DistributingAuthTokenToAPI/index.html +++ b/docs/recipes/DistributingAuthTokenToAPI/index.html @@ -10,8 +10,8 @@ - - + +

    Distributing Auth Token to APISauce

    +

    Distributing Auth Token to APISauce

    Building off of the Ignite Boilerplate, this recipe will show you how to connect your Mobx State Tree Authentication Store with an Apisauce instance to make authenticating your API requests a breeze.

    Review of API Instance and Auth Store

    To start off let's quickly review the boilerplate Auth Store and API Instance.

    diff --git a/docs/recipes/EASUpdate/index.html b/docs/recipes/EASUpdate/index.html index 89afd032..4566a0b2 100644 --- a/docs/recipes/EASUpdate/index.html +++ b/docs/recipes/EASUpdate/index.html @@ -10,8 +10,8 @@ - - + +

    EAS Update

    +

    EAS Update

    This guide will teach you how to set up over-the-air (OTA) updates with Expo and EAS Update within an Ignite project.

    Appetizer

    You'll also need eas-cli globally installed and and an Expo account if you don't already have one.

    diff --git a/docs/recipes/EnforcingImportOrder/index.html b/docs/recipes/EnforcingImportOrder/index.html index 795a465b..fee11778 100644 --- a/docs/recipes/EnforcingImportOrder/index.html +++ b/docs/recipes/EnforcingImportOrder/index.html @@ -10,8 +10,8 @@ - - + +

    Enforcing JS/TS Import Order

    +

    Enforcing JS/TS Import Order

    Overview

    With Intellisense and Copilot at our fingertips, it's easy to forget that the order of imports in a file can have a big impact on the readability and maintainability of your code. Import helpers typically just put the autogenerated import at the bottom of all the other imports.

    This recipe will show you how to enforce a consistent import order in your project using the @serverless-guru/prettier-plugin-import-order prettier plugin.

    diff --git a/docs/recipes/EnvironmentVariables/index.html b/docs/recipes/EnvironmentVariables/index.html index f419b8fd..7dce49c2 100644 --- a/docs/recipes/EnvironmentVariables/index.html +++ b/docs/recipes/EnvironmentVariables/index.html @@ -10,8 +10,8 @@ - - + +

    Environment Variables

    +

    Environment Variables

    Setup

    Install

    yarn add -D dotenv babel-plugin-inline-dotenv
    diff --git a/docs/recipes/ExpoRouter/index.html b/docs/recipes/ExpoRouter/index.html index 108fa289..47482a0e 100644 --- a/docs/recipes/ExpoRouter/index.html +++ b/docs/recipes/ExpoRouter/index.html @@ -10,8 +10,8 @@ - - + +

    Expo Router

    +

    Expo Router

    Overview

    Expo Router brings file-based routing to React Native and web applications allowing you to easily create universal apps. Whenever a file is added to your src/app directory, a new path is automatically added to your navigation.

    For the full documentation by Expo, head on over to the Introduction to Expo Router.

    diff --git a/docs/recipes/GeneratorComponentTests/index.html b/docs/recipes/GeneratorComponentTests/index.html index f270b46a..652e6592 100644 --- a/docs/recipes/GeneratorComponentTests/index.html +++ b/docs/recipes/GeneratorComponentTests/index.html @@ -10,8 +10,8 @@ - - + +

    Add component tests to npx ignite-cli generate component

    +

    Add component tests to npx ignite-cli generate component

    Did you know that Ignite automatically generates files for you? And that you can customize those generators?

    Here is how to automatically generate components and tests for them using @testing-library/react-native

    Setup @testing-library/react-native

    diff --git a/docs/recipes/LocalFirstDataWithPowerSync/index.html b/docs/recipes/LocalFirstDataWithPowerSync/index.html index 6ef8c14f..28b17686 100644 --- a/docs/recipes/LocalFirstDataWithPowerSync/index.html +++ b/docs/recipes/LocalFirstDataWithPowerSync/index.html @@ -10,8 +10,8 @@ - - + +

    PowerSync and Supabase for Local-First Data Management

    +

    PowerSync and Supabase for Local-First Data Management

    Introduction

    This guide helps you integrate PowerSync with Supabase in an Ignite app for efficient local-first data management.

    PowerSync allows your app to work smoothly offline while keeping the data in sync with your backend database.

    diff --git a/docs/recipes/MaestroSetup/index.html b/docs/recipes/MaestroSetup/index.html index 83d9ffac..d0d6c2e4 100644 --- a/docs/recipes/MaestroSetup/index.html +++ b/docs/recipes/MaestroSetup/index.html @@ -10,8 +10,8 @@ - - + +

    Setting Up Maestro in Ignite

    +

    Setting Up Maestro in Ignite

    Overview

    End-to-end (e2e) testing is a critical part of any application but it can be difficult to set up and maintain. Maestro is a tool that promises to be easy to set up and maintain e2e tests. This recipe will walk you through setting up Maestro in your Ignite project.

    Maestro Installation

    diff --git a/docs/recipes/MigratingToI18Next/index.html b/docs/recipes/MigratingToI18Next/index.html index 55adab4d..f28643bd 100644 --- a/docs/recipes/MigratingToI18Next/index.html +++ b/docs/recipes/MigratingToI18Next/index.html @@ -10,8 +10,8 @@ - - + +

    Migrating from i18n-js to react-i18next within an Ignite project

    +

    Migrating from i18n-js to react-i18next within an Ignite project

    Overview

    In this guide, we will be going through the steps required to migrate your Ignite generated project from using i18n-js to the react-i18next library. It is meant specifically for projects generated with version 9 and below.

    react-i18next will be included in Ignite's version 10. If you're using an earlier version, this guide provides the necessary steps to successfully complete the migration.

    diff --git a/docs/recipes/MigratingToMMKV/index.html b/docs/recipes/MigratingToMMKV/index.html index c3ffc8f0..7e2592ae 100644 --- a/docs/recipes/MigratingToMMKV/index.html +++ b/docs/recipes/MigratingToMMKV/index.html @@ -10,8 +10,8 @@ - - + +

    Migrating to MMKV

    +

    Migrating to MMKV

    Overview

    MMKV is said to be the fastest key/value storage for React Native. It has encryption support for secure local storage and also uses synchronous storage to simplify your application code.

    In this recipe, we'll convert our the Ignite demo project from using AsyncStorage to MMKV.

    diff --git a/docs/recipes/MonoreposOverview/index.html b/docs/recipes/MonoreposOverview/index.html index b0f78a2b..eebf7d1c 100644 --- a/docs/recipes/MonoreposOverview/index.html +++ b/docs/recipes/MonoreposOverview/index.html @@ -10,8 +10,8 @@ - - + +

    Choosing the right monorepo strategy for your project

    +

    Choosing the right monorepo strategy for your project

    Introduction

    When embarking on a software development project, particularly in the context of React Native and using the Ignite framework, the decision to adopt a monorepo structure is critical. A monorepo can streamline development, foster better code sharing, and simplify dependency management. However, it's not a one-size-fits-all solution. This document explores when you should consider using a monorepo, when you might want to avoid it, the most common monorepo options available, and typical setups for software applications.

    When to use a monorepo

    diff --git a/docs/recipes/PatchingBuildingAndroid/index.html b/docs/recipes/PatchingBuildingAndroid/index.html index fc1b36b1..df976636 100644 --- a/docs/recipes/PatchingBuildingAndroid/index.html +++ b/docs/recipes/PatchingBuildingAndroid/index.html @@ -10,8 +10,8 @@ - - + +

    Patching/Building Android .aar From Source

    Why?

    +

    Patching/Building Android .aar From Source

    Why?

    Sometimes, a situation arises when you might want to update the react-native Android source code without upgrading react-native itself. For example, there's a new bug on Android 12 where the application crashes due to some bug with the animation queue. The potential fix is available on the main (unreleased) branch, but your app version is a few patches behind. Another situation is when you simply can't upgrade your react-native version yet, but need a fix from future version. In these cases, you can use this approach to "patch" your Android source files and build new .aar binary and use that for your app.

    Official Guides

    The official steps to build from source are provided by react-native and you can find them here.

    diff --git a/docs/recipes/PrepForEASBuild/index.html b/docs/recipes/PrepForEASBuild/index.html index 0769c82e..7ef94d08 100644 --- a/docs/recipes/PrepForEASBuild/index.html +++ b/docs/recipes/PrepForEASBuild/index.html @@ -10,8 +10,8 @@ - - + +

    Prepping Ignite for EAS Build

    +

    Prepping Ignite for EAS Build

    This guide will teach you how to set up an Expo development build which prepares your project for native code via Config Plugins, but keeps you in Expo's managed workflow.

    Appetizer

    Start with a fresh Ignite app, but choose the prebuild workflow:

    diff --git a/docs/recipes/ReactNativeVisionCamera/index.html b/docs/recipes/ReactNativeVisionCamera/index.html index 253cfaf8..6eaf4fe3 100644 --- a/docs/recipes/ReactNativeVisionCamera/index.html +++ b/docs/recipes/ReactNativeVisionCamera/index.html @@ -10,8 +10,8 @@ - - + +

    VisionCamera

    +

    VisionCamera

    Overview

    VisionCamera is a powerful, high-performance React Native Camera library. It's both feature-rich and flexible! The library provides the necessary hooks and functions to easily integrate camera functionality in your app.

    In this example, we'll take a look at wiring up a barcode scanner. This tutorial is written for the Ignite v9 Prebuild workflow, however it generally still applies to DIY or even a bare react-native project.

    diff --git a/docs/recipes/Redux/index.html b/docs/recipes/Redux/index.html index 1c965808..555db1c5 100644 --- a/docs/recipes/Redux/index.html +++ b/docs/recipes/Redux/index.html @@ -10,8 +10,8 @@ - - + +

    Redux

    +

    Redux

    This guide will show you how to migrate a MobX-State-Tree project (Ignite's default) to Redux, using a newly created Ignite project as our example:

    npx ignite-cli new ReduxApp --yes --removeDemo

    If you are migrating an existing project these steps still apply, but you may need to migrate your existing state tree and other additional functionality.

    diff --git a/docs/recipes/RemoveMobxStateTree/index.html b/docs/recipes/RemoveMobxStateTree/index.html index b6e99c3a..292ebda5 100644 --- a/docs/recipes/RemoveMobxStateTree/index.html +++ b/docs/recipes/RemoveMobxStateTree/index.html @@ -10,8 +10,8 @@ - - + +

    Remove Mobx-State-Tree

    +

    Remove Mobx-State-Tree

    By default, Ignite uses MobX-State-Tree as the default state management solution. While we love MobX-State-Tree at Infinite Red, we understand the landscape is rich with great alternatives that you may want to use instead.

    This guide will show you how to remove Mobx-State-Tree from an Ignite-generated project and get to a "blank slate" with no state management at all.

    Steps

    diff --git a/docs/recipes/RequiringHardwareFeaturesWithExpo/index.html b/docs/recipes/RequiringHardwareFeaturesWithExpo/index.html index ec725574..2088f50c 100644 --- a/docs/recipes/RequiringHardwareFeaturesWithExpo/index.html +++ b/docs/recipes/RequiringHardwareFeaturesWithExpo/index.html @@ -10,8 +10,8 @@ - - + +

    Requiring Hardware Features with Expo

    +

    Requiring Hardware Features with Expo

    Overview

    iOS and Android allow you to specify specific hardware that your app needs in order to be able to run. When users go to download your app from the respective app stores, if their device doesn't meet this hardware requirement, the store will not allow the user to download the app to that device.

    For this recipe, we're going to be creating an expo config plugin to add the required properties to our app's prebuild system so that users with devices that DO NOT have a front-facing camera or a microphone won't be able to download our app.

    diff --git a/docs/recipes/SampleYAMLCircleCI/index.html b/docs/recipes/SampleYAMLCircleCI/index.html index 1c982085..7e748ce6 100644 --- a/docs/recipes/SampleYAMLCircleCI/index.html +++ b/docs/recipes/SampleYAMLCircleCI/index.html @@ -10,8 +10,8 @@ - - + +

    Sample YAML for CircleCi for Ignite

    Sample YAML File

    +

    Sample YAML for CircleCi for Ignite

    Sample YAML File

    # Javascript Node CircleCI 2.0 configuration file
    #
    # Check https://circleci.com/docs/2.0/language-javascript/ for more details
    #

    defaults: &defaults
    docker:
    # Choose the version of Node you want here
    - image: circleci/node:10.11
    working_directory: ~/repo

    version: 2
    jobs:
    setup:
    <<: *defaults
    steps:
    - checkout
    - restore_cache:
    name: Restore node modules
    keys:
    - v1-dependencies-{{ checksum "package.json" }}
    # fallback to using the latest cache if no exact match is found
    - v1-dependencies-
    - run:
    name: Install dependencies
    command: |
    yarn install
    - save_cache:
    name: Save node modules
    paths:
    - node_modules
    key: v1-dependencies-{{ checksum "package.json" }}

    tests:
    <<: *defaults
    steps:
    - checkout
    - restore_cache:
    name: Restore node modules
    keys:
    - v1-dependencies-{{ checksum "package.json" }}
    # fallback to using the latest cache if no exact match is found
    - v1-dependencies-
    - run:
    name: Install React Native CLI and Ignite CLI
    command: |
    sudo npm i -g ignite-cli react-native-cli
    - run:
    name: Run tests
    command: yarn ci:test # this command will be added to/found in your package.json scripts

    publish:
    <<: *defaults
    steps:
    - checkout
    - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
    - restore_cache:
    name: Restore node modules
    keys:
    - v1-dependencies-{{ checksum "package.json" }}
    # fallback to using the latest cache if no exact match is found
    - v1-dependencies-
    # Run semantic-release after all the above is set.
    - run:
    name: Publish to NPM
    command: yarn ci:publish # this will be added to your package.json scripts

    workflows:
    version: 2
    test_and_release:
    jobs:
    - setup
    - tests:
    requires:
    - setup
    - publish:
    requires:
    - tests
    filters:
    branches:
    only: master

    Is this page still up to date? Did it work for you?

    \ No newline at end of file diff --git a/docs/recipes/SelectFieldWithBottomSheet/index.html b/docs/recipes/SelectFieldWithBottomSheet/index.html index 4f94f3c9..f4b562b9 100644 --- a/docs/recipes/SelectFieldWithBottomSheet/index.html +++ b/docs/recipes/SelectFieldWithBottomSheet/index.html @@ -10,8 +10,8 @@ - - + +

    SelectField using react-native-bottom-sheet

    +

    SelectField using react-native-bottom-sheet

    In this guide, we'll be creating a SelectField component by extending the TextField with a scrollable options View and additional props to handle its customization.

    We will be using the react-native-bottom-sheet library for the options list, the ListItem component for displaying individual options, and the TextField component for opening the options list and displaying selected options.

    @@ -84,6 +84,6 @@

    import {
    BottomSheetBackdrop,
    BottomSheetFlatList,
    BottomSheetFooter,
    BottomSheetModal,
    } from "@gorhom/bottom-sheet";
    import React, { forwardRef, Ref, useImperativeHandle, useRef } from "react";
    import { TouchableOpacity, View, ViewStyle } from "react-native";
    import { useSafeAreaInsets } from "react-native-safe-area-context";
    import type { ThemedStyle } from "app/theme";
    import { useAppTheme } from "app/utils/useAppTheme";
    import { Button } from "./Button";
    import { Icon } from "./Icon";
    import { ListItem } from "./ListItem";
    import { TextField, TextFieldProps } from "./TextField";

    export interface SelectFieldProps
    extends Omit<TextFieldProps, "ref" | "onValueChange" | "onChange" | "value"> {
    value?: string[];
    renderValue?: (value: string[]) => string;
    onSelect?: (newValue: string[]) => void;
    multiple?: boolean;
    options: { label: string; value: string }[];
    }
    export interface SelectFieldRef {
    presentOptions: () => void;
    dismissOptions: () => void;
    }

    function without<T>(array: T[], value: T) {
    return array.filter((v) => v !== value);
    }

    export const SelectField = forwardRef(function SelectField(
    props: SelectFieldProps,
    ref: Ref<SelectFieldRef>
    ) {
    const {
    value = [],
    onSelect,
    renderValue,
    options = [],
    multiple = true,
    ...TextFieldProps
    } = props;
    const sheet = useRef<BottomSheetModal>(null);
    const { bottom } = useSafeAreaInsets();
    const {
    themed,
    theme: { colors },
    } = useAppTheme();

    const disabled = TextFieldProps.editable === false || TextFieldProps.status === "disabled";

    useImperativeHandle(ref, () => ({ presentOptions, dismissOptions }));

    const valueString =
    renderValue?.(value) ??
    value
    .map((v) => options.find((o) => o.value === v)?.label)
    .filter(Boolean)
    .join(", ");

    function presentOptions() {
    if (disabled) return;

    sheet.current?.present();
    }

    function dismissOptions() {
    sheet.current?.dismiss();
    }

    function updateValue(optionValue: string) {
    if (value.includes(optionValue)) {
    onSelect?.(multiple ? without(value, optionValue) : []);
    } else {
    onSelect?.(multiple ? [...value, optionValue] : [optionValue]);
    if (!multiple) dismissOptions();
    }
    }

    return (
    <>
    <TouchableOpacity activeOpacity={1} onPress={presentOptions}>
    <View pointerEvents="none">
    <TextField
    {...TextFieldProps}
    value={valueString}
    RightAccessory={(props) => <Icon icon="caretRight" containerStyle={props.style} />}
    />
    </View>
    </TouchableOpacity>

    <BottomSheetModal
    ref={sheet}
    snapPoints={["50%"]}
    stackBehavior="replace"
    enableDismissOnClose
    backdropComponent={(props) => (
    <BottomSheetBackdrop {...props} appearsOnIndex={0} disappearsOnIndex={-1} />
    )}
    footerComponent={
    !multiple
    ? undefined
    : (props) => (
    <BottomSheetFooter
    {...props}
    style={themed($bottomSheetFooter)}
    bottomInset={bottom}
    >
    <Button text="Dismiss" preset="reversed" onPress={dismissOptions} />
    </BottomSheetFooter>
    )
    }
    >
    <BottomSheetFlatList
    style={{ marginBottom: bottom + (multiple ? spacing.xl * 2 : 0) }}
    data={options}
    keyExtractor={(o) => o.value}
    renderItem={({ item, index }) => (
    <ListItem
    text={item.label}
    topSeparator={index !== 0}
    style={themed($listItem)}
    rightIcon={value.includes(item.value) ? "check" : undefined}
    rightIconColor={colors.palette.angry500}
    onPress={() => updateValue(item.value)}
    />
    )}
    />
    </BottomSheetModal>
    </>
    );
    });

    const $bottomSheetFooter: ThemedStyle<ViewStyle> = ({ spacing }) => ({
    paddingHorizontal: spacing.lg,
    paddingBottom: spacing.xs,
    });

    const $listItem: ThemedStyle<ViewStyle> = ({ spacing }) => ({
    paddingHorizontal: spacing.lg,
    });

    And we're done!

    -
    Demo Preview
    import { SelectField } from "../components/SelectField";

    const teams = [
    { label: "Hawks", value: "ATL" },
    { label: "Celtics", value: "BOS" },
    // ...
    { label: "Jazz", value: "UTA" },
    { label: "Wizards", value: "WAS" },
    ];

    function FavoriteNBATeamsScreen() {
    const [selectedTeam, setSelectedTeam] = useState<string[]>([]);
    const [selectedTeams, setSelectedTeams] = useState<string[]>([]);

    return (
    <>
    <SelectField
    label="NBA Team(s)"
    helper="Select your team(s)"
    placeholder="e.g. Knicks"
    value={selectedTeam}
    onSelect={setSelectedTeam}
    options={teams}
    multiple={false}
    containerStyle={{ marginBottom: spacing.lg }}
    />

    <SelectField
    label="NBA Team(s)"
    helper="Select your team(s)"
    placeholder="e.g. Trail Blazers"
    value={selectedTeams}
    onSelect={setSelectedTeams}
    options={teams}
    containerStyle={{ marginBottom: spacing.lg }}
    renderValue={(value) => `Selected ${value.length} Teams`}
    />
    </>
    );
    }

    yulolimum-capture-2023-02-15--05-11-11

    Is this page still up to date? Did it work for you?

    +
    Demo Preview
    import { SelectField } from "../components/SelectField";

    const teams = [
    { label: "Hawks", value: "ATL" },
    { label: "Celtics", value: "BOS" },
    // ...
    { label: "Jazz", value: "UTA" },
    { label: "Wizards", value: "WAS" },
    ];

    function FavoriteNBATeamsScreen() {
    const [selectedTeam, setSelectedTeam] = useState<string[]>([]);
    const [selectedTeams, setSelectedTeams] = useState<string[]>([]);

    return (
    <>
    <SelectField
    label="NBA Team(s)"
    helper="Select your team(s)"
    placeholder="e.g. Knicks"
    value={selectedTeam}
    onSelect={setSelectedTeam}
    options={teams}
    multiple={false}
    containerStyle={{ marginBottom: spacing.lg }}
    />

    <SelectField
    label="NBA Team(s)"
    helper="Select your team(s)"
    placeholder="e.g. Trail Blazers"
    value={selectedTeams}
    onSelect={setSelectedTeams}
    options={teams}
    containerStyle={{ marginBottom: spacing.lg }}
    renderValue={(value) => `Selected ${value.length} Teams`}
    />
    </>
    );
    }

    yulolimum-capture-2023-02-15--05-11-11

    Is this page still up to date? Did it work for you?

    \ No newline at end of file diff --git a/docs/recipes/SettingUpYarnMonorepo/index.html b/docs/recipes/SettingUpYarnMonorepo/index.html new file mode 100644 index 00000000..2798d113 --- /dev/null +++ b/docs/recipes/SettingUpYarnMonorepo/index.html @@ -0,0 +1,262 @@ + + + + + +Setting up a Yarn monorepo with Ignite | Ignite Cookbook for React Native + + + + + + + + + + +

    Setting up a Yarn monorepo with Ignite

    +

    👋 Hello and welcome to this monorepo guide! We know setting up a project using a monorepo structure can be sometimes challenging, therefore we created this guide to lead you through process. We'll be focusing on React Native projects using the Ignite framework and the Yarn tool.

    +

    This guide starts by setting up the monorepo structure, then create a React Native app using the Ignite CLI, to finally end up generating two shared utilities: a form-validator utility and a shared UI library, that we will be integrate into the app.

    +

    Prerequisites

    +

    Before we begin, we want to ensure you have these standard tools installed on your machine:

    +
      +
    • Node.js (version 18 or later)
    • +
    • Yarn (version 3.8 or later)
    • +
    +

    Now, let’s dive into the specific use case this guide will address.

    +

    Use case

    +

    In a monorepo setup with multiple applications, like a React Native mobile app and a React web app, can share common functionalities.

    +

    In this guide we will be focusing on that premise and creating/utilizing shared utilities within the monorepo. For instance, if you have several apps that need to share an ESLint configuration or UI components, you can create reusable packages that can be integrated across all your apps.

    +
    info

    Wait! How do I even know if my project will benefit from a monorepo structure? No worries! We have more documentation on monorepo tools and whether you want to choose this way of organization. You can find it here.

    +

    By centralizing these kind of utilities, you can reduce code duplication and simplify maintenance work, ensuring any updates or bug fixes are immediately available in all your apps within the monorepo.

    +

    So in summary we’ll create a React Native app along with two shared packages: one for holding a common ESLint configuration and another for shared UI components, to finally integrate those back into the app.

    +

    Let's begin!

    +

    Step 1: Setting up the monorepo

    +

    First, read carefully what Expo documentation on setting up monorepos says.

    +

    After this step, you'll get a folder with a packages/ and apps/ directories and a package.json file with basic workspace configuration.

    +
      +
    1. Initialize the monorepo:
    2. +
    +
    mkdir monorepo-example
    cd monorepo-example
    yarn init -y
    +
      +
    1. Configure workspaces in package.json:
    2. +
    +
    {
    "name": "monorepo-example",
    "packageManager": "yarn@3.8.4"
    "packageManager": "yarn@3.8.4",
    "private": true,
    "workspaces": [
    "apps/*",
    "packages/*"
    ]
    }
    +
    info

    We recommend organizing your monorepo's folder structure in a way that best suits the needs of your project. While this guide suggests using apps/ and packages/, you can rename or add directories like, for example, services/ or libs/ to fit your workflow.

    The key here is to keep your monorepo clear and organized, ensuring that it’s easy to manage and navigate for you and your team 🤜🏻.

    +
      +
    1. Create directory structure:
    2. +
    +
    mkdir apps packages
    +

    Step 2: Create mobile app using Ignite

    +

    Ignite is Infinite's Red battle-tested React Native boilerplate. We're proud to say we use it every time we start a new project.

    +

    In this step we'll take advantage of Ignite's CLI and create a React Native app within the monorepo.

    +
      +
    1. Install the Ignite CLI (if you haven't already):
    2. +
    +
    npx ignite-cli@latest
    +
      +
    1. Generate a new app: +Navigate to the apps/ directory and run the following command to create a new app:
    2. +
    +
    cd apps
    npx ignite-cli new mobile
    +

    We recommend the following answers to the CLI prompts:

    +
    📝 Do you want to use Expo?: Expo - Recommended for almost all apps [Default]
    📝 Which Expo workflow?: Expo Go - For simple apps that don't need custom native code [Default]
    📝 Do you want to initialize a git repository?: No
    📝 Remove demo code? We recommend leaving it in if it's your first time using Ignite: No
    📝 Which package manager do you want to use?: yarn
    📝 Do you want to install dependencies?: No
    +
      +
    1. Open the metro.config.js file:
    2. +
    +
    touch mobile/metro.config.js
    +
      +
    1. In order to fit a monorepo structurem we need to adjust the Metro configuration. Let's do that by updating these lines in the metro.config.js file (this changes are taken from the Expo guide):
    2. +
    +
    // Learn more https://docs.expo.io/guides/customizing-metro
    const { getDefaultConfig } = require('expo/metro-config');

    // Get monorepo root folder
    const monorepoRoot = path.resolve(projectRoot, '../..');

    /** @type {import('expo/metro-config').MetroConfig} */
    const config = getDefaultConfig(__dirname);
    const config = getDefaultConfig(projectRoot);

    config.transformer.getTransformOptions = async () => ({
    transform: {
    // Inline requires are very useful for deferring loading of large dependencies/components.
    // For example, we use it in app.tsx to conditionally load Reactotron.
    // However, this comes with some gotchas.
    // Read more here: https://reactnative.dev/docs/optimizing-javascript-loading
    // And here: https://github.com/expo/expo/issues/27279#issuecomment-1971610698
    inlineRequires: true,
    },
    });

    // 1. Watch all files within the monorepo
    config.watchFolders = [monorepoRoot];
    // 2. Let Metro know where to resolve packages and in what order
    config.resolver.nodeModulesPaths = [
    path.resolve(projectRoot, 'node_modules'),
    path.resolve(monorepoRoot, 'node_modules'),
    ];

    // This helps support certain popular third-party libraries
    // such as Firebase that use the extension cjs.
    config.resolver.sourceExts.push("cjs")

    module.exports = config;
    +

    Awesome! We have our mobile app created ⭐️.

    +

    Step 3: Install dependencies

    +

    Let's make sure all of our dependencies are installed for the mobile app.

    +
      +
    1. Run yarn at the root of the project:
    2. +
    +
    cd ..
    yarn install
    +

    Step 4: Add a shared ESLint configuration with TypeScript

    +

    Maintaining consistent code quality across TypeScript and JavaScript projects within a monorepo is crucial for a project's long-term success.

    +

    A good first step we recommend is to share a single ESLint configuration file between apps to ensure consistency and streamline the development process.

    +

    Let's create a shared utility for that purpose.

    +
      +
    1. Create a shared ESLint configuration package:
    2. +
    +

    Inside your monorepo, create a new package for your shared ESLint configuration.

    +
    mkdir packages/eslint-config
    cd packages/eslint-config
    +
      +
    1. Initialize the package:
    2. +
    +

    Initialize the package with a package.json file.

    +
    yarn init -y
    +
      +
    1. Install ESLint and TypeScript dependencies:
    2. +
    +

    Install ESLint, TypeScript, and any shared plugins or configurations that you want to use across the apps. We recommend the follow:

    +
    yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-plugin-react-native eslint-plugin-reactotron eslint-config-standard eslint-config-prettier --dev
    +
      +
    1. Create the tsconfig.json file:
    2. +
    +

    packages/eslint-config/tsconfig.json

    +
    {
    "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "lib": ["es6", "dom"],
    "jsx": "react",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
    }
    }
    +
      +
    1. Create the shared ESLint configuration file:
    2. +
    +

    Create an index.ts file in the root of your eslint-config package.

    +

    For this guide we will reuse Ignite’s boilerplate ESLint configuration and then replace the original configuration with it.

    +

    packages/eslint-config/index.ts

    +
    module.exports = {
    root: true,
    parser: "@typescript-eslint/parser",
    extends: [
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:react-native/all",
    "standard",
    "prettier",
    ],
    plugins: [
    "@typescript-eslint",
    "react",
    "react-native",
    "reactotron",
    ],
    parserOptions: {
    ecmaFeatures: {
    jsx: true,
    },
    },
    settings: {
    react: {
    pragma: "React",
    version: "detect",
    },
    },
    globals: {
    __DEV__: false,
    jasmine: false,
    beforeAll: false,
    afterAll: false,
    beforeEach: false,
    afterEach: false,
    test: false,
    expect: false,
    describe: false,
    jest: false,
    it: false,
    },
    rules: {
    "@typescript-eslint/ban-ts-ignore": 0,
    "@typescript-eslint/ban-ts-comment": 0,
    "@typescript-eslint/explicit-function-return-type": 0,
    "@typescript-eslint/explicit-member-accessibility": 0,
    "@typescript-eslint/explicit-module-boundary-types": 0,
    "@typescript-eslint/indent": 0,
    "@typescript-eslint/member-delimiter-style": 0,
    "@typescript-eslint/no-empty-interface": 0,
    "@typescript-eslint/no-explicit-any": 0,
    "@typescript-eslint/no-object-literal-type-assertion": 0,
    "@typescript-eslint/no-var-requires": 0,
    "@typescript-eslint/no-unused-vars": [
    "error",
    {
    argsIgnorePattern: "^_",
    varsIgnorePattern: "^_",
    },
    ],
    "comma-dangle": 0,
    "multiline-ternary": 0,
    "no-undef": 0,
    "no-unused-vars": 0,
    "no-use-before-define": 0,
    "no-global-assign": 0,
    "quotes": 0,
    "react-native/no-raw-text": 0,
    "react/no-unescaped-entities": 0,
    "react/prop-types": 0,
    "space-before-function-paren": 0,
    "reactotron/no-tron-in-production": "error",
    },
    }
    +

    This configuration (originally sourced from Ignite) will provide a strong foundation for TypeScript, React and React Native projects. You can always refine the rules later to align with the specific requirements of your project.

    +
      +
    1. Compile the TypeScript configuration:
    2. +
    +
    npx tsc
    +

    This will generate a index.js file from your index.ts file.

    +

    Step 6: Use the shared ESLint configuration in the mobile app

    +

    Now we'll use the utility we just made and add it to the React Native app. Let’s get started!

    +
      +
    1. Navigate to the mobile app:
    2. +
    +
    cd ..
    cd ..
    cd apps/mobile
    +
      +
    1. Add the ESLint shared package to the package.json file:
    2. +
    +

    apps/mobile/package.json

    +
    "eslint": "8.17.0",
    "eslint-config": "workspace:^",
    "eslint-config-prettier": "8.5.0",
    +
    info

    This guide mainly focuses on a private monorepo, but let’s also talk about publishing packages publicly. If your monorepo includes packages meant for public release, avoid using workspace:^ for dependencies. Instead, set specific package versions to make sure everything works as expected. To handle versioning and publishing for multiple packages, we recommend trying out changesets — it makes the process much easier!

    +
      +
    1. Replace the shared ESLint configuration in package.json:
    2. +
    +

    apps/mobile/package.json

    +
    "eslintConfig": {
    "root": true,
    "parser": "@typescript-eslint/parser",
    "extends": [
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:react-native/all",
    "standard",
    "prettier"
    ],
    "plugins": [
    "@typescript-eslint",
    "react",
    "react-native",
    "reactotron"
    ],
    "parserOptions": {
    "ecmaFeatures": {
    "jsx": true
    }
    },
    "settings": {
    "react": {
    "pragma": "React",
    "version": "detect"
    }
    },
    "globals": {
    "__DEV__": false,
    "jasmine": false,
    "beforeAll": false,
    "afterAll": false,
    "beforeEach": false,
    "afterEach": false,
    "test": false,
    "expect": false,
    "describe": false,
    "jest": false,
    "it": false
    },
    "rules": {
    "@typescript-eslint/ban-ts-ignore": 0,
    "@typescript-eslint/ban-ts-comment": 0,
    "@typescript-eslint/explicit-function-return-type": 0,
    "@typescript-eslint/explicit-member-accessibility": 0,
    "@typescript-eslint/explicit-module-boundary-types": 0,
    "@typescript-eslint/indent": 0,
    "@typescript-eslint/member-delimiter-style": 0,
    "@typescript-eslint/no-empty-interface": 0,
    "@typescript-eslint/no-explicit-any": 0,
    "@typescript-eslint/no-object-literal-type-assertion": 0,
    "@typescript-eslint/no-var-requires": 0,
    "@typescript-eslint/no-unused-vars": [
    "error",
    {
    "argsIgnorePattern": "^_",
    "varsIgnorePattern": "^_"
    }
    ],
    "comma-dangle": 0,
    "multiline-ternary": 0,
    "no-undef": 0,
    "no-unused-vars": 0,
    "no-use-before-define": 0,
    "no-global-assign": 0,
    "quotes": 0,
    "react-native/no-raw-text": 0,
    "react/no-unescaped-entities": 0,
    "react/prop-types": 0,
    "space-before-function-paren": 0,
    "reactotron/no-tron-in-production": "error"
    }
    }
    "eslintConfig": {
    extends: ["@monorepo-example/eslint-config"],
    }
    +
    warning

    In this guide, we use @monorepo-example as the placeholder name for the monorepo. Be sure to replace it with your actual monorepo name if it’s different.

    +

    By completing this step, you now have an app (and maybe more in the future) that benefits from a shared ESLint configuration. Great work!

    +

    Step 7: Create a shared UI components package

    +

    Now that we are familiar with the creation of a shared package, let's create another one.

    +

    As we mentioned earlier, a common need in projects is sharing UI components across multiple apps. In this step, we’ll create a shared UI package featuring a Badge component. A Badge is a simple yet versatile element often used to show small pieces of information, like notifications, statuses, or labels.

    +
      +
    1. Navigate to the packages folder:
    2. +
    +
    cd ..
    cd ..
    cd packages
    +
      +
    1. Create the package directory:
    2. +
    +
    mkdir ui-components
    cd ui-components
    +
      +
    1. Initialize the package:
    2. +
    +

    Initialize the package with a package.json file.

    +
    yarn init -y
    +
      +
    1. Install dependencies:
    2. +
    +

    Install any necessary dependencies, such as React, React Native, and TypeScript, which will be used across both platforms.

    +
    yarn add react react-native typescript --peer
    yarn add @types/react @types/react-native --dev
    +
      +
    1. Create the tsconfig.json file:
    2. +
    +

    packages/ui-components/tsconfig.json

    +
    {
    "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "es2017"],
    "module": "commonjs",
    "jsx": "react",
    "declaration": true,
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
    },
    "include": ["src"],
    "exclude": ["node_modules"]
    }
    +
      +
    1. Now let's create the badge component:
    2. +
    +

    Inside the packages/ui-components directory, create a src folder and add your Badge component.

    +
    mkdir src
    touch src/Badge.tsx
    +
      +
    1. Build the badge component:
    2. +
    +

    packages/ui-components/src/Badge.tsx

    +
    import React, { FC } from "react"
    import { View, Text, StyleSheet, ViewStyle, TextStyle } from "react-native"

    interface BadgeProps {
    label: string
    color?: string
    backgroundColor?: string
    style?: ViewStyle
    textStyle?: TextStyle
    }

    export const Badge: FC<BadgeProps> = ({ label, color = "white", backgroundColor = "red", style, textStyle }) => {
    return (
    <View style={[styles.badge, { backgroundColor }, style]}>
    <Text style={[styles.text, { color }, textStyle]}>{label}</Text>
    </View>
    )
    }

    const styles = StyleSheet.create({
    badge: {
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 12,
    alignSelf: "flex-start",
    } satisfies ViewStyle,
    text: {
    fontSize: 12,
    fontWeight: "bold",
    } satisfies TextStyle,
    })
    +

    A Badge component, as defined above, is a simple UI element designed to display a label with customizable colors. This makes it versatile and useful in various parts of your app, like showing notification counts, statuses, or category labels.

    +
      +
    1. Export the badge component:
    2. +
    +

    Ensure that your component is exported in the package's main entry file.

    +

    packages/ui-components/src/index.ts

    +
    export * from "./Badge"
    +
      +
    1. Compile the package:
    2. +
    +

    Compile your TypeScript code to ensure it's ready for consumption by other packages.

    +
    npx tsc
    +

    Awesome! We have now a second package within our monorepo and a UI component we can share across apps. Onward!

    +

    Step 8: Use the shared UI package in the mobile app

    +

    To finish integrating our shared UI package, we also need to include it in the mobile app.

    +
      +
    1. Navigate now to the mobile app:
    2. +
    +
    cd ..
    cd ..
    cd apps/mobile
    +
      +
    1. Add the shared UI package to the package.json file:
    2. +
    +

    apps/mobile/package.json

    +
        "react-native-screens": "3.31.1",
    "react-native-web": "~0.19.6"
    "react-native-web": "~0.19.6",
    "ui-components": "workspace:^"
    },
    +
      +
    1. Add the Badge component to the UI
    2. +
    +

    Now, let’s add the Badge component to the app! For this example, we’ll place it on the login screen—right below the heading and above the form fields—to show the number of login attempts if they go over a certain limit.

    +

    apps/mobile/apps/screens/LoginScreen.tsx

    +
    import { AppStackScreenProps } from "../navigators"
    import { colors, spacing } from "../theme"
    import { Badge } from "ui-components"

    ...

    <Text testID="login-heading" tx="loginScreen.logIn" preset="heading" style={themed($logIn)} />
    {attemptsCount > 0 && (
    <Badge
    label={`Attempt ${attemptsCount}`}
    backgroundColor={attemptsCount > 2 ? "red" : "blue"}
    />
    )}
    +

    Great work! Now the mobile app is using the Badge component from the shared UI library.

    +

    Step 9: Run mobile app to make sure logic was added

    +

    Alright, we’re almost done! The final step is to make sure everything is set up correctly. Let’s do this by running the mobile app.

    +
      +
    1. Navigate to the root of the project:
    2. +
    +
    cd ..
    cd ..
    +
      +
    1. Make sure dependencies are installed:
    2. +
    +
    yarn
    +
      +
    1. Run the React Native app (make sure you have your environment setup):
    2. +
    +

    For iOS:

    +
    cd apps/mobile
    yarn ios
    +

    For Android:

    +
    cd apps/mobile
    yarn android
    +

    You should now see the login screen with a Badge displayed between the heading and the form fields. Amazing! 🎉

    +

    Step 10: Add Yarn global scripts (optional)

    +

    Just when we thought we were done! If you're still with us, here's an extra step that can make your workflow even smoother.

    +

    One of the great features of Yarn Workspaces is the ability to define and run scripts globally across all packages in your monorepo. This means you can handle tasks like testing, building, or linting right from the root of your project—no need to dive into individual packages.

    +

    In this optional section, we’ll show you how to set up and use global scripts with Yarn. To start, let's add a global script for the mobile app to run both iOS and Android projects.

    +
      +
    1. Add a global script to the mobile app package.json file:
    2. +
    +

    apps/mobile/package.json

    +
      "scripts": {
    ...
    "serve:web": "npx server dist",
    "prebuild:clean": "npx expo prebuild --clean"
    "prebuild:clean": "npx expo prebuild --clean",
    "mobile:ios" : "yarn workspace mobile ios",
    "mobile:android" : "yarn workspace mobile android"
    },
    +

    Even though this script is locally defined within the app's package.json file, it will available everywhere within the monorepo by running yarn mobile:ios or yarn mobile:android. Very neat!

    +
    info

    For more information on Yarn's global scripts, check this link.

    +

    Conclusion

    +

    🎉 Congratulations on reaching the end of this guide! You’ve set up a powerful monorepo with shared utilities, learned how to integrate them into a React Native app created using Ignite, and even explored optional enhancements to streamline your workflow.

    +

    We hope this guide has been helpful and gives you more confidence when working with a monorepo setup!

    +

    For more information, you can check the following resources:

    +

    Is this page still up to date? Did it work for you?

    + + \ No newline at end of file diff --git a/docs/recipes/SwitchBetweenExpoGoCNG/index.html b/docs/recipes/SwitchBetweenExpoGoCNG/index.html index e3ef7df7..2ec93094 100644 --- a/docs/recipes/SwitchBetweenExpoGoCNG/index.html +++ b/docs/recipes/SwitchBetweenExpoGoCNG/index.html @@ -10,8 +10,8 @@ - - + +

    Switch a Project Between Expo Go and Expo CNG

    +

    Switch a Project Between Expo Go and Expo CNG

    If you created an Ignite project using the Expo Go workflow and you need to transition to Expo CNG (Continuous Native Generation) or visa versa, this guide will teach you how to reconfigure your project.

    Expo Go -> Expo CNG

    If you started with Expo Go but now need to add a library with native code, create your own custom native code, or modify native project configuration, you'll no longer be able to run your app inside Expo Go.

    @@ -81,6 +81,6 @@

    Run the app!

    -

    That's it! You should be able to run yarn start and tap i or a in terminal to launch iOS or Android respectively in Expo Go.

    Is this page still up to date? Did it work for you?

    +

    That's it! You should be able to run yarn start and tap i or a in terminal to launch iOS or Android respectively in Expo Go.

    Is this page still up to date? Did it work for you?

    \ No newline at end of file diff --git a/docs/recipes/Theming-Emotion/index.html b/docs/recipes/Theming-Emotion/index.html index ff369066..57f57c21 100644 --- a/docs/recipes/Theming-Emotion/index.html +++ b/docs/recipes/Theming-Emotion/index.html @@ -10,8 +10,8 @@ - - + +

    Theming Ignite

    +

    Theming Ignite

    When it comes to styling we acknowledge the popularity and effectiveness of libraries like styled-components, emotion.js and unistyles. Our boilerplate is designed to work seamlessly with these styling solutions, offering you the flexibility to choose the one that aligns with your preferences and project requirements.

    The theming system in Ignite Boilerplate is crafted to be adaptable and easy to customize. By simply replacing colors and fonts through the designated theme files, you can tailor the look and feel of your application.

    Using Emotion

    diff --git a/docs/recipes/Theming-StyledComponents/index.html b/docs/recipes/Theming-StyledComponents/index.html index bb6baf62..1b4613fa 100644 --- a/docs/recipes/Theming-StyledComponents/index.html +++ b/docs/recipes/Theming-StyledComponents/index.html @@ -10,8 +10,8 @@ - - + +

    Theming Ignite

    +

    Theming Ignite

    When it comes to styling we acknowledge the popularity and effectiveness of libraries like styled-components, emotion.js and unistyles. Our boilerplate is designed to work seamlessly with these styling solutions, offering you the flexibility to choose the one that aligns with your preferences and project requirements.

    The theming system in Ignite Boilerplate is crafted to be adaptable and easy to customize. By simply replacing colors and fonts through the designated theme files, you can tailor the look and feel of your application.

    Using styled-components

    diff --git a/docs/recipes/Theming-Unistyles/index.html b/docs/recipes/Theming-Unistyles/index.html index a6be7bf8..8f807fcc 100644 --- a/docs/recipes/Theming-Unistyles/index.html +++ b/docs/recipes/Theming-Unistyles/index.html @@ -10,8 +10,8 @@ - - + +

    Theming Ignite

    +

    Theming Ignite

    When it comes to styling we acknowledge the popularity and effectiveness of libraries like styled-components, emotion.js and unistyles. Our boilerplate is designed to work seamlessly with these styling solutions, offering you the flexibility to choose the one that aligns with your preferences and project requirements.

    The theming system in Ignite Boilerplate is crafted to be adaptable and easy to customize. By simply replacing colors and fonts through the designated theme files, you can tailor the look and feel of your application.

    Using Unistyles

    diff --git a/docs/recipes/TypeScriptBaseURL/index.html b/docs/recipes/TypeScriptBaseURL/index.html index f5903cfe..bf18496b 100644 --- a/docs/recipes/TypeScriptBaseURL/index.html +++ b/docs/recipes/TypeScriptBaseURL/index.html @@ -10,8 +10,8 @@ - - + +

    TypeScript baseUrl Configuration

    +

    TypeScript baseUrl Configuration

    Overview

    Depending on your project structure, sometimes you'll end up with longer relative imports like this:

    import { Thing } from "../../../../../components/thing";
    diff --git a/docs/recipes/UnrenderedItemInScrollView/index.html b/docs/recipes/UnrenderedItemInScrollView/index.html index 04095193..08a1b2ac 100644 --- a/docs/recipes/UnrenderedItemInScrollView/index.html +++ b/docs/recipes/UnrenderedItemInScrollView/index.html @@ -10,8 +10,8 @@ - - + +

    Scrolling to a location that hasn't been rendered using FlatList or SectionList

    Calling scrollViewRef.current.scrollToLocation() on a React Native FlatList or SectionList will fail on occasion because it can't scroll to a location that hasn't been rendered yet.

    +

    Scrolling to a location that hasn't been rendered using FlatList or SectionList

    Calling scrollViewRef.current.scrollToLocation() on a React Native FlatList or SectionList will fail on occasion because it can't scroll to a location that hasn't been rendered yet.

    The solution to this is implementing onScrollToIndexFailed with some sort of recovery functionality to keep trying the scroll. This is a Higher Order Component (HOC) for SectionList that handles this for us.

    This component basically tries over and over to scroll to the requested location until it gets it right and no longer calls onScrollToIndexFailed.

    import * as React from 'react';
    import { SectionList, SectionListProps, SectionListScrollParams } from 'react-native';

    interface SectionListHandle {
    scrollToLocation: (params: SectionListScrollParams) => void;
    }

    /**
    * This is a wrapper around react-native's SectionList that adds protection against scrolling to an
    * unknown (not rendered yet) location. This is useful for cases where the user wants to scroll to a
    * position very far down the list but we haven't rendered that far yet.
    *
    * This adds onScrollToIndexFailed property to SectionList so that if the scroll fails, we calculate the approximate
    * scroll position, scroll there, and then try again to get the exact position requested.
    *
    * Essentially, it's a "guess the position and retry the operation" strategy until the list is scrolled to the
    * correct location.
    */
    export const ScrollProtectedSectionList = React.forwardRef<
    SectionListHandle,
    SectionListProps<any, any>
    >((props, forwardedRef) => {
    const internalRef = React.useRef<SectionList>(null);
    const [lastScrollRequest, setLastScrollRequest] = React.useState<SectionListScrollParams>();
    const timeout = React.useRef<ReturnType<typeof setTimeout>>();

    const onScrollToIndexFailed = (info: {
    index: number;
    highestMeasuredFrameIndex: number;
    averageItemLength: number;
    }) => {
    console.log('ScrollProtectedSectionList.onScrollToIndexFailed', info);

    // Calculate the possible position of the item and scroll there using the internal scroll responder.
    const offset = info.averageItemLength * info.index;
    internalRef.current?.getScrollResponder()?.scrollTo({ x: 0, y: offset, animated: false });

    // If we know exactly where we want to scroll to, we can just scroll now since the item is likely visible.
    // Otherwise it'll call this function recursively again.
    if (lastScrollRequest) {
    timeout.current = setTimeout(() => {
    internalRef.current?.scrollToLocation(lastScrollRequest);
    }, 100);
    }
    };

    // Clear the timeout if it still exists when the component unmounts.
    React.useEffect(() => {
    return () => timeout.current && clearTimeout(timeout.current);
    }, []);

    React.useImperativeHandle(
    forwardedRef,
    () => ({
    scrollToLocation: (params: SectionListScrollParams) => {
    internalRef.current?.scrollToLocation(params);
    setLastScrollRequest(params);
    },
    }),
    [internalRef],
    );

    return <SectionList {...props} ref={internalRef} onScrollToIndexFailed={onScrollToIndexFailed} />;
    });

    Is this page still up to date? Did it work for you?

    diff --git a/docs/recipes/UpdatingDependencies/index.html b/docs/recipes/UpdatingDependencies/index.html index fdaabd2a..fc4ac4e2 100644 --- a/docs/recipes/UpdatingDependencies/index.html +++ b/docs/recipes/UpdatingDependencies/index.html @@ -10,8 +10,8 @@ - - + +

    Updating Dependencies with Yarn Audit, Outdated and Upgrade

    If you get a bunch of warnings in the git command output about vulnerabilities, similar to this: remote: Github found 80 vulnerabilities on <branch>..., you can examine these vulnerabilities with yarn audit, get a list of outdated packages with yarn outdated, and update each dependency using yarn update

    +

    Updating Dependencies with Yarn Audit, Outdated and Upgrade

    If you get a bunch of warnings in the git command output about vulnerabilities, similar to this: remote: Github found 80 vulnerabilities on <branch>..., you can examine these vulnerabilities with yarn audit, get a list of outdated packages with yarn outdated, and update each dependency using yarn update

    Yarn Audit Checks for known security issues with the installed packages. Issue the command from the root of your project. The output is a list of known issues.

    Usage:

    yarn audit
    diff --git a/docs/recipes/UpdatingIgnite/index.html b/docs/recipes/UpdatingIgnite/index.html index 86649bd2..32c1de39 100644 --- a/docs/recipes/UpdatingIgnite/index.html +++ b/docs/recipes/UpdatingIgnite/index.html @@ -10,8 +10,8 @@ - - + +

    Updating Ignite boilerplate with ignite-diff-purge

    Many React Native developers aks this question:

    +

    Updating Ignite boilerplate with ignite-diff-purge

    Many React Native developers aks this question:

    How can I update my Ignite boilerplate with the latest changes from the Ignite boilerplate?

    diff --git a/docs/recipes/UsingScreenReaders/index.html b/docs/recipes/UsingScreenReaders/index.html index 6db3b36e..e90910a1 100644 --- a/docs/recipes/UsingScreenReaders/index.html +++ b/docs/recipes/UsingScreenReaders/index.html @@ -10,8 +10,8 @@ - - + +

    Using Screen Readers

    +

    Using Screen Readers

    iOS

    On a simulator

    Setting it up diff --git a/docs/recipes/Zustand/index.html b/docs/recipes/Zustand/index.html index bbd14657..58c1b60f 100644 --- a/docs/recipes/Zustand/index.html +++ b/docs/recipes/Zustand/index.html @@ -10,8 +10,8 @@ - - + +

    Zustand

    +

    Zustand

    Zustand is a "bearbones" state management solution (hence the cute bear mascot). Its a relatively simple and unopinionated option to manage application state, with a hooks-based API for easy use in a React app.

    This guide will show you how to migrate a MobX-State-Tree project (Ignite's default) to Zustand, using a new Ignite project as an example:

    diff --git a/docs/tags/accessibility/index.html b/docs/tags/accessibility/index.html index 46b0adfd..e2e94c41 100644 --- a/docs/tags/accessibility/index.html +++ b/docs/tags/accessibility/index.html @@ -10,8 +10,8 @@ - - + +
    +
    updated 7 weeks ago
    Animation Image

    Backed By A Community of React Native Experts

    The Ignite Cookbook isn’t just a random group of code snippets. It’s a curated collection of usable code samples that the Infinite Red team’s used in their own React Native projects. Having worked with some of the biggest clients in the tech industry, we know a thing or two about keeping our code to a high standard. You can code confidently!

    Animation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation Image
    \ No newline at end of file diff --git a/search/index.html b/search/index.html index 09fef098..7cc2b850 100644 --- a/search/index.html +++ b/search/index.html @@ -10,8 +10,8 @@ - - + +