DEV Community

Chukwuma Anyadike
Chukwuma Anyadike

Posted on • Updated on

The Global State of Affairs with React Context

A while ago, I talked about information transfer in React. I think it was a family affair that involved children and props. Here is the link if you want to review this article.

It is a nice review of how information is passed between React components. Let me reiterate a few principles about information transfer.
  1. Parent components directly pass information to child components through props.
  2. Child components cannot directly pass information to parent components. However, they indirectly pass information to parent components through callback functions (passed as props by the parent). This is known as inverse data flow.
  3. Siblings cannot directly pass information to each other either. One child can "pass" information to the parent through a callback function. The parent then passes information to the other child (sibling) through props.

This is fine for applications with a relatively small number of components with a simple tree structure. However, as your applications become more complicated and components become more nested you may not want to keep passing information up and down multiple levels. Furthermore, if information is reused among multiple components at multiple levels, would it not be nice to just be able to access that piece of data without all the prop drilling and multitude of callback functions. Fortunately there is a way. Our variables can be stored globally. One way is React Context, and this is the topic of discussion right now. The other way is Redux, but I will not discuss this at this time. This is what I used for my last project which is an electronic medical record prototype. Just to give you an idea, this application contains nearly fifty components and do not get me started on the component tree for this applications. Let's just say it's complicated. Back to the topic at hand.

In order to utilize React Context we need to do create two things.

  1. The actual context object
  2. A context provider component

First, I will show you how this is done with some generic boiler plate code, then I will demonstrate how I applied it in my own application. A typical application of context is to fetch data from a logged in user and store it in global state (a literal global state of affairs).

// src/context/user.js
import React from "react";

// create the context
const UserContext = React.createContext();

// create a provider component
function UserProvider({ children }) {
  // the value prop of the provider will be our context data
  // this value will be available to child components of this provider
  return <UserContext.Provider value={null}>{children}</UserContext.Provider>;
}

export { UserContext, UserProvider };
Enter fullscreen mode Exit fullscreen mode

This code above is the heart of creating global variables with values that can be passed to any React component. The const UserContext = React.createContext() method creates a context object that can store a multitude of variables in global context. The UserProvider component is literally a way to provide these variables to React components. This UserProvider functional component returns the UserContext.Provider element. This element allows us to take variables in a value prop object and pass them to a child component wrapped with the UserProvider component.

Let's start with something fundamental like seeing if there is a logged in user or not.

import React, {useState, useEffect} from 'react'

const UserContext = React.createContext()

function UserProvider({children}) {
    const [user, setUser] = useState(null)
    useEffect(()=>{
        fetch('/me')
        .then(res=>{
            if (res.ok){
                res.json().then(setUser)
            }
        })      
    }, [])

    return (
        <UserContext.Provider value={{user, setUser}}>
            {children}
        </UserContext.Provider>
    )
}

export {UserContext, UserProvider}
Enter fullscreen mode Exit fullscreen mode

In this chunk of code this is what is happening. React Context is established. Our state variable user and setter function setUser are initialized in our UserProvider functional component thus putting these variables in global context. A fetch request is performed and if there is a user logged in then user will be set to that value, otherwise user will remain null. The variables user and setUser are passed into the value prop as an object. Note that the value is a object consisting of the variables as object attributes passes as props to UserContext.Provider. UserContext.Provider also takes children. Any children (aka child elements) wrapped with UserContext.Provider via UserProvider can access these values. UserContext and UserProvider are exported for later access.

Now we know how to put values in global context and subsequently pass them into child elements. Now how do we retrieve them. Let's look at this next code snippet.

import React from 'react'
import HealthCareSystemInterface from './HealthCareSystemInterface'
import { UserProvider } from './User'

function App() {
    return (
        <UserProvider>
            <HealthCareSystemInterface />
        </UserProvider>
    )
}

export default App
Enter fullscreen mode Exit fullscreen mode

Here, it may not look like much is going on but there is something important happening here. We have just given our application access to all of our global variables in context. In the third line we are importing our context provider, UserProvider. Our top most component HealthCareSystemInterface is wrapped with UserProvider and becomes a child of said provider. As a result HealthCareSystemInterface and all subsequent child components have access to global variables from our context.

