react-solid-flow
Advanced tools
Comparing version 0.2.3 to 1.0.0
@@ -1,8 +0,26 @@ | ||
# managed-timeout changelog | ||
# react-solid-flow changelog | ||
All notable changes to this project will be documented in this file. | ||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), | ||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
## Unreleased | ||
## [1.0.0] - 2024-01-23 | ||
### Added | ||
- additional typescript checks if useResource value is a function type, so it's | ||
impossible to accidently miss the external function wrapper during initialization | ||
- `<Match />` narrows down renderProp type to non-nullable values | ||
### Changed | ||
- `<Await />` now renders nothing instead of throwing an error, if nullish `for` prop was supplied | ||
- default module format switched from cjs to mjs | ||
- builder changed from vite to tsup | ||
- exports declaration was changed for better cjs/mjs support | ||
- stricter internal dispatch checkings in useResource | ||
- documentation and changelog typos, wordings and better examples | ||
- source code is ommited from the npm bundle | ||
### Security | ||
- devDependencies bump | ||
## [0.2.3] - 2023-02-20 | ||
@@ -9,0 +27,0 @@ ### Changed |
@@ -1,12 +0,221 @@ | ||
export { For } from "./components/For"; | ||
export { Show } from "./components/Show"; | ||
export { Switch } from "./components/Switch"; | ||
export { Match } from "./components/Match"; | ||
export { ErrorBoundary } from "./components/ErrorBoundary"; | ||
export { Dynamic } from "./components/Dynamic"; | ||
export { Portal } from "./components/Portal"; | ||
export { Await } from "./components/Await"; | ||
export * from "./models/Resource"; | ||
export { AbortError } from "./models/AbortError"; | ||
export { NullishError } from "./models/NullishError"; | ||
export { useResource } from "./hooks/useResource"; | ||
import React, { ReactNode, ReactElement, Component, ReactPortal } from 'react'; | ||
interface ForProps<T, U extends ReactNode> { | ||
/** Array to iterate over */ | ||
each: ReadonlyArray<T> | undefined | null; | ||
/** RenderProp for children generation | ||
* OR a static element displayed each.length times */ | ||
children: ReactNode | ((item: T, idx: number) => U); | ||
/** Fallback item, displayed if *each* has zero length, or isn't an array */ | ||
fallback?: ReactNode; | ||
} | ||
/** Component for mapping an array into collection of ReactNode's | ||
* Omits nullish children and provides keys if they're not specified. | ||
*/ | ||
declare function For<T, U extends ReactNode>({ children, each, fallback, }: ForProps<T, U>): ReactElement | null; | ||
interface ShowProps<T> { | ||
/** predicate */ | ||
when: T | undefined | null | false; | ||
/** content (or renderProp) to display when predicate is truthy */ | ||
children: ReactNode | ((item: NonNullable<T>) => ReactNode); | ||
/** content to display when predicate is falsy */ | ||
fallback?: ReactNode; | ||
} | ||
/** Conditional rendering component */ | ||
declare function Show<T>({ fallback, ...props }: ShowProps<T>): ReactElement | null; | ||
interface SwitchProps { | ||
children: ReactNode; | ||
/** content to display if no Match predicate is truthy */ | ||
fallback?: ReactNode; | ||
} | ||
/** Component to display one exclusive condition out of many, | ||
* using Match component | ||
*/ | ||
declare function Switch(props: SwitchProps): ReactElement | null; | ||
interface MatchProps<T> { | ||
/** predicate */ | ||
when: T | undefined | null | false; | ||
/** content (or renderProp) to display if predicate is truthy */ | ||
children?: ReactNode | ((item: NonNullable<T>) => ReactNode); | ||
} | ||
/** Single branch of Switch component. */ | ||
declare function Match<T>({ when, children }: MatchProps<T>): ReactElement | null; | ||
interface ErrorBoundaryProps { | ||
/** renderProp (or static content) to display if error has occured */ | ||
fallback?: ReactNode | ((err: unknown, reset: () => void) => ReactNode); | ||
/** content to display when no error was catched */ | ||
children?: ReactNode; | ||
/** callback to call, when an error happens */ | ||
onCatch?: (error: unknown, errorInfo: unknown) => void; | ||
} | ||
interface ErrorBoundaryState { | ||
error: unknown; | ||
} | ||
/** General ErrorBoundary component */ | ||
declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> { | ||
constructor(props: ErrorBoundaryProps); | ||
state: ErrorBoundaryState; | ||
static getDerivedStateFromError(error: unknown): { | ||
error: unknown; | ||
}; | ||
componentDidCatch(error: unknown, errorInfo: unknown): void; | ||
resetError(): void; | ||
render(): ReactNode; | ||
} | ||
/** This component lets you insert an arbitrary Component or tag and passes | ||
* the props through to it. | ||
* For example, it can be usefull when you need to conditionally render | ||
* <a> or <span> */ | ||
declare const Dynamic: <T extends {}>(props: T & { | ||
children?: any; | ||
component?: string | React.ComponentClass<T, any> | React.FunctionComponent<T> | undefined; | ||
} & React.RefAttributes<unknown>) => ReactElement | null; | ||
interface PortalProps { | ||
mount?: Element | DocumentFragment | string; | ||
children?: ReactNode; | ||
} | ||
/** Component for rendering children outside of the Component Hierarchy root node. */ | ||
declare function Portal({ mount, ...props }: PortalProps): ReactPortal | null; | ||
interface ResourceLike<T> { | ||
/** Is new data currently loading */ | ||
loading?: boolean; | ||
/** Resolved resource data for sync access */ | ||
data: Awaited<T> | undefined; | ||
/** Rejected resource error */ | ||
error: unknown; | ||
} | ||
type ResourceState = "unresolved" | "pending" | "ready" | "refreshing" | "errored"; | ||
declare class Resource<T> implements ResourceLike<T> { | ||
loading: boolean; | ||
data: Awaited<T> | undefined; | ||
error: unknown; | ||
/** State name | ||
* | ||
* | state | data | loading | error | | ||
* |:-----------|:-----:|:-------:|:-----:| | ||
* | unresolved | No | No | No | | ||
* | pending | No | Yes | No | | ||
* | ready | Yes | No | No | | ||
* | refreshing | Yes | Yes | No | | ||
* | errored | No | No | Yes | | ||
*/ | ||
state: ResourceState; | ||
/** last resolved value | ||
* | ||
* This can be useful if you want to show the out-of-date data while the new | ||
* data is loading. | ||
*/ | ||
latest: Awaited<T> | undefined; | ||
constructor(init?: Partial<ResourceLike<T>>, previous?: { | ||
latest?: Awaited<T>; | ||
}); | ||
static from<T>(data: Promise<T> | Awaited<T> | undefined, pend?: boolean): Resource<T>; | ||
/** | ||
* Determine resource-like state, based on its fields values. | ||
* | ||
* | state | data | loading | error | | ||
* |:-----------|:-----:|:-------:|:-----:| | ||
* | unresolved | No | No | No | | ||
* | pending | No | Yes | No* | | ||
* | ready | Yes | No | No | | ||
* | refreshing | Yes | Yes | No* | | ||
* | errored | No* | No | Yes | | ||
* | ||
* Values marked with * are expected to equal the specified value, | ||
* but actually ignored, when determining the status. | ||
*/ | ||
static getState(r: ResourceLike<unknown>): ResourceState; | ||
} | ||
interface AwaitProps<T> { | ||
/** resource to wait for */ | ||
for: ResourceLike<T>; | ||
/** renderProp (or static content) to display while loading */ | ||
fallback?: (() => ReactNode) | ReactNode; | ||
/** renderProp (or static content) to display if resource was rejected */ | ||
catch?: ((err: unknown) => ReactNode) | ReactNode; | ||
/** renderProp (or static content) to display when resource is resolved */ | ||
children?: ((data: Awaited<T>) => ReactNode) | ReactNode; | ||
} | ||
/** Component for displaying a Resource */ | ||
declare function Await<T>(props: AwaitProps<T>): ReactElement | null; | ||
declare class AbortError extends Error { | ||
readonly name: "AbortError"; | ||
readonly code: 20; | ||
constructor(message?: string); | ||
} | ||
declare class NullishError extends Error { | ||
readonly name: "NullishError"; | ||
} | ||
/** State initializer. | ||
* | ||
* Either a value, or a function returning a value, for lazy computing. | ||
* If state type is a function, then initializer is always lazy. | ||
*/ | ||
type Initializer<T> = T extends Function ? () => T : T | (() => T); | ||
type ResourceReturn<T, TArgs extends readonly unknown[]> = [ | ||
Resource<T>, | ||
{ | ||
/** Manually set the value. | ||
* | ||
* If fetcher was currently pending, it's aborted. | ||
*/ | ||
mutate: (v: Awaited<T>) => void; | ||
/** | ||
* Call refetch with supplied args. | ||
* | ||
* Fetcher opts added automatically. If fetcher was currently pending, it's aborted. | ||
*/ | ||
refetch: (...args: TArgs) => Promise<T> | T; | ||
/** Imperatively abort the current fetcher call. | ||
* | ||
* If abort is performed with no reason, or with AbortError instance, then | ||
* the state is still considered pending/refreshing, resource.error is | ||
* not updated, and onError callback is not called. | ||
* Any other reason will result in erorred resource state. | ||
* | ||
* Resource won't be refetched untill deps change again. | ||
*/ | ||
abort: (reason?: any) => void; | ||
} | ||
]; | ||
type ResourceOptions<T> = { | ||
/** Initial value for the resource */ | ||
initialValue?: Initializer<Awaited<T>>; | ||
/** resolve callback */ | ||
onCompleted?: (data: Awaited<T>) => void; | ||
/** rejection callback */ | ||
onError?: (error: unknown) => void; | ||
/** Skip first run (before params change) */ | ||
skipFirstRun?: boolean; | ||
/** Skip calls of fetcher (can still be called manually with refresh) | ||
* | ||
* Can be useful if you're waiting for some of deps to be in certain state | ||
* before calling the fetcher or if you want to trigger the fetcher only | ||
* manually on some event. | ||
*/ | ||
skip?: boolean; | ||
/** Don't memoize getter, rerun it every time it changes */ | ||
skipFnMemoization?: boolean; | ||
}; | ||
interface FetcherOpts { | ||
/** is true, if the call to fetcher was triggered manually with refetch function, | ||
* false otherwise */ | ||
refetching: boolean; | ||
/** can be used to abort operations in fetcher function, i.e. passed to fetch options */ | ||
signal: AbortSignal; | ||
} | ||
declare function useResource<T, TArgs extends readonly any[]>(fetcher: ((...args: [...TArgs, FetcherOpts]) => Promise<T> | T) | ((...args: TArgs) => Promise<T> | T), deps?: [...TArgs], { initialValue, onCompleted, onError, skipFirstRun, skip, skipFnMemoization, }?: ResourceOptions<T>): ResourceReturn<T, TArgs>; | ||
export { AbortError, Await, Dynamic, ErrorBoundary, For, Match, NullishError, Portal, Resource, type ResourceLike, type ResourceState, Show, Switch, useResource }; |
@@ -0,0 +0,0 @@ Copyright (c) 2022 Viacheslav Azarov |
{ | ||
"name": "react-solid-flow", | ||
"version": "0.2.3", | ||
"version": "1.0.0", | ||
"license": "MIT", | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "tsc && vite build", | ||
"preview": "vite preview", | ||
"build": "tsup --dts", | ||
"test": "vitest --run --coverage", | ||
@@ -13,2 +11,23 @@ "lint": "eslint .", | ||
}, | ||
"type": "module", | ||
"main": "./dist/lib.js", | ||
"types": "./dist/lib.d.ts", | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./dist/lib.d.ts", | ||
"default": "./dist/lib.js" | ||
}, | ||
"require": { | ||
"types": "./dist/lib.d.cts", | ||
"require": "./dist/lib.cjs" | ||
} | ||
} | ||
}, | ||
"files": [ | ||
"dist", | ||
"README.md", | ||
"CHANGELOG.md", | ||
"useResource-examples.md" | ||
], | ||
"repository": { | ||
@@ -23,21 +42,20 @@ "type": "git", | ||
"devDependencies": { | ||
"@testing-library/jest-dom": "^5.16.5", | ||
"@testing-library/react": "^14.0.0", | ||
"@types/react": "^18.0.28", | ||
"@types/react-dom": "^18.0.11", | ||
"@types/testing-library__jest-dom": "^5.14.5", | ||
"@typescript-eslint/eslint-plugin": "^5.52.0", | ||
"@typescript-eslint/parser": "^5.52.0", | ||
"@vitejs/plugin-react": "^3.1.0", | ||
"@vitest/coverage-c8": "^0.28.5", | ||
"eslint": "^8.34.0", | ||
"@testing-library/jest-dom": "^6.2.1", | ||
"@testing-library/react": "^14.1.2", | ||
"@types/react": "^18.2.48", | ||
"@types/react-dom": "^18.2.18", | ||
"@types/testing-library__jest-dom": "^5.14.9", | ||
"@typescript-eslint/eslint-plugin": "^6.19.1", | ||
"@typescript-eslint/parser": "^6.19.1", | ||
"@vitejs/plugin-react": "^4.2.1", | ||
"@vitest/coverage-v8": "^1.2.1", | ||
"eslint": "^8.56.0", | ||
"eslint-config-google": "^0.14.0", | ||
"eslint-plugin-react": "^7.32.2", | ||
"jsdom": "^21.1.0", | ||
"eslint-plugin-react": "^7.33.2", | ||
"jsdom": "^24.0.0", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"typescript": "^4.9.5", | ||
"vite": "^4.1.3", | ||
"vite-plugin-dts": "^1.7.3", | ||
"vitest": "^0.28.5" | ||
"tsup": "^8.0.1", | ||
"typescript": "^5.3.3", | ||
"vitest": "^1.2.1" | ||
}, | ||
@@ -70,12 +88,3 @@ "keywords": [ | ||
"use-promise" | ||
], | ||
"main": "dist/react-solid-flow.umd.js", | ||
"es2015": "dist/react-solid-flow.es.mjs", | ||
"types": "dist/react-solid-flow.es.d.ts", | ||
"exports": { | ||
".": { | ||
"import": "./dist/react-solid-flow.es.mjs", | ||
"require": "./dist/react-solid-flow.umd.js" | ||
} | ||
} | ||
] | ||
} |
@@ -11,3 +11,3 @@ # react-solid-flow | ||
- Native Typescript support | ||
- Lightweight: (5kb minified UMD, 2.5kb gzip), tree-shakable, | ||
- Lightweight: (5kb minified, 2.5kb gzip), tree-shakable, | ||
- Zero third-party dependencies, except React and React-DOM | ||
@@ -21,2 +21,3 @@ - Modern: React 16.8+ .. 18.x, no legacy APIs or weird hacks | ||
- Covers common pitfalls (missed keys in maps, primitives as children, etc.) | ||
- Made to be effective in React 16 and 17 and compatible with older webpack setups | ||
- โกโก๐ฉ๐ฉ bLaZinGly FaSt ๐ฉ๐ฉโกโก | ||
@@ -52,10 +53,10 @@ | ||
Rendering a collection of items from the _each_ prop. | ||
Rendering a collection of items from the `each` prop. | ||
The _children_ prop can be either a render prop function (more useful) | ||
The `children` prop can be either a render prop function (more useful) | ||
or a static element. | ||
If _each_ isn't an array or has zero length, display the optional _fallback_. | ||
If `each` isn't an array or has zero length, display the optional `fallback`. | ||
Any nullish child is omitted. If every child is omitted, the _fallback_ prop is shown. | ||
Any nullish child is omitted. If every child is omitted, the `fallback` prop is shown. | ||
@@ -83,4 +84,4 @@ You can specify a key prop directly on the root element of a child, using | ||
``` | ||
Conditionally renders, depending on truthiness of the _when_ prop, either the | ||
_children_ prop or (optionally) the _fallback_ prop. | ||
Conditionally renders, depending on truthiness of the `when` prop, either the | ||
`children` prop or (optionally) the `fallback` prop. | ||
@@ -97,3 +98,3 @@ #### Switch / Match | ||
when: T | undefined | null | false; | ||
children?: ReactNode | ((item: T) => ReactNode); | ||
children?: ReactNode | ((item: NonNullable<T>) => ReactNode); | ||
}): ReactElement | null; | ||
@@ -116,8 +117,8 @@ ``` | ||
Akin to switch-case, it renders one of the mutually exclusive conditions | ||
(described in the _when_ prop of the Match component) of a switch. | ||
(described in the `when` prop of the Match component) of a switch. | ||
The _Match_ component should be a direct descendant of the _Switch_ component, | ||
and only the first _Match_ with a truthy _when_ prop will be rendered. | ||
The `Match` component should be a direct descendant of the `Switch` component, | ||
and only the first `Match` with a truthy `when` prop will be rendered. | ||
If no _Match_ component has a truthy _when_ prop, the optional _fallback_ prop | ||
If no `Match` component has a truthy `when` prop, the optional `fallback` prop | ||
will be shown. | ||
@@ -153,7 +154,7 @@ | ||
The _fallback_ prop can be a static element or a render prop function, which | ||
receives the occurred error and the _reset_ callback as its arguments. | ||
The `fallback` prop can be a static element or a render prop function, which | ||
receives the occurred error and the `reset` callback as its arguments. | ||
A call to the _reset_ function clears the occurred error and performs a | ||
re-render of _children_ after that. | ||
A call to the `reset` function clears the occurred error and performs a | ||
re-render of `children` after that. | ||
@@ -178,3 +179,3 @@ #### Await | ||
A component for displaying resource-like async data. It can be used with a | ||
resource returned by the _useResource_ hook in this library, or any other | ||
resource returned by the `useResource` hook in this library, or any other | ||
object that conforms to the required interface (such as responses from | ||
@@ -217,3 +218,3 @@ the Apollo Client). | ||
This component allows you to insert an arbitrary component or tag and pass props | ||
to it (excluding the _component_ prop). | ||
to it (excluding the `component` prop). | ||
@@ -244,6 +245,6 @@ Props are controlled by Typescript for Components, but not JSX intrinsic | ||
``` | ||
This component renders _children_ outside of the component hierarchy's root node. | ||
This component renders `children` outside of the component hierarchy's root node. | ||
React events will still function as usual. | ||
The _mount_ prop can be either a native node or a query selector for such a node. | ||
The `mount` prop can be either a native node or a query selector for such a node. | ||
@@ -253,3 +254,3 @@ If no node is provided, the component will render nothing. | ||
Plase notice, it requires react-dom as its depenndency. | ||
<!-- _useShadow_ places the element in Shadow Root for style isolation --> | ||
<!-- `useShadow` places the element in Shadow Root for style isolation --> | ||
@@ -357,7 +358,4 @@ ### Hooks | ||
The `state` field represents the current resource state: | ||
2 / 2 | ||
The `state` field represents the current state of the resource: | ||
The `state` field represents the current state of the resource. | ||
| state | data | loading | error | | ||
@@ -415,3 +413,3 @@ |:-----------|:-----:|:-------:|:-----:| | ||
The _useResource_ hook accepts several options to customize its behavior: | ||
The `useResource` hook accepts several options to customize its behavior: | ||
@@ -421,4 +419,4 @@ - **`initial value`**: | ||
resource initial value. If an initial value is passed, it sets the initial | ||
state to either "ready" or "refreshing" (depending on whether _skip_ or | ||
_skipFirstRun_ opts are true or not.) | ||
state to either "ready" or "refreshing" (depending on whether `skip` or | ||
`skipFirstRun` opts are true or not.) | ||
- **`onCompleted`** and **`onError`**: | ||
@@ -428,3 +426,3 @@ Callbacks that are called when the resource resolves or rejects respectively. | ||
If set to true, it skips calls to the fetcher function, but it can still be | ||
called manually with the _refresh_ function. This can be useful if you want | ||
called manually with the `refresh` function. This can be useful if you want | ||
to wait for certain dependencies to be in a certain state before calling | ||
@@ -440,5 +438,5 @@ the fetcher or if you want to trigger the fetcher only manually on some event. | ||
To avoid flickering of content, the resource initial state depends on the _skip_ | ||
and _skipFirstRun_ options. If any of them is true, the resource state will be | ||
"unresolved" or "ready" depending on whether the _initialValue_ is defined. | ||
To avoid flickering of content, the resource initial state depends on the `skip` | ||
and `skipFirstRun` options. If any of them is true, the resource state will be | ||
"unresolved" or "ready" depending on whether the `initialValue` is defined. | ||
If both of them are false, the resource state will be "pending" or "refreshing" | ||
@@ -457,3 +455,3 @@ correspondingly, so we can correctly show a preloader right away. | ||
Check out useResource-examples.md to see different forms of it in action. | ||
Check out [useResource examples](./useResource-examples.md) to see different forms of it in action. | ||
@@ -460,0 +458,0 @@ ## Contributing |
@@ -50,7 +50,15 @@ # Examples of different usage of useResource hook | ||
```tsx | ||
import axios from 'axios'; | ||
import axios, { isCancel } from 'axios'; | ||
const Foo = ({ id }: { id: number }) => { | ||
const [ resource ] = useResource( | ||
(id, { signal }) => axios.get(`/api/v1/employees/${encodeUriComponent(id)}`, { signal }), | ||
(id, { signal }) => axios.get(`/api/v1/employees/${encodeUriComponent(id)}`, undefined, { signal }) | ||
.catch((e: unknown) => { | ||
// Notice, that axios CancelError is not the same as AbortError, so we need to catch and rethrow | ||
// it, to avoid error flickering. | ||
if (isCancel(e)) { | ||
throw { name: "AbortError" }; | ||
} | ||
throw e; | ||
}), | ||
[id] | ||
@@ -97,2 +105,3 @@ ); | ||
}, | ||
// `abort` is memoized, you can safely omit it from deps. Or you can add it, whatever | ||
[externalSignal] | ||
@@ -99,0 +108,0 @@ ); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
18
2
Yes
103649
11
246
446
1