hoverboard v3.0.0-alpha.3
Hoverboard
A very lightweight (anti-gravity?) data model and Flux store with actions and a state change listener.
Installation
You can use npm to install Hoverboard, or download the raw file here.
For more information, check out the Concept, Usage, Documentation and FAQ below.
npm install hoverboard
Concept
Hoverboard greatly simplifies Flux while staying true to the concept.
The basic usage of Hoverboard is:
var store = Hoverboard({
action: function (state, value) {
return { value: value };
}
});
store.action("Hello, World!");
Hoverboard makes it incredibly easy to subscribe to state changes. You can add a listener by calling the model as a function. Your callback will be called immediately at first, and again whenever the state changes. This works great with React.js, because you can easily re-render your component whenever the store changes its state.
store(function (props) {
ReactDOM.render(
<UserProfile {...props} actions={store} />,
document.getElementById('user-profile')
);
});
Hoverboard was inspired by other Flux implementations, like Redux, Alt and Reflux. Those versions are very lightweight, but Hoverboard is practically weightless.
Usage
Here's how you might use Hoverboard to keep track of clicks with a ClickCounter.
var ClickCounter = Hoverboard({
click: function(state, text) {
return {
value: state.value + 1,
log: state.log.concat(text)
};
},
reset: function() {
// go back to defaults
return {
value: 0,
log: []
};
}
});
// initialize with defaults
ClickCounter.reset();
// listen to changes to the state
var unsubscribe = ClickCounter(function (clickState) {
document.write(JSON.stringify(clickState) + "<br>");
});
ClickCounter.click('first');
ClickCounter.click('second');
// reset back to empty state
ClickCounter.reset();
unsubscribe();
ClickCounter.click("This won't show up");
If you run this example, you'll see this:
{"value":0,"log":[]}
{"value":1,"log":["first"]}
{"value":2,"log":["first","second"]}
{"value":0,"log":[]}
To see how Hoverboard fits into a larger app, with ReactJS and a router, check out the Hoverboard TodoMVC.
Documentation
Hoverboard is a function that takes an actions object and returns a store object.
Syntax
store = Hoverboard(actions[, initialState]);
actions
object
- Any properties of the actions object will be exposed as methods on the returned
store
object. - If your state is a plain object, and you return plain objects from your actions, they will be shallow merged together.
Note that your actions will automatically receive
state
as the first parameter, followed by the arguments you pass in when calling it.```javascript store = Hoverboard({ hideItem: function(state, id) { var items = state.items; items[id].hidden = true; // return the new state return { items: items }; }, items: function(state, items) { return { items: items }; }, error: function(state, error) { return { error: error }; } }); // you could load the store contents asynchronously and pass in as action api.getItems(function (error, items) { if (error) { return store.error(error); } store.items(items); }); // later store.hideItem("abc"); ```
Return value
Hoverboard(actions[, initialState])
will return a store
object.
store
object methods
store()
- Returns the store's current state.
unsubscribe = store(function)
- Adds a listener to the state of a store. - The listener callback will be called immediately, and again whenever the state changed.
Returns an unsubscribe function. Call it to stop listening to the state.
```javascript unsubscribe = store(function(state) { alert(state.value); });
// stop listening unsubscribe();
```
store.someAction(arg1, arg2, ..., argN)
- Calls an action handler on the store, passing through any arguments. ```javascript store = Hoverboard({ add: function(state, number) { return (state || 0) + number; } }); store.add(5); store.add(4); result = store(); // returns 9 ```
Hoverboard.compose
Hoverboard.compose
takes a definition and creates a store,
subscribing to any store members of the definition.
Hoverboard.compose
can take static variables, objects or arrays.
// create two stores
var scoreStore = Hoverboard({
add: function (state, score) {
return state + score;
}
}, 0);
var healthStore = Hoverboard({
hit: function (state, amount) {
return state - amount;
}
}, 100);
// compose the two stores into a single store
var gameStore = Hoverboard.compose({
score: scoreStore,
// create an anonymous store to nest objects
character: Hoverboard.compose({
health: healthStore
})
});
// stores and actions can be accessed with the same structure
gameStore.score.add(2);
gameStore.character.health.hit(1);
You can also pass zero or more translate functions after your compose definition, to automatically translate or map the state every time it gets updated.
These translate functions will receive a state
argument, and must return the resulting state.
// create stores to contain the active and completed todos
var activeTodoStore = Hoverboard.compose(todoStore, function (todos) {
return _.filter(todos, { completed: false });
});
var completedTodoStore = Hoverboard.compose(todoStore, function (todos) {
return _.filter(todos, { completed: true });
});
FAQ
Q: Is this really Flux?
Yes. Flux requires that data flows in one direction, and Hoverboard enforces that.
When you call an action on a store, you can't get back a return value. The only way to get
data out of a store is by registering a change listener. So this ensures that data flows following
the Action -> Dispatcher -> Store -> View
flow that is central to Flux.
Q: Does Hoverboard depend on React.js? Or can I use ____ instead?
You can use Hoverboard with any framework or library. It works really well with React, just because it's simple to pass an entire state object as props to a component, and have React figure out how to update the DOM efficiently.
That said, Hoverboard can still work with any other method of programming, but you might have to do more work to decide how to update your views when the state changes.
As other frameworks start adopting React's strategy of updating the DOM, Hoverboard will be a good fit to use with those frameworks as well.
Check out virtual-dom as an alternative to using React in your projects.
Q: Is Hoverboard universal? Can I use it on a node.js server?
Yes, it can work on the server. You can add listeners to stores, and render the page once you have everything you need from the stores. To be safe, you should probably unsubscribe from the store listeners once you've rendered the page, so you don't render it twice.
If you want to be able to "re-hydrate" your stores after rendering on the server, you can
pass the initial state as the second parameter when creating your Hoverboard
store.
Q: How does Hoverboard handle asynchronous loading of data from an API?
There are three ways to achieve this. One way is to load the API outside of the store, and call actions to pass in the loading state, data and/or error as it arrives:
var store = Hoverboard({
loading: function(state, isLoading) {
return { isLoading: isLoading };
},
data: function(state, data) {
return { data: data };
},
error: function(state, error) {
return { error: error };
}
});
store.loading(true);
getDataFromAPI(params, function(error, data){
store.loading(false);
if (error) {
return store.error(error);
}
store.data(data);
});
Another way is to call the API from within your store itself.
var store = Hoverboard({
load: function (state, params) {
getDataFromAPI(params, function (error, data) {
store.done(error, data);
});
return { isLoading: true, error: null, data: null };
},
done: function(state, error, data) {
return { isLoading: false, error: error, data: data };
}
});
store.load(params);
Q: If Hoverboard stores only have a single getter, how can I have both getAll and getById?
You can use actions and state. If you have a list of items, and want to view a single item, then you might want to have an items property that contains the list, and an item property that contains the item you need. Something like this:
var initialState = { abc: 123, /* etc... */ };
var itemStore = Hoverboard({
// update item whenever ID changes
viewById: function (state, id) {
return {
item: state.items[id],
items: state.items
};
},
viewAll: function (state) {
return {
items: state.items
}
}
}, initialState);
// getAll
var items = itemStore().items;
// getById
itemStore.viewById(123);
var state = itemStore();
if (state.item) {
// render single item
} else {
// render list of items
}
Q: Hold on. There's no global dispatcher and there's no waitFor
, so are you sure it's really Flux?
Yes. Ultimately Hoverboard acts as the dispatcher. Every action calls one specific action handler in one store, so the dispatching is simple. Like Facebook's Dispatcher, Hoverboard ensures that only one action is handled at a time, and won't allow action handlers to pass data back to the action caller.
waitFor
is a mechanism that Facebook's Dispatcher provides to help multiple stores
coordinate their response to a particular action. In Hoverboard, a store doesn't need
to know about which actions were called, or if some asynchronous response triggered
a change. Whenever one store changes, the other can update itself immediately.
How would you avoid using waitFor
in Hoverboard? Let's compare using an example from the
Facebook Flux Dispatcher tutorial:
Here's how the example from the link above would work with Hoverboard:
// Keeps track of which country is selected
var CountryStore = Hoverboard({
update: function (state, selectedCountry) {
return selectedCountry;
}
});
// Keeps track of which city is selected
var CityStore = Hoverboard({
update: function (state, selectedCity) {
return selectedCity;
}
});
// listen to the CountryStore
CountryStore(function (country) {
// Select the default city for the new country
if (country && CityStore() === undefined) {
CityStore.update(getDefaultCityForCountry(country));
}
});
// Keeps track of the base flight price of the selected city
var FlightPriceStore = Hoverboard({
updatePrice: function (state, country, city) {
return getFlightPriceStore(country, city);
}
});
// called when either country or city change
function updateFlightPrice() {
var country = CountryStore();
var city = CityStore();
FlightPriceStore.updatePrice(country, city);
}
// listen to changes from both the country and city stores
CountryStore(updateFlightPrice);
CityStore(updateFlightPrice);
// When a user changes the selected city, we call an action:
CityStore.update('paris');
// When the user selects a country, we call an action:
CountryStore.update('australia');
It's pretty much the same code, just written a different way. In both examples, the FlightPriceStore waits for the CountryStore and CityStore to change, and the flow of data moves through the same logic and processes.
Versioning
Hoverboard follows semver versioning. So you can be sure that the API won't change until the next major version.
Testing
Clone the GitHub repository, run npm install
, and then run npm test
to run the tests. Hoverboard has 100% test coverage.
Contributing
Feel free to fork this repository on GitHub, make some changes, and make a Pull Request.
You can also create an issue if you find a bug or want to request a feature.
Any comments and questions are very much welcome as well.
Author
Jesse Skinner @JesseSkinner
License
MIT
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago