Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Improvement/add context to async options#247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
MrHus wants to merge1 commit intoasync-library:next
base:next
Choose a base branch
Loading
fromMrHus:improvement/add-context-to-async-options

Conversation

@MrHus
Copy link

Description

ThepromiseFn and thedeferFn have been unified. They now share the following signature:

exporttypeAsyncFn<T,C>=(context:C|undefined,props:AsyncProps<T,C>,controller:AbortController)=>Promise<T>

Before thedeferFn andpromiseFn had this signature:

exporttypePromiseFn<T>=(props:AsyncProps<T>,controller:AbortController)=>Promise<T>exporttypeDeferFn<T>=(args:any[],props:AsyncProps<T>,controller:AbortController)=>Promise<T>

The difference is the idea of having acontext, the context will contain all parameters
toAsyncProps which are not native to theAsyncProps. For example:

useAsync({promiseFn:loadPlayer,playerId:1})

In the above example the context would be{playerId: 1}.

This means that you know need to expect three parameter for thepromiseFn instead of two.

So before in< 10.0.0 you would do this:

import{useAsync}from"react-async"// Here loadPlayer has only two argumentsconstloadPlayer=async(options,controller)=>{constres=awaitfetch(`/api/players/${options.playerId}`,{signal:controller.signal})if(!res.ok)thrownewError(res.statusText)returnres.json()}constMyComponent=()=>{const{ data, error, isPending}=useAsync({promiseFn:loadPlayer,playerId:1})}

In11.0.0 you need to account for the three parameters:

import{useAsync}from"react-async"// With two arguments:constloadPlayer=async(context,options,controller)=>{constres=awaitfetch(`/api/players/${context.playerId}`,{signal:controller.signal})if(!res.ok)thrownewError(res.statusText)returnres.json()}constMyComponent=()=>{// You can either pass arguments by adding extra keys to the AsyncPropsconst{ data, error, isPending}=useAsync({promiseFn:loadPlayer,playerId:1})// Or you can explicitly define the context which is TypeScript friendlyconst{ data, error, isPending}=useAsync({promiseFn:loadPlayer,context:{playerId:1}})}

For thedeferFn this means no longer expecting an array of arguments but instead a singular argument.
Therun now accepts only one argument which is a singular value. All other arguments torun but
the first will be ignored.

So before in< 10.0.0 you would do this:

importAsyncfrom"react-async"constgetAttendance=()=>fetch("/attendance").then(()=>true,()=>false)constupdateAttendance=([attend,userId])=>fetch(`/attendance/${userId}`,{method:attend ?"POST" :"DELETE"}).then(()=>attend,()=>!attend)constuserId=42constAttendanceToggle=()=>(<AsyncpromiseFn={getAttendance}deferFn={updateAttendance}>{({ isPending,data:isAttending, run, setData})=>(<Toggleon={isAttending}onClick={()=>{run(!isAttending,userId)}}disabled={isPending}/>)}</Async>)

In11.0.0 you need to account for for the parameters not being an array:

importAsyncfrom"react-async"constgetAttendance=()=>fetch("/attendance").then(()=>true,()=>false)constupdateAttendance=({ attend, userId})=>fetch(`/attendance/${userId}`,{method:attend ?"POST" :"DELETE"}).then(()=>attend,()=>!attend)constuserId=42constAttendanceToggle=()=>(<AsyncpromiseFn={getAttendance}deferFn={updateAttendance}>{({ isPending,data:isAttending, run, setData})=>(<Toggleon={isAttending}onClick={()=>{run({attend:isAttending, userId})}}disabled={isPending}/>)}</Async>)

Breaking changes

Yes

Checklist

Make sure you check all the boxes. You can omit items that are not applicable.

  • Implementation for both<Async> anduseAsync()
  • Added / updated the unit tests
  • Added / updated the documentation
  • Updated the PropTypes
  • Updated the TypeScript type definitions

Erdwolf, valentin-nemcev, andylei, and johnnyreilly reacted with thumbs up emojifreeman and stphnlngdncoding reacted with hooray emoji
@codesandbox-ci
Copy link

codesandbox-cibot commentedJan 21, 2020
edited
Loading

This pull request is automatically built and testable inCodeSandbox.

To see build info of the built libraries, clickhere or the icon next to each commit SHA.

Latest deployment of this branch, based on commit2a45ae2:

SandboxSource
quiet-cloud-hkxiwConfiguration

package-lock.json
yarn.lock

.vscode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This should probably be in yourglobal gitignore too!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I've added it because the project explicitly instructs you to create settings for.vscode in theCONTRIBUTING.md.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yeah I agree we should add it. Just wanted to let you know you can use a global gitignore file for it too.

