Where should I handle sorting in Redux App?
You could sort the data when @connect -ing your React component with the Redux store:
function mapStateToProps(state) {
var items = state.items.slice(0);
items.sort()
return {
items: items
}
}
@connect(mapStoreToProps)
class MyComponent extends React.Component {
render() {
var items = this.props.items;
}
}
The Redux documentation shows a similar case in the Todo example: https://redux.js.org/basics/usage-with-react
IMO, the right place to sort data is not directly in the reducers but in the selectors.
From redux docs:
Computing Derived Data
Reselect is a simple library for creating memoized, composable selector functions. Reselect selectors can be used to efficiently compute derived data from the Redux store.
I'm currently using selectors to filter and sort data.
- No data repetition in the state. You don't have to store a copy of the item sorted by one specific way.
- The same data can be used in different components, each one using a different selector function to sort for example.
- You can combine selector applying many data computations using selector that you already have in the application.
- If you do right, your selectors will be pure functions, then you can easily test them.
- Use the same selector in many components.
I save the items, sortKey and sortKind (asc/desc) in the Redux Store.
In my Angular component (I believe would be same for React), I get the store state as an Observable so that I can display the items, sortKey and sortOrder in the UX.
When the user clicks on the table columns to change sort key (order) I dispatch the new keys/sort order to reducer for the state.
The reducer then performs the new sorting, and returns the new state with the updated values.
The Observable in the component thus sparks an event which updates the UX.
Advantage:
keep sorting logic out of the component
by saving the sortKey and sortKind in the state, you can restore precisely the UX if the user refreshes the browser (I use Redux-LocalStorage to sync)
as the store has the sorted items, you'll only perform sorting when the user actively wants it.
the sorted items are remembered for when the user might return to the component.
My reducer ( "bizzes" is my items list, and I use Immutable.List to store the items)
import { List } from 'immutable';
import { IBizz, IBizzState } from './bizz.types';
import { BIZZES_SET, BIZZES_SORT} from 'store/constants';
const SORT_ASC = 'asc';
const SORT_DESC = 'desc';
const defaultSortKey = 'serialNo';
const defaultSortOrder = SORT_ASC;
const INITIAL_STATE: IBizzState = {
bizzes: List([]),
sortKey: defaultSortKey,
sortOrder: defaultSortOrder
};
export function bizzReducer(state: IBizzState = INITIAL_STATE, action: any): IBizzState {
switch (action.type) {
case BIZZES_SET:
return {
bizzes: List(action.payload.bizzes),
sortKey: action.payload.sortKey || defaultSortKey,
sortOrder: action.payload.sortOrder || defaultSortOrder
};
case BIZZES_SORT:
let sortKey = action.payload.sortKey || defaultSortKey;
if(sortKey === state.sortKey) {
state.sortOrder = state.sortOrder === SORT_ASC ? SORT_DESC : SORT_ASC;
}
return {
bizzes: List(state.bizzes.sort( (a, b) => {
if( a[sortKey] < b[sortKey] ) return state.sortOrder === SORT_ASC ? -1 : 1;
if( a[sortKey] > b[sortKey] ) return state.sortOrder === SORT_ASC ? 1: -1;
return 0;
})),
sortKey: sortKey,
sortOrder: state.sortOrder
};
default: return state;
}
}
And my component ( I use Ng2-Redux to get the store as Observables):
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
import { select } from 'store';
import { BizzActions } from 'actions/index';
@Component({
selector: 'bizzlist',
templateUrl: './bizz-list.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BizzListComponent implements OnInit {
@select([ 'bizzState']) bizzState$;
public sortOrder: string;
public sortKey: string;
public bizzes = [];
private bizzStateSubscription;
constructor(
public bizzActions: BizzActions
) { }
ngOnInit() {
this.bizzStateSubscription = this.bizzState$.subscribe( bizzState => {
this.bizzes = bizzState.bizzes;
this.sortKey = bizzState.sortKey;
this.sortOrder = bizzState.sortOrder;
});
}
ngOnDestroy() {
this.bizzStateSubscription.unsubscribe();
}
public sortBizzes(key) {
this.bizzActions.sortBizzes(key);
}
}
As you can see, I am using an Action (called BizzActions) to do the actual Redux dispatch. You could do it in your component, but I prefer to separate these things. For good measure, here's my BizzActions (a Service):
import { Injectable } from '@angular/core';
import { NgRedux, IAppState } from 'store';
import {
BIZZES_SET,
BIZZES_SORT
} from 'store/constants';
@Injectable()
export class BizzActions {
constructor (private ngRedux: NgRedux<IAppState>) {}
public setBizzes = (bizzes: any) => {
return this.ngRedux.dispatch({
type: BIZZES_SET,
payload: {
bizzes: bizzes
}
});
};
public sortBizzes = (key:string) => {
return this.ngRedux.dispatch({
type: BIZZES_SORT,
payload: {
sortKey: key
}
});
};
}