How to communicate between Mobx stores created with React.createContext - reactjs

I have my useStores.ts file which has two Mobx stores initialized in the following way.
import StoreA from "./stores/StoreA";
import StoreB from "./stores/StoreB";
const storesContext = createContext({
storeA: new StoreA(),
storeB: new StoreB(),
});
export const useStores = () => useContext(storesContext);
So I can use these stores inside any components in the following way.
const { storeA } = useStores();
But I'm not sure how to access storeA inside storeB.
Can someone please help me with the same?

You should create a root store that contains all other stores, and it passes itself to the child stores, so they can "go up" to the root store, and then to any other store.
class RootStore {
storeA = new StoreA(this)
storeB = new StoreB(this)
}
class StoreA{
constructor(rootStore){
this.rootStore = rootStore
// get to store B
// this.rootStore.storeB
}
}
Use with createContext
const storesContext = createContext(new RootStore());
Or even better, follow the dependency injection principles and pass child stores to root store in the RootStore constructor.
class RootStore {
constructor(a,b){
this.storeA = a
this.storeB = b
this.storeA.setRoot(this)
this.storeB.setRoot(this)
}
const storesContext = createContext(new RootStore(new StoreA(),new StoreB());

Related

Unit test case when Mobx + RootStore + useContext hook

When mobx store is used from useContext() hook what will be best approach to write unit test case? Should we use enzyme or react testing library? Here Provider or #inject() is not used
Store design:
//StoreA
export class StoreA {
rootStore: RootStore;
constructor(rootStore: RootStore) {
this.rootStore = rootStore;
}
//StoreA implementations...
}
//StoreB
export class StoreB {
rootStore: RootStore;
constructor(rootStore: RootStore) {
this.rootStore = rootStore;
}
//StoreB implementations...
}
//RootStore
export class RootStore {
StoreA: StoreA;
StoreB: StoreB;
constructor() {
this.storeA = new storeA(this);
this.storeB = new storeB(this);
}
}
export const RootStoreContext = createContext(new RootStore());
In component store is used from useContext hook
const SomeComponent = ({}) => {
const rootStore = useContext(RootStoreContext);
const { storeAdata, storeAmethods } = rootStore.storeA;
//Component code....
}
Stackblitz Link:
https://stackblitz.com/edit/react-ts-qec5vu?file=Hello.tsx
In below scenario,
How to write Unit test case only for individual stores (its having circular dependency)
If react testing library used, how to mock stores and useContext?
Is it possible to use enzyme?
Which lib's are suitable for writing UTC?
If you are just unit testing the stores, don't use components. Instantiate the stores directly and test them.
If you are testing component interaction (integration) then I would suggest you use the react testing library. Create the context provider and wrap you components with the provider for each test.
more info: https://testing-library.com/docs/example-react-context

Export a property from child components to the parent component using HOC

I am looking to pass a schema from child components and combine the schema and use it at the main
App(parent level). Its basically yup schema which is at component level which needs to be merged
from components and used in the main App.
Component1
const schema = Yup.object().shape({Name:Yup.string().required()})
const comp1 = () => {
}
Component2
const schema = Yup.object().shape({Age:Yup.string().required()})
const comp2 = () => {
}
<App validationSchmea={mergedSchemas}/>
Inside the App the components live.
Hoc will take in the Schemas and concat it and export it to the main App (parent)
What would be the best way to export the schema using a HoC and then on the main app use the HoC to
get all schemas from all components.
The Logic to Merge Schemas :
import { emailSchema } from '../components/Email'
import { dateSchema } from '../components/DateofBirth'
import { nameSchema } from '../components/Name'
import { taxIdSchema } from '../components/TaxId'
import { phoneSchema } from '../components/Phone'
import { maritalSchema } from '../components/MartialStatus'
import { citizenSchema } from '../components/CitizenStatus'
import { addressSchema } from '../components/Address'
const mergeSchemas = (...schemas) => {
const [first, ...rest] = schemas;
const merged = rest.reduce(
(mergedSchemas, schema) => mergedSchemas.concat(schema),
first
);
return merged;
}
const mergedSchemas = mergeSchemas(emailSchema, dateSchema, nameSchema,
taxIdSchema, phoneSchema, maritalSchema, citizenSchema, addressSchema)
export default mergedSchemas
I am looking best practices to use the Hoc from the component level to the top level.Any suggestions ?

Correct way of Creating multiple stores with mobx and injecting it into to a Component - ReactJs

As suggested here in the Mobx documentation I have created multiple stores in the following manner:
class bankAccountStore {
constructor(rootStore){
this.rootStore = rootStore;
}
...
class authStore {
constructor(rootStore){
this.rootStore = rootStore;
}
...
And finally creating a root store in the following manner. Also I prefer to construct children stores within master's store constructor. Moreover, I found that sometimes my child store has to observe some data from parent store, so I pass this into child constructors
class RootStore {
constructor() {
this.bankAccountStore = new bankAccountStore(this);
this.authStore = new authStore(this);
}
}
Providing to the App in following manner:
<Provider rootStore={new RootStore()}>
<App />
</Provider>
And injecting to the component in like this:
#inject('rootStore')
#observer
class User extends React.Component{
constructor(props) {
super(props);
//Accessing the individual store with the help of root store
this.authStore = this.props.rootStore.authStore;
}
}
Question 1: Is it this the correct and most efficient way to inject the root store into the component every time even if it needs a part of the root store?
Question 2: If not, what is the best way to inject the auth store into the user component?
EDIT: I have made an answer concluding the github discussion. Link of the discussion provided in the answer
This answer may be opinionated but it may help the community indirectly. After a lot of research, I saw below approaches used in practice by many. General methods Have a root store that can act as a communication channel between stores.
Question 1: What's the best way to organise stores and inject them into the component?
Approach 1:
App.js
// Root Store Declaration
class RootStore {
constructor() {
this.userStore = new UserStore(this);
this.authStore = new AuthStore(this);
}
}
const rootStore = new RootStore()
// Provide the store to the children
<Provider
rootStore={rootStore}
userStore={rootStore.userStore}
authStore={rootStore.authStore}
>
<App />
</Provider>
Component.js
// Injecting into the component and using it as shown below
#inject('authStore', 'userStore')
#observer
class User extends React.Component {
// only this.props.userStore.userVariable
}
Approach 2:
App.js
class RootStore {
constructor() {
this.userStore = new UserStore(this);
this.authStore = new AuthStore(this);
}
}
const rootStore = new RootStore()
<Provider rootStore={rootStore}>
<App />
</Provider>
Component.js
// Injecting into the component and using it as shown below
#inject(stores => ({
userStore: stores.userStore,
authStore: stores.authStore,
})
)
#observer
class User extends React.Component {
// no this.props.rootStore.userStore,userVariable here,
// only this.props.userStore.userVariable
}
Approach 1 and Approach 2 doesn't make any difference other than syntax difference. Okay! that is the injection part!
Question 2: What's the best way to communicate between stores? (Try to avoid it)
Now I know a good design keeps stores independent and
less coupled. But somehow consider a scenario where I want the
variable in UserStore to change if a certain variable in AuthStore
is changed. Use Computed. This approach is common for both the above approaches
AuthStore.js
export class AuthStore {
constructor(rootStore) {
this.rootStore = rootStore
#computed get dependentVariable() {
return this.rootStore.userStore.changeableUserVariable;
}
}
}
I hope this helps the community. For more detailed discussion you can
refer to the issue raised by me on
Github
I would recommend you to have multiple stores, to avoid chaining of stores. As we do in our application:
class RootStore {
#observable somePropUsedInOtherStores = 'hello';
}
class AuthStore {
#observeble user = 'Viktor' ;
constructor(rootStore) {
this.rootStore = rootStore;
}
// this will reevaluate based on this.rootStore.somePropUsedInOtherStores cahnge
#computed get greeting() {
return `${this.rootStore.somePropUsedInOtherStores} ${this.user}`
}
}
const rootStore = new RootStore();
const stores = {
rootStore,
bankAccountStore: new BankAccountStore(rootStore),
authStore = new AuthStore(rootStore)
}
<Provider {...stores}>
<App />
</Provider>
In such a manner you can access exactly the store you need, as mostly one store covers one domain instance. Still, both sub-stores are able to communicate to rootStore. Set its properties or call methods on it.
If you do not need a cross store communication - you may not need a rootStore at all. Remove it and don't pass to other stores. Just keep 2 siblings stores
Answering your question on injecting not a whole store, you may benefit from mapperFunction (like mapStateToProps in redux) docs here
#inject(stores => ({
someProp: stores.rootStore.someProp
})
)
#observer
class User extends React.Component {
// no props.rootStore here, only props.someProp
}
Initializing your RootStore directly in api.js file before passing it to Provider is sometimes not what you want.
This can make injecting the instance of main store class harder into other js files:
Example 1:
app.js - Creates new instance before passing it to Provider:
//Root Store Declaration
class RootStore {
constructor() {
...
}
}
const rootStore = new RootStore()
// Provide the store to the children
<Provider rootStore={rootStore}>
<App />
</Provider>
Example 2:
RootStore.js - creates new instance directly in RootStore class:
// Root Store Declaration
class RootStore {
constructor() {
...
}
}
export default new RootStore();
Example 1 compared to Example 2, makes harder to access/inject the store in another part of the application, like in Api.js described below.
Api.js file represents axios wrapper (in my case it handles global loading indicator):
import rootStore from '../stores/RootStore'; //right RootStore is simply imported
const axios = require('axios');
const instance = axios.create({
...
});
// Loading indicator
instance.interceptors.request.use(
(request) => {
rootStore.loadingRequests++;
return request;
},
(error) => {
rootStore.loadingRequests--;
return Promise.reject(error);
}
)
And using React Hooks, you can inject the store that way:
import { observer, inject } from "mobx-react";
const YourComponent = ({yourStore}) => {
return (
...
)
}
export default inject('yourStore')(observer(YourComponent));

