I have a Redux (well, rxjs-store) structure that's very generic. Basically, my data-types are all different versions of a generic data structure called an "Element" (each defined by a different "ElementType").
The goal is I don't have to add a whole new set of data objects to the store, API, backend, database etc when a new data object comes along.
This is working well so far, but I've come across a problem with Redux & Reselect where the selector is re-calculating multiple times.
Here's an example. For my page I need to load 4 sets of Elements of a particular type:
// subscribe to data
const elements$ = Observable.combineLatest(
this.elementTypeManager.getElementTypeByName('DistributionNetworkNode'),
this.elementTypeManager.getElementTypeByName('Socket'),
this.elementTypeManager.getElementTypeByName('Area'),
this.elementTypeManager.getElementTypeByName('DemandType'))
.switchMap(nodeAndSocketTypes => Observable.combineLatest(
this.elementManager.getElementsByType(nodeAndSocketTypes[0].id),
this.elementManager.getElementsByType(nodeAndSocketTypes[1].id),
this.elementManager.getElementsByType(nodeAndSocketTypes[2].id),
this.elementManager.getElementsByType(nodeAndSocketTypes[3].id)));
The elementTypeManager and elementManager is the service that sends out a Redux Action, and uses reselect to query the store, like this:
public getElementsByType(elementTypeId: AAGUID): Observable<Elements> {
// dispatch an action to the store (triggers http and storing data)
this.store.dispatch(this.elementActions.loadElementsForType(elementTypeId));
// use reselect to query store
return this.store.select(getElementsByTypeFactory(elementTypeId));
}
The Load Elements action triggers a http request to retrieve the data, and emits a Store Elements Data action, which the reducer takes and puts in the store.
The reselect selector is pretty simple and looks like this:
export const getElements = (state: AppState) => state.elements;
export function getElementsByTypeFactory(elementTypeId: AAGUID, includeValues: boolean = false) {
return createSelector([getElements],
(elements: Elements) => elements.filter(e => e.elementTypeId === elementTypeId));
}
It takes the elements, and filters by the specified type.
The problem is, the actions are called in sequence:
Load Elements (DistributionNetworkNode)
Load Elements (Socket)
Load Elements (Area)
Load Elements (DemandType)
Then, stored in sequence:
Store Element Data (DistributionNetworkNode)
Store Element Data (Socket)
Store Element Data (Area)
Store Element Data (DemandType)
Each time the reducer puts data into the store, reselect recalculates. Of course, it hasn't finished putting all the data in yet, so the other selectors return 0 results.
As a result, my CombineLatest emits 4 times, one with just the DistributionNetworkNode data, one with the DistributionNetworkNode and Socket data, one with the DistributionNetworkNode, Socket and Area data etc etc.
This is a continuous problem with the way my data is structured and how I'm using re-select, and I'm not sure how to overcome it without compromising the generic way the data is structured.
How can make sure all my Store Element Data actions have taken place before querying/emitting data with reselect?
Related
I'm learning redux (in Flutter using Firestore, but I don't think that matters), trying to get beyond the basics, and I'm confused about where 'computed state' (not even sure what to call it) should go.
Say I have app state like this:
user: an instance of user
movies: a list of user's movies
recentFavoriteMovie: one of the movies above that user has marked "favorite" and has the most recent creation date
I'm able to set user (login success action) and request user's movies (login success middleware). When the movies query completes, I'm confused about where to initialize recentFavoriteMovie. There seems to be many choices....
SetMovies middleware can compute it, and then call a SetRecentFavorite action.
Can the SetMovies reducer do it? Or is that considered a side-effect of the reducer, which is not allowed?
I'd like to do it lazily. Is it considered okay in redux to give the app state object a method that computes and caches it? If so, I'd still need to clear the cached value when a new movie list is set. But that seems like the same problem as (b) above.
I could put the movies property and favoriteMovies (property or method) in my user object (they kinda belong there), and then just dispatch an UpdateUser action each time one or the other changes. In general, I don't know when/whether to "promote" some sub attribute of my app state to the top level so the app can react to it.
Are all or any of these valid choices? I hope this makes sense as a question. I might even be too behind the curve to ask this properly.
You almost ther with computed state. From documentation
Reselect provides a function createSelector for creating memoized selectors. createSelector takes an array of input-selectors and a transform function as its arguments. If the Redux state tree is changed in a way that causes the value of an input-selector to change, the selector will call its transform function with the values of the input-selectors as arguments and return the result. If the values of the input-selectors are the same as the previous call to the selector, it will return the previously computed value instead of calling the transform function.
That is essentially what yu want with lazy selection of movies.
In state you store user and movies. Some movies marked as favorites for specific user (so when user marks movie as favorite you only modify movies and not re-run selector).
When some component needs list of favorite movies, it calls selector which computes derived state (list of favorite movies) and returns it. Also selector will memorize results and recompute them only when store changes but not on each render.
This approach can be considered best practice as you may later implement some filters for movies list and selectors will help to extract filtered list of movies.
When using selector you don't required to store selected data (list of favorite movies) in you store.
Computed state is used in mapStateToPros for each component that require computed state like so
const makeMapStateToProps = () => {
const getFavoriteMovies = makeGetFavoriteMovies();
const mapStateToProps = (state) => (
{
favoriteMovies: getFavoriteMovies(state.user, state.movies),
movies: state.movies,
user: state.user
});
return mapStateToProps;
}
And makeGetFavoriteMovies may look like
const getFavoriteMovies = (state) => state.movies;
const getUser = (state) => state.user;
export function makeGetFavoriteMovies () {
return createSelector(
[getFavoriteMovies, getUser],
(favoriteMovies, user) => {
// Here can be complicated logic to select only favorite movies
return movies.filter (movie => movie.isFavorite);
}
);
)
Reducer and/or middleware can compute favorite movies list. But this is not their responsibility. So to better separate concerns its better to use selectors for this task.
Also there is not reason for specific middleware (ogin success middleware). You may implement logic in actions and reducer.
When using a selector, I thought that I could do whatever I wanted with the variable without modifying the state, so I was surprised that the state became mutated.
So if this is wrong (in a redux saga):
const filters = yield select(state => state.filters.filters);
filters.terms['helloo'] = "mutated";
//send data with request
yield put(requestData(filters)
How come that first line is a direct reference to the state?
Anyway, if I try using Object.assign, it also mutates state:
const filters = Object.assign({}, yield select(state => state.filters.filters));
filters.terms['helloo'] = "mutated";
How do I create a selection that is a copy of the state?
There's truly no "magic" involved here. Redux's getState() is literally just return state, and both hand-written selectors and Reselect return whatever you have written the functions to return. So, in that example, filters is the actual object reference that's nested inside the store state, because that's what your function returned.
Per the Redux docs page on "Immutable Update Patterns", you need to copy all levels of nesting that you want to update. In your example, you're making a copy of filters, but not filters.terms, so terms is also still the original object that's in the store. You would need to make a copy of that as well, and modify the copy.
My question relates to ngrx effects and reducers.
I need to transform data retrieved from the backend before putting it into the ngrx store. The data retrieved from the backend is an plain array of Message (Message is a custom type in my application):
Message[]
I need to transform the array into the following:
Map<string, Message[]>
Basically I am grouping a user's messages by the counterparty (recipient or sender) ID (the key).
I am not sure where to perform the transformation from Message[] to Map<string, Message[]>: should I put the transformation business logic into the #Effect or into the reducer function?
The transformation could go into either the effect or the reducer.
If there were any validation that would need to be performed, I'd put it in the effect - where I'd have the option of dispatching an error action.
Otherwise, I'd put it into the reducer, as that's where I'd typically be transforming action payloads into state.
There is another option, too: you could use a selector. That is, the messages could be stored in the state as a simple array and a selector could be used to transform the state's messages, grouping them by counterparty - or whatever. If I had multiple ways of grouping messages, this is the option that I'd choose.
The #ngrx/example-app contains some examples of selectors:
/**
* A selector function is a map function factory. We pass it parameters and it
* returns a function that maps from the larger state tree into a smaller
* piece of state. This selector simply selects the `books` state.
*
* Selectors are used with the `select` operator.
*
* ```ts
* class MyComponent {
* constructor(state$: Observable<State>) {
* this.booksState$ = state$.select(getBooksState);
* }
* }
* ```
*/
export const getBooksState = (state: State) => state.books
The way i do it is to fetch and transform data in a service just like in the old days.
Effects react to an action and make the call through the service to get the data back and dispatch other actions based on the responses it receives.
This makes testing much easier since the service is kept separate from the effect which main purpose is to react on a certain action, not packaging data.
Reducer can be used for this but again you should keep it clean for readability purposes
My opinions:
Data retrieved from backend will be kept unchanged in the store.
Using selectors as your business logic (combine, transform and etc.) for the dumb components.
Maybe the only transformation is to use normalizr to flat the data.
I have a form that asks the user to type in some numbers. Then, some calculations are made using those numbers that are stored in a Redux store. Right now I do not store the calculations in the Redux store, but just do them inside the render function of my component.
Now I need the submitted form to include those calculated values in my HTTP request. How should I go about it? Should I make the Redux store hold the calculations?
If you plan on using the raw values as well as the calculated values in your UI, I can see a benefit to keeping both inside of your redux store.
If you only need to use the calculation when you make the HTTP request, and the calculated data is not being used as a part of the UI, a cleaner implementation might be to separate the function that does the calculation into a utility file, and importing and using the function in the component/container or actions file that the calculation is needed.
I think this would create a clearer separation of concerns in each file, and the calculation utility could be used in multiple files if necessary.
// Utility file
export const calculationFunction = (someParam) => {
\\ Calculation here
return yourAnswer
}
then
// Actions File (Note this is how my action dispatch is set up, yours might look a bit different
import { calculationFunction } from 'utilities/calculations';
export const sendData = (data) => ({
type: types.SEND_DATA,
responseTypes: [types.SEND_DATA_SUCCESS, types.SEND_DATA_FAILURE],
promise: (client: any) => client.post('/data', calculationFunction(data)),
});
I am creating simple app, which makes GET requests to the server, then prepares recieved data and creates chart. There are few questions:
Where should I place code responsible for checking and preparing raw data. Currently I have it in my action creators, but maybe it needs to be in the component itself?
I need to check and compare prepared data with the data which is already used for the chart, and do not call re-render if it's the same or not valid. Where should I put this check? For now I think to place it inside action creators too. But for that I need to use getState() for accessing the state, doesn't look right.
Action creators seems right place for all these checks for me, because if data is not valid, I can simply not update my state with it, (e.g. do not dispatch certain action creator) Or maybe I have to update state with new data despite it is not valid?
given these action creators, what is the best place for described checks?:
export function fetchPopulations(term = "") {
return function (dispatch) {
dispatch(fetchingPopulations())
term=toTitleCase(term)
return fetch(`${API_URL}${term.replace(/\s/g, '%20')}`)
.then(response => response.json())
.then(json => dispatch(requestPopulations(json)))
}
}
export function requestPopulations(data = []) {
return {
type: REQUEST_POPULATIONS,
payload: data,
}
}
export function fetchingPopulations() {
return {
type: FETCHING_POPULATIONS
}
}
I would say you are doing it right.
In your example, requestPopulations and fetchingPopulations are the real action creators and fetchPopulations is a composing function (yes, composing functions for the win!).
Where should I place code responsible for checking and preparing raw
data. Currently I have it in my action creators, but maybe it needs
to be in the component itself?
Components are not the place for putting the business logic of our application. Components should only represent the View in our MVC. No API calls, no business logic, only props and state.
I need to check and compare prepared data with the data which is
already used for the chart, and do not call re-render if it's the same
or not valid. Where should I put this check? For now I think to place
it inside action creators too. But for that I need to use getState()
for accessing the state, doesn't look right.
Create modular functions (it really shines with code maintenance and reuse) for performing these checks, compose them together in another one along with your real action creators, and you can dispatch only if needed. Further optimization can be done inside component life cycle hook shouldComponentUpdate(nextProps, nextState). Also I think it is definitely not an anti-pattern to use methods with a signature like this:
export function myComposingFunction(params) {
return (dispatch, getState) => {
// ...
So you can use getState().
Action creators seems right place for all these checks for me, because
if data is not valid, I can simply not update my state with it, (e.g.
do not dispatch certain action creator) Or maybe I have to update
state with new data despite it is not valid?
No, do not update the state with useless data. If you do that you will re-render the entire tree for nothing. You were absolutely right to say "if data is not valid, I can simply not update my state with it, (e.g. do not dispatch certain action creator)"