Redux reducers on array items - redux

My redux state looks like this:
user: {},
devices: [
{name: "foo", ip: "192.168.0.1", settings: {
isRunning: true
}},
{name: "bar", ip: "192.168.0.2", settings: {
isRunning: true
}},
{name: "baz", ip: "192.168.0.3", settings: {
isRunning: false
}},
]
The array is actually larger than this, which is why I need a way to update isRunning for the correct device (based on its IP). Is it possible to do this? I've previously only used redux actions where there is one key-value to map an action to.
Ideally I want just
const toggleIsRunning = (isRunning) => {
return store.dispatch({
type: 'TOGGLE_IS_RUNNING',
value: isRunning
});
}
where this is a reducer to the settings object. But now I am forced to have one enormous reducer for devices and from there find the right index, then change the correct property, etc, which is quite cumbersome.

Related

RTK Query invalidatesTags doesn't seem to remove cached data every time

I have an RTK Query mutation endpoint rejectApplication, that invalidates the getApplication query. These are in the same API.
rejectApplication: builder.mutation<RejectResponse, string>({
query: (applicationId) => ({
url: `/applications/${applicationId}`,
method: "DELETE",
}),
invalidatesTags: (_result, _error, applicationId) => [
"Status",
{ type: "Application", id: "LIST" },
{ type: "Application", id: applicationId },
],
}),
getApplication: builder.query<ApplicationResponse, string>({
query: (applicationId: string) => ({
method: "GET",
url: `/applications/${applicationId}`,
}),
providesTags: (_result, _error, id) => [{ type: "Application", id: id }],
}),
Problem is that I have two components that use the useRejectApplicationMutation hook, but for some reason only one of them seems to correctly remove the query result from cache after it has been invalidated. I can observe this through the Redux devtools, where I can see the removeQueryResult actions being dispatched after the reject mutation has fulfilled in one component, but not firing in the other component. This leads to the getApplication data in the component not changing, which breaks the flow of the application.
const {
data,
isLoading: getApplicationIsLoading,
isError: getApplicationIsError,
} = useGetApplicationQuery(props.application.applicationId as string);
useEffect(() => {
if (data) {
dispatch(setIncompleteApplication(data));
}
}, [data]);
So in this case, the useEffect with data is not called because data doesn't seem to be refetched, although it should be invalidated after reject mutation is fulfilled. Weirdly, in the console it does look like it should be correctly refetching the application and status which are invalidated, as the MSW endpoints are hit after the delete request.
[MSW] 12:37:38 DELETE /v1/applications/XA1234567 (200 OK)
[MSW] 12:37:38 GET /v1/status (200 OK)
[MSW] 12:37:39 GET /v1/applications/XA1234567 (200 OK)
To me, the problem seems to be that the cache isn't properly cleared for some reason, so although the tags are invalidated and refetches made the data isn't properly reset. Any ideas to what might be causing this inconsistancy?
invalidatesTags does not always remove things from cache. It only removes things from cache for cache entries that are not being used in a component at the moment - for everything else it triggers a refetch, so the request is fired off again and if there is new data in the response, the response is updated accordingly.
So in your component, isFetching will switch to true, but the data will not disappear - in most cases that is the preferred behaviour, as you don't want everything to jump to a loading indicator, but just update displayed data after a mutation.
Now, if your endpoint returns data that is structurally equal to the data it returned before, it will also not be a new data object, but just the old one. On a refetch, RTK-Query compares the old and new result and tries to keep as much of it referentially equal as possible, so useEffects are not fired off when the underlying data did actually not change at all.
Is your applicationId representing only an id or it represent an object with properties in it? Because if represents an object you should refactor your code from
invalidatesTags: (_result, _error, applicationId) => [
"Status",
{ type: "Application", id: "LIST" },
{ type: "Application", id: applicationId },
],
to
invalidatesTags: (_result, _error, { applicationId }) => [
"Status",
{ type: "Application", id: "LIST" },
{ type: "Application", id: applicationId },
],
Notice the curly braces around applicationId? This way you are destructuring your object from the result of the http request and you're passing it to the id property for invalidating.

Is there a way to dynamically assign a country to defaultCountry in firebase phone authentication for web?

