Search code examples
redux-persist

Persisted value won't affect the code down the chain


This is the filterAction action that emits a value to to the persisted reducer. This action is called with dispatch() in a dropdown in a component.

import { SORT_BY_TITLE, SORT_BY_RELEASED_AT } from './types';

// sort by title
export const sortByTitle = () => ({
  type: SORT_BY_TITLE
});

// sort by released at
export const sortByReleasedAt = () => ({
  type: SORT_BY_RELEASED_AT
});

The corresponding filterReducer

import { SORT_BY_TITLE, SORT_BY_RELEASED_AT } from '../actions/types';

const initialState = {
  sortBy: 'title'
};

export default function(state = initialState, action) {
  switch(action.type) {
    case SORT_BY_TITLE:
      return {
        ...state,
        sortBy: 'title'
      };
    case SORT_BY_RELEASED_AT:
      return {
        ...state,
        sortBy: 'releasedAt'
      };
    default:
      return state;
  }
};

The filterReducer value is the one persisted in the main combined reducer.

export default combineReducers({
  books: booksReducer,
  book: bookReducer,
  form: addBookFormReducer,
  filter: filterReducer
});

The app's store

import { createStore, applyMiddleware } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';

const middleware = [thunk];

const persistConfig = {
    key: 'root',
    storage,
    whitelist: ['filter']
};

const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(persistedReducer, {}, applyMiddleware(...middleware));
export const persistor = persistStore(store);

The value persisted on the filter part of the main reducer gets displayed in the dropdown changes on which call the filterAction with dispatch().

Here's the booksReducer that creates the books part of the app's store so books are displayed in a component.

import {
    GET_BOOKS,
    BOOKS_LOADING,
    DELETE_BOOK,
    SORT_BY_TITLE,
    SORT_BY_RELEASED_AT
} from '../actions/types';

const initialState = {
  books: [],
  loading: false
};

const booksReducer = (state = initialState, action) => {
  switch(action.type) {
    case BOOKS_LOADING:
      return {
        ...state,
        loading: true
      };
    case GET_BOOKS:
      return {
        ...state,
        books: action.payload,
        loading: false
      };
    case DELETE_BOOK:
      return {
        books: [...state.books.filter(book => book._id !== action.payload.id)]
      };
        case SORT_BY_TITLE:
            return {
                ...state,
                books: [...state.books.sort((a, b) => a.title < b.title ? -1 : 1 )]
            };
        case SORT_BY_RELEASED_AT:
            return {
                ...state,
                books: [...state.books.sort((a, b) => a.releasedAt < b.releasedAt ? -1 : 1 )]
            };
    default:
      return state;
  }
};

export default booksReducer;

The filter part of the main reducer persists Ok on the page reload however the books list is displayed with the default by title sort.

How do I get the app to persist the sort on the page reload? The complete repo is on https://github.com/ElAnonimo/booklister


Solution

  • On the BookList load the getBooks() action return reset the sorted books list to its default sorting so it was easier to persist only the filter prop of the store then sort the books list on each load of the BookList component.

    The changes were made

    to booksReducer

    import {
        GET_BOOKS,
        BOOKS_LOADING,
        DELETE_BOOK
    } from '../actions/types';
    
    const initialState = {
      books: [],
        loading: false
    };
    
    const booksReducer = (state = initialState, action) => {
      switch(action.type) {
        case BOOKS_LOADING:
          return {
            ...state,
            loading: true
          };
        case GET_BOOKS:
          return {
            ...state,
            books: action.payload,
            loading: false
          };
        case DELETE_BOOK:
          return {
            books: [...state.books.filter(book => book._id !== action.payload.id)]
          };
        default:
          return state;
      }
    };
    
    export default booksReducer;
    

    to BookList

    class BookList extends Component {
      componentDidMount() {
        this.props.getBooks();
      }
    
      applySorting(books) {
        const sortBy = this.props.filter.sortBy;
    
        if (!sortBy) {
            return books;
            }
    
            return books.sort((a, b) => a[sortBy] < b[sortBy] ? -1 : 1);
        }
    
      render() {
        const { books, loading } = this.props.books;
    
        let booksContent;
    
        if (!books || loading) {
          booksContent = <Spinner />;
        } else {
          if (books.length > 0) {
            booksContent = this.applySorting(books).map(book => <BookItem book={book} key={book._id} />);
          } else {
            booksContent = <h4>No books found</h4>;
          }
        }
    ...
    ...