Search code examples
reactjsreact-nativereduxexpo

How how update redux store from layout file in react native expo


I'm building an expo-app and I manage state with react-redux, so my <Provider> in is in the top level _layout file.

My problem is I'm working with expo notification and I need to get the title, body, and some data received from the server through the notification and dispatch it to the store for later use, but when I try to dispatch I got an error that says:

could not find react-redux context value

I realize that this error occurs because I try to use dispatch before wrapping the whole app in redux <Provider> but I don't know how I can do this. Below is my code.

import React from "react";
import { Stack, SplashScreen } from "expo-router";
import { useEffect } from "react";
import { useFonts } from "expo-font";
import FontAwesome from "@expo/vector-icons/FontAwesome";
import { Provider } from "react-redux";
import { persistor, store } from "../src/redux/store/store";
import { PersistGate } from "redux-persist/integration/react";
import * as Notifications from "expo-notifications";
import { router } from "expo-router";
import { useDispatch } from "react-redux";
import { addMessage } from "../src/redux/slice/system-messages";

export {
  // Catch any errors thrown by the Layout component.
  ErrorBoundary,
} from "expo-router";

export const unstable_settings = {
  // Ensure that reloading on `/modal` keeps a back button present.
  initialRouteName: "(tabs)",
};

// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();

function saveNotificationResponse() {
  const dispatcher = useDispatch();
  const subscription = Notifications.addNotificationResponseReceivedListener(
    (response) => {
      console.log(response.notification.request.content.data);
      dispatcher(addMessage(response.notification.request.content.data));
    }
  );

  return () => {
    subscription.remove();
  };
}

export default function _layout() {
  useEffect(() => {
    saveNotificationResponse();
  }, []);

  const [loaded, error] = useFonts({
    SpaceMono: require("../src/assets/fonts/SpaceMono-Regular.ttf"),
    DMBold: require("../src/assets/fonts/DMSans-Bold.ttf"),
    DMMedium: require("../src/assets/fonts/DMSans-Medium.ttf"),
    DMRegular: require("../src/assets/fonts/DMSans-Regular.ttf"),
    ...FontAwesome.font,
  });

  // Expo Router uses Error Boundaries to catch errors in the navigation tree.
  useEffect(() => {
    if (error) throw error;
  }, [error]);

  useEffect(() => {
    if (loaded) {
      SplashScreen.hideAsync();
    }
  }, [loaded]);

  if (!loaded) {
    return null;
  }
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <Stack>
          <Stack.Screen
            name="index"
            options={{
              title: "Home",
            }}
          />
          <Stack.Screen
            name="[missing]"
            options={{
              title: "404",
            }}
          />
          <Stack.Screen
            name="(tabs)"
            options={{
              headerShown: false,
            }}
          />
          <Stack.Screen
            name="order/index"
            options={{
              headerShown: false,
            }}
          />
          <Stack.Screen
            name="search/searchpage"
            options={{
              headerShown: false,
            }}
          />
        </Stack>
      </PersistGate>
    </Provider>
  );
}

So when I try to run saveNotificationResponse() I got this error how can I fix this and still save my response to the store? thanks


Solution

  • You cannot use the useDispatch from a non-React function or custom hook, e.g. it can't be called from within the saveNotificationResponse function.

    You can use the store object to directly dispatch actions to the store.

    ...
    import { ..., store } from "../src/redux/store/store";
    ...
    
    function setupNotificationListener() {
      const subscription = Notifications.addNotificationResponseReceivedListener(
        (response) => {
          store.dispatch(addMessage(
            response.notification.request.content.data
          ));
        }
      );
    
      // Return the subscription so callees can unsubscribe when they need to
      return subscription;
    }
    
    ...
    
    export default function _layout() {
      useEffect(() => {
        const subscription = setupNotificationListener();
    
        return () => {
          subscription.remove();
        };
      }, []);
    

    The alternative is to create a React component that is rendered under the Redux Provider component such that it can access the Redux context.

    Example:

    const NotificationListener = () => {
      const dispatch = useDispatch();
    
      useEffect(() => {
        const subscription = Notifications.addNotificationResponseReceivedListener(
          (response) => {
            dispatch(addMessage(response.notification.request.content.data));
          }
        );
    
        return subscription.remove;
      }, [dispatch]);
    
      return null;
    };
    
    export default function _layout() {
      const [loaded, error] = useFonts({
        ...
      });
    
      ...
    
      if (!loaded) {
        return null;
      }
    
      return (
        <Provider store={store}>
          <PersistGate loading={null} persistor={persistor}>
            <NotificationListener />
            <Stack>
              ...
            </Stack>
          </PersistGate>
        </Provider>
      );
    }