NGRX selectors: factory selector within another selector without prop in createSelector method - ngrx

Using the factory selector pattern const selectA = (id: number) => createSelector(...) I have an instance where I want to reuse this selector within another selector (that iterates through an array of IDs) but I don't know the value to pass into the factor selector when calling createSelector.
So I have a selector that I use whenever I want to get a slice of the state for component A.
const selectA = (id: number) =>
createSelector(
selector1.selectEntityMap,
selector2.selectEntityMap,
selector3ById(id),
(
thing1,
thing2,
thing3
) => {
return ...
});
Now I want to get a list of component A for each item in an array.
const selectListOfA = (ids: number[]) =>
createSelector(
selectA,
(selectorA) => {
return ids.map((id) => selectorA(id));
});
The problem is selectA, which is now a factory selector, expects a parameter, but I don't know it when calling createSelector.
I can get the code to compile by creating another factory onto of the factory
const selectAFactory = () => selectA;
And then reference the new factory in the createSelector
const selectListOfA = (ids: number[]) =>
createSelector(
selectAFactory, <<< here
(selectorA) => {
return ids.map((id) => selectorA(id));
});
But of course, what's now happening is the selector is returning a list of MemoizedSelector[].
This pattern doesn't seem like it should be this complicated, are people not reusing their selectors in this way, what am I missing?

The function returned by selectA is a standard function, ie nothing magical about it, as explained well in this blog post: https://dev.to/zackderose/ngrx-fun-with-createselectorfactory-hng
This means selectListOfA can simply call the function returned from selectA for each id and an array of the state slices for component A will be returned:
export const selectListOfA = (ids: number[]) =>
createSelector(
(state) => state,
(state) => ids.map((id) => selectA(id)(state))
);
This works as expected but since the projector function will be executed every time anything in the store changes (recreating the selector for every id) this solution will have major performance issues.
We could just as well simplify the code to this with equally poor performance:
const selectListOfA = (ids: number[]) =>
(state) => ids.map(
(id: number) => selectA(id)(state)
);
If we instead supply an array of selectors as input to the createSelector call then Ngrx will be able to correctly determine when it has to reevaluate the selectA selectors:
const selectListOfA = (ids: number[]) =>
createSelector(
ids.map((id) => selectA(id)), // This results in an array of selectors
(...resultArr) => resultArr
);
However, Typescript will complain since the createSelector method does not have a matching overload declared for an array of variable length so we need to loosen up the input type of the array (to any) as well as specify the return type of selectListOfA.
The answer to the question is thus:
const selectListOfA = (ids: number[]) =>
(createSelector(
ids.map((id) => selectA(id)) as any,
(...resultArr) => resultArr
) as (state) => string[]);

Related

How to destructure an Array from an Object in Redux React?

I am using redux and I want to destructure teamMembers - Array from an Object name - teamData which I am getting from REDUX. This is what I have done below. I just want to confirm whether is this the right way to do.
const teamData = useSelector((state) => state.team.teamData);
const { description, heading } = teamData;
const teamMembers = useSelector((state) => state.team.teamData.teamMembers);
If possible, you should not do
const teamData = useSelector((state) => state.team.teamData);
const { description, heading } = teamData;
Your code here is interested in description and heading, but your useSelector selects the whole teamData.
Now, if there is a third property on teamData, let's say winStreak and that winStreak changes, your component will rerender. Because that change to winData.teamStreak caused winData to change. And your useSelector is watching winData for changes.
If you instead do
const description = useSelector((state) => state.team.teamData.description);
const heading = useSelector((state) => state.team.teamData.heading );
your component will only rerender, when description or heading change. Because your useSelector now watches those two properties for changes. It does not care if teamData changes, it only cares if teamData.descriptionorteamData.heading` change.
So, in your case you should do
const description = useSelector((state) => state.team.teamData.description);
const heading = useSelector((state) => state.team.teamData.heading );
const teamMembers = useSelector((state) => state.team.teamData.teamMembers );
and not use any destructuring here.
If you have to destructure the result of a useSelector call, that likely means that your useSelector call is also observing data you are not interested in in the first place.

Pass a whole object to redux reselect selector, but change it only if one property of the object changes

Started working with Reselect and there's one thing I can't seem to find an answer for.
Say I have a helper fn (getVehicleList) which does some heavy calculations, so I don't want it to re-run too much. I use state selectors to get the properties I need, something like:
const getVehicles = (state) => state.vehicles.vehicles;
const getVehicle = (state) => state.vehicles.vehicle;
const getUserId = (state) => state.auth.user.id;
I then have implemented the createSelector:
const getVehicles = createSelector(
[getVehicles,
getVehicle,
getUserId],
(vehicles, vehicle, id) => getVehicleList(
vehicles,
vehicle,
id,
),
);
Now, vehicle returns an object with multiple fields. If any of these fields change, the object changes and so everything is recomputed again. Is there a way to stop this recomputing until the id and only the id of the vehicle changes?
I tried doing a state selector for the id, like
const getVehicle = (state) => state.vehicles.vehicle.id;
But that doesn't work for me, cause I need the whole vehicle object inside my helper fn and not just the id.
Thanks in advance for the help!
You can try the following:
const getVehicles = (state) => state.vehicles.vehicles;
const getVehicle = (state) => state.vehicles.vehicle;
const getUserId = (state) => state.auth.user.id;
const selectVhicleId = createSelector(
[getVehicle],
({ id }) => id //return only the id
);
const selectVehicles = createSelector(
[getVehicles, selectVhicleId, getUserId],
(vehicles, vehicleId, id) =>
getVehicleList(vehicles, { id: vehicleId }, id)
);
Here is some information about how I use reselect with React.
Here is an example that re calculate vehicles when vehicle.id changes (or any of the other dependencies). It will not re calculate if other values of vehicle change so the vehicle used getVehicleList gets a stale vehicle passed to it that is only refreshed when vehicle.id changes:
const getVehicles = (state) => state.vehicles.vehicles;
const getVehicle = (state) => state.vehicles.vehicle;
const getUserId = (state) => state.auth.user.id;
const createSelectVehicles = (vehicle) =>
createSelector([getVehicles, getUserId], (vehicles, id) =>
getVehicleList(vehicles, vehicle, id)
);
const Component = () => {
//only re calculate vehicle if vehicle.id changes
const vehicle = useSelector(
getVehicle,
(a, b) => a?.id === b?.id
);
//only create the selector when vehicle changes
// vehicle only changes when vehicle.id changes
const selectVehicles = React.useMemo(
() => createSelectVehicles(vehicle),
[vehicle]
);
//vehicles is re calculated when vehicle.id changes
// or when state.vehicles.vehicles changes or
// when state.auth.user.id changes
const vehicles = useSelector(selectVehicles);
};

redux-observable dispatch actions

I need to dispatch some actions in some order using redux-observable however, it takes just last action to dispatch. Please see example:
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mapTo(fetchClientsPending(true))
.mergeMap(() => {
return ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
});
});
fetchClientsSuccess is dispatched with clients but fetchClientsPending not, I totally do not get it why. I could use dispatch because I get it in params, but I feel it is not good solution(?). It should be done in the stream I guess. I am starting with RxJs and redux-observable. Is it possible to do?
Operators are chains of Observables where the input of one stream is the output of another. So when you use mapTo you're mapping one action to the other. But then your mergeMap maps that Pending action and maps it to that other inner Observable that does the ajax and such, effectively throwing the Pending action away. So think of RxJS as a series of pipes where data flows through (a stream)
While there is no silver bullet, in this particular case what you want to achieve can be done by using startWith at the end of your inner Observable
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mergeMap(() => {
return ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
})
.startWith(fetchClientsPending(true)); // <------- like so
});
This is in fact the same thing as using concat with of(action) first, just shorthand.
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mergeMap(() => {
return Observable.concat(
Observable.of(fetchClientsPending(true)),
ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
})
);
});
That said, I would recommend against synchronously dispatching another action to set the state that fetching is pending and instead rely on the original fetchClients action itself for the same effect. It should be assumed by your reducers that if such an action is seen, that some how the fetching still start regardless. This saves you the boilerplate and helps a bit on micro-perf since you don't need to run through the reducers, epics, and rerender twice.
There's no rules though, so if you feel strongly about this, go for it :)

How to use Reselect selectors inside a Redux reducer

My app already has a large collection of selectors used by the various container objects. These are great for accessing different parts of the state and make refactoring the state much easier.
Now I want to use my selectors inside some of my reducer functions. The problem is that inside the reducer, the state parameter refers to a specific slice of the state, whereas the selector functions expect to be called with the state root object.
Contrived Example:
/* Selectors */
const getTodos = state => state.todos;
const getUncompletedTodos = createSelector(
[ getTodos ],
todos => todos.filter(t => !t.completed)
);
/* Reducer */
const todosReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
];
case 'REMOVE_COMPLETED_TODOS':
return getUncompletedTodos(state); // <-- this won't work
}
}
You selector works from root state object.
To fake this you could do
return getUncompletedTodos({todos: state});
But IMHO a better idea would be to reuse filtering function
/* Selectors */
const getTodos = state => state.todos;
const filterCompleted = todos => todos.filter(t => !t.completed)
const getUncompletedTodos = createSelector(
[ getTodos ],
filterCompleted
);
// inside reducer
case 'REMOVE_COMPLETED_TODOS':
return filterCompleted(state);
The answer by Yury works, but doesn't take advantage of memoization (see comments). If you want that, the solution would be to write the selector only for the slice of the state that it needs.
The selector would become:
const getUncompletedTodos = createSelector(
[todos => todos], // Not sure if there's a way to skip this redundancy and still take advantage of memoization with reselect.
todos => todos.filter(t => !t.completed)
);
In the reducer, you would simply use it like this:
case 'REMOVE_COMPLETED_TODOS':
return getUncompletedTodos(state);
However, when using the selector on the root state somewhere else, you use it like this:
getUncompletedTodos(state.todos)
The only downside I see is that you would have to remember to call the selector with the right part of the state, though of course if you're using TypeScript properly, it will remind you of this.

how to pipe functions, when a promise in the promise in the middle checks authorization?

i'm trying to compose some functions together:
compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
checkAuthorization returns a promise that check if a user is authorized.
buildParams receives someRequestData, and pipes the result to searchItem.
checkAuthorization()
.then(() => {
compose(
searchItem,
buildParams
)(someRequestData)
}, (e) => {
handleError(e)
})
I think it's OK, but I wish to have a more elegant look for readability, something like:
compose(
searchItem,
checkAuthorization
buildParams
)(someRequestData)
so what will happen is:
1) build params
2) checkAuth
3) search item
Any suggestions?
No, that's not possible, since checkAuthorisation does not receive and pass through the params. And even if you would rewrite it to do that, it still would be weird and a reader would assume that you're building the params whose authorisation should be checked. So don't do that - you have a non-linear flow, and trying to force it into some linear composition is no good.
Btw, I would recommend to avoid compose when you're calling the function immediately anyway:
checkAuthorization().then(() =>
searchItem(buildParams(someRequestData))
, e =>
handleError(e)
);
or maybe
checkAuthorization().then( compose(searchItem, buildParams, ()=>someRequestData)
, handleError ); // ^^^^ "const"
Here's a composer to handle both sync functions and Promises. Looks like it works correctly maintaining the order:
// Async pipe try. Pass functions left to right
const pipePromises = (...fns) => x => fns.reduce((p, fn) => p.then(fn), Promise.resolve(x));
// functions for the test
const me = x => new Promise(function(resolve, reject) {
setTimeout(() => resolve(x), 10)
})
const double = x => new Promise(function(resolve, reject) {
setTimeout(() => resolve(x * 2), 30)
})
const inc = x => new Promise(function(resolve, reject) {
setTimeout(() => resolve(x + 1), 400)
})
const log = x => { console.log('log: ', x); return x }
const syncTriple = x => x * 3; // sync function
// let's call our chain
pipePromises(
me, log, // 3
double, log, // 6
syncTriple, log, // 18 -- SYNC
inc, log, // 19
double, log, // 38
inc, log, // 39
syncTriple, log, // 117 -- SYNC
inc, log // 118
)(3) // 3
I just made an npm module to handle elegant Promise composition.
It's still in early stage, but you're welcome to check out the code and change it as it fits your needs and standards.
Basically it offers two methods which might meet your needs:
Combine
With Promise.combine({...}) you can combine several Promises by providing an object with a series of functions returning Promises and accepting the result of previous ones as input like this:
Promise.combine({
item: () => searchItem,
auth: ({item}) => checkAuth,
params: ({item, auth}) => buildParams
}).then(({item, auth, params}) => {
// here you can do what you need
})
Reduce
With Promise.reduce([...]) you can chain Promises in an array of functions returning Promises and accepting as input the output of the previously executed Promise:
Promise.reduce([
() => searchItem,
(item) => checkAuth,
(auth) => buildParams
]).then((params) => {
// here you can do what you need
})
Notice in this case you won't have access to item in the .then() function, but you could always compose the result of the checkAuth Promise in order to pass the item downstream as well:
Promise.reduce([
() => searchItem,
(item) => checkAuth.then((auth) => {
return {auth, item}
}),
({auth, item}) => buildParams.then((params) => {
return {params, item}
}),
]).then(({params, item}) => {
// here you can do what you need
})
Input
You can also add some input data from the request like this:
Promise.reduce([
(requestData) => searchItem,
(item) => checkAuth,
(auth) => buildParams
], requestData).then((params) => {
// here you can do what you need
})
See I passed the requestData as second parameter of Promise.reduce([...], requestData) and it gets passed as parameter to the first function.
Here you can see the functions code.
Hope this helps.

Resources