Closed flipjs closed 8 years ago
In general there are three ways in which you can pass stores in MobX
Your current solution is a combination of 2.
and 3.
, like mobx-connect and redux provider
you use a HoC, but it grabs the stores from the the global state instead of the context. So the principle is fine, but if you would use context in your HoC to grab the stores from, you will discover that your components are easier to test. I am considering to provide a standardized Provider
in the mobx-react
package as well that uses context to pass stores (or anything you like) around.
I agree Provider
is the better solution. I'll create HoC to use React's Context
to pass the store to the components. A standardized Provider
option built-in to mobx-react
would be great. Thanks @mweststrate !
Or use a DI solution like InversifyJS as we use in https://github.com/Mercateo/dwatch. It is currently for us the best way which works with any framework and is useful for mocks in unit tests.
@flipjs this is a good example of what you're setting out to accomplish: ContextProvider & connect function
@evankline For my use case, a simple decorator is enough to inject the whole store (or a subset of it) to the component. MobX is not redux where injecting the whole store has perfomance implications where it will re-render every time a part of store has changed. Thats why redux uses mapStateToProps to inject only what the component needs.
Import stores in the components directly and just use them :) It's the MVP of passing stores around, but stand alone testing of components becomes tricky quickly as you have to make sure your global stores are in the right state first
how to konw the store had changed? I think Explicitly via props
is not the good choice. And React's context
is not a stable API, so I think that is not a perfect solution.
Note: Context is an advanced and experimental feature. The API is likely to change in future releases. Most applications will never need to use context. Especially if you are just getting started with React, you likely do not want to use context. Using context will make your code harder to understand because it makes the data flow less clear. It is similar to using global variables to pass state through your application. If you have to use context, use it sparingly. Regardless of whether you're building an application or a library, try to isolate your use of context to a small area and avoid using the context API directly when possible so that it's easier to upgrade when the API changes.
@stephenkingsley although context is indeed an experimental feature, I wouldn't be afraid to use. Many core libraries in react land, like router and redux, depend on it, so I cannot imagine that it would be removed without offering a clear alternative.
With MobX you don't need to know that the store has changed; any stuff from the store that is used in the render will be reacted to. If all data is observable, you simply cannot under (or over) subscribe. For the same reason there is no performance penalty if you pass stores which are not actually used.
any stuff from the store that is used in the render will be reacted to
If data in store had changed, the component that is used @observable
will render again automatically? If it true, That is awesome!
Eh yeah..., doing that is basically the whole point of MobX :)
See pull request #65 , build 3.4.0-beta.1
can be used to test and give feedback on the new Provider mechanism. Docs: https://github.com/mobxjs/mobx-react/blob/provider/README.md#provider-experimental
@mweststrate that just the same i create last night. I also create a provide and connect
Provider
/ context support is now released as mobx-react@3.4.0
Cool stuff! Will try it out. Thanks @mweststrate !
@mweststrate how do you unit-test a component that observes usinh this mechanism? It throws an error since it expects one of its ancestors to be wrapped with 'provider', but I'm rendering only it directly in the test
@liady wrapping with provider is not required if all requested stores are passed in as normal props to the injected component.
@mweststrate sorry, I was unclear in my question.. Hope I can clarify. I do have a Provider wrapping my App component, and deep inside this App I have a Button component that gets the store via the decorator - @observes(['uiStore']). When I'm unit testing only this Button, I'm only rendering it in the test (as a standalone component). However, it fails since it expects a store to be passed from above. How can I override it when rendering only the Button component?
@liady manually providing a prop uiStore
should supress that error, so something like:
React.createElement(Button, { uiStore : getSomeUiStore(), ...other props })
or <Button uiStore={getSomeUiStore()} ... />
. In that case @observer["uiStore"]
should grab the uiStore from the props instead of looking for the store that is provided by a Provider
@liady most test examples i met manually pass store as props. It will ensure that component works properly without any concern about his environment
Can some one link any example of using Provider? i got strange error while implementing onlyChild must be passed a children with exactly one child.
Pass only one component into it. Wrap with a div for example if needed
Op zo 28 aug. 2016 00:10 schreef Pavel Poberezhnyi <notifications@github.com
:
Can some one link any example of using Provider? i got strange error while implementing onlyChild must be passed a children with exactly one child.
— You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx/issues/300#issuecomment-242943675, or mute the thread https://github.com/notifications/unsubscribe-auth/ABvGhM5FB0t04uivJi676O5BFlsWfj68ks5qkLXmgaJpZM4ItTgg .
thanks for such rapid solution! i'm very happy with this approach
I'm using React Router, which supports supplying a custom createElement
function.
I also have a rootStore
which, as the name implies, is the root of my state.
class TodoStore {
@observable todos = []
constructor({ root }) {
this.root = root
}
@computed todosForUser() {
// Reference state from another store.
const user = this.root.sessionStore.currentUser
return this.todos.filter(todo => todo.userId === user.id)
}
}
class SessionStore {
@observable currentUser
constructor({ root }) {
this.root = root
}
}
class RootStore {
constructor() {
this.todoStore = new TodoStore({ root: this })
this.sessionStore = new SessionStore({ root: this })
}
}
In React Router:
// Singleton, same instance used for the lifetime of the app
const rootStore = new RootStore()
const createElement = (Element, props) => <Element rootStore={rootStore} {...props})
const AppRouter = () => (
<Router createElement={createElement}>
...
</Router>
)
Now my root store is passed into my screen components as props.
You could also use context to pass the store to deeply nested components.
I seem remember there is a connect
and provide
function in mobX
? and you can create another one, cause I think is not difficult.
@KaySackey @stepenhingsley yep see inject
/ Provider
in the mobx-react package
@mweststrate As per your comment on Jun3rd three approaches for using Stores .Can you suggest the best practice ? and Share few examples how to use them in that approach .
Hi @mweststrate
Just finally getting around to experimenting with mobX in a project. Similar to @Kamaraju333's request, I think that making note of practices regarding store importing / passing stores around in React in the docs would be pretty cool :) I might have missed something when reading through the docs, but I also have found myself a little anxious wondering if I'm "over-importing" the store, causing some inefficient code.
Either way I thought I'd let you know that I found this comment you made helpful:
With MobX you don't need to know that the store has changed; any stuff from the store that is used in the render will be reacted to. If all data is observable, you simply cannot under (or over) subscribe. For the same reason there is no performance penalty if you pass stores which are not actually used.
@mweststrate I too would love to see some guidelines/best practices/examples around how best to import/inject stores in to components similar to @Kamaraju333 and @teesloane.
I think mobx is awesome from what I've seen so far but all examples seem to focus on small applications with a few stores at most. I have a large application with dozens of stores and views and am finding it hard to wrap my head around how to use mobx in almost a 1 to 1 relationship between views and stores.
I found this persons write up useful for people who are using React-Router with Mobx. http://frontendinsights.com/connect-mobx-react-router/
I'm wondering if anyone can weigh in on using the Provider to pass the stores down to components like this? Is there any good reason not to? It seems like it simply takes advantage of Router being the main container component for the app.
I have seen a few references to accessing stores via context using @observer(['storeName'])
as a decorator to a component -- instead of using @inject. Can it truly be done both ways? If so, is there a difference between the two? I agree that some clear documentation of this with style recommendations would be helpful. @mweststrate (thanks for creating/stewarding such a great tool!)
What's the consensus on accessing the store from inside the component? Do you access it directly using import or pass as props?
Importing and accessing directly is very convenient, but how do you test it?
I started a new project a few weeks ago using Redux, and have migrated to using MobX completely. I pass store as props for easier testing. I use a decorator to pass the store:
My store looks like this:
I have
injectStore()
stored somewhere so I just import it when I need to use the store. Do you think this is viable? Will there be implications of passing a big fat store? My application seems working fine with it, but I'm still on the early stage of development.I like the idea of importing the store directly, it just feels normal and straightforward. I'm just not sure how to test it and if its considered an anti-pattern.