React Native のプロジェクトに Recoil を導入して、値をコンポーネント間で共有する

準備

shell
npm i recoil
shell
mkdir -p lib/states/{atoms,selectors}

App.tsx で、 RecoilRoot でコンポーネントを囲む

App.tsx
import CTBottomNavigation from './components/CTBottomNavigation';
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { Provider as PaperProvider } from 'react-native-paper';
import { enGB, registerTranslation } from 'react-native-paper-dates';
import { RecoilRoot } from 'recoil';
import 'reflect-metadata';

export default function App() {
  registerTranslation('en', enGB);

  return (
    <PaperProvider>
      <RecoilRoot>
        <CTBottomNavigation />
      </RecoilRoot>
      <StatusBar style="auto" />
    </PaperProvider>
  );
}

ユースケース

Recoil を使用する想定ケースです。

ボトムナビゲーションのインデックスを Recoil で管理する

ボトムナビゲーションの値が変更されたタイミングで、値をリロードしたケースがありました。
親コンポーネントのメニューから、描画されるスクリーンに対して、 propsnavigationIndex を渡していましたが、 Recoil でグローバルで管理することで、いちいち navigationIndex を渡さなくても良くなりました。

元々は以下のように propsbottomNavigationIndex を渡して、 bottomNavigationIndex が更新されたタイミングで再ロードするような処理を書いていました。

component.tsx
interface CTAgendaProps {
  bottomNavigationIndex: number;
}
const CTAgenda: React.FC<CTAgendaProps> = ({ bottomNavigationIndex }) => {
  useEffect(() => {}, [bottomNavigationIndex])
}

このような propsbottomNavigationIndex を渡すのをやめて、 Recoil 経由で渡せるようにしたいです。

atom を作成する

lib/states/atoms/BottomNavigationIndexState.ts
import { atom } from "recoil";

export const BottomNavigationIndexState = atom({
  key: 'bottomNavigationIndexState',
  default: 0,
})

ナビゲーションのインデックスの変更を Recoil で管理する

NavigationSample.tsx
const SampleBottomNavigation = () => {
   const [bottomNavigationIndex, setBottomNavigationIndex] = useRecoilState(
    BottomNavigationIndexState
  );
    const onIndexChange = (index: number) => {
    setBottomNavigationIndex(index);
  };
  return (
    <BottomNavigation
      navigationState={{ index: bottomNavigationIndex, routes }}
      onIndexChange={onIndexChange}
      renderScene={renderScene}
    />
  )
}

Recoil から状態を取得する

AnalysisScreen.tsx
import {
  ANALYSIS_SCREEN_INDEX,
  BottomNavigationIndexState,
} from '../../lib/states/atoms/BottomNavigationIndexState';
import MonthlyExpense from '../MonthlyExpense';
import React, { useEffect } from 'react';
import { SafeAreaView, ScrollView, StyleSheet } from 'react-native';
import { useRecoilValue } from 'recoil';

export default function AnalysisScreen() {
  const bottomNavigationIndex = useRecoilValue(BottomNavigationIndexState);
  useEffect(() => {
    if (bottomNavigationIndex !== ANALYSIS_SCREEN_INDEX) {
      return;
    }
    console.log(`bottomNavigationIndex: ${bottomNavigationIndex}`);
  }, [bottomNavigationIndex]);
  return (
    <SafeAreaView style={styles.container}>
      <ScrollView>
        <MonthlyExpense />
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginHorizontal: 10,
  },
});

Recoil から状態を取得して、自分のスクリーンが開かれたときに何か処理を行うようにしています。