@ghengeveld
Copy link
Member

Impressive work! I'm going to need to find some time to take a closer look at this. Very nice to see you updated all the docs too ❤️

freeman reacted with thumbs up emoji

Comment on lines 18 to 20
KEYS_OF_FETCHOPTIONS.forEach(k=>{
deletecontext[k]
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It seems this means it is impossible to have keys in context that conflict withAsyncOptions ...

Shouldn't it delete entries fromoptions but not fromoptions.context ?

Given that this PR is breaking change maybe it should only support passing arguments throughoptions.context to bypass this processing ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

+1 for only passing arguments throughoptions.context. It'll be a pretty big change but for good reason. Still having the option to also use rest props onAsyncProps only complicates things.

I would also like to see thecontext arg onrun to be merged on top ofoptions.context.

phryneas reacted with thumbs up emoji
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@ghengeveld so just to be sure you want the api to change from:

<AsyncpromiseFn={loadPlayer}playerId={1}suspense={true}>useAsync(loadPlayer,{playerId:1,suspense:true});// AltuseAsync({promiseFn:loadPlayer,playerId:1,suspense:true});

To:

<AsyncpromiseFn={loadPlayer}context={{playerId:1}}suspense={true}>useAsync(loadPlayer,{context:{playerId:1},suspense:true});// AltuseAsync({promiseFn:loadPlayer,context:{playerId:1},suspense:true});

I think this would be a great change.

I'm also for making thecontext pure, as in nothing gets magically added to it or deleted from it. This will make the code more readable.

But I'd also recommend overloadinguseAsync in the future to allow:

useAsync(loadPlayer,{playerId:1},{suspense:true});

As I think this would result in the most readable api.

freeman reacted with heart emoji
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

If we are changing APIs here anyways I'm all for moving from positional parameters to named parameters (one parameter with a keyed object).

So I'd prefer to be only left with

<AsyncpromiseFn={loadPlayer}context={{playerId:1}}suspense={true}>useAsync({promiseFn:loadPlayer,context:{playerId:1},suspense:true});

as that will make it more readable at a glance, allow for easier future expansion and will keep the typings more readable/less error-prone.

ghengeveld reacted with thumbs up emoji
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm in favor of dropping the overloaded signatures and only support the single object argument. The shorthand where you pass in the async function as first argument was added to support some very basic use cases where it'd be theonly argument, but it's not worth the extra complexity in code and API consistency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

By the way we're going to have to write a codemod for this, because it would otherwise require quite a bit of manual work to upgrade.

Copy link
Member

@ghengeveldghengeveldJan 27, 2020
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm also for making the context pure, as in nothing gets magically added to it or deleted from it. This will make the code more readable.

Agreed. People can always spread it themselves. This meansdeferFn will always only receive thecontext fromrun, and thecontext fromoptions is only passed topromiseFn.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I've updated the code so:

  1. There is no more overload of useAsync.
  2. There is no more magical context spreading / building.

@MrHusMrHusforce-pushed theimprovement/add-context-to-async-options branch fromef374bf to57d2143CompareJanuary 27, 2020 09:14
@codecov
Copy link

codecovbot commentedJan 27, 2020
edited
Loading

Codecov Report

Merging#247 intonext willincrease coverage by<.01%.
The diff coverage is100%.

Impacted file tree graph

@@            Coverage Diff             @@##             next     #247      +/-   ##==========================================+ Coverage   98.58%   98.59%   +<.01%==========================================  Files           8        8                Lines         424      427       +3       Branches      148      148              ==========================================+ Hits          418      421       +3  Misses          6        6
Impacted FilesCoverage Δ
packages/react-async/src/propTypes.ts100% <ø> (ø)⬆️
packages/react-async/src/useAsync.tsx99.24% <100%> (+0.01%)⬆️
packages/react-async/src/Async.tsx100% <100%> (ø)⬆️
packages/react-async/src/reducer.ts94.11% <100%> (ø)⬆️
packages/react-async/src/status.ts100% <100%> (ø)⬆️

Continue to review full report at Codecov.

Legend -Click here to learn more
Δ = absolute <relative> (impact),ø = not affected,? = missing data
Powered byCodecov. Last update2664df0...2a45ae2. Read thecomment docs.

@MrHusMrHusforce-pushed theimprovement/add-context-to-async-options branch 3 times, most recently fromc3dd34f to131646eCompareJanuary 27, 2020 13:24
The `promiseFn` and the `deferFn` have been unified. They now share thefollowing signature:```tsexport type AsyncFn<T, C> = (  context: C | undefined,  props: AsyncProps<T, C>,  controller: AbortController) => Promise<T>```Before the `deferFn` and `promiseFn` had this signature:```tsexport type PromiseFn<T> = (props: AsyncProps<T>, controller: AbortController) => Promise<T>export type DeferFn<T> = (  args: any[],  props: AsyncProps<T>,  controller: AbortController) => Promise<T>```The big change is the introduction of the `context` parameter. The ideabehind this parameter is that it will contain the parameters which arenot known to `AsyncOptions` for use in the `promiseFn` and `asyncFn`.Another goal of this commit is to make TypeScript more understandingof the `context` which `AsyncProps` implicitly carries around. Beforethis commit the `AsyncProps` accepted extra prop via `[prop: string]: any`.This breaks TypeScript's understanding of the divisions somewhat. Thisalso led to missing types for `onCancel` and `suspense`, which have beenadded in this commit.To solve this we no longer allow random extra properties that are unknownto `AsyncProps`. Instead only the new `context` of `AsyncProps` is passed.This means that the `[prop: string]: any` of `AsyncProps` is removed thismakes TypeScript understand the props better.The other big change of this commit that `useAsync` no longer supportsan overload. This means that the user can no longer do:```tsconst state = useAsync(loadPlayer, { context: { playerId: 1 } })```But have to be more explicit:```tconst state = useAsync({ promiseFn: loadPlayer, context: { playerId: 1 } })```These changes are of course a breaking change.Also now compiling TypeScript on `yarn test` this should prevent typeerrors from slipping in.Closes:async-library#246WIP: Trying to fix buildasdf
@moroine
Copy link

What's the status of this PR?

@ghengeveld
Copy link
Member

What's the status of this PR?

In need of a close review. It's been a while since I looked at it.

@LRNZ09
Copy link

Just my 2c here: I like the fact that in v10 you can use the short version of theuseAsync hook by calling it with the promise function as the first parameter. It's probably a poor choice moving it to thepromiseFn key, being it always mandatory.

@LRNZ09
Copy link

Any update on this?

Copy link

@ZebulanStanphillZebulanStanphill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I have some experience with TypeScript, so I decided to take a look at this PR.

I found some type weirdness in packages/react-async/src/useAsync.tsx, as well as some potential simplifications and wording improvements that could be made in other files. Also, this PR needs a rebase.

-[`promise`](#promise) An already started Promise instance.
-[`promiseFn`](#promisefn) Function that returns a Promise, automatically invoked.
-[`deferFn`](#deferfn) Function that returns a Promise, manually invoked with`run`.
-[`context`](#context) The first argument for the`promise` and`promiseFn` function.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
-[`context`](#context) The first argument for the`promise` and`promiseFn` function.
-[`context`](#context) The first argument for the`promiseFn` or`deferFn` function.

A function that returns a promise. It is automatically invoked in`componentDidMount` and`componentDidUpdate`. The function receives all component props\(or options\) and an AbortController instance as arguments.

>Be aware that updating`promiseFn` will trigger it to cancel any pending promise and load the new promise. Passing an inline (arrow) function will cause it to change and reload on every render of the parent component. You can avoid this by defining the`promiseFn` value**outside** of the render method. If you need to pass variables to the`promiseFn`, pass themas additionalpropsto`<Async>`, as`promiseFn` will be invoked with these props. Alternatively you can use`useCallback` or[memoize-one](https://github.com/alexreardon/memoize-one) to avoid unnecessary updates.
>Be aware that updating`promiseFn` will trigger it to cancel any pending promise and load the new promise. Passing an inline (arrow) function will cause it to change and reload on every render of the parent component. You can avoid this by defining the`promiseFn` value**outside** of the render method. If you need to pass variables to the`promiseFn`, pass themvia the`context`propsof`<Async>`, as`promiseFn` will be invoked with these props. Alternatively you can use`useCallback` or[memoize-one](https://github.com/alexreardon/memoize-one) to avoid unnecessary updates.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
>Be aware that updating`promiseFn` will trigger it to cancel any pending promise and load the new promise. Passing an inline (arrow) function will cause it to change and reload on every render of the parent component. You can avoid this by defining the`promiseFn` value**outside** of the render method. If you need to pass variables to the`promiseFn`, pass them via the`context`props of`<Async>`, as`promiseFn` will be invoked with these props. Alternatively you can use`useCallback` or[memoize-one](https://github.com/alexreardon/memoize-one) to avoid unnecessary updates.
>Be aware that updating`promiseFn` will trigger it to cancel any pending promise and load the new promise. Passing an inline (arrow) function will cause it to change and reload on every render of the parent component. You can avoid this by defining the`promiseFn` value**outside** of the render method. If you need to pass variables to the`promiseFn`, pass them via the`context`prop of`<Async>`, as`promiseFn` will be invoked with these props. Alternatively you can use`useCallback` or[memoize-one](https://github.com/alexreardon/memoize-one) to avoid unnecessary updates.

Comment on lines +29 to +32
The difference is the idea of having a`context`, the context will contain all parameters
to`AsyncProps` which are not native to the`AsyncProps`. Before you could pass any parameter
to`AsyncProps` and it would pass them to the`deferFn` and`promiseFn`, now you need to use
the`context` instead.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
The difference is the idea of having a`context`, the context will contain all parameters
to`AsyncProps` which are not native to the`AsyncProps`. Before you could pass any parameter
to`AsyncProps` and it would pass them to the`deferFn` and`promiseFn`, now you need to use
the`context` instead.
The difference is the introduction of a`context` parameter. The context will contain all parameters
to`AsyncProps` which are not native to`AsyncProps`. Before you could pass any parameter
to`AsyncProps` and it would pass them to the`deferFn` and`promiseFn`; now you need to put them in the`context` instead.

Comment on lines 38 to 43
```jsx
constMyComponent= ()=> {
const {data,error,isPending }=useAsync(loadPlayer,options)
const {data,error,isPending }=useAsync({ promiseFn:loadPlayer,context: { playerId:1 } })
// ...
}
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This example no longer makes any sense if you're going to just remove the shorthand version, so this entire section should be removed.

```

As you can see, the`deferFn` is invoked with 3 arguments:`args`,`props` and the AbortController.`args` is anarray
As you can see, the`deferFn` is invoked with 3 arguments:`context`,`props` and the AbortController.`context` is anobject

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Oxford comma.

Suggested change
As you can see, the`deferFn` is invoked with 3 arguments:`context`,`props` and the AbortController.`context` is an object
As you can see, the`deferFn` is invoked with 3 arguments:`context`,`props`, and the AbortController.`context` is an object

count,
watch:count,
context:{
a:1,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What's the purpose thea property in this test?

constisMounted=useRef(true)
constlastArgs=useRef<any[]|undefined>(undefined)
constlastOptions=useRef<AsyncOptions<T>>(options)
constlastArgs=useRef<C|undefined>(undefined)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Shouldn't this just be:

Suggested change
constlastArgs=useRef<C|undefined>(undefined)
constlastArgs=useRef<C>(undefined)

I haven't tested, but I think this may remove the need to explicitly cast toC in a few places.

*@returns {AsyncState<T, FetchRun<T>>}
*/
functionuseAsyncFetch<T>(
functionuseAsyncFetch<T,C>(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The typing for this function is kinda wack.

First of all, theinit parameter is using aRequestInit type that doesn't seem to actually exist. I think the type parameterC here should replace every instance ofRequestInit... or rather, perhapsC should just be renamed toRequestInit here?

Second, the 3rd argument's type probably shouldn't beFetchOptions, but rather a subset ofFetchOptions (that may not need the 2nd type parameterC at all), because passing in an object containingpromiseFn ordeferFn props doesn't make any sense in this context, yet it's technically allowed by the types here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I foundRequestInit defined innode_modules/@types/react-native/globals.d.ts. Appears to be a global type which should explain why it isn't declared in this file.
And I think C works just fine here as it's the type for theContext rather than the inits. So, it probably shouldn't replace any instance ofRequestInit.


constpromiseFn=useCallback(
(_:AsyncOptions<T>,{ signal}:AbortController)=>{
(context:C,_:AsyncOptions<T,C>,{ signal}:AbortController)=>{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Since the 2nd parameter's name starts with an underscore to indicate it isn't used, the 1st one should do the same for consistency/clarity.

Suggested change
(context:C,_:AsyncOptions<T,C>,{ signal}:AbortController)=>{
(_context:C,_options:AsyncOptions<T,C>,{ signal}:AbortController)=>{

Also, my other comment aboutC andRequestInit being the same thing applies here as well.

@Dysron
Copy link

Dysron commentedJan 6, 2022
edited
Loading

Any activity on this still? Looks like a great update to the package!

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@ghengeveldghengeveldghengeveld left review comments

@phryneasphryneasAwaiting requested review from phryneas

+3 more reviewers

@freemanfreemanfreeman left review comments

@DysronDysronDysron left review comments

@ZebulanStanphillZebulanStanphillZebulanStanphill requested changes

Reviewers whose approvals may not affect merge requirements

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

8 participants

@MrHus@ghengeveld@moroine@LRNZ09@Dysron@freeman@phryneas@ZebulanStanphill

[8]ページ先頭

©2009-2025 Movatter.jp