Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loading plugin counts the number of outstanding effects per effect, module, and global #245

Merged
merged 4 commits into from
Mar 13, 2018

Conversation

crazy4groovy
Copy link
Contributor

I was having problems with the loading module resolving a pending effect too early, because out of a batch of dispatched effects, the first completed one set the loading state to false.

This implements an integer counter instead of a boolean value.

Also cleaned up the plugin tests slightly.

package.json Outdated
@@ -39,7 +39,8 @@
"test": "jest"
},
"dependencies": {
"redux": "^3.7.2"
"redux": "^3.7.2",
"redux-persist": "^5.9.1"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a strange thing, but some tests were failing without it...... dunno how it was working before?

Copy link
Member

Choose a reason for hiding this comment

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

I noticed that in your PR. Tests "should" pass with 'redux-persist' as a dev dependency.

},
effects: {
async timeout() {
await delay(200)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

a more standard async delay call

dispatch.count.timeout()
expect(store.getState().loading.global).toBe(true)
expect(store.getState().loading.global).toBe(2)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is the gist of the new functionality


dispatch.count.timeout()
dispatch.count.timeout()
expect(store.getState().loading.effects.count.timeout).toBe(2)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is the gist of the new functionality

dispatch.count.timeout2()
expect(store.getState().loading.effects.count.timeout1).toBe(1)
expect(store.getState().loading.effects.count.timeout2).toBe(1)
expect(store.getState().loading.global).toBe(2)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

making sure global increments for every dispatched effect

await origEffect(...props)
// waits for dispatch function to finish before calling "hide"
} finally {
dispatch.loading.hide({ name, action })
Copy link
Contributor Author

Choose a reason for hiding this comment

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

omit catch, but decrements counter even on an error

@ShMcK
Copy link
Member

ShMcK commented Mar 6, 2018

Note that the loading plugin will likely require some changes once #236 is merged. Working on this at the moment.

At that point, loading can be handled with 2 actions, rather than the current 3.

  1. show loading & action
  2. hide loading

@ShMcK
Copy link
Member

ShMcK commented Mar 6, 2018

@crazy4groovy - interesting idea - an integer count is certainly more useful than a boolean. I would use this, for its debugging value alone.

The only issue I can see is that users with typechecking (TypeScript, Flow) may find it annoying.

Do you think it's possible to make it an "opt in" feature?

@crazy4groovy
Copy link
Contributor Author

crazy4groovy commented Mar 6, 2018

@ShMcK interesting point about typechecking. Honestly I'm not strong on the topic so I don't have any insight, sorry. But I didn't see any boolean typing info that I was converting into number so maybe you can help me understand your concern with an example?

Boolean -> number is a breaking change, so opt-in would help maintain backwards-compatibility. That is often a smart move, unless the added feature completely trumps the existing functionality. Can you see any case where the new functionality is not better than current? Alternatively a v1.x (semver) bump may be the right move.

Thoughts?

@ShMcK
Copy link
Member

ShMcK commented Mar 6, 2018

Can you see any case where the new functionality is not better than current?

Nope. You have a good idea.

Help me understand your concern with an example

In JS, there is no issue with dynamically typing variables.

var loading = 0
if (loading) {
  // do something
}

But when using TypeScript, for example, the compiler expects variables to be statically typed.

var loading = 0
if (loading) {          // warning, expected a boolean
  // do something
}

Then you have to do some nasty coercion to make the compiler happy.

var loading = 0
if (!!loading) {
  // do something
}

I believe these two solutions can co-exist very simply.

  1. store loading as a number
  2. if users "opt in" to a config for "valueType" = "boolean", then the number is coerced into a boolean, but defaults to "number"

What do you think?

@crazy4groovy
Copy link
Contributor Author

crazy4groovy commented Mar 6, 2018

Then you have to do some nasty coercion to make the compiler happy.

I see. Yes, in JS world (or TS?) I would just expect to check loading like this:

var loading = 0
if (loading > 0) { // always boolean
  // do something
}

I'm not opposed to a config param here, but it's not obvious to me how to store state as a number, and retrieve it as a boolean. Except maybe a selector of some kind? A hint here would be interesting for me.

@crazy4groovy
Copy link
Contributor Author

^^ poke?

@ShMcK
Copy link
Member

ShMcK commented Mar 8, 2018

Sorry, I'll need a bit more time. I'll try to get back to you later this evening.

@ShMcK
Copy link
Member

ShMcK commented Mar 9, 2018

I think both solutions can co-exist. I came up with something like this:

const createLoadingAction = (valueFn) =>
  (show) =>
    (state, { name, action }: any) => ({
      ...state,
      global: valueFn[show](state.global),
      models: {
        ...state.models,
        [name]: valueFn[show](state.models[name]),
      },
      effects: {
        ...state.effects,
        [name]: {
          ...state.effects[name],
          [action]: valueFn[show](state.effects[name][action]),
        },
      },
    })

 let valueFn
 let defaultValue
  if (config.valueType === 'number') {
    valueFn = {
      hide: () => 0,
      show: (v) => v + 1,
    }
   defaultValue = 0
  } else {
    valueFn = {
      hide: () => false,
      show: () => true,
    }
    defaultValue = false
  }

  const loadingAction = createLoadingAction(valueFn)

  const loading: Model = {
    name: loadingModelName,
    reducers: {
      hide: loadingAction('hide'),
      show: loadingAction('show'),
    },
    state: {
      effects: {},
      global: defaultValue,
      models: {},
    },
  }

  /* add defaultValue where needed */

In this case, users can opt in to using numbers over booleans by adding a config.valueType = 'number'.

This way, TypeScript/Flow folks are happy, no breaking changes, and the README can emphasize that numbers are an excellent option. What do you think?

@crazy4groovy
Copy link
Contributor Author

Interesting idea! Feels pretty complicated but it's an interesting concept, I'll try to take a look this weekend.

Also, I think the numerical hide isn't quire right, I think it should just tick down the number instead of setting it to 0.

@ShMcK
Copy link
Member

ShMcK commented Mar 9, 2018

Great, also have a look at #266. Can you think of a way to resolve this with the loading plugin?


const loadingModelName = config.name || 'loading'

const converter =
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm pretty happy with this solution, it keeps a "background" counter called cntState of dispatched effects, but converts it onto its public state as either a number or boolean.

@@ -0,0 +1,327 @@
const delay = ms => new Promise(r => setTimeout(r, ms))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are a lot more tests now.. I just duplicated them and tested for boolean in one file, and number in this one.

@@ -62,21 +62,28 @@ init({

## Options

### name
### asNumber
Copy link
Contributor Author

Choose a reason for hiding this comment

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

yay for docs! 👍

@@ -6,7 +6,7 @@ import createLoadingPlugin from '@rematch/loading'
import App from './App'
import example from './example'

const loadingPlugin = createLoadingPlugin()
const loadingPlugin = createLoadingPlugin({ asNumber: true })
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The example uses numbers, I think it's way more practical personally

@ShMcK
Copy link
Member

ShMcK commented Mar 13, 2018

Quite an elegant solution with the "converter". Thanks for putting in all the effort @crazy4groovy, great work, and sorry this took so long. I really appreciate your patience.

@ShMcK ShMcK merged commit 5911a9b into rematch:master Mar 13, 2018
@ShMcK
Copy link
Member

ShMcK commented Mar 13, 2018

Getting a strange error from the Rollup bundler.

TypeError: parse is not a function in lib/index.js

There is no "parse" in that file.

WIll have to look at this tomorrow morning before I'm able to publish as @rematch/[email protected].

@crazy4groovy
Copy link
Contributor Author

Drat sorry. Here's a 100% shot in the dark to try: could be the export default statement I consolidated, try pulling it back out to the bottom maybe (I've just heard someone else mention that on a different project.)

@crazy4groovy
Copy link
Contributor Author

Maybe this issue: rollup/rollup-plugin-commonjs#299

@ShMcK
Copy link
Member

ShMcK commented Mar 13, 2018

Something changed in Rollup. Before I could use the peer dependencies in the root "rematch" directory, but now I had to install them all as dev dependencies in the plugins.

@ShMcK
Copy link
Member

ShMcK commented Mar 13, 2018

Thanks for your help. Published as @rematch/[email protected].

@semoal
Copy link
Member

semoal commented Sep 11, 2020

@tianzhich

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants