Redux Reducer example – filter & sort data

In this tutorial, we’re gonna look at how to use Redux Reducer to filter & sort data.

Related Post: Redux combineReducers example

Example Description

From previous post, we had created a Redux Application that has:
– state that contains 2 components: books array and filters.
– 3 types of Actions:
+ 'ADD_BOOK', 'REMOVE_BOOK' for books.
+ 'FILTER_TEXT' for filters.
– 2 child Reducers (booksReducer and filtersReducer) that will be combined using combineReducers() function.

We can add/remove books to/from books, set filters.text value.
=> Today, we’re gonna add 4 types of Actions for filters: 'START_YEAR', 'END_YEAR', 'SORT_BY' and 'CLEAR'. Then we will add a function that can filter and sort books with filters condition.

Practice

Setup environment

This step is just like step in previous post:
– In package.json:


{
  ...
  "dependencies": {
    ...
    "babel-plugin-transform-object-rest-spread": "6.26.0",
    "redux": "3.7.2",
    "uuid": "3.2.1"
  }
}

Then run cmd yarn install.

– Add plugin to .babelrc:


{
    ...
    "plugins": [
        "transform-object-rest-spread"
    ]
}

Create Redux Actions

Book Actions


const addBook = ({
    title = '',
    description = '',
    author = '',
    published = 0
} = {}) => ({
    type: 'ADD_BOOK',
    book: {
        id: uuid(),
        title,
        description,
        author,
        published
    }
});

const removeBook = ({ id } = {}) => ({
    type: 'REMOVE_BOOK',
    id
});

Filter Actions


const filterText = (text = '') => ({
    type: 'FILTER_TEXT',
    text
});

const startYear = (startYear) => ({
    type: 'START_YEAR',
    startYear
});

const endYear = (endYear) => ({
    type: 'END_YEAR',
    endYear
});

const sortBy = (sortType) => ({
    type: 'SORT_BY',
    sortType
});

const clear = () => ({
    type: 'CLEAR',
    defaultFilter: filtersReducerDefaultState
});

Create Redux Reducer

Books Reducer


const booksReducerDefaultState = [];

const booksReducer = (state = booksReducerDefaultState, action) => {
    switch (action.type) {
        case 'ADD_BOOK':
            return [
                ...state,
                action.book
            ];
        case 'REMOVE_BOOK':
            return state.filter(({ id }) => id !== action.id);
        default:
            return state;
    }
};

Filters Reducer


const filtersReducerDefaultState = {
    text: '',
    sortBy: '',
    startYear: undefined,
    endYear: undefined
};

const filtersReducer = (state = filtersReducerDefaultState, action) => {
    switch (action.type) {
        case 'FILTER_TEXT':
            return {
                ...state,
                text: action.text
            };
        case 'START_YEAR':
            return {
                ...state,
                startYear: action.startYear
            };
        case 'END_YEAR':
            return {
                ...state,
                endYear: action.endYear
            };
        case 'SORT_BY':
            return {
                ...state,
                sortBy: action.sortType
            };
        case 'CLEAR':
            return {
                ...state,
                text: action.defaultFilter.text,
                sortBy: action.defaultFilter.sortBy,
                startYear: action.defaultFilter.startYear,
                endYear: action.defaultFilter.endYear
            };
        default:
            return state;
    }
}

Create Redux Store


const store = createStore(
    combineReducers({
        books: booksReducer,
        filters: filtersReducer
    })
);

Filter and Sort function

books is filtered by text, startYear and endYear. Then the filtered result will be sorted by sortBy parameter (‘title’ or ‘published’).


const getVisibleBooks = (books, { text, sortBy, startYear, endYear }) => {
    return books.filter(book => {
        const textMatch =
            book.title.toLowerCase().includes(text.toLowerCase()) ||
            book.description.toLowerCase().includes(text.toLowerCase());

        const startYearMatch = typeof startYear !== 'number' || startYear <= book.published;
        const endYearMatch = typeof endYear !== 'number' || book.published <= endYear;

        return textMatch && startYearMatch && endYearMatch;
    }).sort((book1, book2) => {
        if (sortBy === 'title') {
            return book1.title.localeCompare(book2.title);
        } else if (sortBy === 'published') {
            return book1.published < book2.published ? -1 : 1;
        }
    });
}

Subscribe & Unsubscribe

Every time we call store.dispatch(action), getVisibleBooks(books, filters) is invoked to return list of books with filters condition.


const unsubscribe = store.subscribe(() => {
    const state = store.getState();
    const books = getVisibleBooks(state.books, state.filters);
    console.log(books);
});

// store.dispatch(action)...

unsubscribe();
// store.dispatch(action) << not do anything...

Run and Check result

Full code:


import { createStore, combineReducers } from "redux";
import uuid from 'uuid';

const demoState = {
    books: [
        {
            id: '123abcdefghiklmn',
            title: 'Origin',
            description: 'Origin thrusts Robert Langdon into the dangerous intersection of humankind’s two most enduring questions.',
            author: 'Dan Brown',
            published: 2017
        }
    ],
    filters: {
        text: 'ori',
        sortBy: 'published', // published or title
        startYear: undefined,
        endYear: undefined
    }
};

// action creators for Books Reducer
const addBook = ({
    title = '',
    description = '',
    author = '',
    published = 0
} = {}) => ({
    type: 'ADD_BOOK',
    book: {
        id: uuid(),
        title,
        description,
        author,
        published
    }
});

const removeBook = ({ id } = {}) => ({
    type: 'REMOVE_BOOK',
    id
});

const booksReducerDefaultState = [];
// Books Reducer
const booksReducer = (state = booksReducerDefaultState, action) => {
    switch (action.type) {
        case 'ADD_BOOK':
            return [
                ...state,
                action.book
            ];
        case 'REMOVE_BOOK':
            return state.filter(({ id }) => id !== action.id);
        default:
            return state;
    }
};

// action creators for Filters Reducer
const filterText = (text = '') => ({
    type: 'FILTER_TEXT',
    text
});

const startYear = (startYear) => ({
    type: 'START_YEAR',
    startYear
});

const endYear = (endYear) => ({
    type: 'END_YEAR',
    endYear
});

const sortBy = (sortType) => ({
    type: 'SORT_BY',
    sortType
});

const clear = () => ({
    type: 'CLEAR',
    defaultFilter: filtersReducerDefaultState
});

const filtersReducerDefaultState = {
    text: '',
    sortBy: '',
    startYear: undefined,
    endYear: undefined
};
// Filters Reducer
const filtersReducer = (state = filtersReducerDefaultState, action) => {
    switch (action.type) {
        case 'FILTER_TEXT':
            return {
                ...state,
                text: action.text
            };
        case 'START_YEAR':
            return {
                ...state,
                startYear: action.startYear
            };
        case 'END_YEAR':
            return {
                ...state,
                endYear: action.endYear
            };
        case 'SORT_BY':
            return {
                ...state,
                sortBy: action.sortType
            };
        case 'CLEAR':
            return {
                ...state,
                text: action.defaultFilter.text,
                sortBy: action.defaultFilter.sortBy,
                startYear: action.defaultFilter.startYear,
                endYear: action.defaultFilter.endYear
            };
        default:
            return state;
    }
}

// Store
const store = createStore(
    combineReducers({
        books: booksReducer,
        filters: filtersReducer
    })
);

const unsubscribe = store.subscribe(() => {
    const state = store.getState();
    const books = getVisibleBooks(state.books, state.filters);
    console.log(books);
});

const getVisibleBooks = (books, { text, sortBy, startYear, endYear }) => {
    return books.filter(book => {
        const textMatch =
            book.title.toLowerCase().includes(text.toLowerCase()) ||
            book.description.toLowerCase().includes(text.toLowerCase());

        const startYearMatch = typeof startYear !== 'number' || startYear <= book.published;
        const endYearMatch = typeof endYear !== 'number' || book.published <= endYear;

        return textMatch && startYearMatch && endYearMatch;
    }).sort((book1, book2) => {
        if (sortBy === 'title') {
            return book1.title.localeCompare(book2.title);
        } else if (sortBy === 'published') {
            return book1.published < book2.published ? -1 : 1;
        }
    });
}

const book1 = store.dispatch(addBook({
    title: 'Origin',
    description: 'Origin thrusts Robert Langdon into the dangerous intersection of humankind’s two most enduring questions.',
    author: 'Dan Brown',
    published: 2017
}));

const book2 = store.dispatch(addBook({
    title: 'Harry Potter and the Deathly Hallows',
    description: 'The seventh and final novel of the Harry Potter series.',
    author: 'J. K. Rowling',
    published: 2007
}));

const book3 = store.dispatch(addBook({
    title: 'The 100-Year-Old Man Who Climbed Out the Window and Disappeared',
    author: 'Jonas Jonasson',
    published: 2009
}));

console.log("filterText: 'ow'");
store.dispatch(filterText('ow'));
store.dispatch(clear());

console.log("startYear: 2008");
store.dispatch(startYear(2008));
store.dispatch(clear());

console.log("endYear: 2012");
store.dispatch(endYear(2012));
store.dispatch(clear());

store.dispatch(startYear(2008));
console.log("startYear: 2008 - endYear: 2012");
store.dispatch(endYear(2012));
store.dispatch(clear());

console.log("sortBy: 'published'");
store.dispatch(sortBy('published'));
store.dispatch(clear());

console.log("sortBy: 'title'");
store.dispatch(sortBy('title'));

Result in Browser Console:
- filter text:
redux-reducer-filter-sort-example-filter-text

- filter startYear:
redux-reducer-filter-sort-example-filter-start-year

- filter endYear:
redux-reducer-filter-sort-example-filter-end-year

- filter startYear & endYear:
redux-reducer-filter-sort-example-filter-start-end-year

- sortby 'published':
redux-reducer-filter-sort-example-sortby-published

- sortby 'title':
redux-reducer-filter-sort-example-sortby-title

Source code

ReduxFilterSort

For running:
- yarn install
- yarn run dev-server or yarn run build, then yarn run serve.



By grokonez | April 14, 2018.

Last updated on March 31, 2021.



Related Posts


Got Something To Say:

Your email address will not be published. Required fields are marked *

*