martyjs / marty Goto Github PK
View Code? Open in Web Editor NEWA Javascript library for state management in React applications
Home Page: http://martyjs.org
License: MIT License
A Javascript library for state management in React applications
Home Page: http://martyjs.org
License: MIT License
Now that React v0.13 is supporting ES6 classes its only a matter of time before the whole community goes that way. We should support ES6 classes out of the box. Fortunately the concepts in Marty largely map well to classes:
class UserStore extends Store {
handlers: {
addUser: UserConstants.ADD_USER
},
constructor() {
this.state = {};
},
addUser: function (user) {
this.state[user.id] = user;
this.hasChanged();
}
}
class UserComponentState extends StatefulComponent {
listenTo: UserStore,
getState() {
return {
users: UserStore.getAll()
}
}
}
class User : UserComponentState {
render() {
console.log(this.state.users);
}
}
My major concern is how to map constants to action creators. I believe that the entire action function should have a type associated with it, not just when it dispatches. This was why we went with constants being functions. There have been some issues with this approach (#83) so would like to explore alternative solutions.
One idea I had is to use field annotations. I realise this isn't in a spec but neither is JSX. This would work out the box with Traceur however we need to support 6to5 and people using ES5. My suggestion is writing a annotation specific transformation (e.g. annotationify for browserify, equivalent for webpack and require.js). We would keep the original syntax for those who'd prefer it
class UserActionCreators extends ActionCreators {
@ActionType(UserConstants.ADD_USER)
addUser(user) {
this.dispatch(user);
}
}
Update
@KidkArolis has half convinced me its a bad idea to introduce annotations (although he agrees it solves the problem well. His suggestion was to call constants from inside the action function. This has the benefit that we don't have to mess with function contexts or introduce new tooling.
class UserActionCreators extends ActionCreators {
addUser(name) {
return Constants.ADD_USER((dispatch) => {
var user = UserUtils.createUser(name);
dispatch(user);
return UserHttpAPI.createUser(user);
});
}
}
The other possible solution would be to define the types in a hash. On the upside its simpler and follows the same syntax as Stores, on the downside it does require a little mental work to work out which type is associated with an action creator.
class UserActionCreators extends ActionCreators {
types: {
addUser: Constants.ADD_USER
},
addUser(user) {
this.dispatch(user);
}
}
When you are getting something from a store, you must assume it will require an asynchronous operation to fetch the data (right now we are only concerned with HTTP requests). Currently if we don't have the data yet we send off an HTTP request and return null/undefined. This approach leaves a number of unanswered questions
Currently when you dispose of a store, it is still registered with the dispatcher and continues to update state.
Is this intended behavior?
I realise this is obvious but took me a little time to realise my stupidity.
var TestActionCreators = Marty.createActionCreators({
dispatch: TestConstants.DISPATCH(function (foo) {
this.dispatch(foo);
})
})
dispatch
should be a reserved keyword, we should throw an error if you try to create an action creator called dispatch
Batch operations
This will only cause a single change event
this.batch(function () {
this.state = {};
this.state = { a: 1 }
});
Dont cause change event
this.quietly(function () {
this.state = {};
});
Mixins
var LocalProfileStore = Marty.createStore({
mixins: [FooMixin],
...
});
module.exports = LocalProfileStore;
Stores are responsible for fetching their own data so getInitialState is a good place to make any API calls.
Came across this in the docs and I'm wondering if it would be better practice to call UsersAPI.getAll
indirectly through an action creator instead.
var UsersStore = Marty.createStore({
getInitialState: function () {
UsersAPI.getAll();
return [];
}
});
// vs
var UsersStore = Marty.createStore({
getInitialState: function () {
UserActionCreators.getAll();
return [];
}
});
The more I think about it the less I think it's the store's responsibility at all.
We want to have a mechanism to allow you to rollback optimistic actions (e.g. inserting an entity into a store, sending an HTTP request which then fails)
var UserActionCreators = Marty.createActionCreators({
saveUser: function (user) {
var action = this.dispatch(Constants.Users.ADD_USER, user);
action.rollback(); // dispatch returns an action object that has a rollback function
UserAPI.saveUser(user, action.rollback) // You could then pass that rollback function to API
}
});
Within the store handler, if you wanted to rollback, you return a function. All state is within the closure and don't need to do much.
var UserStore = Marty.createStore({
handlers: {
addUser: Constants.Users.ADD_USER
},
addUser: function (user) {
this.state = this.state.concat([foo]);
return function rollback() {
this.removeUser(user);
}
},
removeUser: function (user) {
...
}
});
It seems pretty common that you create an action creator that just dispatches whatever arguments you pass in. We could automatically create this function for them
var ActionActionCreators = Marty.createActionCreators({
addFoo: FooConstants.ADD_FOO(),
// same as
addFoo: FooConstants.ADD_FOO(function (foo, bar) {
this.dispatch(foo, bar);
})
});
Want to support a more succinct way of defining constants for action creators
Marty.createActionCreator({
createUser: Constants.CREATE_USER(function (user) {
this.dispatch(user);
})
})
Constants are actually functions
function CREATE_USER(creator) {
creator.type = "CREATE_USER";
return creator;
}
CREATE_USER.toString = function () {
return "CREATE_USER";
}
So that I can have better SEO and initial render time I want to be able to render the representation of the application on the server. i.e. isomorphism.
Concurrency issues
One of the biggest problems right now is that everything in Marty is a singleton. However you could also think of createStore
, createActionCreators
, etc as registration functions. If we have an internal registry of all types, action creators, etc we could then create new instances of them. The interface you return from createStore
would just be a proxy to whatever instance of the store you specify
var FooStore = Marty.createStore({
id: "foo",
displayName: "Foo"
});
var FooActionCreators = Marty.createActionCreators({
id: "foo",
bar: function () { ... }
});
var instance = Marty.createInstance();
instance.getActionCreators("id").bar(123);
var html = Marty.renderToString(<Foo/>, instance);
(De)Hydration
Stores have the getInitialState()
function which is where you get the initial state of the store and, if required, make any HTTP calls to hydrate the store.
var Store = Marty.createStore({
getInitialState: function () {
SomeAPI.getSomeState();
return [];
}
});
I propose we allow you to specify the initial state on the server which, when the store is loaded, is passed to the stores getInitialState
var Store = Marty.createStore({
getInitialState: function (serverState) {
if (serverState) {
return serverState;
} else {
return [];
}
}
});
This way you can do any transformations (e.g. turning the object into a immutable object).
Marty.renderToString(component, instance)
would a string that should be injected into the page that would include the html as well as the initial dehydrated state.
<div class="foo">...</div>
<script type="text/javascript">
(window.__marty||(window.__marty={})).state={someStore:{foo:'bar'}}
</script>
http://martyjs.org/guides/constants/index.html contains the following code snippet regarding the second argument to Constants functions:
var UserActionCreators = Marty.createActionCreators({
deleteUser: UserConstants.DELETE_USER(function (user) {
this.dispatch(user);
}, { foo: true })
});
var FooStore = Marty.createStore({
handlers: {
fooAction: { foo: true }
},
fooAction: function () {
...
}
})
In the example it looks like by suppling a 2nd arg you're changing the signature used to identify the action in the handler registration later. So you can write { foo: true }
instead of UserConstants.DELETE_USER
.
Is that what's going on and if so, what's the purpose of it?
๐
In some es6 transpilers, ES6 arrow functions are bound to the current scope making it impossible to call this.dispatch(...)
or other methods of the ActionCreators inside the callback.
By passing context as the last argument to the ActionCreator, one can use context.dispatch(...)
or other methods of the ActionCreator.
Since context is passed as the last argument, all existing code work as is and if you don't use es6 arrow syntax, you're not affected.
Either we make getInitialState
required or we fix bug
Often you want to call another component function from within a when handler. Right now there's no way to set the function context so you end up having to assigning functions to local variables.
Ideally you should be able to optionally pass in a function context as the second argument (when(handlers, context)
). The problem is we already set the function context to the handlers so that you can call other handlers.
Not sure how to solve this, needs some thought.
It should be easier to access all the types within Marty, e.g. require('marty/actions')
. I think we can support this using aliasify or remapify.
Aliases I want
marty/dispatcher
=> ./lib/dispatcher.jsmarty/store
=> ./lib/store.jsmarty/constants
=> ./lib/constants.jsmarty/actionCreators
=> ./lib/actionCreators.jsmarty/repository
=> ./lib/repository.jsmarty/actions
=> ./lib/stores/actions.jsmarty/stateMixin
=> ./lib/mixins/stateMixin.jsmarty/errors/*
=> ./lib/errors/*.jsMaybe I'm misunderstanding what should be happening, but it doesn't look to me like the done
handler is getting called when the data is fetched asynchronously in remotely
. Here's a Plunker that reproduces it:
http://plnkr.co/edit/DKQ6nriYimZ4KeYJCrLc?p=preview
It seems to be an issue when remotely returns the http promise, which then resolves and sets the state (simulated with a setTimeout). The only handler that gets called is pending
.
make release
fails a lotIf you are dispatching lots of actions (e.g. mouse events) then memory usage skyrockets since we store all actions. Being able to get the status of an action is still a valid use case but its the exception rather than the norm. Instead we should have a way to tell Marty to remember (Is that the right terminology?) an action for later querying.
Some ideas about how to implement
var remember = require('marty/remember');
var UserActionCreators = Marty.createActionCreator({
// action creators could have a fluent interface
createUser: Constant.CREATE_USER(function () {
this.dispatch()
}).memorable(),
//pass an object literal as a second object
createUser: Constant.CREATE_USER(function () {
this.dispatch()
}, { remember: true })
// compose functions
createUser: remember(Constant.CREATE_USER, function () {
this.dispatch()
}))
})
Also, we should not listen to actions by default. Lets make it explicit instead but make it easy to find the action store
var Actions = require('marty').Actions;
var UserMixin = Marty.createStateMixin({
listenTo: [Actions],
getState: function () {
return {
status: Actions.getAction(this.state.token)
}
}
})
To make it easier for people to get started with Marty, we should create a yeoman template. It will be a simple express app using react-router, browserify & grunt
Folder structure will be
/app
/actions
/components
/constants
/stores
/apis
/server
/handlers
/views
/config
/build
/tasks
Gruntfile.js
Makefile
package.json
README.md
Similar to react
With flux in general, having "actions" and "action creators" is not only confusing but also misleading. The name implies their role is simply to create actions, when really this is where the meat of your async and validation code should go.
@jhollingworth what do you think about calling "action creators" services instead?
var Service = Marty.createService({
createUser: function(name, email) { ... }
});
Service.createUser(...);
This is the code for the 'get' method of jsonStorage:
get: function (key) {
var raw = getStorage().getItem(getNamespacedKey(key));
try {
var payload = JSON.parse(raw);
return payload.value;
} catch (e) {
throw new Error('Unable to parse JSON from storage');
}
}
If a key is not defined, this will throw an exception saying cannot parse, even though the issue is actually that it will throw a 'cannot read property value of payload'.
Really it should just return null if raw is null. I'll do a PR.
Right now we have to manually call Action#rollback if an error occurs. That should happen automatically if the action fails
The HttpApi feature is not scalable enough for all scenarios, so instead Marty should have the concept of generic repositories with mixins that extend their functionality. A repository is an abstracted method of reading and writing data to and from a source (regardless of whether it is remote or local). A repository has a definition and implementations. It can be implemented in multiple types, allowing for an application to choose what type it makes use of (e.g. in a test environment you might want to use localStorage as the repository type instead of an HTTP API).
Repositories are be defined like so:
var UserRepository = Marty.defineRepository({
name: "user",
methods: [
"getUser",
"addUser"
],
implementedBy: [UserRepositoryLocal, UserRepositoryHTTP]
});
Repository implementations look like this:
var UserRepositoryLocal = Marty.implementRepository({
type: "localStorage",
prefix: "users",
get: function (key) {
return localStorage.getItem(this.prefix + key);
},
set: function (key, value) {
return localStorage.setItem(this.prefix + key, value);
},
getUser: function (id) {
return this.get(id);
}
});
With mixins implementations can be simplified:
var UserRepositoryLocal = Marty.implementRepository({
mixins: [LocalStorage({ prefix: "users" })]
getUser: function (id) {
return this.get(id);
},
addUser: function (id, user) {
return this.set(id, user);
}
});
Repositories are accessed by requiring in the definition, not the implementation:
var UserRepository = require("repositories/user");
UserRepository.getUser(1234);
Note: If this concept of defining and then implementing a repository multiple is too niche then we can just skip the definition step and simply create repositories.
Currently 90k, should aim for 50k.
Call it what it is
I narrowed it down to a line in the fetch library, but in case this isn't an issue with fetch, I'm copying here. See JakeChampion/fetch#83
Following the getting started guide here: http://martyjs.org/guides/getting-started/
npm install -g yo generator-marty
mkdir example && cd example
yo marty
yo marty:domain user
npm start
open http://localhost:5000/user/134 in chrome
Inspecting the api call to http://localhost:5000/api/users/134
yields Failed to load response data
in the preview tab. When I console.log the api response, it shows {id: "4"}
, which makes it seem like response body is being returned somewhere, so this might not break anything, just make debugging responses difficult.
The [Exception: DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').]
error described in the fetch issue is still happening, despite the response apparently coming back.
In my original issue on my own implementation, returning when
returns a 404 error, but when I right click > open in new tab on the API request from the chome debugger, the request works just fine (in a new tab, by its self) and I receive the response back with no issues. Something in marty/fetch is corrupting/not returning the response.
Let me know if this is reproducible. I've tried this with node v0.10.33 and v0.11.14
Hi!
I'm starting to use marty and I have a problem. My application has a PeopleStore and a PeopleAPI which returns some paginated list of json results.
So in my main component view I display those first results and a pagination component which just handles the on click event on any of it's page links, so when they are clicked it tries to fetch again the data from the server for the selected page results...
Te problem is that in my store it always returns the already loaded results from the first time as it always fetches locally first... is there a way of forcing it to fetch again remotely from the server?
Here's my code:
@PeopleStore = Marty.createStore
displayName: 'PeopleStore'
getInitialState: ->
meta:
totalPages: 0
currentPage: 1
handlers:
receivePeople: PeopleConstants.RECEIVE_PEOPLE
findPeople: PeopleConstants.FIND_PEOPLE
findPeople: (params) ->
@.fetch
id: 'all-people'
locally: () ->
@state.people
remotely: () ->
PeopleAPI.findPeople(params)
paginationMeta: ->
@state.meta
receivePeople: (response) ->
@setState
people: response.people
meta: response.meta
@PeopleAPI = Marty.createStateSource
type: 'http'
findPeople: (params) ->
searchText = if params and params.searchText then params.searchText else ''
pageNumber = if params and params.pageNumber then params.pageNumber else 1
req =
url: Routes.people_path(
search: searchText
page: pageNumber
)
@get(req).then (res) ->
PeopleSourceActionCreators.receivePeople res.body
I have a full demo, only using React components, of what I'm trying to achieve with using marty.js here's the link http://rails-and-react-iii.herokuapp.com/
Thank you very much in advance!
We need a router that can be run the client and server so we can start building isomorphic applications. You should be able to
Rather than building our own we should use one of the many react-routers out there. react-router offers server side rendering and all the APIs we need. To avoid too many dependencies on the core module lets have this as its own package
var MartyRouter = require('marty-react-router');
var RoutesStore = MartyRouter.createRoutesStore(routes);
var NavigitationActionCreators = MartyRouter.createNavigationActionCreators(router);
The problem is here: state.js:41
When store.serialize
is undefined
, store.getState
is called with a broken this
reference.
Here's a simplified recreation of the problem:
var x = {
fn: function () { return this; }
};
x.fn() // => returns `x`
(x.fn)() // => returns `x`
(undefined || x.fn)() // => returns `window` ... wat
I think we can all conclude that JavaScript is retarded. Probably still needs to be fixed though.. : ]
A chrome developer tools tab that allows you to visualise the state of the application and how data is flowing through
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.