Compare values before and after updating redux state - react-native

I am listening to a Firebase node.....then when a change occurs in FBS Im comparing the snapshot versus what's currently in Redux. Crazy thing is that Redux state already reflects the updated value in FBS (from snapshot) even before I've dispatched
const listenToFbsMyUserNode = (dispatch) => {
database()
.ref('profiles/users/')
.on('value', snapshot => {
let state = store.getState(); //at this point Redux somehow already knows about the updated value before Ive dispatched!!!
//now can dispatch
dispatch(_refreshMyProfile(snapshot.val()));
});
};

That means that you are probably copying Firebase objects into your store.
Since Firebase does not work immutably, Firebase does modify these objects - essentially also modifying your store with a dispatch, since your store now contains references to exactly these objects. Something like that should of course not happen - convert your objects into flat data before dispatching them instead of dispatching the Firebase objects directly.

Related

Does returning a value in a Vuex action defeat the whole purpose of the Store?

Say I have an action like this:
async getYoutubeReport({ commit }, payload) {
return await this.$axios
.get(
`youtube/reports/${payload.date}/${payload.coin}`,
).then((res) => {
let today = utils.yearMonthDay(new Date())
let report = coinData.data.Items.find(Report => utils.yearMonthDay(new Date(Report.createdAt)) == today)
commit('SET_YOUTUBE_REPORT', report)
return report
})
}
}
Does this make any sense at all?
After all, the whole purpose of the Vuex Store is to horizontally make variables accesible across the app which can easily be done with reactive getters.
If the answer is "Yes, it is totally Fine", under which circumstances would make sense?
IMO, the only circumstance where I see it useful and where I do it is for showing a message response from the API in your Vue component.
If you need to access your state data you should use reactive getters.
Otherwise, there is no reason to maintain a vuex store and use data returned from your API.

How to copy a Vuex store and import it into a new store

I am trying to write a way for in Electron to duplicate Vuex commits from one Window to another with IPC. This way devs can use the Vuex store between browser windows seamlessly without manually using IPC calls to send everything, causing lots of duplicate code.
So far, I have written a basic Vue plugin to handle pushing commits around
With only some base code using store.subscribe, I IPC send all commits to main, which then forwards the commits to all renderer windows, applying the commits to their respective stores. Each new browser Windows is able to forward the data to all others. To prevent endless loops, I check the event and payload and verify it's different than the current value before applying it.
This all works. It's not even that slow amazingly. But there is a problem, When the window is opened, the store is fresh. I need a way to export the entire store, IPC it to the new window, and import the store.
Is there any way in Vuex to generically export the entire state, then import it again?
After some time, I found a way to sync vuex store between main window and another IPCRenderer Window. I would not explain how to subscribe to mutation using plugins since the question is about replacing the state. Here are the logic steps to replace the state in newWindow:
When opening a new window from main window, we can call IPCRenderer
and send our entire state (this.$store.state)
For example:
this.ipcRenderer.send("open-new-window",cloneObject(this.$store.state));
What we should note here, we could not send a complex object like DOM Object to our new window because you will get circular JSON error, so here I use script (UTIL.cloneObject(this.$store.state)) for convert complex object to avoid circular JSON Error. You can see the code here or you can find in another place since this question is not about converting to circular json
In ipcMain you can store this state in the global variable, for example:
let temporaryState
ipcMain.on('open-new-window', async(event, data) => {
//Another code...
newWindow = new BrowserWindow(options);
temporaryState = data
//this is data is your temporary state from mainWindow
//Another code for open new window
})
Request to rehydrate state in new window if new window already in "mounted stage", for example:
mounted() {
this.ipcRenderer.send("ask-rehydrate");
}
Back to ipcMain, you can listen to rehydrate request from newWindow and send the state that you already store in global variable, for example:
ipcMain.on('ask-rehydrate', (event) => {
newWindow.webContents.send("currentState", temporaryState);
}
Listen again in mounted stage to replace the current stage in "newWindow":
mounted() {
this.ipcRenderer.send("ask-rehydrate");
this.ipcRenderer.once("currentState", (event, state) => {
new Promise((resolve, reject) => {
logger.debug("Current state will be replaced with %o", state);
this.$store.replaceState(state);
resolve();
}).then(() => {
//Do something});
});
}
Congratulation now your state in newWindow will replaced with the current state in main window, here the mutation also sync if you subscribe the mutation using plugin. We should do this in mounted stage of vue since we need to wait until the store already initiated.

Reflux setState with a Callback

EDIT AGAIN: Opened an issue with Reflux here: https://github.com/reflux/refluxjs/issues/544
EDIT: Reflux setState does not provide any callback for setState. They require you to use the component lifecycle methods to ensure the state is set prior to running any code. If you ever need to use the reflux setState outside of a component, where you do not have lifecycle methods, you will not be guaranteed the state is set. This is due to how Reflux does their setState. It loops all listening components and calls those components' setState methods. If Reflux were refactored to wait until all the listening components' setState calls complete then call a callback passed into its own setState method, that may work, but it would likely require a large rework of Reflux. I have started using a singleton class to manage some of these variables, as they are fully outside the component lifecycle.
Can you use setState with a callback in ReactNative or is that only in React? I'm using the below syntax and the first debugger is hit, but the second debugger and console log never get hit.
EDIT: After digging some more, it seems this does not occur when using setting the state directly, but only when running it through a reflux store and/or not using a component.
See snack here: https://snack.expo.io/S1dm3eFoM
debugger
this.setState(
params,
() => {
debugger
console.log("CALLIN IT BACK")
}
)
I'm the creator of Reflux's ES6 styled stores/component hookups. Hopefully I can shed some light on this for you.
Here's the important points:
1) Reflux sets its store state immediately upon setState calls.
Reflux's store state doesn't have the same problems as React and doesn't need React's workaround (callback). You are guaranteed that your change is immediately reflected in the store's state, that's why there is not a callback. The very next line of code will reflect the store's new state.
tl;dr, no workaround is required.
// in Reflux stores this works
this.setState({foo:'foo'});
console.log(this.state.foo === 'foo') // true
this.setState({foo:'bar'});
console.log(this.state.foo === 'bar') // true
2) Stores can never depend upon components!
The idea that the setState would give a callback about when the dependent components have all updated their state is a major violation of the single most fundamental of all flux principles: 1 way data flow.
If your store requires knowledge about whether or not components are doing something then you are already doing it wrong, and all the problems you are experiencing are XY problems of fundamentally not following flux in the first place. 1-way data flow is a main flux principle.
And that principle exists for good reason. Flux doesn't require 1:1 mapping of store state properties to component state properties. You can map anything to anything, or even just use the store's state for the building blocks of how you will run your own logic to create completely new state properties on the components. For example having loaded and transitioned as separate properties in store state, but mapping to a loadedAndTransitioned property in one component, and a notLoadedOrTransitioned in another component via your own custom logic. That's a hugely powerful part of flux. But your suggestion would pretty much destroy all that, since Reflux can't map people's custom logic.
1-way data flow must be maintained; Store's must operate the same independently of what components utilize them. Without this, the power of flux falls apart!
Store's listen to actions, components listen to stores, actions are called from wherever. All flux-based data flows from action -> store -> component only.
I've checked the library for the refluxjs and the problem and the workaround are as mentioned below.
Problem
The library provides with a new instance of the setState which is not exactly similar to ReactJS setState, which omits the callback as mentioned in their code below.
/dist/reflux.js
proto.setState = function (obj) {
// Object.assign(this.state, obj); // later turn this to Object.assign and remove loop once support is good enough
for (var key in obj) {
this.state[key] = obj[key];
}
// if there's an id (i.e. it's being tracked by the global state) then make sure to update the global state
if (this.id) {
Reflux.GlobalState[this.id] = this.state;
}
// trigger, because any component it's attached to is listening and will merge the store state into its own on a store trigger
this.trigger(obj);
};
Also as mentioned here in the docs
That store will store its state on a this.state property, and mutate its state via this.setState() in a way that is extremely similar to React classes themselves.
WorkAround
The library provides with the listener functions, which provide us with the callbacks of the setState obj of the ReactJS as mentioned in the below snippet.
/dist/reflux.js
componentDidMount: function() {
var me = this;
_.extend(me, ListenerMethods);
this.listenTo(listenable, function(v) {
me.setState(_.object([key],[v]));
});
},
You can use them in the following way
this.listenTo(action, callback)
Hope it clears the doubts
Edit:
Usage as per the docs
To listen inside of the store
constructor()
{
super();
this.state = {count: 0};
this.listenTo(increment, this.incrementItUp);
}
incrementItUp()
{
var newCount = this.state.count + 1;
this.setState({count: newCount});
}
To listen outside of the store anywhere
// listen directly to an action
myActions.actionName.listen(myCallbackFunc);
// listen to a child action
myActions.load.completed.listen(myCallbackFunc);
Here's the link to the snack with working callbacks based on Promises

How to subscribe() to a combineReducer on Redux?

I`m using Redux on react-native with redux and react-redux. I use combinereducers() to combine two reducers:
const reducers = combineReducers({
sessiontype,
userdata
})
Now I subscribe to some change in my reducers:
store.subscribe((state, previousState) => {
//Do something
});
How I subscribe only to my userdata reducer? Because when I change the state of my sessiontype reducer I need to change the state of my userdata reducer (and It create a infinite cycle in my store.subscribe() because detect the whole reducer has modified.... (sorry my bad english)
The concept around Redux is that you have only one state and one reducer. The idea that you have separate reducers is just an implementation detail introduced by combineReducers() - it allows you to think of your reducers in parts, but to your application there's simply one reducer. Check out the discussion on this GitHub thread.
The mapStateToProps function inside the react-redux package allows you to select what parts of a reducer a component has access to. This may help you solve your issue. When a component with access to sessiontype updates the reducer, you can have that same component update userdata. To do that, you simply dispatch two separate actions to the store. Also, remember that subscribe does not actually give you access to the store - it simply let's you know that something has changed. Inside your subscribe function, you need to call getState() to actually read the data. Read more here.
Try something like this:
store.subscribe((state, prevState) => {
// run a check of conditions for an action being dispatched
// if you don't specify conditions, you'll end up in an
// infinite loop
if (stateHasFieldOrValue) {
// dispatch update to sessiontype
store.dispatch(updateSessionType(payload));
// dispatch update to userdata
store.dispatch(updateUserData(payload));
}
});

Sending static props to component via selector, best practice

I sometimes have need to send static props to a component, but the data actually comes from my Redux store. I.e. I need a access to state to fetch the data.
With static, I mean that this data won't change during the life of the component, so I don't want to select it from the store on each render.
This is how I solved it at first (the mapStateToProps part):
(state, ownProps) => ({
journalItemType: selectJournalItemType(state, ownProps.journalItemTypeId)
})
The component gets a JournalItemTypeId and the mapStateToProps looks it up in the store and sends the journalItemType to the component. JournalItemType is static metadata and won't change very often, and certainly not during the life of the component.
static propTypes = {
journalItemType: ImmutablePropTypes.map.isRequired,
}
The problem with this is that I call the selector at each render. Not a big performance hit, but feels wrong anyway.
So, I changed to this:
(state, ownProps) => ({
getJournalItemType: () => selectJournalItemType(state, ownProps.journalItemTypeId)
})
The first thing I do in the components constructor is to call getJournalItemType and store the result in the local state. This way the selector is only called once.
static propTypes = {
getJournalItemType: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
this.state = {
journalItemType: props.getJournalItemType()
}
}
Question:
Is this the right way to do this?
Another way would be to let the component know about state so the component could call the selector itself. But I think it's cleaner to keep the state out of the component.
I could also call the selector and fetch the static data earlier in the call chain, but I don't have state naturally available there either.
Clarification:
Why would I store JournalItemTypes in the Redux store if it is static data? All of the apps metadata is in my redux store so it can be easily refreshed from the server. By keeping it in Redux I can treat metadata in the same way as all other data in my synchronisation sagas.
Added clarification after Mika's answer
I need to use the local state because the component is a quite complex input form with all sorts of inputs (input fields, camera, qr-reader, live updated SVG sketch based on input).
A JournalItem in my app is "all or nothing". I.e. if every required field is filled in the user is allowed to save the item. My store is persisted to disk, so I don't want to hit the store more often than needed. So the JournalItem-object (actually an Immutable.map) lives in state until it's ready to be saved.
My selectors are memoized with reselect. This makes my first solution even less impacting on performance. But it still feels wrong.
The component gets updated via props due to other events, so it's re-rendered now and then.
You have a few different options here:
Option 1: the original way
This is the most basic and most 'Redux' way of doing it. If your selectJournalItemType function is moderately light, your app won't suffer much of a performance hit as mapStateToProps is only called when the store is updated according to react-redux docs.
Option 2: the constructor
It is generally recommended to avoid using the Component's state with Redux. Sometimes it is necessary (for example forms with inputs) but in this case it can, and in my opinion should, be avoided.
Option 3: optimizing option 1
If your function is computationally expensive, there are at least a few ways to optimize the original solution.
In my opinion one of the simpler ones is optimizing the react-redux connect. Short example:
const options = {
pure: true, // True by default
areStatesEqual: (prev, next) => {
// You could do some meaningful comparison between the prev and next states
return false;
}
};
export default ContainerComponent = connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
options
)(PresentationalComponent);
Another possibility is to create a memoized function using Reselect