I am trying to use firebase phone authentication for web and vuejs. I want to detect the country of the user and assign the detected country as the defaultCountry in the firebaseui config.
signInOptions: [
firebase.auth.EmailAuthProvider.PROVIDER_ID,
{
provider: firebase.auth.PhoneAuthProvider.PROVIDER_ID,
recaptchaParameters: {
type: 'image',
size: 'invisible',
badge: 'bottomleft'
},
defaultCountry: `'${this.countryCode}'`
}
]
Below is the method I used to successfully get the country and assign to a variable in data ()
created() {
this.getDefaultCountry()
},
I even tried
defaultCountry: this.countryCode
If I hardcode a countryCode ('US', 'NZ', ... ), it works.
Thank you
If this.getDefaultCountry() is synchronous (i.e. doesn't require any database lookups, promises, etc), you should be able to use the following code, where defaultCountry is swapped out for a getter instead of a static value:
signInOptions: [
firebase.auth.EmailAuthProvider.PROVIDER_ID,
{
provider: firebase.auth.PhoneAuthProvider.PROVIDER_ID,
recaptchaParameters: {
type: 'image',
size: 'invisible',
badge: 'bottomleft'
},
get defaultCountry() {
// contents of getDefaultCountry here
}
}
]
If your this.getDefaultCountry() is asynchronous, you will instead have to show some form of loading screen while you get the value, build signInOptions, give it to your FirebaseUI config and then finally render it.

Redux Toolkit - assign entire array to state

How can I assign an entire array to my intialState object using RTK?
Doing state = payload or state = [...state, ...payload] doesn't update anything.
Example:
const slice = createSlice({
name: 'usersLikedPosts',
initialState: [],
reducers: {
getUsersLikedPosts: (state, { payload }) => {
if (payload.length > 0) {
state = payload
}
},
},
})
payload looks like this:
[
0: {
uid: '1',
title: 'testpost'
}
]
update
Doing this works but I don't know if this is a correct approach. Can anyone comment?
payload.forEach((item) => state.push(item))
immer can only observe modifications to the object that was initially passed into your function through the state argument. It is not possible to observe from outside the function if that variable was reassigned, as it only exists in the scope within the function.
You can, however, just return a new value instead of modifying the old one, if you like that better. (And in this case, it is probably a bit more performant than doing a bunch of .push calls)
So
return [...state, ...payload]
should do what you want.

How to dispatch an action in NgRx Store DevTools with NgRx 8?

I've updated my ngrx code to version 8 (with Action Creators etc.), but I don't know now how to use Dispatcher within NgRx Store DevTools.
Before I was able to dispatch actions like this:
{
type: '[Something] something loaded',
payload: {
results: ['a', 'b', 'c']
}
}
In my simple app I have the following Action:
export const SaveUserInfo = createAction(
'[User] Save user info',
props<{ user: IUser}>()
);
and the IUser model
export interface IUser {
name: string;
money: number;
}
Than in DevTools I dispatch like this:
{
user: {
name: 'coiso',
money: 1000
},
type: '[User] Save user info'
}
Hope it works for you.

Meteor subscription doesn't refresh despite WaitOn

I'm using Iron Router. I have a RouterController that looks something like this:
var loggedInUserController = RouteController.extend({
layoutTemplate: "GenericLayout",
waitOn: function () {
return Meteor.subscribe("TheDataINeed");
}
});
And I have a route defined which uses this controller to wait for the 'TheDataINeed':
Router.route("/myapp", {
name: "Landing",
controller: loggedInUserController,
data: function () {
if(this.ready()){
return {content: "page-landing"};
}
}
});
Now, the problem is the data I am subscribed to is conditional: meaning, depending on the user's role, I publish different data, like so:
if (!Roles.userIsInRole(this.userId, 'subscribed') ) {
return [
myData.getElements({}, { fields: { _id: 1, title: 1}, limit: 5 })
];
} else {
return [
myData.getElements({}, { fields: { _id: 1, title: 1} })
];
}
When the user's role is not 'subscribed', I limit the published data to 5 elements.
The problem is publishing is not reactive, so when the user changes his role for the first time to 'subscribed' and I navigate to my route ("/myapp"), the user still sees the limited number of elements instead of all of them.
Is there a way to manually re-trigger the subscription when I am loading this route? If possible, I'd like to do this without adding new packages to my app.
Not sure about that approach but can you try to set session value in route instead of subscription code. Then in a file on client side where your subscriptions are you can wrap Meteor.subscribe("TheDataINeed") in Tracker.autorun and have a session as a subscription parameter. Every time that session value is changed autorun will rerun subscription and it will return you data based on a new value.

Resources