How I can use React Hook & Context API with MobX-State-Tree #2034
Replies: 33 comments
-
Here is a basic example of import React, { useContext, forwardRef } from 'react';
const MSTContext = React.createContext(null);
// eslint-disable-next-line prefer-destructuring
export const Provider = MSTContext.Provider;
export function useMst(mapStateToProps) {
const store = useContext(MSTContext);
if (typeof mapStateToProps !== 'undefined') {
return mapStateToProps(store);
}
return store;
}
const RootStore = types.model({
count: 0,
}).actions(self => ({
inc() {
self.count += 1;
}
}));
const rootStore = RootStore.create({});
// in root component
return (
<Provider value={rootStore}>
<Counter />
</Provider>
);
// somewhere in the app
function Counter() {
const { count, inc } = useMst(store => ({
count: store.count,
inc: store.inc,
}));
return (
<div>
value: {count}
<button onClick={inc}>Inc</button>
</div>
);
} |
Beta Was this translation helpful? Give feedback.
-
Dear terrysahaidak |
Beta Was this translation helpful? Give feedback.
-
thx @terrysahaidak , I guess this should be added to docs. |
Beta Was this translation helpful? Give feedback.
-
Hi I have followed the exact same steps and my store is working i.e data is being updated in the store however it is not getting reflected in my react component. For some strange reason changes to the store is not triggering rerender of my component |
Beta Was this translation helpful? Give feedback.
-
@dayemsiddiqui You should use |
Beta Was this translation helpful? Give feedback.
-
Hey guys, I am using Typescript + React + Hooks with mobx-state-tree. I am also exclusively using functional components in my code and I don't have any class-based component. I tried a lot of things to get my component to listen to state changes but no avail. The observer does not seem to be working with functional components but I figured out a solution to fix this problem that I wanted to share: Basically I have written a custom hook called useObserveStore that listens to store changes using the getSnapshot() method and updates the state accordingly. We can simply use this hook in any component that we want to rerender based on store changes. Here is the code:
Then in my component I simply use:
I hope people might find it useful. Do let me know if there are any potential problems in my code |
Beta Was this translation helpful? Give feedback.
-
Hi @dayemsiddiqui, you have to use |
Beta Was this translation helpful? Give feedback.
-
@dayemsiddiqui thanks for providing that workaround! @terrysahaidak @Romanior Could either of you provide an example of how to use My specific use case is having a rootStore that uses a useContext.Provider at the top level App. I would like a functional component to ask the rootStore for a piece of data. If the rootStore has it, then it delivers that data, which renders, otherwise it performs an async (flow) api call to get the data. I would like when that data comes back, the store updates itself. When the store updates itself it should cause a rerender of the component that initially asked for the data. I feel like this should be a very common pattern. But I can not figure out how to make it work. So something like
export const RootStore = types
.model({
dataMap: types.map(MyData),
})
.actions(self => ({
loadData(date) {
self.dataMap[date] = MyData.create({date: date, state: "init"});
self.dataMap[date].loadDay(date)
},
}))
.views(self => ({
getData(date) {
if (!(date in self.dataMap)) {
self.loadData(date)
}
return self.dataMap[date]
}
}));
const MyData = types
.model({
date: types.optional(types.string, ""),
// the data here is irrelevant
data: types.optional(types.integer, 0),
state: types.enumeration("State", ["init", "pending", "done", "error"])
})
.actions(self => ({
// noticed that we cannot load data in afterCreate as it does not change snapshot
//afterCreate(){
//self.loadDay(self.date)
//},
loadDay: flow(function* loadDay(curDate) {
self.date = curDate;
self.state = "pending";
try {
// ... yield can be used in async/await style
const ret = yield callApi(...) // any async function here returning the data
self.data = ret
self.state = "done"
} catch (error) {
// ... including try/catch error handling
console.error("Failed to fetch composition date", error)
self.state = "error"
}
})
}));
const GlobalStoreContext = React.createContext(null);
function App() {
return (
<GlobalStoreContext.Provider value={globalStore}>
<MyGreatApp/>
</GlobalStoreContext.Provider>
);
}
export default App;
function SomeComponent(props) {
const rootstore = useContext(GlobalStoreContext)
const wanteddata = rootstore.getData('2019-10-24')
return(
<div>
{wanteddata.state}
</div>
)
} |
Beta Was this translation helpful? Give feedback.
-
@ssolari just wrap it with observer: const SomeComponent = observer((props) => {
const rootstore = useContext(GlobalStoreContext)
const wanteddata = rootstore.getData('2019-10-24')
return(
<div>
{wanteddata.state}
</div>
)
}); You can check out a working example here: |
Beta Was this translation helpful? Give feedback.
-
@terrysahaidak the above code example is with a mobx store not with mobx-state-tree store. I checked the codesandbox link. It seems to be working with mobx-store but I can't get it to work with mobx-state-tree store like @ssolari has explained |
Beta Was this translation helpful? Give feedback.
-
@dayemsiddiqui There is no difference between mobx store and mobx-state-tree store since mobx-state-tree is an abstraction over mobx. |
Beta Was this translation helpful? Give feedback.
-
@terrysahaidak, @dayemsiddiqui is correct. Also, the point of the example is when a mobx-state-tree store updates itself in the background. When a button is added to the component I think the functional component is rerendered anyway. I can make this work because somehow the button click forces the rerender. But wrapping the component in observer as you stated does not work. |
Beta Was this translation helpful? Give feedback.
-
@ssolari could you please provide minimum reproducing codesandbox so I can debug it? |
Beta Was this translation helpful? Give feedback.
-
@terrysahaidak yes, let me try to mock up a minimal example. |
Beta Was this translation helpful? Give feedback.
-
@terrysahaidak thank you for your patience and leading me to figure the problem out. As you stated, In summary (referring to the below example), the problem was that I wrapped For a reference, could you maybe explain why this is the case? all three files in same directory. import React from 'react';
import ReactDOM from 'react-dom';
import {MyGreatApp} from "./myGreatApp"
import {globalStore, GlobalStoreContext} from "./rootStore";
function App() {
return (
<GlobalStoreContext.Provider value={globalStore}>
<MyGreatApp/>
</GlobalStoreContext.Provider>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
import React, {useContext} from 'react';
import {observer} from "mobx-react-lite";
import {GlobalStoreContext} from "./rootStore";
const SubComponent = observer((props) => {
return (
<div><h2>State to update: {props.dt.state}</h2></div>
)
});
export function MyGreatApp (props) {
const gstore = useContext(GlobalStoreContext);
const dt = gstore.getData('2019-10-24');
return (
<SubComponent dt={dt}></SubComponent>
)
};
import React from "react";
import {types, flow} from "mobx-state-tree";
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
};
const MyData = types
.model({
date: types.optional(types.string, ""),
// the data here is irrelevant
data: types.optional(types.integer, 0),
state: types.enumeration("State", ["init", "pending", "done", "error"])
})
.actions(self => ({
// noticed that we cannot load data in afterCreate as it does not change snapshot
//afterCreate(){
//self.loadDay(self.date)
//},
loadDay: flow(function* loadDay(curDate) {
self.date = curDate;
self.state = "pending";
try {
// mocking an async call that has a 2 second delay. The state should change after that delay.
yield sleep(2000)
self.state = "done"
} catch (error) {
console.error("Failed to fetch composition date", error)
self.state = "error"
}
})
}));
const RootStore = types
.model({
dataMap: types.map(MyData),
})
.actions(self => ({
loadData(date) {
self.dataMap[date] = MyData.create({date: date, state: "init"});
self.dataMap[date].loadDay(date)
},
}))
.views(self => ({
getData(date) {
if (!(date in self.dataMap)) {
self.loadData(date)
}
return self.dataMap[date]
}
}));
export let globalStore = RootStore.create({dataMap: {}});
export const GlobalStoreContext = React.createContext(null); |
Beta Was this translation helpful? Give feedback.
-
I don't have time to do a PR, but if it would help someone else, I'm happy if any of the code above ends up in examples in docs (no credit needed). It seems that the future of React is functional components with hooks and there are not quite as many examples to go off of yet. |
Beta Was this translation helpful? Give feedback.
-
Decided to give mst a go today and experimented before finding this thread. I've noticed that my experiment worked without using a Context Provider. Is this even required since we are using I've linked a codesandbox for feedback below: |
Beta Was this translation helpful? Give feedback.
-
For observer to work, it doesn't matter how the component got a hold on the
store, props, state, context, from some global closure, singleton. It
doesn't matter at all. Context is just one way (and a very elegant one
which I recommend), to give the component that pointer to the store. In
other words, context is the dependency injection mechanism, observer +
observable is the change tracking mechanism.
…On Wed, Oct 30, 2019 at 10:57 AM impulse ***@***.***> wrote:
Decided to give mst a go today and experimented before finding this thread.
I've noticed that my experiment worked without using a Context Provider.
Is this even required since we are using observer or will I run into any
issues by doing this?
I've linked a codesandbox for feedback below:
[image: Edit mst-hooks-typescript]
<https://codesandbox.io/s/mst-hooks-typescript-w6vlb?fontsize=14>
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#1363?email_source=notifications&email_token=AAN4NBDZCS3AVATZJUX4CFLQRFSBJA5CNFSM4IKYT4BKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECTXJ2Q#issuecomment-547845354>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBGATDXMN6JFEJVXKNDQRFSBJANCNFSM4IKYT4BA>
.
|
Beta Was this translation helpful? Give feedback.
-
Thanks for the explanation @mweststrate. I've created a template project with React Hooks + MST + TypeScript: Demo: https://react-hooks-mobx-state-tree.vercel.app |
Beta Was this translation helpful? Give feedback.
-
Looking good! I'll link it in the next version of the docs
…On Thu, Oct 31, 2019 at 4:28 PM impulse ***@***.***> wrote:
Thanks for the explanation @mweststrate <https://github.com/mweststrate>.
I've created a template project with React Hooks + MST + TypeScript:
Demo: https://react-hooks-mobx-state-tree.netlify.com/
GitHub: https://github.com/impulse/react-hooks-mobx-state-tree
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1363?email_source=notifications&email_token=AAN4NBGAAP6UGWINAC6R5XTQRMBUDA5CNFSM4IKYT4BKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECYNAJY#issuecomment-548458535>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBD2YFH3J2VQ63OBIHDQRMBUDANCNFSM4IKYT4BA>
.
|
Beta Was this translation helpful? Give feedback.
-
And about
Quite simple component
|
Beta Was this translation helpful? Give feedback.
-
@Ridermansb please open a new issue, and create a minimal reproduction. But superficially, this looks totally unrelated to MST / mobx / React. Probably best checks the props you are passing in. |
Beta Was this translation helpful? Give feedback.
-
For anyone dropping in on this, thought I'd mention a npm package I made to help with using mobx-state-tree along with React Hooks. Figured others might be able to use it, or that it might help you with figuring out how to handle this in your own apps. |
Beta Was this translation helpful? Give feedback.
-
What is the benefit of using React Context with MST? |
Beta Was this translation helpful? Give feedback.
-
@cgradwohl I know you didn't tag me... but to put my $0.02 in... one of the big benefits is it lets you write more testable components. Basically because you can 'inject' a mocked model in place of your real one (via the That said, I think there are other issues with direct context use, and I outlined them here: http://mobx-store-provider.overfoc.us/motivation.html#cant-i-just-use-react-context |
Beta Was this translation helpful? Give feedback.
-
Yeah the most important argument is testing I'd say. With direct imports
you will end up with singletons that need resetting. With context every
rendered react tree can be given it's own instance of the store
…On Wed, Mar 11, 2020 at 4:43 PM Jonathan Newman ***@***.***> wrote:
@cgradwohl <https://github.com/cgradwohl> I know you didn't tag me... but
to put my $0.02 in... one of the big benefits is it lets you write more
testable components.
Basically because you can 'inject' a mocked model in place of your real
one (via the Provider). If you just import an instance your created in
another module then you can't mock that with fake/false data.
That said, I think there are other issues with direct context use, and I
outlined them here:
http://mobx-store-provider.overfoc.us/motivation.html#cant-i-just-use-react-context
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1363 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBG5HAXUUX4ZDEIJQNTRG65RDANCNFSM4IKYT4BA>
.
|
Beta Was this translation helpful? Give feedback.
-
Context can also be very useful for scenarios where you want to provide different instances of the same model type to different parts of your component tree. My use case is that I have an MST + context works wonderfully in this case! I can't think of any other way to do it that would be as simple, elegant, and performant. |
Beta Was this translation helpful? Give feedback.
-
I wonder what difference does it make to define the For example, suppose we define a export const rootStore = RootStore.create({}) ; Later in component#x files located in components directory we import the global variable as: import {rootStore} from "../App" ;
import {rootStore} from "../App" ; |
Beta Was this translation helpful? Give feedback.
-
How important is the null while creating context? |
Beta Was this translation helpful? Give feedback.
-
Hey folks! There's been a lot of great discussion here. I really appreciate everyone chiming in. This issue was marked to be closed a long while back, and has been active for a while as well. I'm going to convert this to a discussion, which will close out the issue, but y'all are very welcome to continue chatting over there. @osmannassar2019 - I would encourage you to mark one of these comments as the "answer" - if there is one. That will help other folks understand the helpful comments to look for when they stumble on the discussion. Thanks all! |
Beta Was this translation helpful? Give feedback.
-
How I can use React Hook & Context API with MobX-State-Tree
I am using React Functional Component. So, I need to use React Hook (useContext()) to get all action from the store.
Please help me
Thanks
Beta Was this translation helpful? Give feedback.
All reactions