Last active
July 15, 2022 19:14
-
-
Save mikaelbr/45ef683e894b0b4da6d91b599dda1289 to your computer and use it in GitHub Desktop.
React Context Module Pattern - Gists for blogpost
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// in App.tsx | |
type AlertWithId = { | |
message: string; | |
type: "info" | "error"; | |
id: number; | |
}; | |
// Part 1: The container to hold values | |
const AlertContext = createContext<AlertWithId[]>([]); | |
function Content() { | |
// Part 2: Here we access the data from behind the scenes. | |
const alerts = useContext(AlertContext); | |
return ( | |
<div> | |
{alerts.map((i) => ( | |
<div key={i.id}> | |
{i.type}: {i.message} | |
</div> | |
))} | |
</div> | |
); | |
} | |
function App() { | |
// Part 3: The state | |
const [alerts, setAlerts] = useState<AlertWithId[]>([]); | |
// Part 1: Populate value to the provider | |
return ( | |
<AlertContext.Provider value={alerts}> | |
<Content /> | |
</AlertContext.Provider> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const initialState: AlertContextState = { | |
alerts: [], | |
+ // Add a no-op that satisifes the type definition | |
+ addAlert: (i) => ({ ...i, id: 0 }), | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// === in alert-context.ts === | |
export type Alert = { | |
message: string; | |
type: "info" | "error"; | |
}; | |
export type AlertWithId = Alert & { id: number }; | |
type AlertContextState = { | |
alerts: AlertWithId[]; | |
addAlert(alert: Alert): AlertWithId; | |
}; | |
const initialState: AlertContextState = { | |
alerts: [], | |
addAlert: (i) => ({ ...i, id: 0 }), | |
}; | |
// Note we export the AlertContext | |
export const AlertContext = createContext<AlertContextState>(initialState); | |
// === in our App.tsx === | |
import { AlertContext } from "./alert-context"; | |
function App() { | |
const [alerts, setAlerts] = useState<AlertWithId[]>([]); | |
const addAlert = useCallback((alert: Alert) => { | |
const alertWithId = { | |
...alert, | |
id: Date.now(), | |
}; | |
setAlerts((all) => [alertWithId].concat(all)); | |
return alertWithId; | |
}, []); | |
return ( | |
<AlertContext.Provider value={{ alerts, addAlert }}> | |
<MyComponent /> | |
</AlertContext.Provider> | |
); | |
// ... | |
} | |
// === and in component === | |
import { AlertContext } from "./alert-context"; | |
function MyComponent({ message }: { message: string }) { | |
const { alerts, addAlert } = useContext(AlertContext); | |
// ... | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// in alert-context.ts | |
export function useAlertContextState() { | |
const [alerts, setAlerts] = useState<AlertWithId[]>([]); | |
const addAlert = useCallback((alert: Alert) => { | |
const alertWithId = { | |
...alert, | |
id: Date.now(), | |
}; | |
setAlerts((all) => [alertWithId].concat(all)); | |
return alertWithId; | |
}, []); | |
return { alerts, addAlert }; | |
} | |
// in app.ts | |
import { AlertContext, useAlertContextState } from "./alert-context"; | |
function App() { | |
const state = useAlertContextState(); | |
return ( | |
<AlertContext.Provider value={state}> | |
<MyComponent /> | |
</AlertContext.Provider> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// in alert-context.tsx | |
// NOTE: Now we have to rename it to .tsx as it has JSX. | |
// Note we export the AlertContext | |
export const AlertContext = createContext<AlertContextState>(initialState); | |
type AlertContextProviderProps = PropsWithChildren<{}>; | |
export function AlertContextProvider({ children }: AlertContextProviderProps) { | |
const [alerts, setAlerts] = useState<AlertWithId[]>([]); | |
const addAlert = useCallback((alert: Alert) => { | |
const alertWithId = { | |
...alert, | |
id: Date.now(), | |
}; | |
setAlerts((all) => [alertWithId].concat(all)); | |
return alertWithId; | |
}, []); | |
return ( | |
<AlertContext.Provider value={state}>{children}</AlertContext.Provider> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-import { AlertContext, useAlertContextState } from "./alert-context"; | |
+import { AlertContextProvider } from "./alert-context"; | |
function App() { | |
- const state = useAlertContextState(); | |
- | |
return ( | |
- <AlertContext.Provider value={state}> | |
+ <AlertContextProvider> | |
<MyComponent /> | |
- </AlertContext.Provider> | |
+ </AlertContextProvider> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// in alert-context.tsx | |
type AlertContextProviderProps = PropsWithChildren<{ | |
+ initialAlerts: AlertWithId[]; | |
}>; | |
export function AlertContextProvider({ | |
children, | |
+ initialAlerts, | |
}: AlertContextProviderProps) { | |
- const [alerts, setAlerts] = useState<AlertWithId[]>([]); | |
+ const [alerts, setAlerts] = useState<AlertWithId[]>(initialAlerts); | |
// ... | |
} | |
// in App.tsx | |
import { AlertContextProvider } from "./alert-context"; | |
function App() { | |
return ( | |
- <AlertContextProvider> | |
+ <AlertContextProvider initialAlerts={[myAlert]}> | |
<MyComponent /> | |
</AlertContextProvider> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Importing the AlertContext manully | |
import { useContext } from "react"; | |
import { AlertContext } from "./alert-context"; | |
function MyComponent({ message }: { message: string }) { | |
// Passing context to useContext | |
const { alerts, addAlert } = useContext(AlertContext); | |
// ... | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// in alert-context.tsx | |
// Now we can also hide AlertContext from the rest of the world by not exporting it | |
-export const AlertContext = createContext<AlertContextState>(initialState); | |
+const AlertContext = createContext<AlertContextState>(initialState); | |
// Export our own custom hook | |
+export function useAlert() { | |
+ return useContext(AlertContext); | |
+} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Importing the AlertContext manully | |
-import { useContext } from "react"; | |
-import { AlertContext } from "./alert-context"; | |
+import { useAlert } from "./alert-context"; | |
function MyComponent({ message }: { message: string }) { | |
// Passing context to useContext | |
- const { alerts, addAlert } = useContext(AlertContext); | |
+ const { alerts, addAlert } = useAlert(); | |
// ... | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// file: DESCRIPTIVE_NAME-context.tsx | |
// 1. Domain modes | |
type DESCRIPTIVE_NAME = {}; | |
// 2. Context types | |
type DESCRIPTIVE_NAMEContextState = {}; | |
const initialState: DESCRIPTIVE_NAMEContextState = {}; | |
// 3. Creating context | |
const DESCRIPTIVE_NAMEContext = | |
createContext<DESCRIPTIVE_NAMEContextState>(initialState); | |
// 4. Hiding context by providing wanted API. | |
export type DESCRIPTIVE_NAMEContextProviderProps = PropsWithChildren<{}>; | |
export function DESCRIPTIVE_NAMEContextProvider({ | |
children, | |
}: DESCRIPTIVE_NAMEContextProviderProps) { | |
return ( | |
<DESCRIPTIVE_NAMEContext.Provider value={{}}> | |
{children} | |
</DESCRIPTIVE_NAMEContext.Provider> | |
); | |
} | |
export function useDESCRIPTIVE_NAME() {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type AlertContextState = { | |
alerts: AlertWithId[]; | |
}; | |
const initialState: AlertContextState = { | |
alerts: [], | |
}; | |
const AlertContext = createContext<AlertContextState>(initialState); | |
function App() { | |
const [alerts, setAlerts] = useState<AlertWithId[]>([]); | |
return ( | |
<AlertContext.Provider value={{ alerts }}> | |
<Content /> | |
</AlertContext.Provider> | |
); | |
} | |
// Using it | |
function Content() { | |
const { alerts } = useContext(AlertContext); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function App() { | |
return ( | |
<AlertContextProvider initialAlerts={[myAlert]}> | |
<MyComponent /> | |
</AlertContextProvider> | |
); | |
} | |
// in component | |
function MyComponent({ message }: { message: string }) { | |
const { alerts, addAlert } = useAlert(); | |
// ... | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function App() { | |
return ( | |
- <AlertContextProvider initialAlerts={[myAlert]}> | |
<MyComponent /> | |
- </AlertContextProvider> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
+const initialState: AlertContextState = { | |
+ alerts: [], | |
+ addAlert: (i) => ({ ...i, id: 0 }), | |
+}; | |
// Passing in default state | |
-const AlertContext = createContext<AlertContextState>(); | |
+const AlertContext = createContext<AlertContextState>(initialState); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// in alert-context.tsx | |
// Note: No longer initial state, and extended to be either state or undefined | |
-const AlertContext = createContext<AlertContextState>(initialState); | |
+const AlertContext = createContext<AlertContextState | undefined>(); | |
// ... | |
// We've wrapped this, making it easier to change. | |
export function useAlert(): AlertsState { | |
const context = useContext(AlertContext); | |
+ if (context === undefined) { | |
+ throw new Error("useAlert must be used within a AlertContextProvider"); | |
+ } | |
return context; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Note: Introduce Alert along with AlertWithId | |
// to support adding alert without id as input. | |
type Alert = { | |
message: string; | |
type: "info" | "error"; | |
}; | |
type AlertWithId = Alert & { id: number }; | |
function App() { | |
const [alerts, setAlerts] = useState<AlertWithId[]>([]); | |
const addAlert = (alert: Alert) => { | |
const alertWithId = { | |
...alert, | |
id: Date.now(), | |
}; | |
// Insert at top | |
setAlerts((all) => [alertWithId].concat(all)); | |
return alertWithId; | |
}; | |
// Note: Adding function. | |
return ( | |
<AlertContext.Provider value={{ alerts, addAlert }}> | |
<Content /> | |
</AlertContext.Provider> | |
); | |
} | |
function Content() { | |
// Still works as before | |
const { alerts } = useContext(AlertContext); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function MyComponent() { | |
// Still works as before | |
const { alerts, addAlert } = useContext(AlertContext); | |
const add = () => { | |
addAlert({ type: "info", message: "Hello" }); | |
}; | |
return ( | |
<div> | |
<button onClick={add}>Add new</button> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function MyComponent({message}: {message: string}) { | |
// Still works as before | |
const { alerts, addAlert } = useContext(AlertContext); | |
useEffect({ | |
addAlert({ type: "info", message: "Hello" }); | |
}, [message, addAlert]); | |
// ... | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function App() { | |
// ... | |
- const addAlert = (alert: Alert) => { | |
+ const addAlert = useCallback((alert: Alert) => { | |
const alertWithId = { | |
...alert, | |
id: Date.now(), | |
}; | |
// Insert at top | |
setAlerts((all) => [alertWithId].concat(all)); | |
return alertWithId; | |
- }; | |
+ }, []); | |
// ... | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<AlertContext.Provider value={{ alerts, addAlert }}> | |
// ----------------------------------------^ | |
// ⤷ 'addAlert' does not exist in type 'AlertsState' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type AlertContextState = { | |
alerts: AlertWithId[]; | |
+ addAlert(alert: Alert): AlertWithId; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Property 'addAlert' is missing in type '{ alerts: never[]; }' | |
// ⥅ but required in type 'AlertsState'. | |
const initialState: AlertContextState = { | |
alerts: [], | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
createContext, | |
PropsWithChildren, | |
useCallback, | |
useContext, | |
useState, | |
} from "react"; | |
import useInterval from "./use-interval"; | |
// 0. Constants | |
const UPDATE_FREQUENCY_MS = 1000; | |
// 1. Relevant domain types | |
type Alert = { | |
message: string; | |
type: "info" | "error"; | |
}; | |
type AlertWithId = { id: number } & Alert; | |
// 2. Context types | |
type AlertsState = { | |
alerts: AlertWithId[]; | |
addAlert(alert: Alert): AlertWithId; | |
removeAlert(alert: AlertWithId): AlertWithId; | |
cleanAlerts(): void; | |
}; | |
// 3. Creating context | |
const AlertContext = createContext<AlertsState | undefined>(undefined); | |
// 4. Hiding context by providing wanted API. | |
export type AlertContextProps = PropsWithChildren<{ | |
initialAlerts?: AlertWithId[]; | |
timeoutInMs?: number; | |
}>; | |
export function AlertContextProvider({ | |
children, | |
initialAlerts = [], | |
timeoutInMs = 5000, | |
}: AlertContextProps) { | |
const [alerts, setAlerts] = useState(initialAlerts); | |
const addAlert = useCallback((alert: Alert) => { | |
const alertWithId = { | |
...alert, | |
id: Date.now(), | |
}; | |
// Insert at top | |
setAlerts((all) => [alertWithId].concat(all)); | |
return alertWithId; | |
}, []); | |
const removeAlert = useCallback((alert: AlertWithId) => { | |
// Filter out alert | |
setAlerts((all) => all.filter((i) => i.id !== alert.id)); | |
return alert; | |
}, []); | |
const filterOldAlerts = useCallback(() => { | |
// Filter out alert | |
if (!alerts.length) return; | |
const threshold = Date.now() - timeoutInMs; | |
setAlerts((all) => all.filter((i) => i.id > threshold)); | |
}, [alerts.length, timeoutInMs]); | |
const cleanAlerts = useCallback(() => { | |
setAlerts([]); | |
}, []); | |
useInterval(filterOldAlerts, UPDATE_FREQUENCY_MS); | |
return ( | |
<AlertContext.Provider | |
value={{ | |
alerts, | |
addAlert, | |
removeAlert, | |
cleanAlerts, | |
}} | |
> | |
{children} | |
</AlertContext.Provider> | |
); | |
} | |
// 5. Exporting context accessor | |
export function useAlerts(): AlertsState { | |
const context = useContext(AlertContext); | |
if (context === undefined) { | |
throw new Error("useAlerts must be used within a AlertContextProvider"); | |
} | |
return context; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"React Context Module": { | |
"prefix": "reactcontext", | |
"body": [ | |
"import { useContext, createContext, PropsWithChildren } from \"react\";", | |
"", | |
"type $1 = {$2};", | |
"", | |
"type $1ContextState = {$3};", | |
"", | |
"", | |
"const $1Context =", | |
" createContext<$1ContextState | undefined>(undefined);", | |
"", | |
"", | |
"export type $1ContextProviderProps = PropsWithChildren<{}>;", | |
"export function $1ContextProvider({", | |
" children,", | |
"}: $1ContextProviderProps) {", | |
" $0", | |
"", | |
" return (", | |
" <$1Context.Provider value={{}}>", | |
" {children}", | |
" </$1Context.Provider>", | |
" );", | |
"}", | |
"", | |
"export function use$1() {", | |
" const context = useContext($1Context);", | |
" if (context === undefined) {", | |
" throw new Error('use$1 must be used within $1ContextProvider');", | |
" }", | |
" return context;", | |
"}", | |
"" | |
], | |
"description": "React Context Module Pattern" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment