This one is piece of cake in React. If you want your MobX store to be available in any React component, you just use mobx-react #inject component. Something like:
import React from 'react';
import {inject} from 'mobx-react';
#inject('myStore')
class Dummy extends React.Component {
Then, my store is available as a prop:
this.props.myStore.myMethod();
Nice, convenient... and React only. Maybe I'm missing something, but I can't find a way to access my store from a plain ES6 class. How do I get the same result in a plain ES6 class in pure Vanilla Javascript?
Answer found in MobX GitHub account by adonis-work. Quoting the answer:
Exporting store as a singleton:
// Store.js
import { observable, computed } from 'mobx'
class Store {
#observable numClicks = 0
#computed get oddOrEven() {
return this.numClicks % 2 === 0 ? 'even' : 'odd'
}
}
const store = new Store()
export default store
... enables us to import and use the active store anywhere in the app as many times we want.
// Component.jsx
import store from Store
// use the global store in any component without passing it
// down through the chain of props from the root node
store.numClicks = 4
console.log(store.oddOrEven)
I am using this approach without problems for some time now. Are there any caveats I should look out for?
Link to source: https://github.com/mobxjs/mobx/issues/605
You forgot your code starts with:
import { Store } from "./store";
import { Provider } from "mobx-react";
import * as React from "react";
import { render } from "react-dom";
var store = new Store();
render(
<Provider {...stores}>
<Component />
</Provider>,
document.getElementById('root'),
);
Here you have your store variable and you can use it anywhere
If you'd like it even more convenient turn your store into a Singleton
than you can just import it anywhere like:
import { Store, instance } from "./store";
//in store.ts/js
export Store... =
export const instance = new Store(); // your singleton
how it works
the <Provider/> react component puts the Store in its react Context, read here more: https://reactjs.org/docs/context.html.
this means the store is in every child react component of <Provider/>.
Inject just simply copies this: this.props.store = this.context.mobx.store.
Thus making a singleton and using this singleton in your 'plain' javascript class (no react subclass/component), is the same thing.
Related
I'm totally new to Redux.
My understanding is that redux acts like a react hook, for state, and that it is globally available.
I think I should be able to do this (in one component):
import { useStore } from 'react-redux';
function addToStore() { const [user_name, setUser_name] = useStore("jimmy") };
And then recall the variable (in another component) like this:
import { useStore } from 'react-redux';
function getFromStore() { const id = useStore.user_name }
However, this obviously doesn't work. What am I doing wrong? I've tried reading the documentation, but it is too complicated for me, doesn't deal with just a single variable storage.
Easiest Answer:
Turns out this can be done very simply using window.sessionStorage (Mozilla)
So far I've been able to easily store and retrieve variables across the app, without using Redux.
Best Answer:
#liambgs recommended React Context. Turns out this is quite a bit simpler than Redux, but offers much more out of the box than sessionStorage.
To implement Context:
Requires no new installation
Just import { reactContext } from 'react'
Create new component: class UserContextProvider extends Component {}
Wrap all other components in this new component (in App.js)
Move all user api's into UserContextProvider and store data in local state
render() { return ()} the component with value={{this.state}}
This gave all children components access to the UserContext state.
The following React component is given:
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { store, StoreState } from "../../redux/actions";
import { setBackgroundAction } from "../../redux/title.actions";
import "./Loader.scss";
interface ReduxProps {
bgClass: string;
}
interface Props extends ReduxProps {
bgChange?: boolean;
}
export default function Loader(props: Props) {
const [bgClassOld, setBgClassOld] = useState<string>("");
const dispatch = useDispatch();
useEffect(() => {
const { bgChange, bgClass } = props;
if (bgChange) {
setBgClassOld(bgClass);
dispatch(setBackgroundAction("bg-white"));
dispatch(setBackgroundAction(bgClassOld));
}
});
return (
<div className="d-flex">
<div className="loader">
<img src="/loadscreen.gif" />
</div>
</div>
);
}
// function mapping(state: StoreState): ReduxProps {
// return {
// bgClass: state.title.backgroundClass,
// };
// }
This is more a theoretical question to see how to actually do the following change:
The component Loader will be imported from another npm package (shared components).
My problem is that I have a redux state in the current implementation included (changed it from Class to Functional component, so thats mapping() is still in there).
As I only import the component in my "main" client, I will not have the whole redux setup in place. So I think I need to pass the store and the dispatch functions via props.
So should I create a prop store for my component, where I pass the redux store when I import the shared component?
Do I also create two props for each dispatch functions?
Does is make sense or would there be a better approach?
You generally shouldn't import the Redux store directly into components. The hooks allow your component to access whatever Redux store has been injected into the component tree by a <Provider>.
You also don't need to pass dispatch as a prop. Any component can call useDispatch(), and dispatch actions to whatever Redux store is actually being used.
If I understand your question, you're planning on importing this component into an existing app, and it sounds like that app is already configured to use (React-)Redux with a <Provider> at the top. If that's the case, then you don't have to do anything else special to make this work. Just call the React-Redux hooks in any of your components.
I have some difficulties to understand the new way to use react Context API.
I have an app with a custom class Firebase. Now I want to make a hook to pass it. Before I used HOC (higher-order Component) and context.
My questions
Do I need to use HOC or it's a new way to do this?
Do I need the Context.Provider or it's new Hook?
Do I need to declare default value as a null or I can pass my Object
right from context.js
How can I use a new Hook instead of HOC in mine code?
Here is my code with some comments related to questions
// context.js this is my hoc
// index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
export const withFirebase = Component => (props) => {
// I don't need to wrap it to the FirebaseContext.Consumer
// 1 But do I need this HOC or it's a new way?
const firebase = useContext(FirebaseContext)
return <Component {...props} firebase={firebase} />
}
ReactDOM.render(
// 2 Here I'm lost. Do I need the FirebaseContext.Provider or not?
// 3 Do I need to declare value her or I should do it in context.js as a default?
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
// App.jsx
// 4 Can I use a new Hook instead of HOC here and how?
import { withFirebase } from './components/Firebase/context'
const App = () => {
const firebase = this.props.firebase // But should be useContext(FirebaseContext) or something like this?
return(...)
}
export default withFirebase(App) // I don't need this with the Hook
Any help appreciated.
You should understand it first that, useContext is just to make use of Context and acts like a consumer and not Provider.
To answer your questions
Do I need to use HOC or it's a new way to do this?
You don't need an HOC with hooks. Hooks are meant to replace HOCs and render props pattern.
Do I need the Context.Provider or it's new Hook?
There is no hooks equivalent of Context.Provider. You have to use it as is.
Do I need to declare default value as a null or I can pass my Object
right from context.js
The default value to createContext is only used if you don't pass a value props to the Context.Provider. If you pass it the default value is ignored.
How can I use a new Hook instead of HOC in mine code?
Instead of using useContext in the component returned by HOC use it directly within the component
Sample code
/ context.js this is my hoc
// index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
App.jsx
const App = () => {
const firebase = useContext(FirebaseContext)
return(...)
}
export default App;
Do I need to use HOC or it's a new way to do this?
No, you don't need to use HOC as best technique.
Why?
Starting from React v7.0, you can use functional-based components.
From this version efficient is to use the the latest
technique named HOOKS, which were designed to replace class and
provide another great alternative to compose behavior into your
components.
Do I need the Context.Provider or it's new Hook?
Hook like useContext() has a relation with Context.Provider.
Context is designed to share data that can be considered “global”.
The Provider component accepts a
value prop to be passed. Every Context come with a Provider.
Context.Provider component available on the context instance is used to provide the context to its child components, no matter how deep they are.
Do I need to declare default value as a null or I can pass my Object right from context.js?
No, you don't need necessarily to declare a default value.
Example of defining the context in one corner of the codebase without defaultValue.
const CountStateContext = React.createContext() // <-- define the context without defaultValue
How can I use a new Hook instead of HOC in mine code?
index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
Root Component: App.js, where will be used data comes form context:
const App = () => {
const firebase = useContext(FirebaseContext)
return(...)
}
export default App;
Is something like this bad practice?
SendInfoButton.js
import React from 'react';
import { sendInfo } from '../actions/index';
export const SendInfoButton = ({currentUser}) => (
<div>
<button onClick={() => sendInfo(currentUser)} />
</div>
)
actions/index.js
import { store } from '../reducers/index';
import { SEND_INFO } from '../constants/index;
export const sendInfo = (currentUser) => store.dispatch({type: SEND_INFO, payload: currentUser})
It seems more efficient to import actions directly into the components this way, as opposed to using mapDispatchToProps and passing down actions to components that won't use them. I'm also more inclined to import actions like this because I already have components with a large number of props and would rather not add to that.
Importing the action creator, like import { sendInfo } from '../actions/index';, is fine - that's how you're supposed to do it.
However, you should then use connect to "bind" the action creators so that they access the correct store instance at runtime and dispatch the action automatically. This can be made shorter by using the "object shorthand" syntax - just pass an object full of action creators as the second argument to connect, like:
export default connect(null, {sendInfo})(SendInfoButton);
Similarly, you shouldn't import the store directly. As #estes said, that locks your code into the same "production" store instance all the time, and makes it harder to test or reuse your code.
I try to enable HMR on my project with typescript and webpack 2 but whenever I make a change I see the following output in the logs and the store is reset to its original values(discards state)
index.js:832 MobX Provider: Provided store 'appStore' has changed. Please avoid replacing stores as the change might not propagate to all children
The UI is refreshed partially after loading the hot update bundle which is good and expected but since the store lost its state, the UI is not the expected one.
What is the right pattern for keeping the state of mobx stores across HMR updates?
Currently the coode looks like the following:
const uiStore = new UiStore();
const appStore = new AppStore();
function render() {
ReactDOM.render(
<AppContainer>
<Provider appStore={appStore} uiStore={uiStore}><App/></Provider>
</AppContainer>, document.getElementById('root'))
}
const hot = (module as any).hot
if (hot)
hot.accept(() => {
render()
})
render()
The problem was that after every hot reload, my index file that was referencing App component was re-required by the webpack on the client side and this was destroying the uiStore and appStore objects that was initialised in the index file.
Declaring the store as a member of the window object has solved the problem. The stores now survive across hot module replacements.
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {AppContainer} from 'react-hot-loader';
import {observable} from 'mobx';
import {Provider} from 'mobx-react';
import {AppStore, UiStore, Stores} from './types/index';
import App from './components/App';
import './index.css';
declare var stores:Stores;
(window as any).stores = (window as any).stores || new Stores(new UiStore(), new AppStore());
function render() {
ReactDOM.render(
<AppContainer>
<App {...stores} />
</AppContainer>, document.getElementById('root'));
}
const hot = (module as any).hot
if (hot)
hot.accept(() => {
render();
})
render();
Each time you reload new page from another, your top component might be re-rendered which cause call
const uiStore = new UiStore();
const appStore = new AppStore();
each times.
this might be complained by mobx because you are replacing whole stores with new instance, which is not intended by Mobx.
Might be better if you create uiStore, appStore as a state, which still remain renders new pages.