Redux Toolkit Streaming updates - how to deal with relational entity adapters? - redux

I am working on the chat that is utilising RTK Query with entity adapters.
I currently have 2 different entity adapters; one for chat and one for messages.
How can I go about selecting all messages for a specific chat? Do I need to store array of message ids inside of the chat adapter or do I select all messages and filter them by parent_chat_uuid property?
What is more efficient way to approach this? Is there a built in selector that allows you to select entities by its property rather then the id? I imagine mapping over extremely large array to find object with certain property is pretty expensive.
Thanks
edit:
I am initially querying latest chats and once cacheDataLoaded I add many chats to entity adapter.
I also setup websocket connection and subscribe to 'message' event after data is loaded. Everytime message is notified by ws I addOne message. If status like 'delivered' or 'read' is notified I would call updateOne.
I am actually trying to rewrite project that was not-using RTK Query and entity adapters and messages were always added to the messages array inside of the chat object. Websocket connection and updates were handle by middleware that was calling async thunks -> updating store.
I am trying move all the middleware to be handle inside of the rtk queries / mutation logic and call actions from there directly (or thunks with more logic that call actions).
**off topic and I can create a separate post for this but is there any benefit in creating a App Thunk (not async) that just calls an action? Should I just call actions instead in this case?

I'm an RTK maintainer, and "author" of createEntityAdapter (technically I ported it from NgRX, but I did a lot of work on it).
I actually did something like this myself on a previous project. I'd put a simplified version of the code into a gist, but I'll post it here for posterity too.
The technique that I used was nested entity adapters: a top-level one that stores chat rooms, and a nested one inside each room entry that stores that room's messages:
// Example of using multiple / nested `createEntityAdapter` calls within a single Redux Toolkit slice
interface Message {
id: string;
roomId: string;
text: string;
timestamp: string;
username: string;
}
interface ChatRoomEntry {
id: string;
messages: EntityState<Message>;
}
const roomsAdapter = createEntityAdapter<ChatRoomEntry>();
const messagesAdapter = createEntityAdapter<Message>();
const fetchRooms = createAsyncThunk(
"chats/fetchRooms",
chatsAPI.fetchRooms
);
const fetchMessages = createAsyncThunk(
"chats/fetchMessages",
async (roomId) => {
return chatsAPI.fetchMessages(roomId);
}
)
const chatSlice = createSlice({
name: "chats",
initialState: roomsAdapter.getInitialState(),
reducers: {
},
extraReducers: builder => {
builder.addCase(fetchRooms.fulfilled, (state, action) => {
const roomEntries = action.payload.map(room => {
return {id: room.id, messages: messagesAdapter.getInitialState()};
});
roomsAdapter.setAll(state, roomEntries);
})
.addCase(fetchMessages.fulfilled, (state, action) => {
const roomId = action.meta.arg;
const roomEntry = state.entities[roomId];
if (roomEntry) {
messagesAdapter.setAll(roomEntry.messages, action.payload);
}
})
}
})
/*
Resulting state:
{
ids: ["chatRoom1"],
entities: {
chatRoom1: {
id: "chatRoom1",
messages: {
ids: ["message1", "message2"],
entities: {
message1: {id: "message1", text: "hello"},
message2: {id: "message2", text: "yo"},
}
}
}
}
}
*/
That said, your situation sounds somewhat different, because you specifically said you're using RTK Query and trying to stream something. Because of that, I'm actually not sure how you're trying to set up the endpoints and store the data atm. If you can edit your post with more details and then leave a comment, I can try to update this response to give more advice.
Per the last question specifically: no, there's no built-in selectors for searching by properties, and doing that does generally turn into "re-filter all the items any time there's an update".

Related

Binding VuexFire to a collection filtered with a query

I'm unsuccessfully trying to bind a Vuex state attribute to a queried collection in FireStore. I was wondering if anyone with more experience could point me in the right direction. This is what I'm currently doing:
In a Vuex Module called auth I'm declaring the following bind to userArticles
export const bindUserArticles = firestoreAction(({ bindFirestoreRef }, id) => {
return bindFirestoreRef('userArticles', userCollectionRef('articles', id))
})
This in turn points to a firebase method for querying the data (which works)
export const userCollectionRef = (collectionName, id) => {
return firestore().collection(collectionName).where("author.idAuthor", "==", id)
}
And I'm importing and dispatching the method in my Vue file in the following way
computed: {
...mapGetters('user', ['currentUser']),
},
methods: {
...mapActions('articles', ['bindUserArticles']),
},
watch: {
currentUser () {
this.bindUserArticles(this.currentUser.id)
}
}
So when the currentUser is updated upon login the method is triggered. The method is triggered and the right id is being sent, I've tested it with console.log. There is no error being displayed. When I try for example to modify the idAuthor of an existing article in the database, the list userArticles does not update. When I try adding or deleting an article from the database that has the specific idAuthor, the list userArticles does not update. I've also tried placing the this.bindUserArticles(this.currentUser.id) in the created() and mounted() life-cycle, to no avail.Does anyone have a clue where I'm going wrong about this?
Thanks in advance

Decrease response time in Firebase Vue app when liking a post

I have an app with different 'procedures' (think posts or pages), which one can like. Currently the process works: Tap like => run method "likeProcedure" => run dispatch action "likeProcedure" => update UI. It usually happens almost immediately, but sometimes there's a lag that gives this a "non-native" feel. Is there some sort of way that I could return feedback immediately, while stile holding single origin of truth on the firebase database?
Thank you!
Page Code:
<v-icon
v-if="!userProfile.likedProcedures || !userProfile.likedProcedures[procedure.id]"
color="grey lighten-1"
#click="likeProcedure({ id: procedure.id })"
>
mdi-star-outline
</v-icon>
and
computed: {
...mapState(["userProfile"]),
procedures() {
return this.$store.getters.getFilteredProcedures();
},
},
Vuex code:
async likeProcedure({ dispatch }, postId) {
const userId = fb.auth.currentUser.uid;
// update user object
await fb.usersCollection.doc(userId).update({
[`likedProcedures.${postId.id}`]: true,
});
dispatch("fetchUserProfile", { uid: userId });
},
Side note: I'm trying to remove the dispatch("fetchUserProfile") command, but this doesn't work, because then I'm calling dispatch without using it. And I cannot remove dispatch because then the object calling it is empty. And I cannot remove the object, because then the argument ('postId') isn't working. So if anyone knows how to deal with that, that would be extremely helpful.
Thank you :)
So this is the best solution I've come up yet. It kind of destroys the idea of a single source of truth, but at least it provides an immediate UI update:
async likeProcedure({ dispatch, state }, postId) {
console.log("likeProcedure");
const userId = fb.auth.currentUser.uid;
// line below provides immediate update to state and hence to the UI
state.userProfile.likedProcedures[postId.id] = true;
// line below updates Firebase database
await fb.usersCollection.doc(userId).update({
[`likedProcedures.${postId.id}`]: state.userProfile.likedProcedures[
postId.id
],
});
// line below then fetches the updated profile from Firebase and updates
// the profile in state. Kind of useless, but ensures that client and
// Firebase are in-sync
dispatch("fetchUserProfile", { uid: userId });
},
async fetchUserProfile({ commit }, user) {
// fetch user profile
const userProfile = await fb.usersCollection.doc(user.uid).get();
// set user profile in state
commit("setUserProfile", userProfile.data());
// change route to dashboard
if (router.currentRoute.path === "/login") {
router.push("/");
}
},

Redux will execute all subscription callbacks every time an action is dispatched?

Gee, I feel foolish about this, but I have read every part of: http://redux.js.org/ (done the egghead tutorials, and read 4 times the FAQ at: http://redux.js.org/docs/faq/ImmutableData.html
What I did was stub one of my reducers, to always return state, and that is the only reducer being called (checked with breakpoints). Even so, my subscribe event is being called every time the reducer returns state. What Do I not understand? (Action.SetServerStats is being called at a 1Hz rate, and the subscribe is also being called at a 1Hz Rate
BTW the Chrome Redux Extension says thats states are equal, and the React Extension for Chrome with Trace React Updates, is not showing any updates.
I will be glad to remove the question, when someone clues me in. But right now, what I see each each of the reducers being called at 1Hz, and all of them returning the slice of the store that they got (state).
So do I not understand subscribe, and that it returns every time even when the store tree does not get modified (and it is up to react-redux to do shallow compare to figure out what changed if any?)
create store & subscribe
let store = createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunk)))
store.subscribe(() => console.log("current store: ", JSON.stringify(store.getState(), null, 4)))
reducers.js
import A from './actionTypes'
import { combineReducers } from 'redux'
export const GLVersion = (state = '', action) => {
switch (action.type) {
case A.SetGLVersion:
return action.payload
default:
return state
}
}
export const ServerConfig = (state = {}, action) => {
switch (action.type) {
case A.SetServerConfig: {
let { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath } = action.payload
let p = { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath }
return p
}
default:
return state
}
}
export const ServerStats = (state = {}, action) => {
switch (action.type) {
case A.SetServerStats:
return state
// let { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize } = action.payload
// let s = { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize }
// return s
default:
return state
}
}
export default combineReducers({ GLVersion, ServerConfig, ServerStats })
Correct. Redux will execute all subscription callbacks every time an action is dispatched, even if the state is not updated in any way. It is up to the subscription callbacks to then do something meaningful, such as calling getState() and checking to see if some specific part of the state has changed.
React-Redux is an example of that. Each instance of a connected component class is a separate subscriber to the store. Every time an action is dispatched, all of the wrapper components generated by connect will first check to see if the root state value has changed, and if so, run the mapStateToProps functions they were given to see if the output of mapState has changed at all. If that mapState output changes, then the wrapper component will re-render your "real" component.
You might want to read my blog post Practical Redux, Part 6: Connected Lists, Forms, and Performance, which discusses several important aspects related to Redux performance. My new post Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent also goes into detail on how several parts of Redux actually work.

Store browserHistory using history.js in react redux architecture with SSR

How can one persist the full router history of a user visiting an SSR react-redux app? I have tried modifying the react-redux-router package's reducer.js file as such...but when the user loads via SSR, the history array is reset.
/**
* This action type will be dispatched when your history
* receives a location change.
*/
export const LOCATION_CHANGE = '##router/LOCATION_CHANGE'
const initialState = {
locationBeforeTransitions: null,
locationHistory: []
}
/**
* This reducer will update the state with the most recent location history
* has transitioned to. This may not be in sync with the router, particularly
* if you have asynchronously-loaded routes, so reading from and relying on
* this state is discouraged.
*/
export function routerReducer(state = initialState, { type, payload } = {}) {
if (type === LOCATION_CHANGE) {
return { ...state,
locationBeforeTransitions: payload,
locationHistory: state.locationHistory.concat([payload]) }
}
return state
}
ref: https://github.com/reactjs/react-router-redux/blob/master/src/reducer.js
However, I think this is supposed to be achieved in a middleware.
Irregardless, this (storing the entire previous session history) seems like a common enough use case that perhaps someone has already formulated a best practice.??
Perhaps even this full history is accessible via the historyjs object in react-router w/o react-router-redux.
I'm looking for answers to how to fulfill storing the full history of a user's session in the redux state and post it to my api server when the user closes the browser or navigates away from the site. (if this is not possible, i could just post it upon every navigation.) Then I would like to show this history in a 'recently viewed' list of pages on the users' home pages.
First of all, you don't have to meddle with the internals of react-redux-router.
As you can see in the code you presented, react-redux-router exports a LOCATION_CHANGE action.
You can use this action in a reducer of your own. Here's an example:
// locationHistoryReducer.js
import { LOCATION_CHANGE } from 'react-router-redux';
export default function locationHistory(state = [], action) {
if (action.type === LOCATION_CHANGE) {
return state.concat([action.payload]);
}
return state;
}
However, this may be unnecessary. Your assumption that this can be be achieved with middleware is correct. Here's an example of a middleware layer:
const historySaver = store => next => action => {
if (action.type === LOCATION_CHANGE) {
// Do whatever you wish with action.payload
// Send it an HTTP request to the server, save it in a cookie, localStorage, etc.
}
return next(action)
}
And here's how to apply that layer in the store:
let store = createStore(
combineReducers(reducers),
applyMiddleware(
historySaver
)
)
Now, how you save and load data is entirely up to you (and has nothing to do with react-router and the browser's history).
In the official docs, they recommend injecting the initial state on the server side using a window.__PRELOADED_STATE__ variable.

Should I store function references in Redux store?

I'm trying to build keyboard shortcut support into my React/Redux app in an idiomatic React/Redux way. The way I am planning to do this is to have the following action creator and associated action:
registerShortcut(keyCode, actionCreatorFuncReference)
The reducer would then update a registeredShortcuts object in the redux store with a mapping of keyCodes to actionCreatorFuncReferences. Then my root component would listen for keyup and see if there is an associated keyCode registered and if so, then dispatch the mapped action via the action creator function reference.
However, this would be the first time I am storing function references in my Redux store. To date, I've only had objects with keys with vanilla values (strings, ints, etc).
The Redux docs says:
You should do your best to keep the state serializable. Don’t put anything inside it that you can’t easily turn into JSON.
Does this suggest it's a bad idea to store such function references in my Redux store? If so, what is a better way to accomplish what I'm trying to do in React/Redux?
An alternative approach is just to store the mapping of keyCodes and function references in the root react component itself, but that didn't feel very Redux-like since now the application state is not in the Redux store.
No, you should not store function references in the redux store. They are not serializable, and as you mentioned state should be serializable at all times. The most redux-friendly approach I can think of is just to keep the map of hotkeys to their actionCreatorFuncNames.
TL;DR: You don't. The store state must be serializable at all times (as Nathan answered).
The Redux way is via enhancers, or the Redux-Observable way via dependencies.
NL;PR: Based on the Redux docs example, what you want is to pass the reference in your action(1), ignore it your reducer(2) and use it in your enhancer(3):
//... in your action:
const data={val:1}, ref=()=>{};
const action = {type:'ACTION_WITH_REF', data, ref}; //(1)
//... in your reducer:
case 'ACTION_WITH_REF':
return {...state, data: action.data}; //(2)
//... and in your enhancer:
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
export const myRefStore= {};
 
function refHandler({ getState }) {
return next => action => {
switch(action.type){
// this can be done more elegantly with a redux-observable
case 'ACTION_WITH_REF':
myRefStore.aRef = action.ref; // (3)
break;
}
// be sure to maintain the chain of the store
const returnValue = next(action);
// otherwise, your midddeware will break the store
return returnValue;
};
} 
const store = createStore(
reducers,
initialState,
applyMiddleware(refHandler)
);
Note: As far as there are no side-effects in your enhancers, you are good to go. Be aware that you could have obtained the refs directly in the reducers, but such an approach keeps the reference at the reducer-level and misses the point of combineReducers(). With an enhancer, you keep them all in one place(myRefStore).
One final observation is that a redux store is not an any-data store but a state store, thus why we need to handle functions and other non-state related stuff in enhancers. You can leverage the enhancer backbone to Redux-Observable and inject myRefStore via dependencies.
I'm new to redux, but the way I see it, you could pass the key code and an action type.
Then a reducer could be listening for that action type and make changes accordingly.
Here is an example using the library Mousetrap:
// On your Container
function registerShortcut(element, dispatch, keyCode, actionType) {
Mousetrap(element).bind(keyCode, function(e) {
dispatch({
type: actionType,
payload: {
keyCode: keyCode,
event: e
}
});
});
});
mapDispatchToProps = function(dispatch) {
return {
onMount: function(element) {
registerShortcut(element, dispatch, ['command+f', 'ctrl+f'], 'OPEN_SEARCH');
},
onUnmount: function(element) {
Mousetrap(element).unbind(['command+f', 'ctrl+f']);
}
};
};
// On your Component
componentDidMount() {
onMount(ReactDOM.findDOMNode(this));
};
componentWillUnmount() {
onUnmount(ReactDOM.findDOMNode(this));
};
// On your reducer
function reducer(oldState, action)  {
if (action.type == 'OPEN_SEARCH') {
//... make changes ...//
return newState;
}
return oldState;
};
This way, keyboard shortcuts will dispatch an action. The reducer will make the changes necessary to the state. And finally, the application can re-render.

Resources