David Bashford
UI/Node Engineer
Over-arching Theme
Business rules, out
Unnecessary state, out
Proxying state for other components, ideally no
Components are a great place to keep your business rules...
...said no one ever.
What do we mean by "Business Rules"?
Logic that makes decisions about state....
...or that translates user actions into state updates.
...
const PagingComponent = Component.extend({
...
actions: {
firstPage() {
this.send('goToPage', 1);
},
nextPage() {
this.send('goToPage', this.get('pageNumber') + 1);
},
previousPage() {
this.send('goToPage', this.get('pageNumber') - 1);
},
lastPage() {
this.send('goToPage', this.get('lastPageNumber'));
}
}
});
Take user intent, merge it with state, send out new state.
...
const PagerComponent = Ember.Component.extend({
numberItems: Ember.computed('items', function() {
return this.get('items').length;
}),
lastPageNumber: Ember.computed('pageSize', 'numberItems', function() {
return Math.ceil(this.get('numberItems') / this.get('pageSize'));
}),
isFirstPage: Ember.computed('pageNumber', function() {
return this.get('pageNumber') === 1;
}),
isLastPage: Ember.computed('pageNumber', 'lastPageNumber', function() {
return this.get('pageNumber') === this.get('lastPageNumber');
}),
...
})
Logic that generates new state or truth
Stare deep into the heart of your stateToComputed...
...and unsuck it.
const stateToComputed = ({ page: { pageNumber, pageSize, items } }) => ({
pageNumber,
pageSize,
items
});
How can something so simple be so unfortunate?
Don't wire state in, if all you are doing is sending it back out via actionCreator.
actionCreators can get any state they want via redux-thunk's `getState`, let them.
Avoid wiring in state that is only used to derive other state.
Any logic that turns state into more state is a business rule.
const stateToComputed = ({ kitchen }) => ({
limes: kitchen.limes,
salt: kitchen.salt,
cumin: kitchen.cumin,
cayenne: kitchen.cayenne,
onion: kitchen.onion,
jalapeno: kitchen.jalapeno,
tomatoes: kitchen.tomatoes,
cilantro: kitchen.cilantro,
garlic: kitchen.garlic,
avocados: kitchen.avocados,
knife: kitchen.knife,
teaspoon: kitchen.teaspoon,
tablespoon: kitchen.tablespoon
});
limeJuice: Ember.computed('limes', 'knife', function() {
...
}),
mincedJalapenos: Ember.computed('jalapenos', 'knife', function() {
...
}),
measuredSalt: Ember.computed('salt', 'teaspoon', function() {
...
}),
measuredCumin: ...
measuredCayenne: ...
...
guacamole: Ember.computed('limeJuice', 'mincedJalapenos', ..., function() {
...
});
{{guacamole}}
Steve
import { guac } from 'selectors/steve';
const stateToComputed = (state) => ({
guacamole: guac(state)
});
{{guacamole}}
const stateToComputed = ({ items }) => ({
items
});
const ItemsComponent = Ember.Component.extend({
hasItems: Ember.computed('items', function() {
return this.get('items').length > 0;
})
});
const stateToComputed = (state) => ({
hasItems: hasItems(state)
});
const ItemsComponent = Ember.Component.extend({});
{{#if hasItems}}
{{some-component}}
{{another-component}}
{{a-third-component}}
{{/if}}
{{#if showMetaDetails}}
...
{{/if}}
Bound downstream components into Redux so they can fetch their own state
In both cases, some parent may be bound to Redux, but data needs to be passed in via template params.
By David Bashford