Skip to content

Commit

Permalink
prerelease (#22)
Browse files Browse the repository at this point in the history
* prerelease

* new props for provider

* typed override name

* add test and docs

* merge provider props

* pump version
  • Loading branch information
SilentCatD authored Dec 5, 2023
1 parent fc14d84 commit bffc573
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 22 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 1.3.0

- Fix doc typo
- Add `ctor` param for `ProviderProps`
- Separate `ProviderProps` to disallow using `cleanUp` when use it with `source: T`

# 1.2.0

- Cleanup function of `Provider` only called for `create` source.
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Expose a persisted instance between renders. This value will be kept with `useRe
</Provider>,
```

These 2 two types only should be used one at a time for each `Provider` and kept it that way throughout the component's lifeCycle. Use them interchangably may cause unexpected behavior.
These 2 two types only should be used one at a time for each `Provider` and kept it that way throughout the component's lifeCycle. Use them interchangeably may cause unexpected behavior.

#### Named Provider

Expand Down Expand Up @@ -90,6 +90,24 @@ const data: CustomType = {value: "text"};

This mechanism is important for retrieving the value later on, especially when dealing with complex type systems in TypeScript.

#### Abstract Provider

For class instance, `Provider` will automatically infer name of the type based on provided value. But in architecture point of view, there are times we would need to hide the type of implementation and expose abstraction superclass only.
To do this, we can use the `ctor` parameter in `Provider`

You can do the same with the `name` parameter too. Infact, all of this behavior will be overwritten by the `name` parameter when specified.

```tsx
class SuperClass {}

class SubClass extends SuperClasss {}

// map query key will be 'SuperClass'
<Provider ctor={SuperClass} source={new SubClass()}>
<Children />
</Provider>,
```

#### Scoped data overwrite

Be warned that providing duplicated `name` values for data may lead to overwrite behavior. If multiple instances of `Provider` use the same name for their data, the previous provided value with the same name will be replaced by the subsequent one.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-scoped-provider",
"version": "1.2.0",
"version": "1.3.0",
"description": "Library for DI in react without the need of self-creating context everytime.",
"type": "module",
"source": "src/index.ts",
Expand Down
30 changes: 24 additions & 6 deletions src/Provider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { PropsWithChildren, useEffect, useRef } from 'react'
import { ReactNode, useEffect, useRef } from 'react'
import { Create } from '../types'
import { isCreate } from '../utils'
import { getObjectRuntimeName, isCreate } from '../utils'
import { ProviderScope } from '../ProviderScope'

type ProviderProps<T> = {
interface ProviderProps<T> {
source: Create<T> | T
cleanUp?: (data: T) => void
name?: string
children?: ReactNode
ctor?: new (...a: any) => any
}
function Provider<T>({ source, cleanUp, children, name }: PropsWithChildren<ProviderProps<T>>): JSX.Element {

interface CreateProviderProps<T> extends ProviderProps<T> {
source: Create<T>
ctor?: new (...a: any) => T
}
interface ValueProviderProps<T> extends ProviderProps<T> {
source: T
cleanUp?: undefined
ctor?: new (...a: any) => T
}

function Provider<T>(props: ValueProviderProps<T>): JSX.Element
function Provider<T>(props: CreateProviderProps<T>): JSX.Element
function Provider<T>({ source, cleanUp, children, name, ctor }: ProviderProps<T>): JSX.Element {
const createdData = useRef(isCreate(source) ? source() : undefined)
const valueData: T | undefined = isCreate(source) ? undefined : source
useEffect(() => {
Expand All @@ -22,11 +37,14 @@ function Provider<T>({ source, cleanUp, children, name }: PropsWithChildren<Prov
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<ProviderScope name={name} value={createdData.current ?? valueData}>
<ProviderScope
name={name ?? getObjectRuntimeName(ctor ?? createdData.current ?? valueData)}
value={createdData.current ?? valueData}
>
{children}
</ProviderScope>
)
}

export type { ProviderProps }
export type { ProviderProps, CreateProviderProps, ValueProviderProps }
export { Provider }
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
UseProviderConfigsDisAllowUndef,
UseProviderConfigs,
} from './hooks'
import { Provider, ProviderProps } from './Provider'
import { Provider, ProviderProps, CreateProviderProps, ValueProviderProps } from './Provider'
import { ContextData, ProviderContext } from './ProviderContext'
import { ProviderScope, ProviderScopeProps } from './ProviderScope'
import { ResourcesNotProvidedError } from './errors'
Expand Down Expand Up @@ -42,7 +42,7 @@ export type {
NamedConsumerAllowUndefProps,
}
export { Create }
export { ProviderProps, Provider }
export { ProviderProps, Provider, CreateProviderProps, ValueProviderProps }
export { ProviderScope, ProviderScopeProps }
export { ProviderContext, ContextData }
export {
Expand Down
13 changes: 1 addition & 12 deletions test/Provider/cleanup.test.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
import { render } from '@testing-library/react'
import { Provider } from '../../src'

it('cleanup provided value', () => {
const cleanUpFunc = jest.fn((value) => value)
const { unmount } = render(
<Provider source={'value'} cleanUp={cleanUpFunc}>
<div></div>
</Provider>,
)
unmount()
expect(cleanUpFunc.mock.calls).toHaveLength(0)
})

class A {}

it('cleanup provided Create', () => {
const instance = new A()
const cleanUpFunc = jest.fn((value) => value)
const { unmount } = render(
<Provider source={() => instance} cleanUp={(value) => cleanUpFunc(value)}>
<Provider source={() => instance} cleanUp={(data) => cleanUpFunc(data)}>
<div></div>
</Provider>,
)
Expand Down
57 changes: 57 additions & 0 deletions test/Provider/render.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,60 @@ it('render name provided create without error', () => {
const expectedText = 'Rendered'
expect(renderedText).toBe(expectedText)
})

it('render String ctor without error', () => {
const { container } = render(
<Provider ctor={String} source={'value'} name='value'>
<DisplayRendered />
</Provider>,
)
const rendered = getByTestId(container, 'text')
const renderedText = rendered.textContent
const expectedText = 'Rendered'
expect(renderedText).toBe(expectedText)
})

it('render Boolean ctor without error', () => {
const { container } = render(
<Provider ctor={Boolean} source={() => true} name='value'>
<DisplayRendered />
</Provider>,
)
const rendered = getByTestId(container, 'text')
const renderedText = rendered.textContent
const expectedText = 'Rendered'
expect(renderedText).toBe(expectedText)
})

it('render Number ctor without error', () => {
const { container } = render(
<Provider ctor={Number} source={5} name='value'>
<DisplayRendered />
</Provider>,
)
const rendered = getByTestId(container, 'text')
const renderedText = rendered.textContent
const expectedText = 'Rendered'
expect(renderedText).toBe(expectedText)
})

it('render Custom ctor without error', () => {
const { container } = render(
<Provider ctor={A} source={new B(5)} name='value'>
<DisplayRendered />
</Provider>,
)
const rendered = getByTestId(container, 'text')
const renderedText = rendered.textContent
const expectedText = 'Rendered'
expect(renderedText).toBe(expectedText)
})

class A {
count: number
constructor(count: number) {
this.count = count
}
}

class B extends A {}
58 changes: 58 additions & 0 deletions test/Provider/typeNameRespected.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { getByTestId, render } from '@testing-library/react'
import { Provider, useNamedProvider } from '../../src'

const DisplayRendered = ({ name }: { name: string }) => {
useNamedProvider(name)
return <h1 data-testid='text'>Rendered</h1>
}

class SuperClass {
count: number
constructor(count: number) {
this.count = count
}
}

class SubClass extends SuperClass {
count2: number
constructor(count2: number) {
super(0)
this.count2 = count2
}
}

it('respect Subclass type', () => {
const { container } = render(
<Provider source={new SubClass(5)}>
<DisplayRendered name='SubClass' />
</Provider>,
)
const rendered = getByTestId(container, 'text')
const renderedText = rendered.textContent
const expectedText = 'Rendered'
expect(renderedText).toBe(expectedText)
})

it('respect Superclass ctor type', () => {
const { container } = render(
<Provider ctor={SuperClass} source={new SubClass(5)}>
<DisplayRendered name='SuperClass' />
</Provider>,
)
const rendered = getByTestId(container, 'text')
const renderedText = rendered.textContent
const expectedText = 'Rendered'
expect(renderedText).toBe(expectedText)
})

it('respect specified name', () => {
const { container } = render(
<Provider ctor={SuperClass} source={new SubClass(5)} name='custom-name'>
<DisplayRendered name='custom-name' />
</Provider>,
)
const rendered = getByTestId(container, 'text')
const renderedText = rendered.textContent
const expectedText = 'Rendered'
expect(renderedText).toBe(expectedText)
})

0 comments on commit bffc573

Please sign in to comment.