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

Use microstates library i.e. a new API #69

Closed
wants to merge 26 commits into from

Conversation

knownasilya
Copy link
Collaborator

@knownasilya knownasilya commented Jun 7, 2018

WIP, don't merge yet!
Resolves #68

This change removes all of the helpers for a (currently) single HoC state-for component which takes a type class and a value to create a microstate using the default middleware.

I removed all of the tests and update to the latest cli and ember versions. This change will require a major version bump because it fundamentally changes this addon.

New API

Component API

class MyApp {
  counter = Number
}
<StateFor @type={{MyApp}} @value={{value}} @onchange={{action (mut value)}} as |app|>
  {{app.state.counter}}
  <button type='button' onclick={{action app.counter.increment}}>+1</button>
</StateFor>

Service API

// services/current-user.js
import Service from '@ember/service';
import { Store, create } from 'microstates';

class User {
  name = String;
  email = String;
  superuser = Boolean;

  get isFilled() {
    return this.name && this.email;
  }

  get notFilled() {
    return !this.isFilled;
  }
}

const value = {
  superuser: false
};

export default Service.extend({
  init() {
    this._super(...arguments);
    const initial = create(User, value);
    this.set('microstate', Store(initial, (next) => {
      // set the internal state of the component to the next state
      this.set('microstate', next);
    }));
  }
});

Using

import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { alias } from '@ember/object/computed';

export default Component.extend({
  currentUserService: service('current-user'),
  user: alias('currentUserService.microstate'),

  actions: {
    // pretend 'save' action
    save() {
      let userMicrostate = this.user;
      let value = userMicrostate.valueOf();

      alert(JSON.stringify(value));
    }
  }
});

Template

<form {{action 'save' on='submit'}}>
  <input type='text' placeholder='Name' oninput={{action user.name.set value='target.value'}}>
  <input type='text' placeholder='Email' oninput={{action user.email.set value='target.value'}}>
  <label>
    Is an admin?
    <input type='checkbox' checked={{user.superuser.state}} onchange={{action user.superuser.toggle}}>
  </label>
  <button disabled={{user.state.notFilled}}>Submit</button>
</form>

TODO

Trying it out

  • Clone and npm install
  • Enter node_modules/microstates and npm install.
  • In ember-microstates main dir run npm start.
  • Open localhost:4200

Ilya Radchenko added 7 commits June 10, 2018 15:38
```js
import { Service } from 'ember-microstates';

class User {
  name = String;
  email = String;
  superuser = Boolean;
}

export default Service.extend({
  typeClass: User,

  defaultValue() {
    return {
      superuser: false
    };
  }
});
```

```js
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { alias } from '@ember/object/computed';

export default Controller.extend({
  currentUserService: service('user'),
  user: alias('currentUserService.microstate'),

  actions: {
    save() {
      let userMicrostate = this.user;
      let value = userMicrostate.valueOf();

      alert(JSON.stringify(value));
    }
  }
});
```

```hbs
<form {{action 'save' on='submit'}}>
  <input type='text' placeholder='Name' oninput={{invoke user.name 'set' value='target.value'}}>
  <input type='text' placeholder='Email' oninput={{invoke user.email 'set' value='target.value'}}>
  <label>
    Is an admin?
    <input type='checkbox' checked={{user.state.superuser}} onchange={{invoke user.superuser 'toggle' value='target.checked'}}>
  </label>
  <button>Submit</button>
</form>
```
Also remove a debugger
@taras
Copy link
Member

taras commented Jun 12, 2018

With this kind of service, for now it might be better to use service actions instead of the invoke function. This was the approach that Charles and I planned for until the Applicatives wrapper is ready.

What do you think?

@knownasilya knownasilya changed the title Use microstates library Use microstates library i.e. a new API Jun 12, 2018
@knownasilya
Copy link
Collaborator Author

I'm for that as well, it's a little used pattern that should be used more.

// services/current-user.js
export default Service.extend({
  typeClass: User,

  defaultValue() {
    return {
      superuser: false
    };
  },

  actions: {
    setName(name) {
      this.microstate.name.set(name);
    },

    setEmail(email) {
      this.microstate.email.set(email);
    },

    toggleSuperuser() {
      this.microstate.superuser.toggle();
    }
  }
});
<form {{action 'save' on='submit'}}>
  <input type='text' placeholder='Name' oninput={{action 'setName' value='target.value' target=currentUserService}}>
  <input type='text' placeholder='Email' oninput={{action 'setEmail' value='target.value' target=currentUserService}}>
  <label>
    Is an admin?
    <input type='checkbox' checked={{user.superuser.state}} onchange={{action 'toggleSuperuser' value='target.checked' target=currentUserService}}>
  </label>
  <button>Submit</button>
</form>

@knownasilya
Copy link
Collaborator Author

The only issue is that it might be repetitive especially if you are nesting many classes for the "schema".
Also it won't be very clear if you should nest or create a new service with a disconnected microstate.

@cowboyd
Copy link
Member

cowboyd commented Jun 12, 2018

Why not create an implicit action for each custom transition of the microstate?

@taras
Copy link
Member

taras commented Jun 12, 2018

@cowboyd would they be recursive like the microstate?

I don't think it's possible for actions hash to be a computed property. Right?

@knownasilya
Copy link
Collaborator Author

knownasilya commented Jun 13, 2018

Unsure, probably not supported. But you can use functions outside of the actions hash as actions, e.g.

// services/current-user.js
export default Service.extend({
  typeClass: User,

  defaultValue() {
    return {
      superuser: false
    };
  },

  init() {
    this._super(...arguments);
    this.toggleSuperuser = this.microstate.superuser.toggle.bind(this.microstate.superuser);
  }
});
<button type='button' onclick={{action userService.toggleSuperuser}}>
  Toggle Super User
</button>

The above would work if the method was bound before hand (example twiddle).

@knownasilya
Copy link
Collaborator Author

👍

@taras
Copy link
Member

taras commented Jun 19, 2018

@knownasilya ember-auto-import 0.2.6 made it work without the explicit import using es module build. This is sweet!

<label>
Is an admin?
<input type='checkbox' checked={{user.superuser.state}} onchange={{invoke user.superuser 'toggle' value='target.checked'}}>
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You'd still want this value='target.checked' since otherwise you get an event and that is set. I think..

Copy link
Member

Choose a reason for hiding this comment

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

toggle doesn't take any arguments. Event will be ignored.

@taras taras mentioned this pull request Aug 24, 2018
@knownasilya
Copy link
Collaborator Author

@cowboyd or @taras could one of you restart the tests? Trying to figure out if that was a fluke or not.

@knownasilya
Copy link
Collaborator Author

Never mind, I updated and the error seems to have gone. Adding some basic acceptance tests.

@knownasilya
Copy link
Collaborator Author

Closing in favor of #72

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