diff --git a/CHANGELOG.md b/CHANGELOG.md index 508de030..9fa510d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,62 @@ ### 4.0.0 -* Introduced `useStaticRendering(boolean)` to better support server-side rendering scenerios. See [#140](https://github.com/mobxjs/mobx-react/issues/140) -* Introduced `Observer`. Can be used as alternative to the `observer` decorator. Marks a component region as reactiove. See the Readme / [#138](https://github.com/mobxjs/mobx-react/issues/138) +#### `inject(fn, component)` will now track `fn` as well + +`inject(func)` is now reactive as well, that means that transformation in the selector function will be tracked, see [#111](https://github.com/mobxjs/mobx-react/issues/111) + +```javascript +const NameDisplayer = ({ name }) =>

{name}

+ +const UserNameDisplayer = inject( + stores => ({ + name: stores.userStore.name + }), + NameDisplayer +) + +const user = mobx.observable({ + name: "Noa" +}) + +const App = () => ( + + + +) + +ReactDOM.render(, document.body) +``` + +#### Better support for Server Side Rendering + +Introduced `useStaticRendering(boolean)` to better support server-side rendering scenerios. See [#140](https://github.com/mobxjs/mobx-react/issues/140) + +#### Introduced `Observer` as alternative syntax to the `observer` decorator. + +_This feature is still experimental and might change in the next minor release, or be deprecated_ + +Introduced `Observer`. Can be used as alternative to the `observer` decorator. Marks a component region as reactiove. See the Readme / [#138](https://github.com/mobxjs/mobx-react/issues/138) +Example: + +```javascript +const UserNameDisplayer = ({ user }) => ( + + {() =>
{user.name}
} +
+) +``` + +#### Other improvements + * Clean up data subscriptions if an error is thrown by an `observer` component, see [#134](https://github.com/mobxjs/mobx-react/pull/134) by @andykog * Print warning when `inject` and `observer` are used in the wrong order, see #146, by @delaetthomas * export `PropTypes` as well, fixes #153 * Add react as a peer dependency * Added minified browser build: `index.min.js`, fixes #147 +--- + ### 3.5.8 * Fixed issue where `props` where not passed properly to components in very rare cases. Also fixed #115 diff --git a/README.md b/README.md index 8c884944..20f584a6 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,8 @@ const TodoView = observer(({todo}) =>
{todo.title}
) ### `Observer` +_This feature is still experimental and might change in the next minor release, or be deprecated_ + `Observer` is a React component, which applies `observer` to an unanymous region in your component. It takes as children a single, argumentless function which should return exactly one React component. The rendering in the function will be tracked and automatically be re-rendered when needed. @@ -254,7 +256,40 @@ class MyComponent extends React.Component { } ``` +#### Customizing inject + +Instead of passing a list of store names, it is also possible to create a custom mapper function and pass it to inject. +The mapper function receives all stores as argument, the properties with which the components are invoked and the context, and should produce a new set of properties, +that are mapped into the original: + +`mapperFunction: (allStores, props, context) => additionalProps` + +Since version 4.0 the `mapperFunction` itself is tracked as well, so it is possible to do things like: + +```javascript +const NameDisplayer = ({ name }) =>

{name}

+ +const UserNameDisplayer = inject( + stores => ({ + name: stores.userStore.name + }), + NameDisplayer +) + +const user = mobx.observable({ + name: "Noa" +}) + +const App = () => ( + + + +) + +ReactDOM.render(, document.body) +``` +_N.B. note that in this *specific* case neither `NameDisplayer` or `UserNameDisplayer` doesn't need to be decorated with `observer`, since the observable dereferencing is done in the mapper function_ #### Strongly typing inject diff --git a/package.json b/package.json index a566445a..8f10e640 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mobx-react", - "version": "3.5.9", + "version": "4.0.0-beta.2", "description": "React bindings for MobX. Create fully reactive components.", "main": "index.js", "typings": "index", @@ -56,4 +56,4 @@ "reactjs", "reactive" ] -} \ No newline at end of file +} diff --git a/src/inject.js b/src/inject.js index fc0bc203..2eac2319 100644 --- a/src/inject.js +++ b/src/inject.js @@ -1,5 +1,6 @@ import React, { PropTypes } from 'react'; import hoistStatics from 'hoist-non-react-statics'; +import {observer} from './observer'; /** * Store Injection @@ -22,7 +23,6 @@ function createStoreInjector(grabStoresFn, component) { return React.createElement(component, newProps); } - // TODO: should have shouldComponentUpdate? }); Injector.isInjector = true; @@ -77,14 +77,19 @@ export default function inject(/* fn(stores, nextProps) or ...storeNames */) { let grabStoresFn; if (typeof arguments[0] === "function") { grabStoresFn = arguments[0]; + return function(componentClass) { + // mark the Injector as observer, to make it react to expressions in `grabStoresFn`, + // see #111 + return observer(createStoreInjector(grabStoresFn, componentClass)); + }; } else { const storesNames = []; for (let i = 0; i < arguments.length; i++) storesNames[i] = arguments[i]; grabStoresFn = grabStoresByName(storesNames); + return function(componentClass) { + return createStoreInjector(grabStoresFn, componentClass); + }; } - return function(componentClass) { - return createStoreInjector(grabStoresFn, componentClass); - }; } diff --git a/test/inject.js b/test/inject.js index 05692b64..b5dd85e1 100644 --- a/test/inject.js +++ b/test/inject.js @@ -327,5 +327,21 @@ test('inject based context', t => { t.end(); }) + test('using a custom injector is reactive', t => { + const user = mobx.observable({ name: "Noa"}) + const mapper = (stores) => ({ name: stores.user.name }) + + const DisplayName = (props) => e("h1", {}, props.name) + const User = inject(mapper)(DisplayName) + const App = () => e(Provider, { user: user }, e(User, {})) + + const wrapper = mount(e(App)) + t.equal(wrapper.find("h1").text(), "Noa") + + user.name = "Veria" + t.equal(wrapper.find("h1").text(), "Veria") + t.end() + }) + t.end() }) \ No newline at end of file