If we head into our HealthCareSystemInterface component, one can see how we gain access to our global context variables.

import React, { useContext } from "react";
import { Route, Routes } from "react-router-dom";
import {UserContext} from "./User";

import Login from "./Login";
import HomePage from "./HomePage";
import NavBar from "./NavBar";
import SignOut from "./SignOut";

import Providers from "./Providers";
import Appointments from "./Appointments";
import AddSoapNote from "./AddSoapNote";
import AddHistory from "./AddHistory";
import AddConsult from "./AddConsult";
import AddDischargeNote from "./AddDischargeNote";
import AddOperativeReport from "./AddOperativeReport";
import AddProcedureNote from "./AddProcedureNote";
import PatientRecords from "./PatientRecords";

function HealthCareSystemInterface() {
  const {user, setUser} = useContext(UserContext);

  if (!user) return <Login onLogin={setUser} />;
  return (
    <div>
      <NavBar />
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/health_care_providers" element={<Providers />} />
        <Route path="/patient_appointments" element={<Appointments />} />
        <Route path="/signout" element={<SignOut />} />
        <Route path="/patient_records" element={<PatientRecords />} />

      {/* medical documentation routes */}
        <Route path="/add_soap_note/:patientId/:chartId" element={<AddSoapNote />} />
        <Route path="/add_history/:patientId/:chartId" element={<AddHistory />} />
        <Route path="/add_consult/:patientId/:chartId" element={<AddConsult />} />
        <Route path="/add_discharge_note/:patientId/:chartId" element={<AddDischargeNote />} />
        <Route path="/add_operative_report/:patientId/:chartId" element={<AddOperativeReport />} />
        <Route path="/add_procedure_note/:patientId/:chartId" element={<AddProcedureNote />} />
      </Routes>
    </div>
  );
}

export default HealthCareSystemInterface;
Enter fullscreen mode Exit fullscreen mode

Take note of a few things. Note the useContext hook in our first line. This essentially allows us to retrieve variables from our global context. In line three we import UserContext. This is the context containing our variables. Now this line, const {user, setUser} = useContext(UserContext), retrieves our variables from global state. It retrieves two variables user and setUser by using the useContext hook to access UserContext to obtain these values. Just think about it, with three lines of code we can retrieve variables from React Context.

Now, that we have learned something about React Context, why don't we kick it up a notch. Passing one or two variables into global context is commendable but we can do better than that. For a complex application like mine, I need to pass entire functions into global context. For example, in medical documentation everything has to be date and timed. Therefore, I have written some functions for this, namely displayDate, displayDateAsNumbers, and displayTime. I also want to retrieve a list of all appointments for the user and all patients in our database. I would also like to know what todays date and time are so that is another variable (today) to contend with. Check out the code below.

import React, {useState, useEffect} from 'react'

const UserContext = React.createContext()

function UserProvider({children}) {
    const [user, setUser] = useState(null)
    const [patients, setPatients] = useState([])
    const [appointments, setAppointments] = useState([])
    useEffect(()=>{
        fetch('/me')
        .then(res=>{
            if (res.ok){
                res.json().then(setUser)
            }
        })

        fetch('/patients')
        .then(res=>res.json())
        .then(setPatients)

        fetch('/appointments')
        .then(res=>res.json())
        .then(setAppointments)       
    }, [])

    const [today, setToday] = useState(new Date())

    function displayDate(thisDate) {
        const date = new Date(thisDate)
        const months = [
            "January",
            "February",
            "March",
            "April",
            "May",
            "June",
            "July",
            "August",
            "September",
            "October",
            "November",
            "December",
          ];
        const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
        return `${days[today.getDay()]} ${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`
    }
    function displayDateAsNumbers(thisDate){
        const date = new Date(thisDate)
        return `${date.getMonth()+1}/${date.getDate()}/${date.getFullYear()}`
    }
    function displayTime (thisDate){
        const date = new Date(thisDate)
        const hours = date.getHours()%12===0 ? "12":date.getHours()%12
        const minutes = date.getMinutes() < 10? `0${date.getMinutes()}` : date.getMinutes()
        const amOrPm =date.getHours()>12 ? "PM":"AM"
        return `${hours}:${minutes} ${amOrPm}`
    }

    return (
        <UserContext.Provider value={{
            user, setUser,  
            patients, setPatients,
            appointments, setAppointments,
            today, setToday, displayDate, displayTime, displayDateAsNumbers
        }}>
            {children}
        </UserContext.Provider>
    )
}

