-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from craig-waite/extended-context
Extended context
- Loading branch information
Showing
23 changed files
with
1,018 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
pnpm-debug.log* | ||
lerna-debug.log* | ||
|
||
node_modules | ||
package-lock.json | ||
dist | ||
dist-ssr | ||
*.local | ||
|
||
# Editor directories and files | ||
.vscode/* | ||
!.vscode/extensions.json | ||
.idea | ||
.DS_Store | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln | ||
*.sw? |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vite + React + TS</title> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="/src/main.tsx"></script> | ||
</body> | ||
</html> |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"name": "simple-context", | ||
"private": true, | ||
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "tsc && vite build", | ||
"preview": "vite preview" | ||
}, | ||
"dependencies": { | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0" | ||
}, | ||
"devDependencies": { | ||
"@types/react": "^18.0.17", | ||
"@types/react-dom": "^18.0.6", | ||
"@vitejs/plugin-react": "^2.1.0", | ||
"typescript": "^4.6.4", | ||
"vite": "^3.1.0" | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import createFastContext from "./app/createFastContext"; | ||
import MainPage from "./pages/MainPage"; | ||
|
||
export const { | ||
FastContextProvider:AppFastContextProvider, | ||
useFastContextFields:useAppFastContextFields | ||
} = createFastContext({ | ||
first: "" as string, | ||
last: "" as string, | ||
}); | ||
|
||
function App() { | ||
console.log(`App Rendering`) | ||
return ( | ||
<AppFastContextProvider> | ||
<div className="container"> | ||
<img src="../public/DevToolsImage.png" style={{float: 'right', width: '350px'}} alt="Dev Tools"/> | ||
<h1>App (NO RE-RENDERS)</h1> | ||
<p>The App's Fast Context is created and exported here, but could be created in any file.</p> | ||
<p>Switch on the re-render highlight function (see image) in dev tools to see when re-renders happen.</p> | ||
<MainPage /> | ||
</div> | ||
</AppFastContextProvider> | ||
); | ||
} | ||
|
||
export default App; |
83 changes: 83 additions & 0 deletions
83
fast-context-generic-extended/src/app/createFastContext.tsx
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import React, { | ||
useRef, | ||
createContext, | ||
useContext, | ||
useCallback, | ||
useSyncExternalStore, | ||
} from "react"; | ||
|
||
export default function createFastContext<FastContext>(initialState: FastContext) { | ||
function useFastContextData(): { | ||
get: () => FastContext; | ||
set: (value: Partial<FastContext>) => void; | ||
subscribe: (callback: () => void) => () => void; | ||
} { | ||
const store = useRef(initialState); | ||
|
||
const get = useCallback(() => store.current, []); | ||
|
||
const subscribers = useRef(new Set<() => void>()); | ||
|
||
const set = useCallback((value: Partial<FastContext>) => { | ||
store.current = { ...store.current, ...value }; | ||
subscribers.current.forEach((callback) => callback()); | ||
}, []); | ||
|
||
const subscribe = useCallback((callback: () => void) => { | ||
subscribers.current.add(callback); | ||
return () => subscribers.current.delete(callback); | ||
}, []); | ||
|
||
return { | ||
get, | ||
set, | ||
subscribe, | ||
}; | ||
} | ||
|
||
type UseFastContextDataReturnType = ReturnType<typeof useFastContextData>; | ||
|
||
const FastContext = createContext<UseFastContextDataReturnType | null>(null); | ||
|
||
function FastContextProvider({ children }: Readonly<{ children: React.ReactNode }>) { | ||
return ( | ||
<FastContext.Provider value={useFastContextData()}> | ||
{children} | ||
</FastContext.Provider> | ||
); | ||
} | ||
|
||
function useFastContext<SelectorOutput>( | ||
selector: (store: FastContext) => SelectorOutput | ||
): [SelectorOutput, (value: Partial<FastContext>) => void] { | ||
const fastContext = useContext(FastContext); | ||
if (!fastContext) { | ||
throw new Error("Store not found"); | ||
} | ||
|
||
const state = useSyncExternalStore( | ||
fastContext.subscribe, | ||
() => selector(fastContext.get()), | ||
() => selector(initialState), | ||
); | ||
|
||
return [state, fastContext.set]; | ||
} | ||
|
||
function useFastContextFields<SelectorOutput>( | ||
fieldNames: string[] | ||
): { [key: string]: { get: SelectorOutput, set: (value: any) => void } } { | ||
const gettersAndSetters: { [key: string]: { get: SelectorOutput, set: (value: any) => void } } = {}; | ||
for (const fieldName of fieldNames) { | ||
const [getter, setter] = useFastContext((fc) => (fc as Record<string, SelectorOutput>)[fieldName]); | ||
gettersAndSetters[fieldName] = { get: getter, set: (value: any) => setter({ [fieldName]: value } as Partial<FastContext>) }; | ||
} | ||
|
||
return gettersAndSetters; | ||
} | ||
|
||
return { | ||
FastContextProvider, | ||
useFastContextFields, | ||
}; | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions
24
fast-context-generic-extended/src/components/ContentContainer.tsx
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { PropDrivenDisplayContainer, SelfDrivenDisplayContainer } from "./DisplayContainer"; | ||
import { PropDrivenFormContainer, SelfDrivenFormContainer } from "./FormContainer"; | ||
|
||
export function PropDrivenContentContainer() { | ||
console.log(`Prop Driven Content Rendering`) | ||
return ( | ||
<div className="container"> | ||
<h3>'Prop Driven' Content Container (NO RE-RENDERS)</h3> | ||
<PropDrivenFormContainer /> | ||
<PropDrivenDisplayContainer /> | ||
</div> | ||
); | ||
}; | ||
|
||
export function SelfDrivenContentContainer() { | ||
console.log(`Self Driven Content Rendering`) | ||
return ( | ||
<div className="container"> | ||
<h3>'Self Driven' Content Container (NO RE-RENDERS)</h3> | ||
<SelfDrivenFormContainer /> | ||
<SelfDrivenDisplayContainer /> | ||
</div> | ||
); | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { useAppFastContextFields } from "../App"; | ||
|
||
type Props = { | ||
fieldName?: string; | ||
label?: string; | ||
value?: string; | ||
}; | ||
export function PropDrivenDisplay({label, value}: Readonly<Props>) { | ||
console.log(`Prop Driven ${label} display rendering`) | ||
return ( | ||
<div className="value"> | ||
{label ? <label>{label} : </label> : null} | ||
<input value={value} readOnly style={{backgroundColor: "#eee", cursor: 'auto', border: 0}}/> | ||
</div> | ||
); | ||
}; | ||
|
||
export function SelfDrivenDisplay({ fieldName = "", label }: Readonly<Props>) { | ||
console.log(`Self Driven ${label} display rendering`) | ||
const value = useAppFastContextFields([fieldName]); | ||
return ( | ||
<div className="value"> | ||
{label ? <label>{label} : </label> : null} | ||
<input value={value[fieldName].get as string} readOnly style={{backgroundColor: "#eee", cursor: 'auto', border: 0}}/> | ||
</div> | ||
); | ||
}; |
25 changes: 25 additions & 0 deletions
25
fast-context-generic-extended/src/components/DisplayContainer.tsx
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { useAppFastContextFields } from "../App"; | ||
import { PropDrivenDisplay, SelfDrivenDisplay } from "./Display"; | ||
|
||
export function PropDrivenDisplayContainer() { | ||
console.log(`Prop Driven Display Rendering`) | ||
const fields = useAppFastContextFields(['first', 'last']); | ||
return ( | ||
<div className="container"> | ||
<h4>'Prop Driven' Display (Container AND children re-render on field changes)</h4> | ||
<PropDrivenDisplay label="First Name" value={fields.first.get as string} /> | ||
<PropDrivenDisplay label="Last Name" value={fields.last.get as string} /> | ||
</div> | ||
); | ||
}; | ||
|
||
export function SelfDrivenDisplayContainer() { | ||
console.log(`Self Driven Display Rendering`) | ||
return ( | ||
<div className="container"> | ||
<h4>'Self Driven' Display (NO RE-RENDERS - only children re-render on field changes)</h4> | ||
<SelfDrivenDisplay fieldName="first" label="First Name" /> | ||
<SelfDrivenDisplay fieldName="last" label="Last Name" /> | ||
</div> | ||
); | ||
}; |
26 changes: 26 additions & 0 deletions
26
fast-context-generic-extended/src/components/FormContainer.tsx
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { useAppFastContextFields } from "../App"; | ||
import { FormDrivenTextInput, SelfDrivenTextInput } from "./TextInput"; | ||
|
||
export function PropDrivenFormContainer() { | ||
console.log(`Prop Driven Form Rendering`) | ||
const fields = useAppFastContextFields(['first', 'last']); | ||
console.log(`fields:`, fields) | ||
return ( | ||
<div className="container"> | ||
<h4>'Prop Driven' Input Form (Form AND children re-render on field changes)</h4> | ||
<FormDrivenTextInput value={fields.first.get as string} label='First Name' onChange={fields.last.set}/> | ||
<FormDrivenTextInput value={fields.last.get as string} label='Last Name' onChange={fields.last.set} /> | ||
</div> | ||
); | ||
}; | ||
|
||
export function SelfDrivenFormContainer() { | ||
console.log(`Self Driven Form Rendering`) | ||
return ( | ||
<div className="container"> | ||
<h4>'Self Driven' Input Form (NO RE-RENDERS - only children re-render on field changes)</h4> | ||
<SelfDrivenTextInput fieldName="first" label='First Name'/> | ||
<SelfDrivenTextInput fieldName="last" label='Last Name'/> | ||
</div> | ||
); | ||
}; |
35 changes: 35 additions & 0 deletions
35
fast-context-generic-extended/src/components/TextInput.tsx
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { useAppFastContextFields } from "../App"; | ||
|
||
type Props = { | ||
fieldName?: string; | ||
label?: string; | ||
value?: string; | ||
onChange?: (value: string) => void; | ||
}; | ||
export function FormDrivenTextInput( { label = '', value, onChange = (v) => {}}: Readonly<Props> ) { | ||
console.log(`Prop Driven ${label} input rendering`) | ||
return ( | ||
<div className="field"> | ||
{label ? <label>{label} : </label> : null} | ||
<input | ||
value={value} | ||
onChange={(e) => onChange(e.target.value)} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
|
||
export function SelfDrivenTextInput( { fieldName = "", label }: Readonly<Props> ) { | ||
console.log(`Self Driven ${label} input rendering`) | ||
const field = useAppFastContextFields([fieldName]); | ||
return ( | ||
<div className="field"> | ||
{label ? <label>{label} : </label> : null} | ||
<input | ||
value={field[fieldName].get as string} | ||
onChange={(e) => field[fieldName].set(e.target.value)} | ||
/> | ||
</div> | ||
); | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
body { | ||
font-family: Arial, Helvetica, sans-serif; | ||
padding: 1rem; | ||
} | ||
|
||
.container { | ||
margin-top: 0.5rem; | ||
padding: 0.5rem 1.5rem; | ||
border: 1px solid #ccc; | ||
} | ||
|
||
.field, .value { | ||
padding: 0.5rem; | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import React from 'react' | ||
import ReactDOM from 'react-dom/client' | ||
import App from './App' | ||
import './index.css' | ||
|
||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( | ||
<React.StrictMode> | ||
<App /> | ||
</React.StrictMode> | ||
) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { PropDrivenContentContainer, SelfDrivenContentContainer } from "../components/ContentContainer"; | ||
|
||
export default function MainPage() { | ||
console.log(`Page Rendering`) | ||
return ( | ||
<div className="container"> | ||
<h2>Page (NO RE-RENDERS)</h2> | ||
<div style={{display: 'flex', justifyContent: 'space-around', margin: '1em'}}> | ||
<PropDrivenContentContainer /> | ||
<SelfDrivenContentContainer /> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/// <reference types="vite/client" /> |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ESNext", | ||
"useDefineForClassFields": true, | ||
"lib": ["DOM", "DOM.Iterable", "ESNext"], | ||
"allowJs": false, | ||
"skipLibCheck": true, | ||
"esModuleInterop": false, | ||
"allowSyntheticDefaultImports": true, | ||
"strict": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"module": "ESNext", | ||
"moduleResolution": "Node", | ||
"resolveJsonModule": true, | ||
"isolatedModules": true, | ||
"noEmit": true, | ||
"jsx": "react-jsx" | ||
}, | ||
"include": ["src"], | ||
"references": [{ "path": "./tsconfig.node.json" }] | ||
} |
Oops, something went wrong.