My state is:
[
{type: "translateX", x: 10},
{type: "scaleX", x: 1.2}
]
I’m using Two-Way Binding Helpers and I can’t provide a valid key string for linkState:
this.state.map(function(item, i) {
return <div><input valueLink={this.linkState( ??? )}></div>
}
Would be nice if this.linkState accepted some query syntax, such as "0.type" to retrieve "translateX" from my example.
Are there any workarounds?
I wrote DeepLinkState mixin which is a drop-in replacement for React.addons.LinkedStateMixin. Usage example:
this.state.map(function(item, i) {
return <div><input valueLink={this.linkState([i, "x"])}></div>
}
linkState("0.x") is also acceptable syntax.
Edit:
I realized that deep-path for LinkedState is pretty cool so I try to implement it.
The code: https://gist.github.com/tungd/8367229
Usage: http://jsfiddle.net/uHm6k/3/
As the document stated, LinkedState is a wrapper around onChange/setState and meant for simple case. You can always write the full onChange/setState to achieve what you want. If you really want to stick with LinkedState, you can use the non mixin version, for example:
getInitialState: function() {
return { values: [
{ type: "translateX", x: 10 },
{ type: "scaleX", x: 1.2 }
]}
},
handleTypeChange: function(i, value) {
this.state.values[i].type = value
this.setState({ values: this.state.values })
},
render: function() {
...
this.state.values.map(function(item, i) {
var typeLink = {
value: this.state.values[i].type,
requestChange: this.handleTypeChange.bind(null, i)
}
return <div><input valueLink={typeLink}/></div>
}, this)
...
}
Here is working JSFiddle: http://jsfiddle.net/srbGL/
You can implement your own mixin if the base mixin doesn't satisfy you.
See how this mixin is implemented:
var LinkedStateMixin = {
/**
* Create a ReactLink that's linked to part of this component's state. The
* ReactLink will have the current value of this.state[key] and will call
* setState() when a change is requested.
*
* #param {string} key state key to update. Note: you may want to use keyOf()
* if you're using Google Closure Compiler advanced mode.
* #return {ReactLink} ReactLink instance linking to the state.
*/
linkState: function(key) {
return new ReactLink(
this.state[key],
ReactStateSetters.createStateKeySetter(this, key)
);
}
};
/**
* #param {*} value current value of the link
* #param {function} requestChange callback to request a change
*/
function ReactLink(value, requestChange) {
this.value = value;
this.requestChange = requestChange;
}
https://github.com/facebook/react/blob/fc73bf0a0abf739a9a8e6b1a5197dab113e76f27/src/addons/link/LinkedStateMixin.js
https://github.com/facebook/react/blob/fc73bf0a0abf739a9a8e6b1a5197dab113e76f27/src/addons/link/ReactLink.js
So you can easily try to write your own linkState function based on the above.
linkState: function(key,key2) {
return new ReactLink(
this.state[key][key2],
function(newValue) {
this.state[key][key2] = newValue;
}
);
}
Notice that I didn't use the ReactStateSetters.createStateKeySetter(this, key).
https://github.com/facebook/react/blob/fc73bf0a0abf739a9a8e6b1a5197dab113e76f27/src/core/ReactStateSetters.js
By looking at the source code again you can find out this method doesn't do so much except it creates a function and does little caching optimizations:
function createStateKeySetter(component, key) {
// Partial state is allocated outside of the function closure so it can be
// reused with every call, avoiding memory allocation when this function
// is called.
var partialState = {};
return function stateKeySetter(value) {
partialState[key] = value;
component.setState(partialState);
};
}
So you should definitely try to write your own mixin.
This can be very useful if you have in your state a complex object and you want to modify it through the object API.
I do it without using value-link addon.
Here is a demo: http://wingspan.github.io/wingspan-forms/examples/form-twins/
The secret sauce is to only define one onChange function:
onChange: function (path, /* more paths,*/ value) {
// clone the prior state
// traverse the tree by the paths and assign the value
this.setState(nextState);
}
use it like this:
<input
value={this.state['forms']['0']['firstName']}
onChange={_.partial(this.onChange, 'forms', '0', 'firstName')} />
If you have many (value, onChange) pairs that you have to pass around everywhere, it might make sense to define an abstraction around this similar to ReactLink, but I personally got pretty far without using ReactLink.
My colleagues and I recently open sourced wingspan-forms, a React library that helps with with deeply nested state. We leverage this approach heavily. You can see more example demos with linked state on the github page.
I wrote a blogpost about it: http://blog.sendsonar.com/2015/08/04/angular-like-deep-path-data-bindings-in-react/
But basically I created a new component that would accept the 'state' of parent and a deep path, so you don't have to write extra code.
<MagicInput binding={[this, 'account.owner.email']} />
There's a JSFiddle too so you can play with it
Here's the tutorial explaining how to handle things like this.
State and Forms in React, Part 3: Handling the Complex State
TL;DR:
0) Don't use standard links. Use these.
1) Change your state to look like this:
collection : [
{type: "translateX", x: 10},
{type: "scaleX", x: 1.2}
]
2) Take link to the collection:
var collectionLink = Link.state( this, 'collection' );
3) Iterate through the links to its elements:
collectionLink.map(function( itemLink, i ) {
return <div><input valueLink={itemLink}></div>
})
I took a different approach which does not employ mixins and does not automatically mutate the state
See github.com/mcmlxxxviii/react-value-link
Related
I wonder how to subscribe to the changes of a JavaScript object e.g. like Redux does. I read through a lot of JS documentations but I couldn't find a non-deprecated way to handle this problem (Object.protype.watch() as well as Object.observe() are deprecated). Moreover I read a few Stackoverflow questions regarding this topic but they are all at least 5 years old. To visualize my problem I'll show an example.
This could be an object I want to watch for:
const store = {
anArray = [
'Hi',
'my',
'name',
'is'
]
}
.. and this a function which changes the store object:
function addAName() {
store.anArray.push('Bob')
}
My goal in this example is to trigger the following function every time the store object changes
function storeChanged() {
console.log('The store object has changed!')
}
Thank you in advance!
Have you tried using Proxy from ECMA6? I think this is what you are looking for
You only have to define a function as the set of the validator of the Proxy like this:
let validator = {
set: function(target, key, value) {
console.log(`The property ${key} has been updated with ${value}`);
return true;
}
};
let store = new Proxy({}, validator);
store.a = 'hello';
// console => The property a has been updated with hello
To solve this problem without any indirections (in using object) you can use proxy.
By wrapping all objects with observable you can edit your store freely and _base keeps track of which property has changed.
const observable = (target, callback, _base = []) => {
for (const key in target) {
if (typeof target[key] === 'object')
target[key] = observable(target[key], callback, [..._base, key])
}
return new Proxy(target, {
set(target, key, value) {
if (typeof value === 'object') value = observable(value, callback, [..._base, key])
callback([..._base, key], target[key] = value)
return value
}
})
}
const a = observable({
a: [1, 2, 3],
b: { c: { d: 1 } }
}, (key, val) => {
console.log(key, val);
})
a.a.push(1)
a.b.c.d = 1
a.b = {}
a.b.c = 1
You can use Object.defineproperty() to create reactive getters/setters. It has good browser support and looks handy.
function Store() {
let array = [];
Object.defineProperty(this, 'array', {
get: function() {
console.log('Get:', array);
return array;
},
set: function(value) {
array = value;
console.log('Set:', array)
}
});
}
var store = new Store();
store.array; //Get: []
store.array = [11]; //Set: [11]
store.array.push(5) //Set: [11, 5]
store.array = store.array.concat(1, 2, 3) //Set: [11, 5, 1, 2, 3]
It's non-trivial.
There are several approaches that different tools (Redux, Angular, KnockoutJS, etc.) use.
Channeling changes through functions - This is the approach Redux uses (more). You don't directly modify things, you pass them through reducers, which means Redux is aware that you've changed something.
Diffing - Literally comparing the object tree to a previous copy of the object tree and acting on changes made. At least some versions of Angular/AngularJS use(d) this approach.
Wrapping - (Kind of a variant on #1) Wrapping all modification operations on all objects in the tree (such as the push method on your array) with wrappers that notify a controller that they object they're on has been called — by wrapping those methods (and replacing simple data properties with accessor properties) and/or using Proxy objects. KnockoutJS uses a version of this approach.
You can try this npm package
tahasoft-event-emitter
Your code will look like this
import { EventEmitter } from "tahasoft-event-emitter";
const store = {
anArray: ["Hi", "my", "name", "is"]
};
const onStoreChange = new EventEmitter();
function addAName(name) {
onStoreChange.emit(name);
store.anArray.push(name);
}
function storeChanged(name) {
console.log("The store object has changed!. New name is " + name);
}
onStoreChange.add((name) => storeChanged(name));
addAName("Bob");
If you are not interested in the new value of name, you can write it simple like this
import { EventEmitter } from "tahasoft-event-emitter";
const store = {
anArray: ["Hi", "my", "name", "is"]
};
const onStoreChange = new EventEmitter();
function addAName() {
store.anArray.push("Bob");
onStoreChange.emit();
}
function storeChanged() {
console.log("The store object has changed!");
}
onStoreChange.add(storeChanged);
addAName();
Whenever you call addAName, storeChanged will be called
Here is the example for on codesandbox
EventEmitter on an object status change
You can check the package GitHub repository to know how it is created. It is very simple.
I am not sure if there is a way to do it with native functionality and even if there is I think you won't be able to do this without some sort of abstraction.
It doesn't even work natively in react/redux either as you need to specifically call setState explicitly to trigger changes. I recommend a very observer pattern whose implementation roughly looks like this.
var store = {
names: ['Bob', 'Jon', 'Doe']
}
var storeObservers = [];
Now, simple push your observer functions doesn't matter even if they are part of some component or module
storeObservers.push(MyComponent.handleStoreChange)
Now simple expose a function to change the store similar to setState
function changeStore(store) {
store = store
storeObservers.forEach(function(observer){
observer()
})
}
You can obviously modularized this to handle more complex situations or if you only want to change a part of state and allow observers to bind callbacks to partial state changes.
Alejandro Riera's answer using Proxy is probably the correct approach in most cases. However, if you're willing to include a (small-ish, ~90kb) library, Vue.js can have watched properties on its instances, which can sort of do what you want.
It is probably overkill to use it for just change observation, but if your website has other uses for a reactive framework, it may be a good approach.
I sometimes use it as an object store without an associated element, like this:
const store = new Vue({
data: {
anArray: [
'Hi',
'my',
'name',
'is'
]
},
watch: {
// whenever anArray changes, this function will run
anArray: function () {
console.log('The store object has changed:', this.anArray);
}
}
});
function addAName() {
// push random strings as names
const newName = '_' + Math.random().toString(36).substr(2, 6);
store.anArray.push(newName);
}
// demo
setInterval(addAName, 5000);
I'm trying to learn Cycle.js and must say that I'm finding it quite interesting. I'm trying to create a simple app where I have a input and a ul. Every time write some value to the input and I press enter I want to add a new li with the value to the ul, but its failing with the following error.
Uncaught TypeError: observables[_name2].doOnError is not a function
var view = function (state) {
return CycleDOM.body([
CycleDOM.input({ type: 'text', value: '' }),
CycleDOM.ul({ className: 'text' },
state.map(function (value) {
CycleDOM.li({ innerText: value });
}))
]);
};
var intent = function (DOM) {
return DOM.select('input[type=text]').events('keydown').filter(function (ev) {
return ev.which == 13 && ev.target.value.trim().length > 0;
}).map(function (ev) {
return ev.target.value;
});
};
var model = function (action) {
return action.startWith('');
};
var main = function (sources) {
var actions = intent(sources.DOM);
var state = model(actions);
var sinks = {
DOM: view(state)
};
return sinks;
}
var drivers = {
DOM: CycleDOM.makeDOMDriver(document.body)
};
Cycle.run(main, drivers);
First, it's good to see that people are interested in Cycle!
You are missing some points here and that's why you're having some struggle.
You might have not fully understood the concept of reactive programming yet. You should read The introduction to Reactive Programming you've been missing by the creator of Cycle and watch his videos about Cycle. They really help understanding how Cycle works on the inside.
Also, you could adopt the naming convention of Cycle, it really helps. A stream/observable should end with a $, like
var click$ = DOM.select('a').events('click');
As #juanrpozo said your main issue is in your view function because it returns a virtual tree instead of an observable of virtual tree.
Also it is important that you understand that the state variable is an observable, not a javascript array. That's why I think you aren't comfortable with Rx yet. You think you're mapping an array, but actually your mapping an observable, hence returning another observable, not an array.
But as the DOM sink should be an observable, that's perfect, you'd just have to wrap your VTree in the map:
var view = function (state$) {
return state$.map(function (values) {
CycleDOM.body([
CycleDOM.input({ type: 'text', value: '' }),
CycleDOM.ul({ className: 'text' }, values.map(function (value) {
CycleDOM.li(value);
}))
])
};
}
Another issue is your state$ management. You have to understand that state$ is a stream of consecutive states of your component. It's kinda hard to explain this on stackoverflow, but if you read/watch the resources I sent you you'll get it without any problem.
I made you a jsbin of your code once corrected and changed a bit to respect a bit more the Intent/Model/View convention.
You also had other errors, but those were the most important.
I use the following code which is working great but I wonder if in JS there is a way to avoid the if and to do it inside the loop, I want to use also lodash if it helps
for (provider in config.providers[0]) {
if (provider === "save") {
....
You can chain calls together using _.chain, filter by a value, and then use each to call a function for each filtered result. However, you have to add a final .value() call at the end for it to evaluate the expression you just built.
I'd argue that for short, simple conditional blocks, an if statement is easier and more readable. I'd use lodash- and more specifically chaining- if you are combining multiple operations or performing sophisticated filtering, sorting, etc. over an object or collection.
var providers = ['hello', 'world', 'save'];
_.chain(providers)
.filter(function(provider) {
return provider === 'save';
}).each(function(p) {
document.write(p); // your code here
}).value();
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.8.0/lodash.js"></script>
Edit: My mistake; filter does not have an overload where you can just supply a literal value. If you want to do literal value checking you have to supply a function as in my amended answer above.
I'd argue that what you have there is pretty good, clean and readable, but since you mentioned lodash, I will give it a try.
_.each(_.filter(config.providers[0], p => p === 'save'), p => {
// Do something with p
...
});
Note that the arrow function/lambda of ECMAScript 6 doesn't come to Chrome until version 45.
Basically, you are testing to see if config.providers[0], which is an object, contains a property called save (or some other dynamic value, I'm using a variable called provider to store that value in my example code below).
You can use this instead of using a for .. in .. loop:
var provider = 'save';
if (config.providers[0][provider] !== undefined) {
...
}
Or using #initialxy's (better!) suggestion:
if (provider in config.providers[0]) {
...
}
How about:
for (provider in config.providers[0].filter(function(a) {return a === "save"}) {
...
}
Strategy, you are looking for some kind of strategy pattern as,
Currenlty the save is hardcoded but what will you do if its coming from other varible – Al Bundy
var actions = {
save: function() {
alert('saved with args: ' + JSON.stringify(arguments))
},
delete: function() {
alert('deleted')
},
default: function() {
alert('action not supported')
}
}
var config = {
providers: [{
'save': function() {
return {
action: 'save',
args: 'some arguments'
}
},
notSupported: function() {}
}]
}
for (provider in config.providers[0]) {
(actions[provider] || actions['default'])(config.providers[0][provider]())
}
Push „Run code snippet” button will shows two pop-ups - be carefull
It is not clearly stated by the original poster whether the desired output
should be a single save - or an array containing all occurrences of
save.
This answer shows a solution to the latter case.
const providers = ['save', 'hello', 'world', 'save'];
const saves = [];
_.forEach(_.filter(providers, elem => { return elem==='save' }),
provider => { saves.push(provider); });
console.log(saves);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.19/lodash.js"></script>
I'm building a flux app that involves many different types of data, and the CRUD style modification of the resources. This leads to the a large number of ActionTypes. Most of them follow the same pattern, REQUEST_ENTITY, REQUEST_ENTITY_SUCCESS, REQUEST_ENTITY_ERROR, and so on.
How do I separate them into namespaced constants?
Ideally instead of accessing them like:
ActionTypes.REQUEST_ENTITY
I could access them in a more sane way like,
ActionTypes.entity.REQUEST
Why not skip the constants, and just use the string values? Sure, you may mistype one from time to time, but you could just as easily mistype the constant names, right? Your unit tests will fail in the same place, either way, and you'll know what's wrong.
Without compile-time checking, the main value of these kinds of constant lists is that the code is a bit more self-documenting, but if you're that consistent in your naming conventions, it might not be worth the extra effort to write them all out as constants?
(That was kind of a non-answer, I guess, but I've had this same conversation with others, so probably worth adding to the discussion here, too.)
You could simply merge multiple objects (perhaps exported from different files) into ActionTypes.
// entity_actions.js
module.exports = {
entity: {
REQUEST: "entity.REQUEST",
DELETE: "entity.DELETE",
}
};
// user_actions.js
module.exports = {
user: {
REQUEST: "user.REQUEST",
DELETE: "user.DELETE",
}
};
// actions.js
var entityActions = require("./entity_actions");
var userActions = require("./user_actions");
var ActionTypes = Object.assign({}, entityActions, userActions);
You can use something like Underscore#extend or object-assign if Object.assign isn't available in your environment.
I personally use a small module I called nestedKeyMirror that takes a big nested object and automatically generates values based on the nesting:
function nestedKeyMirror(obj, namespace) {
namespace = namespace || [];
for (key in obj) {
if (obj.hasOwnProperty(key) && obj[key] === null) {
obj[key] = namespace.concat([key]).join(":");
} else if (obj.hasOwnProperty(key) && typeof obj[key] === "object") {
obj[key] = nestedKeyMirror(obj[key], namespace.concat([key]));
}
}
return obj;
}
For example, in one app, I have the following action types defined:
var actionTypes = nestedKeyMirror({
LAYOUT: {
RESIZE_PANE: null
},
CANVAS: {
SET_PROPERTY: null
},
SHAPES: {
ADD: null,
SET_PROPERTY: null,
SEND_BACKWARD: null,
SEND_FORWARD: null,
SEND_TO_BACK: null,
SEND_TO_FRONT: null
},
SELECTION: {
SELECT: null,
DESELECT_ALL: null
},
HISTORY: {
ADD: null,
SELECT_INDEX: null
}
});
This would give, e.g., actionTypes.SHAPES.ADD with an automatically-generated string value of "SHAPES:ADD". This technique can be combined with the object-merging strategy, above, to easily create deeply nested action type constants.
[Update: it looks like someone recently released a package that does the nested key mirroring on npm: keymirror-nested]
If the problem is that all your action types look similar, you could easily create a function to generate them (ES6 computed property syntax used here):
function generateActionType(type, base) {
return {
[base]: `${base}_${type}`,
[`${base}_SUCCESS`]: `${base}_${type}_SUCCESS`,
[`${base}_ERROR`]: `${base}_${type}_ERROR`
};
}
ActionTypes.entity = {};
Object.assign(ActionTypes.entity, generateActionType("ENTITY", "REQUEST"));
Object.assign(ActionTypes.entity, generateActionType("ENTITY", "DELETE"));
ActionTypes.entity.REQUEST_SUCCESS === "REQUEST_ENTITY_SUCCESS";
I've been looking into JavaScript frameworks such as Angular and Meteor lately, and I was wondering how they know when an object property has changed so that they could update the DOM.
I was a bit surprised that Angular used plain old JS objects rather than requiring you to call some kind of getter/setter so that it could hook in and do the necessary updates. My understanding is that they just poll the objects regularly for changes.
But with the advent of getters and setters in JS 1.8.5, we can do better than that, can't we?
As a little proof-of-concept, I put together this script:
(Edit: updated code to add dependent-property/method support)
function dependentProperty(callback, deps) {
callback.__dependencies__ = deps;
return callback;
}
var person = {
firstName: 'Ryan',
lastName: 'Gosling',
fullName: dependentProperty(function() {
return person.firstName + ' ' + person.lastName;
}, ['firstName','lastName'])
};
function observable(obj) {
if (!obj.__properties__) Object.defineProperty(obj, '__properties__', {
__proto__: null,
configurable: false,
enumerable: false,
value: {},
writable: false
});
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
if(!obj.__properties__[prop]) obj.__properties__[prop] = {
value: null,
dependents: {},
listeners: []
};
if(obj[prop].__dependencies__) {
for(var i=0; i<obj[prop].__dependencies__.length; ++i) {
obj.__properties__[obj[prop].__dependencies__[i]].dependents[prop] = true;
}
delete obj[prop].__dependencies__;
}
obj.__properties__[prop].value = obj[prop];
delete obj[prop];
(function (prop) {
Object.defineProperty(obj, prop, {
get: function () {
return obj.__properties__[prop].value;
},
set: function (newValue) {
var oldValue = obj.__properties__[prop].value;
if(oldValue !== newValue) {
var oldDepValues = {};
for(var dep in obj.__properties__[prop].dependents) {
if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) {
oldDepValues[dep] = obj.__properties__[dep].value();
}
}
obj.__properties__[prop].value = newValue;
for(var i=0; i<obj.__properties__[prop].listeners.length; ++i) {
obj.__properties__[prop].listeners[i](oldValue, newValue);
}
for(dep in obj.__properties__[prop].dependents) {
if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) {
var newDepValue = obj.__properties__[dep].value();
for(i=0; i<obj.__properties__[dep].listeners.length; ++i) {
obj.__properties__[dep].listeners[i](oldDepValues[dep], newDepValue);
}
}
}
}
}
});
})(prop);
}
}
return obj;
}
function listen(obj, prop, callback) {
if(!obj.__properties__) throw 'object is not observable';
obj.__properties__[prop].listeners.push(callback);
}
observable(person);
listen(person, 'fullName', function(oldValue, newValue) {
console.log('Name changed from "'+oldValue+'" to "'+newValue+'"');
});
person.lastName = 'Reynolds';
Which logs:
Name changed from "Ryan Gosling" to "Ryan Reynolds"
The only problem I see is with defining methods such as fullName() on the person object which would depend on the other two properties. This requires a little extra markup on the object to allow developers to specify the dependency.
Other than that, are there any downsides to this approach?
JsFiddle
advent of getters and setters in JS 1.8.5 - are there any downsides to this approach?
You don't capture any property changes apart from the observed ones. Sure, this is enough for modeled entity objects, and for anything else we could use Proxies.
It's limited to browsers that support getters/setters, and maybe even proxies. But hey, who does care about outdated browsers? :-) And in restricted environments (Node.js) this doesn't hold at all.
Accessor properties (with getter and setter) are much slower than real get/set methods. Of course I don't expect them to be used in critical sections, and they can make code looking much fancier. Yet you need to keep that in the back of your mind. Also, the fancy-looking code can lead to misconceptions - normally you would expect property assignment/accessing to be a short (O(1)) operation, while with getters/setters there might be a lot of more happening. You will need to care not forgetting that, and the use of actual methods could help.
So if we know what we are doing, yes, we can do better.
Still, there is one huge point we need to remember: the synchronity/asynchronity (also have a look at this excellent answer). Angular's dirty checking allows you to change a bunch of properties at once, before the event fires in the next event loop turn. This helps to avoid (the propagation of) semantically invalid states.
Yet I see the synchronous getters/setters as a chance as well. They do allow us to declare the dependencies between properties and define the valid states by this. It will automatically ensure the correctness of the model, while we only have to change one property at a time (instead of changing firstName and fullName all the time, firstName is enough). Nevertheless, during dependency resolving that might not hold true so we need to care about it.
So, the listeners that are not related to the dependencies management should be fired asynchronous. Just setImmediate their loop.