DEV Community

James G. Best for Salted Bytes

Posted on • Edited on

React Native local notifications with hooks and context

In any application there are always times when you need to relay small pieces of information to the user. In web apps, this is usually done with a toast type message. In mobile apps, this is usually some sort of alert or local notification.

In this tutorial, we are gonna run through how to create reusable alert components that are updated with React context and hooks.

Alt Text

We are gonna working with a simple notes app, you can clone the finished project from here.

You can see that it already has the functionality to view and create notes but we want to notify the user if saving the note was a success or if there was an error. Obviously this is just an example of where the alert can be used. It could be used to notify the user of anything!

In the example code, I am using the awesome React Native Paper. Read my recent article to find out why I use it and why I think it the best React Native UI library. We are going to use the Snackbar component as our alert component but you could use anything that is appropriate for your project.

We are breaking the article into a few distinct parts.

We are going to use React.context for holding and setting our alert state. The React documentation states that

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Context is perfect for managing simple pieces of state that need to be available globally.

Global Context Provider

I think it is good practice to split the state that is stored in context based on domain, i.e. having separate contexts for alerts, sidebar, cart etc. By doing this you can avoid unnecessary re-renders, your alert context is not fussed about your sidebar context and so updating one should not re-render components using another.

Context is made available to your application by wrapping your app in a Context.provider. But this can be a "problem" when you have multiple contexts as it makes your App.js bloated and slightly more unreadable. But all is not lost, Scott Tolinsky from LevelUp tuts put me onto a great snippet of code that composes your Context.Providers into a single component. This makes things so much neater.

You have probably worked out that having multiple contexts in you App.js is not actually a problem. I just like to have things neat and tidy.

import * as React from "react";
// we will import our context providers here

function ProviderComposer({ contexts, children }) {
  return contexts.reduceRight(
    (kids, parent) =>
      React.cloneElement(parent, {
        children: kids
      }),
    children
  );
}

function ContextProvider({ children }) {
  return (
    // we add our providers to the contexts prop
    <ProviderComposer contexts={[]}>{children}</ProviderComposer>
  );
}

export default ContextProvider;
Enter fullscreen mode Exit fullscreen mode

Alert provider

First, we need to create the context the can hold our alert state. We use React.createContext and assign it to a variable. Notice that we also export the variable, this means that we can use it later in our other components.

We also create an AlertProvider component that wraps our AlertContext.Provider, this is what gives us access to the state stored in our AlertContext.

import * as React from "react";

export const AlertContext = React.createContext({});

