The Global State of Affairs with React Context
Chukwuma Anyadike
Posted on May 25, 2023
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.
Information Flow in React: Making a Callback() to Children and Giving Them Props
Chukwuma Anyadike ・ Dec 23 '22
- Parent components directly pass information to child components through props.
- 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.
- 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.
- The actual context object
- 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 };
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}
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
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;
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}
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
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>
)
}
Let's review what we did.
- 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. - 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. - 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.
Posted on May 25, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.