mobx-react
Advanced tools
Comparing version 3.4.0 to 3.5.0-fix85
# MobX-React Changelog | ||
### 3.5.0 | ||
* Introduced `inject("store1", "store2")(component)` as alternative syntax to inject stores. Should address #77, #70 | ||
* Introduced the `wrappedComponent` property on injected higher order components, addresses #70, #72 | ||
* Fixed #76: error when no stores are provided through context | ||
* Added typings for devTools related features (@benjamingr). | ||
* Added MobX specific propTypes (@mattruby) | ||
* Merged #44, fixes #73: don't re-render if component was somehow unmounted | ||
### 3.4.0 | ||
@@ -4,0 +13,0 @@ |
/** | ||
* Turns a React component or stateless render function into a reactive component. | ||
*/ | ||
import React = require('react'); | ||
import React = require("react"); | ||
export function observer<P>(clazz: React.StatelessComponent<P>): React.ClassicComponentClass<P>; | ||
export function observer<P>(renderFunction: (props: P) => React.ReactElement<any>): React.ClassicComponentClass<P>; | ||
export function observer<P>(renderFunction: (props: P) => React.ReactElement<P>): React.ClassicComponentClass<P>; | ||
export function observer<P>(clazz: React.ClassicComponentClass<P>): React.ClassicComponentClass<P>; | ||
export function observer<P>(clazz: React.ComponentClass<P>): React.ComponentClass<P>; | ||
export function observer<TFunction extends React.ComponentClass<any>>(target: TFunction): TFunction; // decorator signature | ||
export function observer<P, TFunction extends React.ComponentClass<P>>(target: TFunction): TFunction; // decorator signature | ||
@@ -17,6 +17,42 @@ // with stores | ||
export function observer<P>(stores: string[], clazz: React.ComponentClass<P>): React.ComponentClass<P>; | ||
export function observer(stores: string[]): <TFunction extends React.ComponentClass<any>>(target: TFunction) => TFunction; // decorator signature | ||
export function observer<P>(stores: string[]): <TFunction extends React.ComponentClass<P>>(target: TFunction) => TFunction; // decorator signature | ||
// inject | ||
export function inject<P>(...stores: string[]): <TFunction extends React.ComponentClass<P>>(target: TFunction) => TFunction; // decorator signature | ||
export function inject<T, P>(storesToProps : (stores: any, nextProps: P, context:any) => T): <TFunction extends React.ComponentClass<T | P>>(target: TFunction) => TFunction; // decorator | ||
export class Provider extends React.Component<any, {}> { | ||
} | ||
} | ||
/** | ||
* Enable dev tool support, makes sure that renderReport emits events. | ||
*/ | ||
export function trackComponents():void; | ||
export const renderReporter: RenderReporter; | ||
export interface RenderReporter { | ||
on(handler: (data: IRenderEvent) => void): void; | ||
} | ||
export interface IRenderEvent { | ||
event: "render" | "destroy"; | ||
renderTime?: number; | ||
totalTime?: number; | ||
component: React.ReactElement<any>; // Component instance | ||
node: any; // DOMNode | ||
} | ||
/** | ||
* WeakMap DOMNode -> Component instance | ||
*/ | ||
export const componentByNodeRegistery: any; | ||
export const propTypes: { | ||
observableArray: React.Requireable<any>; | ||
observableMap: React.Requireable<any>; | ||
observableObject: React.Requireable<any>; | ||
arrayOrObservableArray: React.Requireable<any>; | ||
objectOrObservableObject: React.Requireable<any>; | ||
} |
104
custom.js
@@ -99,6 +99,19 @@ (function() { | ||
if (!isRenderingPending) { | ||
isRenderingPending = true; | ||
if (typeof self.componentWillReact === "function") | ||
self.componentWillReact(); | ||
React.Component.prototype.forceUpdate.call(self) | ||
isRenderingPending = true; | ||
if (self.__$mobxMounted) { // componentWillReact *could* cause component to be unmounted.. | ||
React.Component.prototype.forceUpdate.call(self) | ||
} else { | ||
// long story, see: | ||
// https://github.com/mobxjs/mobx-react/issues/85 | ||
// https://github.com/mobxjs/mobx-react/pull/44 | ||
// https://github.com/mobxjs/mobx-react/commit/7ac924c880747920feaa016a3b4c9c3850449e1a | ||
// https://github.com/mobxjs/mobx-react/commit/cc2e14e2706e93de2de6da1253a3b20692396d7b | ||
// in short: if a rerendering is triggered before the component is mounted, | ||
// forceUpdate is not allowed (which we normally used because if observables change we MUST update) | ||
// but setStates are correctly queued | ||
// this will trigger a warning, but yield the correct rendering, see misc.js: 133 | ||
React.Component.prototype.setState.call(self, {}) // fixes 85 | ||
} | ||
} | ||
@@ -129,2 +142,3 @@ }); | ||
this.render.$mobx && this.render.$mobx.dispose(); | ||
this.__$mobxMounted = false; | ||
if (isDevtoolsEnabled) { | ||
@@ -144,2 +158,3 @@ var node = findDOMNode(this); | ||
componentDidMount: function() { | ||
this.__$mobxMounted = true; | ||
if (isDevtoolsEnabled) | ||
@@ -155,6 +170,7 @@ reportRendering(this); | ||
shouldComponentUpdate: function(nextProps, nextState) { | ||
// TODO: if context changed, return true.., see #18 | ||
// if props or state did change, but a render was scheduled already, no additional render needs to be scheduled | ||
if (this.render.$mobx && this.render.$mobx.isScheduled() === true) | ||
return false; | ||
// update on any state changes (as is the default) | ||
@@ -201,3 +217,4 @@ if (this.state !== nextState) | ||
} else { | ||
return createStoreInjector(arg1, observer(arg2)); | ||
// TODO: deprecate this invocation style | ||
return inject.apply(null, arg1)(observer(arg2)); | ||
} | ||
@@ -285,3 +302,3 @@ } | ||
*/ | ||
function createStoreInjector(stores, component) { | ||
function createStoreInjector(grabStoresFn, component) { | ||
var Injector = React.createClass({ | ||
@@ -293,14 +310,9 @@ displayName: "MobXStoreInjector", | ||
newProps[key] = this.props[key]; | ||
var baseStores = this.context.mobxStores; | ||
stores.forEach(function(storeName) { | ||
if (storeName in newProps) // prefer props over stores | ||
return; | ||
if (!(storeName in baseStores)) | ||
throw new Error("MobX observer: Store '" + storeName + "' is not available! Make sure it is provided by some Provider"); | ||
newProps[storeName] = baseStores[storeName]; | ||
}, this); | ||
newProps = grabStoresFn(this.context.mobxStores || {}, newProps, this.context); | ||
return React.createElement(component, newProps); | ||
} | ||
// TODO: should have shouldComponentUpdate? | ||
}); | ||
Injector.contextTypes = { mobxStores: PropTypes.object }; | ||
Injector.wrappedComponent = component; | ||
return Injector; | ||
@@ -310,2 +322,66 @@ } | ||
/** | ||
* higher order component that injects stores to a child. | ||
* takes either a varargs list of strings, which are stores read from the context, | ||
* or a function that manually maps the available stores from the context to props: | ||
* storesToProps(mobxStores, props, context) => newProps | ||
*/ | ||
function inject(/* fn(stores, nextProps) or ...storeNames */) { | ||
var grabStoresFn; | ||
if (typeof arguments[0] === "function") { | ||
grabStoresFn = arguments[0]; | ||
} else { | ||
var storesNames = []; | ||
for (var i = 0; i < arguments.length; i++) | ||
storesNames[i] = arguments[i]; | ||
grabStoresFn = grabStoresByName(storesNames); | ||
} | ||
return function(componentClass) { | ||
return createStoreInjector(grabStoresFn, componentClass); | ||
}; | ||
} | ||
function grabStoresByName(storeNames) { | ||
return function(baseStores, nextProps) { | ||
storeNames.forEach(function(storeName) { | ||
if (storeName in nextProps) // prefer props over stores | ||
return; | ||
if (!(storeName in baseStores)) | ||
throw new Error("MobX observer: Store '" + storeName + "' is not available! Make sure it is provided by some Provider"); | ||
nextProps[storeName] = baseStores[storeName]; | ||
}); | ||
return nextProps; | ||
} | ||
} | ||
/** | ||
* PropTypes | ||
*/ | ||
function observableTypeChecker (type) { | ||
return function(props, propName, componentName) { | ||
if (!mobx['isObservable' + type](props[propName])) { | ||
return new Error( | ||
'Invalid prop `' + propName + '` supplied to' + | ||
' `' + componentName + '`. Expected a mobx observable ' + type + '. Validation failed.' | ||
); | ||
} | ||
}; | ||
} | ||
// oneOfType is used for simple isRequired chaining | ||
var propTypes = { | ||
observableArray: React.PropTypes.oneOfType([observableTypeChecker('Array')]), | ||
observableMap: React.PropTypes.oneOfType([observableTypeChecker('Map')]), | ||
observableObject: React.PropTypes.oneOfType([observableTypeChecker('Object')]), | ||
arrayOrObservableArray: React.PropTypes.oneOfType([ | ||
React.PropTypes.array, | ||
observableTypeChecker('Array') | ||
]), | ||
objectOrObservableObject: React.PropTypes.oneOfType([ | ||
React.PropTypes.object, | ||
observableTypeChecker('Object') | ||
]) | ||
}; | ||
/** | ||
* Export | ||
@@ -316,2 +392,4 @@ */ | ||
Provider: Provider, | ||
inject: inject, | ||
propTypes: propTypes, | ||
reactiveComponent: function() { | ||
@@ -318,0 +396,0 @@ console.warn("[mobx-react] `reactiveComponent` has been renamed to `observer` and will be removed in 1.1."); |
/** | ||
* Turns a React component or stateless render function into a reactive component. | ||
*/ | ||
import React = require('react'); | ||
import React = require("react"); | ||
export function observer<P>(clazz: React.StatelessComponent<P>): React.ClassicComponentClass<P>; | ||
export function observer<P>(renderFunction: (props: P) => React.ReactElement<any>): React.ClassicComponentClass<P>; | ||
export function observer<P>(renderFunction: (props: P) => React.ReactElement<P>): React.ClassicComponentClass<P>; | ||
export function observer<P>(clazz: React.ClassicComponentClass<P>): React.ClassicComponentClass<P>; | ||
export function observer<P>(clazz: React.ComponentClass<P>): React.ComponentClass<P>; | ||
export function observer<TFunction extends React.ComponentClass<any>>(target: TFunction): TFunction; // decorator signature | ||
export function observer<P, TFunction extends React.ComponentClass<P>>(target: TFunction): TFunction; // decorator signature | ||
@@ -17,6 +17,42 @@ // with stores | ||
export function observer<P>(stores: string[], clazz: React.ComponentClass<P>): React.ComponentClass<P>; | ||
export function observer(stores: string[]): <TFunction extends React.ComponentClass<any>>(target: TFunction) => TFunction; // decorator signature | ||
export function observer<P>(stores: string[]): <TFunction extends React.ComponentClass<P>>(target: TFunction) => TFunction; // decorator signature | ||
// inject | ||
export function inject<P>(...stores: string[]): <TFunction extends React.ComponentClass<P>>(target: TFunction) => TFunction; // decorator signature | ||
export function inject<T, P>(storesToProps : (stores: any, nextProps: P, context:any) => T): <TFunction extends React.ComponentClass<T | P>>(target: TFunction) => TFunction; // decorator | ||
export class Provider extends React.Component<any, {}> { | ||
} | ||
} | ||
/** | ||
* Enable dev tool support, makes sure that renderReport emits events. | ||
*/ | ||
export function trackComponents():void; | ||
export const renderReporter: RenderReporter; | ||
export interface RenderReporter { | ||
on(handler: (data: IRenderEvent) => void): void; | ||
} | ||
export interface IRenderEvent { | ||
event: "render" | "destroy"; | ||
renderTime?: number; | ||
totalTime?: number; | ||
component: React.ReactElement<any>; // Component instance | ||
node: any; // DOMNode | ||
} | ||
/** | ||
* WeakMap DOMNode -> Component instance | ||
*/ | ||
export const componentByNodeRegistery: any; | ||
export const propTypes: { | ||
observableArray: React.Requireable<any>; | ||
observableMap: React.Requireable<any>; | ||
observableObject: React.Requireable<any>; | ||
arrayOrObservableArray: React.Requireable<any>; | ||
objectOrObservableObject: React.Requireable<any>; | ||
} |
104
index.js
@@ -99,6 +99,19 @@ (function() { | ||
if (!isRenderingPending) { | ||
isRenderingPending = true; | ||
if (typeof self.componentWillReact === "function") | ||
self.componentWillReact(); | ||
React.Component.prototype.forceUpdate.call(self) | ||
isRenderingPending = true; | ||
if (self.__$mobxMounted) { // componentWillReact *could* cause component to be unmounted.. | ||
React.Component.prototype.forceUpdate.call(self) | ||
} else { | ||
// long story, see: | ||
// https://github.com/mobxjs/mobx-react/issues/85 | ||
// https://github.com/mobxjs/mobx-react/pull/44 | ||
// https://github.com/mobxjs/mobx-react/commit/7ac924c880747920feaa016a3b4c9c3850449e1a | ||
// https://github.com/mobxjs/mobx-react/commit/cc2e14e2706e93de2de6da1253a3b20692396d7b | ||
// in short: if a rerendering is triggered before the component is mounted, | ||
// forceUpdate is not allowed (which we normally used because if observables change we MUST update) | ||
// but setStates are correctly queued | ||
// this will trigger a warning, but yield the correct rendering, see misc.js: 133 | ||
React.Component.prototype.setState.call(self, {}) // fixes 85 | ||
} | ||
} | ||
@@ -129,2 +142,3 @@ }); | ||
this.render.$mobx && this.render.$mobx.dispose(); | ||
this.__$mobxMounted = false; | ||
if (isDevtoolsEnabled) { | ||
@@ -144,2 +158,3 @@ var node = findDOMNode(this); | ||
componentDidMount: function() { | ||
this.__$mobxMounted = true; | ||
if (isDevtoolsEnabled) | ||
@@ -155,6 +170,7 @@ reportRendering(this); | ||
shouldComponentUpdate: function(nextProps, nextState) { | ||
// TODO: if context changed, return true.., see #18 | ||
// if props or state did change, but a render was scheduled already, no additional render needs to be scheduled | ||
if (this.render.$mobx && this.render.$mobx.isScheduled() === true) | ||
return false; | ||
// update on any state changes (as is the default) | ||
@@ -201,3 +217,4 @@ if (this.state !== nextState) | ||
} else { | ||
return createStoreInjector(arg1, observer(arg2)); | ||
// TODO: deprecate this invocation style | ||
return inject.apply(null, arg1)(observer(arg2)); | ||
} | ||
@@ -285,3 +302,3 @@ } | ||
*/ | ||
function createStoreInjector(stores, component) { | ||
function createStoreInjector(grabStoresFn, component) { | ||
var Injector = React.createClass({ | ||
@@ -293,14 +310,9 @@ displayName: "MobXStoreInjector", | ||
newProps[key] = this.props[key]; | ||
var baseStores = this.context.mobxStores; | ||
stores.forEach(function(storeName) { | ||
if (storeName in newProps) // prefer props over stores | ||
return; | ||
if (!(storeName in baseStores)) | ||
throw new Error("MobX observer: Store '" + storeName + "' is not available! Make sure it is provided by some Provider"); | ||
newProps[storeName] = baseStores[storeName]; | ||
}, this); | ||
newProps = grabStoresFn(this.context.mobxStores || {}, newProps, this.context); | ||
return React.createElement(component, newProps); | ||
} | ||
// TODO: should have shouldComponentUpdate? | ||
}); | ||
Injector.contextTypes = { mobxStores: PropTypes.object }; | ||
Injector.wrappedComponent = component; | ||
return Injector; | ||
@@ -310,2 +322,66 @@ } | ||
/** | ||
* higher order component that injects stores to a child. | ||
* takes either a varargs list of strings, which are stores read from the context, | ||
* or a function that manually maps the available stores from the context to props: | ||
* storesToProps(mobxStores, props, context) => newProps | ||
*/ | ||
function inject(/* fn(stores, nextProps) or ...storeNames */) { | ||
var grabStoresFn; | ||
if (typeof arguments[0] === "function") { | ||
grabStoresFn = arguments[0]; | ||
} else { | ||
var storesNames = []; | ||
for (var i = 0; i < arguments.length; i++) | ||
storesNames[i] = arguments[i]; | ||
grabStoresFn = grabStoresByName(storesNames); | ||
} | ||
return function(componentClass) { | ||
return createStoreInjector(grabStoresFn, componentClass); | ||
}; | ||
} | ||
function grabStoresByName(storeNames) { | ||
return function(baseStores, nextProps) { | ||
storeNames.forEach(function(storeName) { | ||
if (storeName in nextProps) // prefer props over stores | ||
return; | ||
if (!(storeName in baseStores)) | ||
throw new Error("MobX observer: Store '" + storeName + "' is not available! Make sure it is provided by some Provider"); | ||
nextProps[storeName] = baseStores[storeName]; | ||
}); | ||
return nextProps; | ||
} | ||
} | ||
/** | ||
* PropTypes | ||
*/ | ||
function observableTypeChecker (type) { | ||
return function(props, propName, componentName) { | ||
if (!mobx['isObservable' + type](props[propName])) { | ||
return new Error( | ||
'Invalid prop `' + propName + '` supplied to' + | ||
' `' + componentName + '`. Expected a mobx observable ' + type + '. Validation failed.' | ||
); | ||
} | ||
}; | ||
} | ||
// oneOfType is used for simple isRequired chaining | ||
var propTypes = { | ||
observableArray: React.PropTypes.oneOfType([observableTypeChecker('Array')]), | ||
observableMap: React.PropTypes.oneOfType([observableTypeChecker('Map')]), | ||
observableObject: React.PropTypes.oneOfType([observableTypeChecker('Object')]), | ||
arrayOrObservableArray: React.PropTypes.oneOfType([ | ||
React.PropTypes.array, | ||
observableTypeChecker('Array') | ||
]), | ||
objectOrObservableObject: React.PropTypes.oneOfType([ | ||
React.PropTypes.object, | ||
observableTypeChecker('Object') | ||
]) | ||
}; | ||
/** | ||
* Export | ||
@@ -316,2 +392,4 @@ */ | ||
Provider: Provider, | ||
inject: inject, | ||
propTypes: propTypes, | ||
reactiveComponent: function() { | ||
@@ -318,0 +396,0 @@ console.warn("[mobx-react] `reactiveComponent` has been renamed to `observer` and will be removed in 1.1."); |
/** | ||
* Turns a React component or stateless render function into a reactive component. | ||
*/ | ||
import React = require('react'); | ||
import React = require("react"); | ||
export function observer<P>(clazz: React.StatelessComponent<P>): React.ClassicComponentClass<P>; | ||
export function observer<P>(renderFunction: (props: P) => React.ReactElement<any>): React.ClassicComponentClass<P>; | ||
export function observer<P>(renderFunction: (props: P) => React.ReactElement<P>): React.ClassicComponentClass<P>; | ||
export function observer<P>(clazz: React.ClassicComponentClass<P>): React.ClassicComponentClass<P>; | ||
export function observer<P>(clazz: React.ComponentClass<P>): React.ComponentClass<P>; | ||
export function observer<TFunction extends React.ComponentClass<any>>(target: TFunction): TFunction; // decorator signature | ||
export function observer<P, TFunction extends React.ComponentClass<P>>(target: TFunction): TFunction; // decorator signature | ||
@@ -17,6 +17,42 @@ // with stores | ||
export function observer<P>(stores: string[], clazz: React.ComponentClass<P>): React.ComponentClass<P>; | ||
export function observer(stores: string[]): <TFunction extends React.ComponentClass<any>>(target: TFunction) => TFunction; // decorator signature | ||
export function observer<P>(stores: string[]): <TFunction extends React.ComponentClass<P>>(target: TFunction) => TFunction; // decorator signature | ||
// inject | ||
export function inject<P>(...stores: string[]): <TFunction extends React.ComponentClass<P>>(target: TFunction) => TFunction; // decorator signature | ||
export function inject<T, P>(storesToProps : (stores: any, nextProps: P, context:any) => T): <TFunction extends React.ComponentClass<T | P>>(target: TFunction) => TFunction; // decorator | ||
export class Provider extends React.Component<any, {}> { | ||
} | ||
} | ||
/** | ||
* Enable dev tool support, makes sure that renderReport emits events. | ||
*/ | ||
export function trackComponents():void; | ||
export const renderReporter: RenderReporter; | ||
export interface RenderReporter { | ||
on(handler: (data: IRenderEvent) => void): void; | ||
} | ||
export interface IRenderEvent { | ||
event: "render" | "destroy"; | ||
renderTime?: number; | ||
totalTime?: number; | ||
component: React.ReactElement<any>; // Component instance | ||
node: any; // DOMNode | ||
} | ||
/** | ||
* WeakMap DOMNode -> Component instance | ||
*/ | ||
export const componentByNodeRegistery: any; | ||
export const propTypes: { | ||
observableArray: React.Requireable<any>; | ||
observableMap: React.Requireable<any>; | ||
observableObject: React.Requireable<any>; | ||
arrayOrObservableArray: React.Requireable<any>; | ||
objectOrObservableObject: React.Requireable<any>; | ||
} |
104
native.js
@@ -99,6 +99,19 @@ (function() { | ||
if (!isRenderingPending) { | ||
isRenderingPending = true; | ||
if (typeof self.componentWillReact === "function") | ||
self.componentWillReact(); | ||
React.Component.prototype.forceUpdate.call(self) | ||
isRenderingPending = true; | ||
if (self.__$mobxMounted) { // componentWillReact *could* cause component to be unmounted.. | ||
React.Component.prototype.forceUpdate.call(self) | ||
} else { | ||
// long story, see: | ||
// https://github.com/mobxjs/mobx-react/issues/85 | ||
// https://github.com/mobxjs/mobx-react/pull/44 | ||
// https://github.com/mobxjs/mobx-react/commit/7ac924c880747920feaa016a3b4c9c3850449e1a | ||
// https://github.com/mobxjs/mobx-react/commit/cc2e14e2706e93de2de6da1253a3b20692396d7b | ||
// in short: if a rerendering is triggered before the component is mounted, | ||
// forceUpdate is not allowed (which we normally used because if observables change we MUST update) | ||
// but setStates are correctly queued | ||
// this will trigger a warning, but yield the correct rendering, see misc.js: 133 | ||
React.Component.prototype.setState.call(self, {}) // fixes 85 | ||
} | ||
} | ||
@@ -129,2 +142,3 @@ }); | ||
this.render.$mobx && this.render.$mobx.dispose(); | ||
this.__$mobxMounted = false; | ||
if (isDevtoolsEnabled) { | ||
@@ -144,2 +158,3 @@ var node = findDOMNode(this); | ||
componentDidMount: function() { | ||
this.__$mobxMounted = true; | ||
if (isDevtoolsEnabled) | ||
@@ -155,6 +170,7 @@ reportRendering(this); | ||
shouldComponentUpdate: function(nextProps, nextState) { | ||
// TODO: if context changed, return true.., see #18 | ||
// if props or state did change, but a render was scheduled already, no additional render needs to be scheduled | ||
if (this.render.$mobx && this.render.$mobx.isScheduled() === true) | ||
return false; | ||
// update on any state changes (as is the default) | ||
@@ -201,3 +217,4 @@ if (this.state !== nextState) | ||
} else { | ||
return createStoreInjector(arg1, observer(arg2)); | ||
// TODO: deprecate this invocation style | ||
return inject.apply(null, arg1)(observer(arg2)); | ||
} | ||
@@ -285,3 +302,3 @@ } | ||
*/ | ||
function createStoreInjector(stores, component) { | ||
function createStoreInjector(grabStoresFn, component) { | ||
var Injector = React.createClass({ | ||
@@ -293,14 +310,9 @@ displayName: "MobXStoreInjector", | ||
newProps[key] = this.props[key]; | ||
var baseStores = this.context.mobxStores; | ||
stores.forEach(function(storeName) { | ||
if (storeName in newProps) // prefer props over stores | ||
return; | ||
if (!(storeName in baseStores)) | ||
throw new Error("MobX observer: Store '" + storeName + "' is not available! Make sure it is provided by some Provider"); | ||
newProps[storeName] = baseStores[storeName]; | ||
}, this); | ||
newProps = grabStoresFn(this.context.mobxStores || {}, newProps, this.context); | ||
return React.createElement(component, newProps); | ||
} | ||
// TODO: should have shouldComponentUpdate? | ||
}); | ||
Injector.contextTypes = { mobxStores: PropTypes.object }; | ||
Injector.wrappedComponent = component; | ||
return Injector; | ||
@@ -310,2 +322,66 @@ } | ||
/** | ||
* higher order component that injects stores to a child. | ||
* takes either a varargs list of strings, which are stores read from the context, | ||
* or a function that manually maps the available stores from the context to props: | ||
* storesToProps(mobxStores, props, context) => newProps | ||
*/ | ||
function inject(/* fn(stores, nextProps) or ...storeNames */) { | ||
var grabStoresFn; | ||
if (typeof arguments[0] === "function") { | ||
grabStoresFn = arguments[0]; | ||
} else { | ||
var storesNames = []; | ||
for (var i = 0; i < arguments.length; i++) | ||
storesNames[i] = arguments[i]; | ||
grabStoresFn = grabStoresByName(storesNames); | ||
} | ||
return function(componentClass) { | ||
return createStoreInjector(grabStoresFn, componentClass); | ||
}; | ||
} | ||
function grabStoresByName(storeNames) { | ||
return function(baseStores, nextProps) { | ||
storeNames.forEach(function(storeName) { | ||
if (storeName in nextProps) // prefer props over stores | ||
return; | ||
if (!(storeName in baseStores)) | ||
throw new Error("MobX observer: Store '" + storeName + "' is not available! Make sure it is provided by some Provider"); | ||
nextProps[storeName] = baseStores[storeName]; | ||
}); | ||
return nextProps; | ||
} | ||
} | ||
/** | ||
* PropTypes | ||
*/ | ||
function observableTypeChecker (type) { | ||
return function(props, propName, componentName) { | ||
if (!mobx['isObservable' + type](props[propName])) { | ||
return new Error( | ||
'Invalid prop `' + propName + '` supplied to' + | ||
' `' + componentName + '`. Expected a mobx observable ' + type + '. Validation failed.' | ||
); | ||
} | ||
}; | ||
} | ||
// oneOfType is used for simple isRequired chaining | ||
var propTypes = { | ||
observableArray: React.PropTypes.oneOfType([observableTypeChecker('Array')]), | ||
observableMap: React.PropTypes.oneOfType([observableTypeChecker('Map')]), | ||
observableObject: React.PropTypes.oneOfType([observableTypeChecker('Object')]), | ||
arrayOrObservableArray: React.PropTypes.oneOfType([ | ||
React.PropTypes.array, | ||
observableTypeChecker('Array') | ||
]), | ||
objectOrObservableObject: React.PropTypes.oneOfType([ | ||
React.PropTypes.object, | ||
observableTypeChecker('Object') | ||
]) | ||
}; | ||
/** | ||
* Export | ||
@@ -316,2 +392,4 @@ */ | ||
Provider: Provider, | ||
inject: inject, | ||
propTypes: propTypes, | ||
reactiveComponent: function() { | ||
@@ -318,0 +396,0 @@ console.warn("[mobx-react] `reactiveComponent` has been renamed to `observer` and will be removed in 1.1."); |
{ | ||
"name": "mobx-react", | ||
"version": "3.4.0", | ||
"version": "3.5.0-fix85", | ||
"description": "React bindings for MobX. Create fully reactive components.", | ||
@@ -32,8 +32,8 @@ "main": "index.js", | ||
"mobx": "^2.2.0", | ||
"react": "^15.1.0", | ||
"react-addons-test-utils": "^15.1.0", | ||
"react-dom": "^15.1.0", | ||
"react": "^15.2.0", | ||
"react-addons-test-utils": "^15.2.0", | ||
"react-dom": "^15.2.0", | ||
"tape": "^4.2.2", | ||
"tape-run": "2.1.0", | ||
"typescript": "^1.7.5" | ||
"typescript": "^1.8.10" | ||
}, | ||
@@ -40,0 +40,0 @@ "dependencies": {}, |
@@ -70,2 +70,5 @@ # mobx-react | ||
It is possible to set a custom `shouldComponentUpdate`, but in general this should be avoid as MobX will by default provide a highly optimized `shouldComponentUpdate` implementation, based on `PureRenderMixin`. | ||
If a custom `shouldComponentUpdate` is provided, it is consulted when the props changes (because the parent passes new props) or the state changes (as a result of calling `setState`), but if an observable used by the rendering is changed, the component will be re-rendered and `shouldComponent` is not consulted. | ||
### `componentWillReact` (lifecycle hook) | ||
@@ -95,4 +98,15 @@ | ||
### `Provider` (Experimental) | ||
### `propTypes` | ||
MobX-react provides the following additional `propTypes` which can be used to validate against MobX structures: | ||
* `observableArray` | ||
* `observableMap` | ||
* `observableObject` | ||
* `arrayOrObservableArray` | ||
* `objectOrObservableObject` | ||
### `Provider` and `inject` (Experimental) | ||
_This feature is marked as experimental as the exact api might change in a next minor, pending any community feedback_. | ||
@@ -102,6 +116,9 @@ | ||
This is useful if you have things that you don't want to pass through multiple layers of components explicitly. | ||
By passing a string array as first argument to `observer`, observer will pick up the named stores from the context and make them available as props of the decorated component: | ||
`inject` can be used to pick up those stores. It is a higher order component that takes a list of strings and makes those stores available to the wrapped component. | ||
Example (based on the official [context docs](https://facebook.github.io/react/docs/context.html#passing-info-automatically-through-a-tree)): | ||
```javascript | ||
@observer(["color"]) | ||
@inject("color") @observer | ||
class Button extends React.Component { | ||
@@ -128,6 +145,2 @@ render() { | ||
class MessageList extends React.Component { | ||
getChildContext() { | ||
return {color: "purple"}; | ||
} | ||
render() { | ||
@@ -146,6 +159,35 @@ const children = this.props.messages.map((message) => | ||
Some note about passing stores around: | ||
Notes: | ||
* If a component ask a store and receives a store via a property with the same name, the property takes precedence. Use this to your advantage when testing! | ||
* Values provided through `Provider` should be final, to avoid issues like mentioned in [React #2517](https://github.com/facebook/react/issues/2517) and [React #3973](https://github.com/facebook/react/pull/3973), where optimizations might stop the propagation of new context. Instead, make sure that if you put things in `context` that might change over time, that they are `@observable` or provide some other means to listen to changes, like callbacks. | ||
* When using both `@inject` and `@observer`, make sure to apply them in the correct order: `observer` should be the inner decorator, `inject` the outher. There might be additional decorators in between. | ||
* The original component wrapped by `inject` is available as the `wrappedComponent` property of created the higher order component. | ||
#### Inject as function | ||
The above example in ES5 would start like: | ||
```javascript | ||
var Button = inject("color")(observer(React.createClass({ | ||
/* ... etc ... */ | ||
}))) | ||
``` | ||
#### Strongly typing inject | ||
`inject` also accepts a function (`(allStores, nextProps, nextContext) => nextProps`) that can be used to pick all the desired stores from the available stores like this: | ||
```typescript | ||
import {IUserStore} from "myStore" | ||
@inject((allStores) => ({ | ||
userStore: allStores.userStore as IUserStore | ||
})) | ||
class MyComponent extends React.Component<{ userStore?: IUserStore; otherProp: number }, {}> { | ||
/* etc */ | ||
} | ||
``` | ||
Make sure to mark `userStore` as optional property. It should not (necessarily) be passed in by parent components after all! | ||
## FAQ | ||
@@ -166,3 +208,9 @@ | ||
`Warning: forceUpdate(...): Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.` | ||
``` | ||
Warning: forceUpdate(...): Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.` | ||
``` | ||
-- or -- | ||
``` | ||
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`. | ||
``` | ||
@@ -169,0 +217,0 @@ Usually this means that (another) component is trying to modify observables used by this components in their `constructor` or `getInitialState` methods. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
75411
1254
250
1