Skip to content

Instantly share code, notes, and snippets.

@LeMark0
Last active October 1, 2023 10:23
Show Gist options
  • Save LeMark0/28c7c8d0edcafb76ea77f4a0948d88fc to your computer and use it in GitHub Desktop.
Save LeMark0/28c7c8d0edcafb76ea77f4a0948d88fc to your computer and use it in GitHub Desktop.
PageContextFactory is a helper designed to reduce boilerplate when creating React contexts. It allows you to easily create contexts for pages, enabling the sharing of state and logic across the component tree.

Usage

useYourSharedLogic.ts

// Hook with the logic you want to share across your page's components
export const useYourSharedLogic = () => {
  // fetch, calculate, etc

  return {
    data
  }
}

context/index.ts

import { useYourSharedLogic } from 'hooks/useYourSharedLogic'

// Create context for the page
export const { 
  PageProvider, 
  usePageContext,
} = createPageContext(useYourSharedLogic)

Page.tsx

import { PageProvider } from './context'

// Wrap your page into the provider
export Page = () => {

  return (
    <PageProvider>
      <YourPageComponentsTreeThatNeedsContext />
    </PageProvider>
  )
}

Component.tsx

// Now, you're free to use it 🎉🎉🎉
export const Component = () => {
  const { data } = usePageContext()

  return <div>Hej, here is my {data}</div>
}
import { renderHook } from '@testing-library/react-hooks'
import { createPageContext } from './PageContextFactory.tsx'
describe('createPageContext', () => {
it('should create context and provide access to the state', () => {
// Mock hook
const stateHook = jest.fn(() => ['Test value', jest.fn()])
// Use the mock hook to create context
const { PageProvider, usePageContext } = createPageContext(stateHook)
const wrapper = ({ children }: { children: any }) => (
<PageProvider>{children}</PageProvider>
)
// Render the hook
const { result } = renderHook(() => usePageContext(), { wrapper })
// The hook should return the initial state
expect(result.current[0]).toBe('Test value')
})
it('should throw an error when usePageContext is used outside of a PageProvider', () => {
// Mock hook
const stateHook = jest.fn(() => ['Test value', jest.fn()])
// Use the mock hook to create context
const { usePageContext } = createPageContext(stateHook)
// Render the hook without a PageProvider
const { result } = renderHook(() => usePageContext())
// It should throw an error
expect(result.error).toEqual(
Error('usePageContext must be used within a PageProvider'),
)
})
})
import { createContext, ReactElement, ReactNode, useContext } from 'react'
type HookType = (...args: any) => any
type PageProviderProps<T extends HookType> = {
children: ReactElement | ReactNode
stateHook: T
PageContext: React.Context<ReturnType<T> | undefined>
}
function PageProvider<T extends HookType>({
children,
stateHook,
PageContext,
}: PageProviderProps<T>) {
const context = stateHook()
return <PageContext.Provider value={context}>{children}</PageContext.Provider>
}
function getUsePageContext<T extends HookType>(
PageContext: React.Context<ReturnType<HookType> | undefined>,
) {
return () => {
const context = useContext<ReturnType<T>>(PageContext)
if (context === undefined) {
throw new Error('usePageContext must be used within a PageProvider')
}
return context
}
}
export function createPageContext<T extends HookType>(stateHook: T) {
const PageContext = createContext<ReturnType<T> | undefined>(undefined)
return {
PageContext,
PageProvider: ({ children }: { children: ReactElement | ReactNode }) => (
<PageProvider<T> PageContext={PageContext} stateHook={stateHook}>
{children}
</PageProvider>
),
usePageContext: getUsePageContext<T>(PageContext),
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment