How can we use a mobx store in utility function?
I have a mobx store and a utility function to make a axio call, I want to use stote value in the utility, how can I do this?
// Store example
export default class SampleStore {
#observable title = "Coding is Love";
#observable user = {
userId: 1,
};
#action
setUser(user) {
this.user = user;
}
#action
updateUser(data) {
this.user = { ...this.user, ...data };
}
#action
clearUser() {
this.user = undefined;
}
#action
setTitle(title) {
this.title = title;
}
}
// Utility function in different file
export function makeApiCall () {
// Use SampleStore here
}
Depends on how you actually initialize your store, how your app is organized and many other factors.
Most simple way is to have singleton store and then you just import it and use directly:
// export an instance instead
export const sampleStore = new SampleStore()
// .. util fil
import { sampleStore } from './SampleStore.js'
export function makeApiCall () {
sampleStore.setUser()
}
Another way is just to pass store to the function, for example if you want to make this call inside useEffect or something:
// Make function accept store as an argument
export function makeApiCall (sampleStore) {
sampleStore.setUser()
}
// ... inside of some React component
// get store from the context (I guess you will have it at that point)
const { sampleStore } = useStores()
useEffect(() => {
// and just pass to the function
makeApiCall(sampleStore)
}, [])
Related
I have two MobX stores:
export default class AccountStore {
accounts : Account[] = [];
constructor() {
makeAutoObservable(this);
}
loadAccounts = async () => {
//call to loadOthers
}
}
and
export default class OtherStore {
others : Others[] = [];
constructor() {
makeAutoObservable(this);
}
loadOthers = async () => {...}
}
In my AccountStore class, in my loadAccounts function I want to make a call to loadOthers from the other MobX store. How can I make this call?
Depends on how you initialize your stores. The most simple way is to have singleton stores, that way you can just import it directly.
Another way is you have some sort of root store, which initializes all other store, and pass itself to every store too, that way you have reference to the root store and to every other store from any store. Something like that:
class RootStore {
constructor() {
this.accountStore = new AccountStore(this)
this.otherStore = new OtherStore(this)
}
}
class AccountStore() {
constructor(rootStore) {
this.rootStore = rootStore.
}
loadAccounts = async () => {
this.rootStore.otherStore.callOthers()
}
}
Third way is just pass OtherStore instance to loadAccounts function. For example if you want to call loadAccounts when some React component mounts you can do that in useEffect, just get both store, and pass one to another:
export default class AccountStore {
// ...
loadAccounts = async (otherStore) => {
// ...
otherStore.loadOthers()
}
}
Svelte store documentation shows String or Integer being updated, but I did not find any dynamic function in store.
I don't understand how to make the getData function as a writable in order to notify the html of the change.
In the following sample, I would like b to be shown after the updateKey function is called.
You will find a minimal code in REPL here: https://svelte.dev/repl/3c86bd48d5b5428daee514765c926e58?version=3.29.7
And the same code here in case REPL would be down:
App.svelte:
<script>
import { getData } from './store.js';
import { updateKey } from './store.js';
setTimeout(updateKey, 1000);
</script>
<h1>{getData()}!</h1>
store.js
import {setContext} from 'svelte';
import {writable} from 'svelte/store';
var data = {
'a': 'a',
'b': 'b'
};
var key = 'a';
export const getData = function() {
return data[key];
}
export const updateKey = () => {
key = 'b';
}
The goal is to work with a dynamic function in the store.
Well, I think you still have a bit of confusion about how things work in Svelte... Not sure how to best answer your question, so here's some code for what's you're trying to achieve, along with some comments. I hope it will help you better understand how things come together in regards to stores.
App.svelte
<script>
import { onMount } from 'svelte'
import { key, data, updateKey } from './store.js'
onMount(() => {
// it's not safe to have an unchecked timer running -- problems would
// occur if the component is destroyed before the timeout has ellapsed,
// that's why we're using the `onMount` lifecycle function and its
// cleanup function here
const timeout = setTimeout(updateKey, 1000);
// this cleanup function is called when the component is destroyed
return () => {
clearTimeout(timeout)
}
})
// this will log the value of the `key` store each time it changes, using
// a reactive expression (a Sveltism)
$: console.log($key)
</script>
<!--
NOTE: we're using the $ prefix notation to access _the value_ of the store,
and not `data`, which would be _the store itself_ (an object with
subscribe, set, etc.)
-->
<h1>{$data}</h1>
store.js
import { writable, derived } from 'svelte/store'
const db = {
'a': 'a',
'b': 'b'
}
// a writable store with initial value 'a'
export const key = writable('a')
export const updateKey = () => {
// a writable store has a `set` method to change its value
key.set('b')
}
// you can use a derived store to compute derived values from
// the current value of other stores
//
// here, we're getting the value from the db when the value of
// the `key` store changes
export const data = derived([key], ([$key]) => db[$key])
if I understood your question correctly, you want to be able to change the function (logic) that is executed by getData() and you want on each function change the html to be updated
for this use case you'll need to create your own custom store
as follows in store.js
import { writable } from 'svelte/store';
// an object to hold our functions
const functions = {
"funcA": () => {
// do something
return "whatevedata for a"
},
"funcB": () => {
// do something
return "the data of b"
}
}
// this how to create a custom store, taken from svelte documentation
function createCustomStore(defaultValue) {
const { subscribe, set, update } = writable(defaultValue);
return {
subscribe,
//custom function change func where suppliedValue the value you input to the store
// set() is a default function for a store to change it's value
changeFunc: (suppliedValue) => set(functions[suppliedValue]),
reset: () => set(defaultValue)
};
}
export const getData = createCustomStore(() => "default");
export const updateKey = () => {
// this to update which function the store uses
getData.changeFunc("funcB")
}
in App.svelte
<script>
import { getData } from './store.js';
import { updateKey } from './store.js';
setTimeout(function() {
updateKey()
}, 1000);
</script>
<h1>{$getData()}</h1>
we added the $ to getData because it's a store that holds reference to functions and the () is there to execute any function referenced by getData store. since it is a store on each value change (function change) of getData, the html will be updated
here is a repl of the implementation
in my react native app I want to use mobx for state management, my store is divided in multiple stores/files and since I want to be able to call a store actions from another stores I'm implementing a GlobalStore where I instantiate the other stores.
I want to be able to do something like this from my components
import { PostStore } from '../stores/PostStore.js'
import { UserStore } from '../stores/UserStore.js'
import { VenueStore } from '../stores/VenueStore.js'
class GlobalStore
{
postStore = new PostStore(this);
userStore = new UserStore(this);
venueStore = new VenueStore(this);
}
export default new GlobalStore;
This makes it so that using react-native Context-Provider API I can call every store action in ALL my compoennts using globalStore as a link:
In any component I can do:
globalStore.postStore.listPosts()
However I'm still not sure how I can access other store actions from within OTHER STORES.
What if inside postStore I want to use spinnerStore (to show axios calls pending, error or success status):
#action.bound getPosts = flow(function * (payload)
{
this.spinnerStore.setData({status: 1});
try
{
this.spinnerStore.setData({status: 2, response: response});
let response = yield axios.get('/api/posts', { params: payload })
return response;
}
catch (error) {
console.error(error);
this.spinnerStore.setData({ status: 3, errors: error });
throw error;
}
})
Here spinnerStore would be undefined...
However I'm still not sure how I can access other store actions from within OTHER STORES.
When you instantiate a store you can assign one of its properties to be another store instance. This is what you included in your example
class Foo {
constructor(instance) {
this.instance = instance
}
}
class Bar {}
const foo = new Foo(new Bar());
foo.instance instanceof Bar; // true
Now your foo instance has access to any public properties/methods defined on Bar
class Foo {
constructor(instance) {
this.instance = instance
}
}
class Bar {
#observable isLoading = false;
#observable data = null;
#action
getData() {
this.isLoading = true;
return axios.get('/foo').then((data) => {
this.isLoading = false;
this.data = data;
}).catch(e => {
this.isLoading = false;
});
}
}
const foo = new Foo(new Bar());
// in react
const MyComponent = ({ foo }) => (
<div>
{foo.instance.isLoading && <Spinner />}
<button onClick={foo.instance.getData}>
will call Bar action from Foo store
</button>
</div>
);
export default lodash.flowRight(
mobx.inject((stores) => ({ foo: stores.fooStore })),
mobx.observer
)(MyComponent)
In your example with generators, you cannot use fat arrows so this isn't bound to your class instance anymore, which is why it will be undefined. Using promises and fat arrows solves that problem.
I am using mobx-react-lite with hooks.
I have two store.
AuthStore
SomeOtherStore
This is my dummy AuthStore
import { observable, decorate, action } from 'mobx';
import { createContext } from 'react';
import { ROLE_LOGISTICS_MANAGER } from '../settings/constants';
import AuthService from '../services/AuthService';
class AuthStore {
constructor() {
this.authService = new AuthService();
}
currentMode = ROLE_LOGISTICS_MANAGER;
authenticating = true;
isLoggedIn = false;
userId = null;
loginLoading = false;
login = async params => {
this.loginLoading = true;
try {
const data = await this.authService.loginAsync(params);
this.loginLoading = false;
this.isLoggedIn = true;
} catch (e) {
console.error(e);
this.loginError = e;
} finally {
this.loginLoading = false;
}
};
}
decorate(AuthStore, {
currentMode: observable,
loginLoading: observable,
isLoggedIn: observable,
authenticating: observable,
userId: observable,
fetchUser: action,
login: action
});
export const AuthStoreContext = createContext(new AuthStore());
Now Lets say I want to change isLoggedIn from another store,
How can I do that? I tried to find ways in docs, couldn't find a solid solution.
I am using hooks with mobx-react-lite
So normally I use mobx like
const authStore = useContext(AuthStoreContext);
It's a common pattern to have stores as properties on a RootStore, each having references back to the root. So you could have a structure like:
class RootStore {
constructor (auth, ui) {
this.auth = new AuthStore(this)
this.ui = new UiStore(this)
}
}
class AuthStore {
constructor (rootStore) {
this.rootStore = rootStore
}
logout() {
this.isLoggedIn = false
}
}
decorate(AuthStore, {
logout: action
})
Then, when you need to call a function on another store, you can use the reference to the root as a pathway. The pattern's described in more detail here. A possible example of use with useContext might be:
const { someStore } = useContext(rootStoreContext)
someStore.rootStore.auth.logout()
When I use the mobx-react ,I use inject decorator to transmit the store.But when I get the store such as
#inject("store") #observer
class item extends Component {
constructor() {
this.store = this.props.store;
}
}
But when I want to call the function of store such as store.getUser() , I found that the context getUser function is not this , how can I bind this to the store ?
PS: the store is such as following :
class Store {
#observable user = "Sarah";
#computed
get getUser() {
return user + "Ok";
}
}
export default new Store();
I use the getUser like
render() {
<div>{this.store.getUser()}</div>
}
class Store {
#observable user = "Sarah";
#computed
get okUser() {
return this.user + "Ok";
}
}
const store = new Store();
console.log(store.okUser);
#computed is getter so you do not need to call it as function.
You have to use this.user in your store:
class Store {
#observable user = "Sarah";
#computed
get getUser() {
return this.user + "Ok";
}
}
export default new Store();
A computed is a getter, so you don't access it with a function call. Just dereference the field:
render() {
<div>{this.store.getUser}</div>
}