export {UserContext, UserProvider}
Enter fullscreen mode Exit fullscreen mode

With this context component right here I am harnessing the power of React Context. Look at how our Clock component uses global context.

import React, {useEffect, useContext} from 'react'
import { UserContext } from './User';

function Clock() {
    const {today, setToday, displayDate} = useContext(UserContext)
    function tickingTime (thisDate){
      const date = new Date(thisDate)
      const hours = date.getHours()%12===0 ? "12":date.getHours()%12
      const minutes = date.getMinutes() < 10? `0${date.getMinutes()}` : date.getMinutes()
      const seconds = date.getSeconds()<10? `0${date.getSeconds()}`: date.getSeconds()
      const amOrPm =date.getHours()>12 ? "PM":"AM"
      return `${hours}:${minutes}:${seconds} ${amOrPm}`
  }

    function refreshClock() {
      setToday(new Date());
    }
    useEffect(()=>{
        const timerId = setInterval(refreshClock, 1000);
        return function cleanup() {
            clearInterval(timerId);
            };
    }, [])
    return (
      <div className='date-container'>
        <p className='date'>Date: {displayDate(today)} Time: {tickingTime(today)}</p>
      </div>
    )
}

export default Clock
Enter fullscreen mode Exit fullscreen mode

See how our ShowProgressNote component uses global context variables.

import React, {useState, useContext} from 'react'
import EditProgressNote from './EditProgressNote'
import DeleteRecord from './DeleteRecord'
import PrintComponent from './PrintComponent'
import { UserContext } from './User'

function ShowProgressNote({note, progressNotes, setProgressNotes}) {
    const {displayDate} = useContext(UserContext)
    const {patient_header, subjective, objective, assessment, plan, provider_header, created_at, updated_at} = note
    const [showEdit, setShowEdit] = useState(false)
    const template = (
      <div className='record'>
        <h3 className='patient'>Progress note</h3>
        <h6 className='patient'>{patient_header}</h6>
        <p>Subjective: {subjective}</p>
        <p>Objective: {objective}</p>
        <p>Assessment: {assessment}</p>
        <p>Plan: {plan}</p>    
        <p className='signature'>Written by {provider_header}</p>
        <p className='signature'>Created: {displayDate(created_at)} || Last updated: {displayDate(updated_at)}</p>          
    </div>
    )
  return (
    <div>
        {showEdit ? null : template}
        <button onClick={()=>setShowEdit(!showEdit)}>{showEdit? "Show progress note": "Edit progress note"}</button>
        {showEdit? <EditProgressNote progressNote={note} display={setShowEdit} progressNotes={progressNotes} setProgressNotes={setProgressNotes} />: null}
        <DeleteRecord record={note} typeOfRecord={"progress_notes"} nameOfRecord={"progress note"} records={progressNotes} recordsSetter={setProgressNotes}/>
        <PrintComponent template={template}/>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Let's review what we did.

  1. A component (User) was created to hold React Context. In this component context (UserContext) and a context provider (UserProvider) were created. Variables were declared and assigned values and subsequently passed as value props. The context provider takes children as props.
  2. Our top level component was wrapped with our context provider (UserProvider) which was imported. Hence our child component and subsequent descendants have access to all variables in global context.
  3. Child components retrieve variables from global context using the useContext hook to tap into our imported context (UserContext).

Now that is React Context in a nutshell. A family affair just became a global state of affairs. Until next time, Love, Peace, and Soul.

Top comments (0)