Originally, I was using new CounterStore inside React.createContext()
context.ts
import React from 'react'
import { stores, PersistState, CounterStore } from '#/store/index'
import type { ICounterStore } from '#/types/index'
export const FrameItContext = React.createContext<ICounterStore>(new CounterStore())
export const useCounterStore = () => React.useContext(FrameItContext)
Then I started using Mobx Persist Store in my app.
persist.ts
import { persistence, StorageAdapter } from 'mobx-persist-store'
import { CounterStore } from '#/store/index'
const read = (name: string): Promise<string> =>
new Promise((resolve) => {
const data = localStorage.getItem(name) || '{}'
console.log('got data: ', data)
resolve(data)
})
const write = (name: string, content: string): Promise<Error | undefined> =>
new Promise((resolve) => {
localStorage.setItem(name, content)
console.log('write data: ', name, content)
resolve(undefined)
})
export const PersistState = persistence({
name: 'CounterStore',
properties: ['counter'],
adapter: new StorageAdapter({ read, write }),
reactionOptions: {
// optional
delay: 2000,
},
})(new CounterStore())
And I changed my code to use PersistState instead of new CounterStore()
context.ts
import React from 'react'
import { stores, PersistState, CounterStore } from '#/store/index'
import type { ICounterStore } from '#/types/index'
export const FrameItContext = React.createContext<ICounterStore>(PersistState)
export const useCounterStore = () => React.useContext(FrameItContext)
It only logs got data: {} to the console. The write function never gets called.
Is there anything I am doing wrong?
Coincidentally, a simple Counter example on Codesandbox works perfectly fine → https://codesandbox.io/s/mobx-persist-store-4l1dm
The example above works on a simple Chrome extension or a web app but just doesn't seem to work with my specific application so I wrote a manual implementation of saving to LocalStorage.
Use toJSON() on the store to keep track of which properties should be saved:
toJSON() {
const { color, counter } = this
return {
color,
counter,
}
}
And add the localStorage logic just below the constructor(). First, check if the localStorage contains latest value & return it, if it does.
If there is nothing saved, then save it inside localStorage.
constructor() {
...
const name = "CounterStore"
const storedJson = localStorage.getItem(name)
if (storedJson) Object.assign(this, JSON.parse(storedJson))
autorun(() => {
localStorage.setItem(name, JSON.stringify(this))
})
}
Codesandbox → https://codesandbox.io/s/mobx-persist-store-manual-implementation-vm38r?file=/src/store.ts
Related
What am I trying to do?
Using Redux Toolkit, I'm trying to access the "store" for a value, specifically "username" which I've created a "slice" for, from a non-React file called SomeFile.js.
What is the code that currently tries to do that?
// userMetadataSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
username: "",
};
const userMetadataSlice = createSlice({
name: "userMetadata",
initialState,
reducers: {
updateUsername: (state, action) => {
const username = action.payload;
state.username = username;
},
},
});
export const { updateUsername } = userMetadataSlice.actions;
export default userMetadataSlice.reducer;
export const selectUsername = (state) => {
return state.userMetadata.username;
}
// SomeFile.js
import { selectUsername } from "../redux/userMetadataSlice";
import { useSelector } from "react-redux";
export const displayUsername = () => {
const username = useSelector(selectUsername);
console.log("Username:", username); // Error.
}
What do I expect the result to be?
To be able to pull the username from the "store".
What is the actual result?
When I try to access the value via "useSelector" from the non-react file an error occurs: React Hook "useSelector" is called in function "selectUsername" which is neither a React function component or a custom React Hook function
What I think the problem could be?
SomeFile.js does not have anything React related within it because it just pulls data from the store and outputs the data.
A solution I've tried that worked was to do this:
// userMetadataSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
username: "",
};
const userMetadataSlice = createSlice({
name: "userMetadata",
initialState,
reducers: {
updateUsername: (state, action) => {
const username = action.payload;
state.username = username;
},
},
});
export const { updateUsername } = userMetadataSlice.actions;
export default userMetadataSlice.reducer;
export const selectUsername = (state) => {
return state.userMetadata.username;
}
// New code here!
export function SelectUsername() {
const username = useSelector(selectUsername);
return username;
}
// SomeFile.js
import { SelectUsername } from "../redux/userMetadataSlice";
export const displayUsername = () => {
console.log("Username:", SelectUsername); // No errors, shows correct output.
}
The solutions I'm looking for is this:
Is my proposed solution the "proper" way to receive info from the "store" in non-React files?
Is there a custom hook solution for this?
Is my proposed solution the "proper" way to receive info from the
"store" in non-React files?
No, it's abusing the Rules of Hooks and React functions. You are directly invoking the SelectUsername React function.
Is there a custom hook solution for this?
No, React hooks work only in React functions and custom React hooks.
You can access your state from your Redux store object.
Store
From your created store object you'll have a getState method to invoke.
getState()
Returns the current state tree of your application. It is equal to the
last value returned by the store's reducer.
Returns
(any): The current state tree of your application.
You can export your created store object for import into non-React JS files and they can invoke the getStore method.
import store from '../path/to/store';
...
const state = store.getState();
The useSelector React hook from react-redux won't work outside a React component, but the selectUsername state selector function will.
// SomeFile.js
import store from '../path/to/store';
import { selectUsername } from "../redux/userMetadataSlice";
...
export const displayUsername = () => {
const state = store.getState();
const username = selectUsername(state);
console.log("Username:", username);
return username;
};
See the other Store Methods for subscribing to state changes and dispatching actions to your store from outside React.
Follow the official example to export your own useStore, and then use it in the component.
import { createStore, Store, useStore as baseUseStore } from 'vuex';
export const key: InjectionKey<Store<RootState>> = Symbol();
export function useStore() {
return baseUseStore(key);
}
use in the component
setup() {
const store = useStore();
const onClick = () => {
console.log(store)
store.dispatch('user/getUserInfo');
}
return {
onClick,
}
},
After running, store is undefined.
It can be obtained normally when I use it in the methods attribute
methods: {
login() {
this.$store.dispatch('user/getToken')
}
}
why? how to fix it
In that simplifying useStore usage tutorial, you still need to register the store and key in main.ts as they did. You will get undefined if you don't do this:
// main.ts
import { store, key } from './store'
const app = createApp({ ... })
// pass the injection key
app.use(store, key)
The reason is that baseUseStore(key) has no meaning until that's done.
I was trying to import and use a function which returns a json object. This function uses some redux state variables in it.
But when I call this to return the json, it returns me something else instead of the said json.
My function is something like this:
import React from 'react';
import { connect } from 'react-redux';
const GenerateYaml = (props) => {
let json = {};
json = props.selected;
return json;
}
const mapStateToProps = (state) => {
return {
selected: state.stepBuilder.selected,
};
};
export default connect(mapStateToProps)(GenerateYaml);
When I tried to log what was returned, I got to know that it was an object of the connect function. I cant paste is here since its too big.
Any help is appreciated. Thanks!!
A jsfiddle/codesandbox would be very helpful here,
What I do in such cases is read the expected value in every line of the code, eg use a debugger!
Also GenerateYml should return a React component (html) not json.
import { connect } from 'react-redux';
const GenerateYaml = (props) => {
const json = { ...props.selected }
console.log(json);
return (
<div>{JSON.stringify(json)}</div>
)
}
const mapStateToProps = (state) => {
return {
selected: state.stepBuilder.selected,
};
};
export default connect(mapStateToProps)(GenerateYaml);
So let's suppose I have a store, with a redux-thunk middleware in it. I created the store and exported it like this:
import myOwnCreateStoreMethod from './redux/createStore';
export const store = myOwnCreateStoreMethod();
I can now access it anywhere in my app. But what if I want to dispatch an action from anywhere? I have them declared e.g. in myAction.js:
export const myAction = () => (dispatch, getState) =>
dispatch({ type: 'SOME_TYPE', payload: ... })
Now I can import them and connect to my store/component like this:
import * as actions from './myActions.js';
const MyComponent = () => <div>Hello World</div>;
const mapStateToProps = () => ({});
export default connect(mapStateToProps, actions)(MyComponent);
My question is - what if I do not have a component and still want to dispatch actions declared like the one above?
You can dispatch actions from the store directly
import store from './myStore';
import { myAction } from './myAction';
store.dispatch(myAction());
Redux is a library by itself.
It has nothing to do with React, they just work well together based on React single source of truth and Redux one global store as the state of our application.
You can use redux in every JavaScript application.
Ah, so easy after #Asaf Aviv wrote his simple answer. So I implemented it like this:
import * as yourActions from './redux/actions/yourActions';
import { store } from './path/to/your/store';
const connectActions = (store, actions) => {
const { dispatch } = store;
return Object.keys(actions).reduce((acc, key) => {
const action = props => dispatch(actions[key](props));
acc[key] = action;
return acc;
}, {});
};
const connectedActions = connectActions(store, yourActions);
I want to use the client of react-apollo to reset part of the cache from a key or query, not clean the entirely cache using the client.resetStore().
Example:
import { withApollo } from "react-apollo";
import { compose } from "recompose";
import MyComponent from "./MyComponent";
const cleanEspecificCache = ({ client }) => () =>{
// What i do here?
}
const enhance = compose(
withApollo,
withHandlers({cleanEspecificCache})
)
export default enhance(MyComponent);
What i do to make it works? Thanks!
Partial Apollo Cache clearing is not supported at the moment as per this issue:
https://github.com/apollographql/apollo-feature-requests/issues/4
You can use fechPolicy: 'network-only' to not cache specific query at all:
const enhance = compose(
graphql(
gql`{
food {
id
name
}
}`,
{
options: { fetchPolicy: 'network-only' },
}
),
...
);
export default enhance(MyComponent);
If you want to go far down into rabbit hole and have a solution now, you can try https://github.com/sysgears/apollo-cache-router
it lets split your cache into two caches or more and then you can reset these caches individually:
import { InMemoryCache } from 'apollo-cache-inmemory';
import ApolloCacheRouter from 'apollo-cache-router';
import { hasDirectives } from 'apollo-utilities';
const generalCache = new InMemoryCache();
const specialCache = new InMemoryCache();
const cache = ApolloCacheRouter.override(
ApolloCacheRouter.route([generalCache, specialCache], document => {
if (hasDirectives(['special'], document)) {
// Pass all queries marked with #special into specialCache
return [specialCache];
} else {
// Pass all the other queries to generalCache
return [generalCache];
}
})
);
And whenever you want to reset specialCache call specialCache.reset() in the code.