I have selector which gets current filter type from store (filterSelector).
And 3 more, which filter entities from store using different logic, let's imagine that their names are smallEntitiesSelector, mediumEntitiesSelector, bigEntititiesSelector.
What is the best way to create selector, which will return filtered entities based on filter values in store?
const filteredEntities = createSelector(
filterSelector,
(filterType) => {
switch filterType:
case "small": ???
case "medium": ???
case "big": ???
}
)
For this particular example I would create one selector, which accepts filter parameter, but in real case I have more complex logic inside this selectors and I don't want to mix them.
UPD:
Due to request in comments I will add some more code. Here is a way how I make it work:
const filteredEntities = createSelector(
filterSelector,
smallEntitiesSelector,
mediumEntitiesSelector,
bigEntititiesSelector,
(filterType, smallEntities, mediumEntities, bigEntities) => {
switch filterType:
case "small": return smallEntities;
case "medium": return mediumEntities;
case "big": return bigEntities;
}
)
But I don't like this solution, because all 3 selectors will be executed every time original entities changed, while I need only one to be executed.
So ti works, but I feel like there is a way to do it better.
As a first attempt, you might set up 2 selectors to derive current filter and current entities data. Then, call the proper selector directly into the result function.
const filteredEntities = createSelector(
filterSelector,
entitiesSelector, // Todo
(filterType, entities) => {
switch filterType:
case "small": return smallEntities(entities);
case "medium": return mediumEntities(entities);
case "big": return bigEntities(entities);
// case default?
}
);
If you step into performance/cache invalidation issues, re-reselect might help you.
import createCachedSelector from 're-reselect';
const filteredEntities = createCachedSelector(
filterSelector,
entitiesSelector, // Todo
(filterType, entities) => {
switch filterType:
case "small": return smallEntities(entities);
case "medium": return mediumEntities(entities);
case "big": return bigEntities(entities);
// case default?
}
)(
filterSelector
);
Related
I'm using ngrx/component-store and loving it so far. Having prior store knowledge building my own simple ones, the only real headache I've had so far is when I've had to update an array and figured out I have to always create a new one for the internal compare() pipe to realize the array got updated.
Anyway, reading through the documentation it talks about updater methods and patchState. To me they do exactly the same thing, but their creation is slightly different. You would call patchState inside of a method while this.updater() returns a method giving you a function you can expose in your service. Anytime I'm updating my state it's always after a network call. I assume there are plenty of scenarios where you'd want to update your state without a network call so this is why you would want to have an updater available to your component to call. The question is if an updater and patchState are really doing the same thing then is it a better practice to call an updater in an effect or use patchState, or maybe am I putting too much logic in my effect?
On a side note, the docs say an updater method is supposed to be a pure function. If you're using it to your push an object onto an array then is it really pure?
// adding the selectors so people know what components are subscribing to
readonly approvals$ = this.select(state => state.requestApprovals);
readonly registration$ = this.select(state => state);
readonly updateAssessment = this.effect(($judgement: Observable<{id: string, isApproved: boolean}>) => {
return $judgement.pipe(
switchMap((evaluation) => {
const state = this.get();
return this.requestApproval.patch(state.id, state.companyName, evaluation.id, evaluation.isApproved).pipe(
tapResponse(
(result) => {
// is it better to call patchState()?
this.patchState((state) => {
for(let i = 0; i < state.requestApprovals.length; i++) {
if(state.requestApprovals[i].id == result.id) {
state.requestApprovals[i].isApproved = result.isApproved;
}
}
// the take away is you must assign a whole new array object when you update it.
state.requestApprovals = Object.assign([], state.requestApprovals);
return state;
});
// or this updater?
// this.applyDecisionPatch(evaluation);
},
// oh look! another updater reassigning my array to the state so
// it propagates to subscribers to reset the UI
() => { this.reverseDecision(); }
)
);
})
);
});
// this is private to make sure this can only be called after a network request
private readonly applyDecisionPatch = this.updater((state, value: {id: string, isApproved: boolean}) => {
for(let i = 0; i < state.requestApprovals.length; i++) {
if(state.requestApprovals[i].id == value.id) {
state.requestApprovals[i].isApproved = value.isApproved;
}
}
state.requestApprovals = Object.assign([], state.requestApprovals);
return state;
});
Note: There's no tag for ngrx-component-store so couldn't tag it.
An updater can be compared to a reducer.
All the options to modify the state should change it in an immutable way.
A library like ngrx-immer can be used to make this easier.
The main difference is that updater receives the current state, and you can change the state based on it. E.g. a conditional update, or can be used with #ngrx/entity
While with setState and patchState, you just set state properties.
setState updates the whole state object, whereas patchState only sets the given properties and doesn't touch the rest of the state object.
These two methods are also easier to use when you just want to set the state, because you don't have to create an updater function.
To answer the side question, push is not immutable. Instead of creating a new instance, it updates the array instance.
I'm using the Nodejs library for talking to Jira called jira-connector. I can get all of the boards on my jira instance by calling
jira.board.getAllBoards({ type: "scrum"})
.then(boards => { ...not important stuff... }
the return set looks something like the following:
{
maxResults: 50,
startAt: 0,
isLast: false,
values:
[ { id: ... } ]
}
then while isLast === false I keep calling like so:
jira.board.getAllBoards({ type: "scrum", startAt: XXX })
until isLast is true. then I can organize all of my returns from promises and be done with it.
I'm trying to reason out how I can get all of the data on pages with Ramda, I have a feeling it's possible I just can't seem to sort out the how of it.
Any help? Is this possible using Ramda?
Here's my Rx attempt to make this better:
const pagedCalls = new Subject();
pagedCalls.subscribe(value => {
jira.board.getAllBoards({ type:"scrum", startAt: value })
.then(boards => {
console.log('calling: ' + value);
allBoards.push(boards.values);
if (boards.isLast) {
pagedCalls.complete()
} else {
pagedCalls.next(boards.startAt + 50);
}
});
})
pagedCalls.next(0);
Seems pretty terrible. Here's the simplest solution I have so far with a do/while loop:
let returnResult = [];
let result;
let startAt = -50;
do {
result = await jira.board.getAllBoards( { type: "scrum", startAt: startAt += 50 })
returnResult.push(result.values); // there's an array of results under the values prop.
} while (!result.isLast)
Many of the interactions with Jira use this model and I am trying to avoid writing this kind of loop every time I make a call.
I had to do something similar today, calling the Gitlab API repeatedly until I had retrieved the entire folder/file structure of the project. I did it with a recursive call inside a .then, and it seems to work all right. I have not tried to convert the code to handle your case.
Here's what I wrote, if it will help:
const getAll = (project, perPage = 10, page = 1, res = []) =>
fetch(`https://gitlab.com/api/v4/projects/${encodeURIComponent(project)}/repository/tree?recursive=true&per_page=${perPage}&page=${page}`)
.then(resp => resp.json())
.then(xs => xs.length < perPage
? res.concat(xs)
: getAll(project, perPage, page + 1, res.concat(xs))
)
getAll('gitlab-examples/nodejs')
.then(console.log)
.catch(console.warn)
The technique is pretty simple: Our function accepts whatever parameters are necessary to be able to fetch a particular page and an additional one to hold the results, defaulting it to an empty array. We make the asynchronous call to fetch the page, and in the then, we use the result to see if we need to make another call. If we do, we call the function again, passing in the other parameters needed, the incremented page number, and the merge of the current results and the ones just received. If we don't need to make another call, then we just return that merged list.
Here, the repository contains 21 files and folders. Calling for ten at a time, we make three fetches and when the third one is complete, we resolve our returned Promise with that list of 21 items.
This recursive method definitely feels more functional than your versions above. There is no assignment except for the parameter defaulting, and nothing is mutated along the way.
I think it should be relatively easy to adapt this to your needs.
Here is a way to get all the boards using rubico:
import { pipe, fork, switchCase, get } from 'rubico'
const getAllBoards = boards => pipe([
fork({
type: () => 'scrum',
startAt: get('startAt'),
}),
jira.board.getAllBoards,
switchCase([
get('isLast'),
response => boards.concat(response.values),
response => getAllBoards(boards.concat(response.values))({
startAt: response.startAt + response.values.length,
})
]),
])
getAllBoards([])({ startAt: 0 }) // => [...boards]
getAllBoards will recursively get more boards and append to boards until isLast is true, then it will return the aggregated boards.
My use case comprises of dispatching two actions from a recursive function (if else construct ) the if part which adds a row in an array ( which is a state of my app) and the else part adds another row and needs to access the length of the array before and call the function itself. What I see here is the length of the array remains same after the first action is being dispatched and thus the call to itself doesn't get the actual value of the length .
My assumption is you are trying to do the second check/call after the first within the component. The component must wait for the new props on the next render. You should move your logic into your action. This is just a guess without more details to the question. Here is an example:
const myAction = (stuff) => {
return (dispatch, getState) => {
let oldLength = getState().myState.stuff.length
dispatch(doStuffToStuff(stuff))
let newLength = getState().myState.stuff.length
dispatch(moreStuffToLength(newLength))
}
}
Thanks the issue was resolved .After every dispatch if the state changes it is required to access the new state by ysing getState() I wasn't doing that .
For example, using a new String wrapper to prevent name clashes:
// actions/forms/types.js
export const SUBMIT = new String('SUBMIT');
// actions/tabs/types.js
export const SUBMIT = new String('SUBMIT');
Thus, when writing a reducer ...
// reducers/forms.js
import { SUBMIT as FORM_SUBMIT } from '../actions/forms/types.js'
import { SUBMIT as TAB_SUBMIT } from '../actions/tabs/types.js'
console.log(FORM_SUBMIT === TAB_SUBMIT); // false;
export default function (state, action) {
switch (action.type) {
case FORM_SUBMIT:
// correctly handle only FORM_SUBMIT, and not TAB_SUBMIT
default: return state;
}
}
Would there be any downside to this? Or is this a good idea? Can't find much against it or for it
That's probably not a good idea, on a couple levels.
Redux itself doesn't actually care what the value of action.type is - it only enforces that action.type is defined. From there, how your reducer logic makes decisions is up to you.
I actually wasn't familiar with the use of new String() to produce non-equal references, so I had to look that one up. Yes, that appears to produce valid differentiating comparisons, per this example:
const a = new String("abcd");
const b = new String("abcd");
const c = "abcd";
const d = "abcd";
function reducer(state, action) {
switch(action.type) {
case a: {
console.log("a");
break;
}
case b: {
console.log("b");
break;
}
case c: {
console.log("c");
break;
}
case d: {
console.log("d");
break;
}
}
}
reducer(undefined, {type : a});
reducer(undefined, {type : b});
reducer(undefined, {type : "abcd"});
HOWEVER... if you attempt to log these actions, and or view the action history in the Redux DevTools, you are not going to be able to tell the difference between them visually. The overall intent of Redux is to make it straightforward to track down what actions have been dispatched and when. As a developer, there's lots of ways you can subvert that intent, but you're just going to be making things harder on yourself.
As for things other than strings, such as numbers or Symbols, those are also bad ideas. Numbers aren't as readable in the action history as strings are, and Symbols are not serializable and will cause problems with debugging. (Also see the Redux FAQ entry on why actions should be serializable.
I'm currently working on a blog post that will discuss the actual technical limitations that Redux requires (and why), vs how Redux is intended to be used, vs how it's possible to use Redux. I'm currently aiming to have that post up early next week. If you're interested, keep an eye on my blog at http://blog.isquaredsoftware.com .
In this example I'm using an action named ADD_TODO
import { createStore, combineReducers } from 'redux';
function todos(state, action) {
state = state || [];
switch (action.type) {
case 'ADD_TODO':
return state.concat([ action.text ]);
default:
return state;
}
}
function counter(state, action){
state = state || 0;
switch (action.type){
case 'INCREMENT':
return state+1;
case 'DECREMENT':
return state-1;
case 'ADD_TODO':
return state+100;
default:
return state;
}
}
var app = combineReducers({
todos: todos,
counter: counter
});
var store = createStore(app);
store.dispatch({ type: 'ADD_TODO': text: 'buy eggs' });
This cause both the "todos" and "counter" reducers to trigger.
Should I make all reducers have unique actions unless I actually intended it?
How can we implement this with multiple reducers that almost do the same thing? Multiple counters for example can have "INCREMENT" and a "DECREMENT" actions.
Should name spacing actions solve it?
eg: "POINT_INCREMENT", "POINT_DECREMENT".
There's nothing inherently wrong with having different reducers respond to the same action -- for example, if you refresh the entire state at once. But yeah, if you have two counters that correspond to different things, you probably want to come up with a naming scheme to differentiate. But I would think the action names probably should have some noun to indicate what they apply to.
This cause both the "todos" and "counter" reducers to trigger. Should I make all reducers have unique actions unless I actually intended it?
Yes, probably they should have different unique actions.
From your example it becomes not really clear what you actually intend.
Should the counter count the amount of todo's ?
In that case it can actually be sensible that a "ADD_ITEM" action would both update the counter and also add a todo item. In that case please refer to the answer of acjay
How can we implement this with multiple reducers that almost do the same thing? Multiple counters for example can have "INCREMENT" and a "DECREMENT" actions.
When displaying a list of counters in the same app, each counter can be assigned a unique identifier (id).
An action should pass along the id of the counter.
export const toggleTodo = id => ({
type: 'INCREMENT',
id
})
The reducer should then check by id which counter to update.
See this example of a todo list in the official redux docs.
https://redux.js.org/basics/example
Redux actions are in a way globals. There are different strategies to workaround this problem on a larger scale.
https://kickstarter.engineering/namespacing-actions-for-redux-d9b55a88b1b1