React Native Tech Blog

supported by maricuru (旧maricuru tech blogです)

ReactNavigation v.5 とReduxでタブのバッジ数を管理する

この記事でやること

Reduxで管理する通知バッジ数をReactNativeのボトムタブに表示させる記事です。
通知バッジ数はスクリーンをまたぐ変数なので、Reduxで管理するのが良いかと思います。

こんな感じのやつです ↓↓ (この記事ではこれの簡易版を作ります)
S__31916034.jpg

ReactNavigation v.4では、ボトムタブのレンダリングのタイミングに癖があった(?)ようです。
Reduxで状態を更新してもボトムタブに即時反映はされませんでした(僕の周りでも何人か言ってましたが、間違っていたら教えてください!)。
僕は無理やりテキトーな変数を入れたNavigationActionsをボトムタブにdispatchすることで、無理やり再レンダリングさせて、即時反映させていました(本当はこの記事はそれを書く予定だった)。

しかしなんと、v.5ではそんな必要がなくなってました…!
ありがてぇ…!

 やりたいこと

画面から通知バッジ数を変更してボトムタブの数字に即時反映

 主な環境

  • Expo 36.0.0
  • react 16.9.0
  • @react-navigation/bottom-tabs 5.0.5
  • @react-navigation/native 5.0.5
  • @react-navigation/stack 5.0.5
  • react-redux 7.1.3
  • redux 4.0.5

ReactNavigationはモジュールの移動が激しいですね。
公式ドキュメントを読んで、必要なライブラリをインストールしていってください。

 画面構成

ホームスクリーンをStackにして、それをひとつのタブに対応させる単純な画面構成です。

 コード

 Redux

初期状態とreducerの定義をします

src/reducers/index.js
const INITIAL_STATE = {
    badgeNumber:0,
}

const reducer = (state=INITIAL_STATE, action) => {
    switch (action.type){
        case "SET_BADGE_NUMBER":
            return {...state, badgeNumber:action.badgeNumber}
        default:
            return state;
    }
}

export default reducer

actionの定義をします

src/actions/index.js
export const setBadgeNumber = badgeNumber => ({
    type:"SET_BADGE_NUMBER",
    badgeNumber
})

storeを作ります

src/store.js
import { createStore } from 'redux'
import reducers from './reducers'

export default createStore(reducers)

 スクリーン

HomeScreenを作り、Reduxと繋げます。badgeNumber+1というテキストをタッチするとバッジ数がインクリメントされる仕様に。

src/screens/Home.js
import React from 'react';
import { Text, View, TouchableOpacity } from 'react-native';
import { setBadgeNumber } from '../../src/actions'
import { connect } from 'react-redux'

const HomeScreen =({ badgeNumber,setBadgeNumber})=>{
  return(
    <View>
      <TouchableOpacity onPress={()=>setBadgeNumber(badgeNumber+1)}>
        <Text>badgeNumber + 1</Text>
      </TouchableOpacity>
    </View>
  )
}
const mapStateToProps = state => ({
  badgeNumber: state.badgeNumber
})

const mapDispatchToProps = {
  setBadgeNumber
}

 const Home = connect(
    mapStateToProps,
    mapDispatchToProps
  )(HomeScreen)

  export default Home

 ナビゲーション

ReactNavigationでナビゲーションを作ります。
StackとTabでHomeScreenをラップしていきます。

App.js
import React from 'react';
import { Text } from 'react-native';
import { createStackNavigator } from '@react-navigation/stack';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Provider, connect } from 'react-redux'
import { setBadgeNumber } from './src/actions'
import store from './src/store'
import Home from './src/screens/Home'

// ここでStackをつくります()今回は1画面だけだけど
const Stack = createStackNavigator();

const HomeStack=()=>{
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={Home} />
    </Stack.Navigator>
  );
}


// ここでBottomTabを作ります(今回は上で作ったStack1つだけ)
const BottomTab = createBottomTabNavigator();

const MyBottomTab=()=>{
  return (
    <BottomTab.Navigator>
      <BottomTab.Screen 
        name="Home" component={HomeStack}
        options={{
          tabBarLabel: 'Home',
          tabBarIcon: () => (
            <Text>{store.getState().badgeNumber}</Text>
          ),
        }} />
    </BottomTab.Navigator>
  );
}


const Main=()=>{
  return(
    <NavigationContainer>
      <MyBottomTab>
      </MyBottomTab>
    </NavigationContainer>
  )
}

const mapStateToProps = state => ({
  badgeNumber: state.badgeNumber
})

const mapDispatchToProps = {
  setBadgeNumber
}

const ConnectedMain = connect(
  mapStateToProps,
  mapDispatchToProps
)(Main)

const App=()=>{
    return (
        <Provider store={store}>
            <ConnectedMain />
        </Provider>
    )
}
export default App

これで、HomeScreenのbadgeNumber+1をタッチすれば、ボトムタブの数字も更新されていくと思います。

 まとめ

基本に忠実なReduxの使い方、構成です。
上記のコードだけでタブに即時反映してくれるようになってとてもありがたいですね。
ReactNavigation v.5はv.4に比べて見通し良くなったと思います(前はcreateなんとかnavigatorみたいなのが何をやっているかわかりづらかった)。

今回は、HomeScreenから通知バッジ数を変更する仕様でしたが、プッシュ通知が来たらバッジ数をインクリメントすることも考えられるかなと。
僕も、プロジェクトにおいてWebsocket対応の優先順位はまだ低いと感じたときには、プッシュ通知が来たのをトリガーにバッジ数をインクリメントしています。
その場合、Focusされているのがどの画面か問わず、storeの状態を書き換えなければいけませんが、上記で言うMainコンポーネントの中でNotifications.addListenerを使ってハンドリングすると上手くいきます。

参考になれば幸いです!

 

Author: はらだ

Twitter @program_diary