Skip to content

Commit

Permalink
feat(jsx/dom): implement "<Context> as a provider" for compatibility …
Browse files Browse the repository at this point in the history
…with React 19 (#2962)
  • Loading branch information
usualoma authored and yusukebe committed Jun 27, 2024
1 parent 37a10f4 commit 032070a
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 33 deletions.
52 changes: 25 additions & 27 deletions src/jsx/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { DOM_RENDERER } from './constants'
import { createContextProviderFunction } from './dom/context'
import type { FC, PropsWithChildren } from './'

export interface Context<T> {
export interface Context<T> extends FC<PropsWithChildren<{ value: T }>> {
values: T[]
Provider: FC<PropsWithChildren<{ value: T }>>
}
Expand All @@ -14,33 +14,31 @@ export const globalContexts: Context<unknown>[] = []

export const createContext = <T>(defaultValue: T): Context<T> => {
const values = [defaultValue]
const context: Context<T> = {
values,
Provider(props): HtmlEscapedString | Promise<HtmlEscapedString> {
values.push(props.value)
let string
try {
string = props.children
? (Array.isArray(props.children)
? new JSXFragmentNode('', {}, props.children)
: props.children
).toString()
: ''
} finally {
values.pop()
}

if (string instanceof Promise) {
return string.then((resString) =>
raw(resString, (resString as HtmlEscapedString).callbacks)
)
} else {
return raw(string)
}
},
}
const context: Context<T> = ((props): HtmlEscapedString | Promise<HtmlEscapedString> => {
values.push(props.value)
let string
try {
string = props.children
? (Array.isArray(props.children)
? new JSXFragmentNode('', {}, props.children)
: props.children
).toString()
: ''
} finally {
values.pop()
}

if (string instanceof Promise) {
return string.then((resString) => raw(resString, (resString as HtmlEscapedString).callbacks))
} else {
return raw(string)
}
}) as Context<T>
context.values = values
context.Provider = context

// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(context.Provider as any)[DOM_RENDERER] = createContextProviderFunction(values)
;(context as any)[DOM_RENDERER] = createContextProviderFunction(values)

globalContexts.push(context as Context<unknown>)

Expand Down
18 changes: 18 additions & 0 deletions src/jsx/dom/context.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@ function runner(
expect(root.innerHTML).toBe('<p>1</p>')
})

it('<Context> as a provider ', async () => {
const Context = createContext(0)
const Content = () => {
const num = useContext(Context)
return <p>{num}</p>
}
const Component = () => {
return (
<Context value={1}>
<Content />
</Context>
)
}
const App = <Component />
render(App, root)
expect(root.innerHTML).toBe('<p>1</p>')
})

it('simple context with state', async () => {
const Context = createContext(0)
const Content = () => {
Expand Down
10 changes: 4 additions & 6 deletions src/jsx/dom/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,9 @@ export const createContextProviderFunction = <T>(values: T[]): Function =>

export const createContext = <T>(defaultValue: T): Context<T> => {
const values = [defaultValue]
const context = {
values,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Provider: createContextProviderFunction(values) as any,
}
globalContexts.push(context)
const context: Context<T> = createContextProviderFunction(values) as Context<T>
context.values = values
context.Provider = context
globalContexts.push(context as Context<unknown>)
return context
}
11 changes: 11 additions & 0 deletions src/jsx/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,17 @@ describe('Context', () => {
})
})

describe('<Context> as a provider ', () => {
it('has a child', () => {
const template = (
<ThemeContext value='dark'>
<Consumer />
</ThemeContext>
)
expect(template.toString()).toBe('<span>dark</span>')
})
})

it('default value', () => {
const template = <Consumer />
expect(template.toString()).toBe('<span>light</span>')
Expand Down

0 comments on commit 032070a

Please sign in to comment.