React Native: HeadslessJS and Redux - How to access store from task - javascript

We have a ReactNative app that uses redux, redux-persist and a HeadlessJS task. This task needs to have access to the store. As the task fires without booting the entire app (and so has no access by default), we thought we could simply create the store inside the task as well so that it would be rehydrated by redux-persist. It turns out, however, that the store created in this way is different from the one in the app: after running, they contain different values. We tested this in several ways and it seems indeed a problem with the stores (and not with the actions for instance)
How should we access a Redux store from an HeadlessJS task?
Relevant code:
store/configure.js:
configureStore = (client) => {
const middleware = createMiddleware(client);
const finalCreateStore = applyMiddleware(thunk, middleware, logger)(createStore);
const store = finalCreateStore(rootReducer, undefined, autoRehydrate());
return store;
};
In use (both in the app and in the service):
const client = new ApiClient();
const store = configureStore(client);
client.setStore(store);
persistStore(store, {
storage: AsyncStorage,
}
In the app we simply use the Provider from react-redux to use the store, in the service we use store.dispatch.

For people looking for solution. I have found the solution in here.
The idea is to bind the store to async method.
https://github.com/react-native-kit/react-native-track-player/issues/63
Copy pasting the solution here.
// index
const store = ...
....registerHeadlessTask('TrackPlayer', () => require('event-handler.js').bind(null, store));
// event-handler.js
module.exports = async (store, data) {
if(data.type == '...') {
store.dispatch(...);
}
};

simply create the store inside the task as well so that it would be rehydrated by redux-persist.
This did indeed happen.
You created two stores (not advisable with redux) which were both hydrate, but not linked, as there is no such thing as linked redux stores.
Every time you run createStore, it's a new store. And every time you dispatch, you do that on a specific store.
Unfortunately async or multithreaded issues are not directly addressed by redux.
It would be possible though with middleware and / or store listeners to keep the two stores in sync.
But redux is also just not a mean for communication between threads (which I assume these tasks are, or you could just give the task a reference to the store once it was created or give the main app the store reference from the task).
It's more a form of Command-Query-Separation and centralized state.

You can access your store directly as reference.
Let's say you have your headless set in index.js, then you can just simply use store there like this:
import { AppRegistry } from 'react-native';
import Store from './src/Redux/Store';
import { someAction } from './src/Redux/Actions/someActions';
import App from './App';
import { name as appName } from './app.json';
const HeadlessTask = async () => {
console.log('Receiving HeadlessTask');
const someParam = await Store.getState().Something.someParam;
if (someParam) {
Store.dispatch(someAction(someParam));
} else {
Store.dispatch(someAction());
}
};
AppRegistry.registerHeadlessTask('HeadlessTask', () => HeadlessTask);
AppRegistry.registerComponent(appName, () => App);

Related

Vuex/quasar - mutate state from firebase 9 on boot

I'm using quasar with firebase 9. I'm trying to set the state with actions during the boot of the project. I have data in firebase that I want to load only once and assign to an array in the Vuex state.
there might be a way in firebase 8 but I can't solve this problem in the newer version of firebase
import { getData } from "src/firebase/config";
// load data from packages collection
export const loadPackages = async () => {
const packages = await getData("packages");
return packages;
}
// load data from menu collection
export const loadMenu = async () => {
const packages = await getData("meals-menu");
return packages;
}
I use the return of each function in the respective vue component but I want to use the return in mutating the vuex state.
any tips?
thank you
as a work around solution; try saving data from firebase into localstorage then set vuex state after localstorage is set
firebase collection has event methods to allow exactly this
Look at this as starting point: https://firebase.google.com/docs/database/web/read-and-write#read_data_once_with_an_observer
Inside event handlers you have the 'realtime' changes/added/removed data
So you can change store

check if user first visit on app with react-native

I'm new to react and I want to show new users illustration of how my app work.
how can I achieve that?
Am I need to download something from npm?
You may use AsyncStorage for that. AsyncStorage is an unencrypted, asynchronous, persistent, key-value storage system that is global to the app. It should be used instead of LocalStorage.
import {AsyncStorage} from 'react-native';
const tutorialShown = async () => {
return await AsyncStorage.getItem('#myApp:TUTORIAL_SHOWN');
}
// somewhere in your render method
if (!tutorialShown()) {
return <Tutorial />
}
//somewhere in your Tutorial's componentDidMount
AsyncStorage.setItem('#myApp:TUTORIAL_SHOWN')
Details: https://facebook.github.io/react-native/docs/asyncstorage

Alternative to getState for recurring variables in Redux

I use getState to get a clientId that I need to include in every api call right now. Problem is that this interrupts data flow as the app doesn't rerender when clientId changes. Do I have to manually get the clientId in every component that I need to include it in or is there a better alternative? (clientId is also in store and is fetched first when the user logs in)
Sounds like a good candidate for the use of Context.
Here's a fictitious example of how you can set the client ID at a high level but reference it in nested components without having to query the Redux store each time using Hooks:
App
const ClientContext = React.createContext(null);
function App(props) {
return (
<ClientContext.Provider value={props.clientId}>
<MyApiComponent />
</ClientContext>
)
}
const mapStateToProps = getState => ({
clientId: getState().clientId
})
export default connect(mapStateToProps, {})(App);
So we only need to connect the App to the store to retrieve the client ID, then using Context we can make this value accessible to nested components. We can make use of useContext to pull that value in to each component
function MyApiComponent() {
const clientId = useContext(ClientContext);
...
return <MyNestedApiComponent />;
}
function MyNestedApiComponent() {
const clientId = useContext(ClientContext);
...
}
Whether it's function or class components you are using, the principle is the same - Context is used to share global state down to nested components.

How can I connect to the store in non-react components

I would like to connect to the store in non-react components. My main problem is that the when i try to getState or dispatch in a non-react class like this: createStoreWithApi(api).dispatch(isLoading(true)) this will create a new instance of the store, and I just want to modify the already created store. And I know that I would have to avoid having the store as a function.
Is it possible to set the withExtraArgumentafter the store is created? Problem is that I cant just import the api in my store file, because I need to fetch the api from the backend first.
This is how my store set-up looks like:
const createStoreWithApi = (api: IApi, initialState?: {}) => {
const middlewares = [
thunkMiddleware.withExtraArgument({
api
})
];
const enhancer = composeWithDevTools(applyMiddleware(...middlewares));
return createStore(rootReducer, initialState!, enhancer);
};
Would love some advice
You can create a single instance of store in a create-store.js file that holds onto the store singleton. Then import that instance into all files that need access to it for store.getState and store.dispatch.
// create-store.js
let store = null;
export const createStoreWithApi = (api: IApi, initialState?: {}) => {
const middlewares = [
thunkMiddleware.withExtraArgument({
api
})
];
const enhancer = composeWithDevTools(applyMiddleware(...middlewares));
store = createStore(rootReducer, initialState!, enhancer);
};
export default store;
In your entrypoint file, you can run createStoreWithApi once with api and initialState.
// index.js
import { createStoreWithApi } from "./create-store";
// init store
createStoreWithApi("api string", {...initialState});
And in all other files that need access to the store:
import store from "./create-store";
const state = store.getState();
As far as setting the API value. I'd recommend setting that in the store with a new reducer action. Then your middleware and other components will have access to it via const state = store.getState();. Then you don't have to worry about initializing your store with it. And your components store.dispatch(setAPI("new api string")) then store.dispatch(isLoading(true)). And your middleware making the calls can run store.getState() and get the current api value before making a backend call.

Where does one hold service instances in a react/redux application?

Suppose I am writing an application in Redux and I am tasked to add logging using a 3rd party library. Its API is as follows:
function createLogger(token) {
// the logger has internal state!
let logCount = 0;
return {
log(payload) {
logCount++; // modify local state
fetch('/someapi', { // ship payload to some API
method: 'POST',
body: payload
});
}
};
}
I would then use the library something like this:
let logger = createLogger('xyz');
logger.log('foobar');
I definitely want to create the logger instance just once during application init. But then the question is: where do I store the logger instance?
First instict is to put it somewhere in the store. But is that a good idea? As I have demonstrated in the code the logger object is stateful, it stores a counter in the closure. I do not get a new instance like I would with an immutable object. As we know, state should only be modified via pure reducer functions.
Other possibilities are to create the instance somewhere in a redux middleware closure or just create a global variable, which is obviously evil in terms of testability.
Is there a best practice for this (I would think) rather common scenario?
Since you are using ES6 modules I would setup your logger as a module, export it, and import it wherever you plan to use it. I think logging from the actions is a solid plan, since it keeps the components unaware, and doesn't pollute the store with side-effects.
function createLogger(token) {
// the logger has internal state!
let logCount = 0;
return {
log(payload) {
logCount++; // modify local state
fetch('/someapi', { // ship payload to some API
method: 'POST',
body: payload
});
}
};
}
export default const logger = createLogger('xyz');
Your action creators
import logger from 'logger-module';
//
logger.log('somestuff');
Testing is still easily achievable by importing the logger and placing whatever spy/stub on its methods that you need to intercept.
From the Redux documentation:
/**
* Sends crash reports as state is updated and listeners are notified.
*/
const crashReporter = store => next => action => {
try {
return next(action)
} catch (err) {
console.error('Caught an exception!', err)
Raven.captureException(err, {
extra: {
action,
state: store.getState()
}
})
throw err
}
}
Raven being a third-party library.
If the library has its own state then it shouldn't be an issue using it in middleware (the state belongs in the library and not your app). If you're creating a state for it, for some reason, then that state should belong in the Redux store, probably under store.logger or something.

Categories

Resources