export const AlertProvider = ({ children }) => {

  return (
    <AlertContext.Provider
      value={// Our context values will go here}>
      {children}
    </AlertContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

Next, we need a way to manage the data stored in our context. We could use React.useState for this but due to the slightly more complex structure of our data and the fact that we will be updating more than one piece of data to fire our alert component, I decided to use React.useReducer instead. It makes both the implementation of the Alert provider and the execution of its method so much neater.

[...]

const initialState = {
  type: "close",
  open: false,
  alertType: "info",
  message: ""
};

const reducer = (state, action) => {
  switch (action.type) {
    case "close":
      return {
        ...initialState
      };
    case "open":
      return {
        open: true,
        alertType: action.alertType,
        message: action.message
      };
    default:
      throw new Error("Action not found");
  }
};

Enter fullscreen mode Exit fullscreen mode

Finally, we need to put it all together and use our reducer in our provider giving us access to all the stored alert state. This combination allows us to update and access any part of the alert state from any part of the app, as long as the app is wrapped in our global context provider.

import * as React from "react";

const initialState = {
  type: "close",
  open: false,
  alertType: "info",
  message: ""
};

export const AlertContext = React.createContext({});

const reducer = (state, action) => {
  switch (action.type) {
    case "close":
      return {
        ...initialState
      };
    case "open":
      return {
        open: true,
        alertType: action.alertType,
        message: action.message
      };
    default:
      throw new Error();
  }
};

export const AlertProvider = ({ children }) => {
  const [alertState, dispatchAlert] = React.useReducer(reducer, initialState);
  return (
    <AlertContext.Provider
      value={{
        alertState,
        dispatchAlert
      }}>
      {children}
    </AlertContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

The Alert Component

As I mentioned at the start of this article we are using React Native Paper and its Snackbar component to alert our users of any information in our apps. But this could be swapped out for anything else. You just need a way to consume the data that is being passed down from the alert context.

This component is quite simple. We are using the React.useContext hook to subscribe to changes to the AlertContext and then opening/closing the popup based on the state. We set the style of the alert box based on the alertState.alertType property to properly convey the meaning of the message.

import * as React from "react";
import { Snackbar } from "react-native-paper";
import { AlertContext } from "../globalState";
import { colors } from "../constants";

const SnackBar = () => {
  const { alertState, dispatchAlert } = React.useContext(AlertContext);
  const [alertSyle, setAlertStyle] = React.useState({
    backgroundColor: colors.info
  });

  React.useEffect(() => {
    switch (alertState.alertType) {
      case "info":
        setAlertStyle({
          backgroundColor: colors.success
        });
        break;
      case "error":
        setAlertStyle({
          backgroundColor: colors.error
        });
        break;
      case "success":
        setAlertStyle({
          backgroundColor: colors.success
        });
        break;
      default:
        setAlertStyle({
          backgroundColor: colors.info
        });
    }
  }, [alertState]);

  const closeMe = () => {
    dispatchAlert({ type: "close" });
  };

  return (
    <>
      {typeof alertState.open === "boolean" && (
        <Snackbar
          style={alertSyle}
          visible={alertState.open}
          onDismiss={() => closeMe()}
          action={{
            label: "Undo",
            onPress: () => {
              console.log("Snackbar closed");
              // Do something
            }
          }}>
          {alertState.message}
        </Snackbar>
      )}
    </>
  );
};

export default SnackBar;
Enter fullscreen mode Exit fullscreen mode

Using our Alert provider

Finally, we are now in a position to use our sweet new AlertContext from anywhere in our app. In the example below, we are notifying the user of the outcome of their GraphQL mutation.

If you want to learn how to easily integrate GraphQL into your React Native Application take a look at this article.

In the same way, we did in our Snackbar component, we are using the useContext hook to gain access to the dispatchAlert method which will allow us to alert the user to the success or errors in there GraphQL mutation.

[...]

const NoteCreateScreen = ({ navigation }) => {
  const { dispatchAlert } = React.useContext(AlertContext);

  const createNoteMutation = useMutation(gql(createNote));
  return (
    <SafeAreaView style={gStyle.container}>
      <ScrollView contentContainerStyle={gStyle.contentContainer}>
        <View style={{ flex: 1, height: '100%', width: '100%' }}>
          <Surface style={styles.surface}>
            <Formik
              initialValues={{ note: '', title: '' }}
              onSubmit={({ note, title }) => {
                const input = {
                  id: uuid(),
                  title,
                  note,
                  createdAt: moment().toISOString()
                };
                createNoteMutation({
                  variables: {
                    input
                  },
                  update: (_, { data, error }) => {
                    if (error) {
                      dispatchAlert({
                        type: 'open',
                        alertType: 'error',
                        message: 'Error creating note'
                      });
                    } else {
                      dispatchAlert({
                        type: 'open',
                        alertType: 'success',
                        message: 'Note created'
                      });
                      navigation.state.params.refetch();
                      navigation.goBack();
                    }
                  }
                });
              }}
            >
              {({ values, handleSubmit, handleChange }) => {
                return (
                  <>
                    [...]
                  </>
                );
              }}
            </Formik>
          </Surface>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

export default NoteCreateScreen;
Enter fullscreen mode Exit fullscreen mode

And there you have it, a highly customisable, reusable and globally executable local notification system. This type of situation is a perfect use for React Context but obviously there are so many more.

What other stuff will you create?

Thanks for reading 🙏

If there is anything I have missed, or if there is a better way to do something then please let me know

Top comments (2)

Collapse
 
rayhan_nj profile image
Raihan Nismara

so great thanks man!

Collapse
 
jimatjibba profile image
James G. Best

No worries