Archimedes Trajano
Archimedes Trajano

Reputation: 41220

Using React context in a component

Say I create a simple React context to check if I am connected

import NetInfo, { NetInfoState } from '@react-native-community/netinfo';
import Constants, { AppOwnership } from 'expo-constants';
import React, { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react';
import { Platform } from 'react-native';

const ConnectionContext = createContext<boolean>(undefined);

export function ConnectionProvider({ children }: PropsWithChildren<any>): JSX.Element {
  const [connected, setConnected] = useState(false);

  function handleConnectionChange(netInfo: NetInfoState) {
    setConnected(
      (Platform.OS === 'ios' && Constants.appOwnership === AppOwnership.Expo) ||
        (netInfo.isConnected && (netInfo.isInternetReachable ?? true))
    );
  }

  useEffect(() => {
    const subscriptionCancel = NetInfo.addEventListener(handleConnectionChange);
    return () => subscriptionCancel();
  }, []);

  return <ConnectionContext.Provider value={connected}>{children}</ConnectionContext.Provider>;
}

export function useConnection() {
  return useContext(ConnectionContext);
}

I was wondering if I want to use it in my existing component XYZ, is there a less roundabout way of doing it than the following

From:

export function XYZ() {
   ...xyz code...
}

to:

export function XYZ() {
  return (
    <ConnectionContextProvider>
      <RealXyz>
    </ConnectionContextProvider>
  );
}
function RealXyz() {
  const connected = useConnection();
  ...xyz code...
}

Upvotes: 1

Views: 718

Answers (2)

Ryan Wheale
Ryan Wheale

Reputation: 28380

I don't think context is really necessary for this since a connection is more of a singleton type of thing. The following code should be in its own file, and you can import this hook anywhere in your app.

let _isConnected = false;

export const useConnection = () => {
  const [isConnected, setConnected] = useState(_isConnected);

  useEffect(() => {
    function handleConnectionChange(netInfo: NetInfoState) {
      _isConnected =  (Platform.OS === 'ios' && Constants.appOwnership === AppOwnership.Expo) ||
        (netInfo.isConnected && (netInfo.isInternetReachable ?? true))
      
      setConnected(_isConnected);
    }
    
    const subscriptionCancel = NetInfo.addEventListener(handleConnectionChange);

    return () => subscriptionCancel();
  }, []);

  return isConnected;
}

Explanation:

Let's say you have two components which use this hook. When your app first renders, only ComponentA is mounted. Some time later the connection state changes to true. Then some time later ComponentB is mounted. We want ComponentB to know that the connection state is currently true, which is why we use the singleton pattern (eg. private global variable _isConnected). It doesn't matter much that there are multiple event listeners as those are cheap and get removed when the component is unmounted.

Upvotes: 1

mstrk
mstrk

Reputation: 101

Context is handy if you have data that needs to be shared across multiple components and you do not want to pass it down the tree by props.

from the docs:

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

In your example I would use useState, but to give you a good idea where you could opt for context check the following snippet:

...

function ABC() {
  const connected = useConnection();
  ...abc code...
}

function ABCParent() {
  return <ABC />
}

...

function XYZ() {
  const connected = useConnection();
  ...xyz code...
}

function XYZParent() {
  return <XYZ />
}

...

function App() {
 return (
  <ConnectionContextProvider>
    <ABCParent />
    <XYZParent />
  </ConnectionContextProvider>
 )
}

The two components that make use of the context are "deep" in the tree and in separate branches. The example is a bit simple and you could easily pass the data that you need through props and still have a maintainable code base.

But ultimately if you feel that your data model can be "global" and you have enough dependents in separate branches or in the same branch in multiple levels go for context API.

Some data model examples where I find the use of context API useful are theme, app settings, routing and translations.

One thing to note: The components that depend on a context will be less reusable (This is more relevant across projects) and sometimes you can opt for composition instead of using the context API. Check the before you use context section of the docs for more information about this.

Upvotes: 0

Related Questions