WiX navigation Access redux store from registered component

I use WiX navigation with redux.
https://wix.github.io/react-native-navigation/#/usage
In app.jsx file i register all my screens with redux store :
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer);
registerScreens(store, Provider);
In registerSceens.js i bind the redux store :
export default (store, Provider) => {
Navigation.registerComponent('aApp.FooComponent', () => FooComponent, store,
Provider);
}
So in FooComponent, how can i easily access to the redux store ?
export default class FooComponent extends Component {
constructor(props) {
super(props);
console.log(store); //i need REDUX store
}
You need to connect your component, then you can get the store from the context, something like this:
class FooComponent extends Component {
static contextTypes = {
store: object, // <--- allow this component to access the store
};
componentWillMount(props) {
const { store } = this.context; // <-- Get it!!
console.log(store); //i need REDUX store
}
}
export default connect()(FooComponent);
Once you have defined the contextTypes, you can access the store from this.context.
While you can do this... the question is... why would you do something like that? connect already allows you to access the state and bind actions, that's usually all you need. As a rule of thumb, is always a good idea to keep our components as dumb as possible.
You need connect your FooComponent to redux using react-redux. After that you can access anything via props.

Redux state value globally

I am having a helper class which provies info about the logged in user. I would like to have it as a static method and use it in several places ( which are not react components)
How to get access the redux store value in a class model ?
If you can manage this in your application it would be cleaner (and more testable) to pass it around with dependency injection (DI) as opposed to using a global variable, static or singleton. In a simple form you could just pass it in the constructor like this:
var store = createStore(...);
var app = new AppClass(store);
If you're not using a bundler like Webpack, Lukas Katayama's answer should work window.store = createStore(...) but if you are using one, you can also expose your redux store by exporting it and importing where you need it.
//store.js
export const store = createStore(...);
//other file
import { store } from './store.js';
const someFuncOrClass = (...) => {
const user = store.getState().userInfo;
}
One way could be set a global variable
window.store = createStore(...)
and use it, accessing window.store
Looks like a straight forward implementation. You can create a getter for store in the same file where you creatStore(...).
store.js
import { createStore } from 'redux';
let store = {};
export default function store(...) {
/*
* things to do before creating store
*/
store = createStore(...);
return store;
}
export function getStore() {
return store;
}
helper.js
import { getStore } from './store';
export function getUserInfo() {
const store = getStore();
const globalState = store.getState();
return globalState.userInfo;
}

Resources