diff --git a/react/features/app/components/AbstractApp.js b/react/features/app/components/AbstractApp.js
index 1ff8e1c79..1ef67aa42 100644
--- a/react/features/app/components/AbstractApp.js
+++ b/react/features/app/components/AbstractApp.js
@@ -14,8 +14,8 @@ import {
 } from '../../base/participants';
 import { Fragment, RouteRegistry } from '../../base/react';
 import {
-    getPersistedState,
     MiddlewareRegistry,
+    PersistencyRegistry,
     ReducerRegistry
 } from '../../base/redux';
 import { getProfile } from '../../base/profile';
@@ -346,7 +346,9 @@ export class AbstractApp extends Component {
             middleware = compose(middleware, devToolsExtension());
         }
 
-        return createStore(reducer, getPersistedState(), middleware);
+        return createStore(
+            reducer, PersistencyRegistry.getPersistedState(), middleware
+        );
     }
 
     /**
diff --git a/react/features/base/profile/reducer.js b/react/features/base/profile/reducer.js
index 2337146c1..e406e177b 100644
--- a/react/features/base/profile/reducer.js
+++ b/react/features/base/profile/reducer.js
@@ -4,7 +4,7 @@ import {
     PROFILE_UPDATED
 } from './actionTypes';
 
-import { ReducerRegistry } from '../redux';
+import { PersistencyRegistry, ReducerRegistry } from '../redux';
 
 const DEFAULT_STATE = {
     profile: {}
@@ -12,6 +12,10 @@ const DEFAULT_STATE = {
 
 const STORE_NAME = 'features/base/profile';
 
+PersistencyRegistry.register(STORE_NAME, {
+    profile: true
+});
+
 ReducerRegistry.register(
     STORE_NAME, (state = DEFAULT_STATE, action) => {
         switch (action.type) {
diff --git a/react/features/base/redux/PersistencyRegistry.js b/react/features/base/redux/PersistencyRegistry.js
new file mode 100644
index 000000000..766212cf1
--- /dev/null
+++ b/react/features/base/redux/PersistencyRegistry.js
@@ -0,0 +1,167 @@
+// @flow
+import Logger from 'jitsi-meet-logger';
+import md5 from 'js-md5';
+
+const logger = Logger.getLogger(__filename);
+
+/**
+ * The name of the localStorage store where the app persists its values to.
+ */
+const PERSISTED_STATE_NAME = 'jitsi-state';
+
+/**
+ * The type of the name-config pairs stored in this reducer.
+ */
+declare type PersistencyConfigMap = { [name: string]: Object };
+
+/**
+ * A registry to allow features to register their redux store
+ * subtree to be persisted and also handles the persistency calls too.
+ */
+class PersistencyRegistry {
+    _checksum: string;
+    _elements: PersistencyConfigMap;
+
+    /**
+     * Initiates the PersistencyRegistry.
+     */
+    constructor() {
+        this._elements = {};
+    }
+
+    /**
+     * Returns the persisted redux state. This function takes
+     * the PersistencyRegistry._elements into account as we may have
+     * persisted something in the past that we don't want to retreive anymore.
+     * The next {@link #persistState} will remove those values.
+     *
+     * @returns {Object}
+     */
+    getPersistedState() {
+        let filteredPersistedState = {};
+        let persistedState = window.localStorage.getItem(PERSISTED_STATE_NAME);
+
+        if (persistedState) {
+            try {
+                persistedState = JSON.parse(persistedState);
+            } catch (error) {
+                logger.error(
+                    'Error parsing persisted state', persistedState, error
+                );
+                persistedState = {};
+            }
+
+            filteredPersistedState
+                = this._getFilteredState(persistedState);
+        }
+
+        this._checksum = this._calculateChecksum(filteredPersistedState);
+        logger.info('Redux state rehydrated as', filteredPersistedState);
+
+        return filteredPersistedState;
+    }
+
+    /**
+     * Initiates a persist operation, but its execution will depend on
+     * the current checksums (checks changes).
+     *
+     * @param {Object} state - The redux state.
+     * @returns {void}
+     */
+    persistState(state: Object) {
+        const filteredState = this._getFilteredState(state);
+        const newCheckSum = this._calculateChecksum(filteredState);
+
+        if (newCheckSum !== this._checksum) {
+            try {
+                window.localStorage.setItem(
+                    PERSISTED_STATE_NAME,
+                    JSON.stringify(filteredState)
+                );
+                logger.info(
+                    `Redux state persisted. ${this._checksum} -> ${newCheckSum}`
+                );
+                this._checksum = newCheckSum;
+            } catch (error) {
+                logger.error('Error persisting Redux state', error);
+            }
+        }
+    }
+
+    /**
+     * Registers a new subtree config to be used for the persistency.
+     *
+     * @param {string} name - The name of the subtree the config belongs to.
+     * @param {Object} config - The config object.
+     * @returns {void}
+     */
+    register(name: string, config: Object) {
+        this._elements[name] = config;
+    }
+
+    /**
+     * Calculates the checksum of the current or the new values of the state.
+     *
+     * @private
+     * @param {Object} filteredState - The filtered/persisted Redux state.
+     * @returns {string}
+     */
+    _calculateChecksum(filteredState: Object) {
+        try {
+            return md5.hex(JSON.stringify(filteredState) || '');
+        } catch (error) {
+            logger.error(
+                'Error calculating checksum for state', filteredState, error
+            );
+
+            return '';
+        }
+    }
+
+    /**
+     * Prepares a filtered state from the actual or the
+     * persisted Redux state, based on this registry.
+     *
+     * @private
+     * @param {Object} state - The actual or persisted redux state.
+     * @returns {Object}
+     */
+    _getFilteredState(state: Object) {
+        const filteredState = {};
+
+        for (const name of Object.keys(this._elements)) {
+            if (state[name]) {
+                filteredState[name] = this._getFilteredSubtree(
+                    state[name],
+                    this._elements[name]
+                );
+            }
+        }
+
+        return filteredState;
+    }
+
+    /**
+     * Prepares a filtered subtree based on the config for
+     * persisting or for retreival.
+     *
+     * @private
+     * @param {Object} subtree - The redux state subtree.
+     * @param {Object} subtreeConfig - The related config.
+     * @returns {Object}
+     */
+    _getFilteredSubtree(subtree, subtreeConfig) {
+        const filteredSubtree = {};
+
+        for (const persistedKey of Object.keys(subtree)) {
+            if (subtreeConfig[persistedKey]) {
+                filteredSubtree[persistedKey]
+                    = subtree[persistedKey];
+            }
+        }
+
+        return filteredSubtree;
+    }
+}
+
+export default new PersistencyRegistry();
diff --git a/react/features/base/redux/functions.js b/react/features/base/redux/functions.js
index b2d736a4a..687eb06f4 100644
--- a/react/features/base/redux/functions.js
+++ b/react/features/base/redux/functions.js
@@ -1,12 +1,6 @@
 /* @flow */
 
 import _ from 'lodash';
-import Logger from 'jitsi-meet-logger';
-
-import persisterConfig from './persisterconfig.json';
-
-const logger = Logger.getLogger(__filename);
-const PERSISTED_STATE_NAME = 'jitsi-state';
 
 /**
  * Sets specific properties of a specific state to specific values and prevents
@@ -44,93 +38,6 @@ export function equals(a: any, b: any) {
     return _.isEqual(a, b);
 }
 
-/**
- * Prepares a filtered state-slice (Redux term) based on the config for
- * persisting or for retreival.
- *
- * @private
- * @param {Object} persistedSlice - The redux state-slice.
- * @param {Object} persistedSliceConfig - The related config sub-tree.
- * @returns {Object}
- */
-function _getFilteredSlice(persistedSlice, persistedSliceConfig) {
-    const filteredpersistedSlice = {};
-
-    for (const persistedKey of Object.keys(persistedSlice)) {
-        if (persistedSliceConfig[persistedKey]) {
-            filteredpersistedSlice[persistedKey] = persistedSlice[persistedKey];
-        }
-    }
-
-    return filteredpersistedSlice;
-}
-
-/**
- * Prepares a filtered state from the actual or the
- * persisted Redux state, based on the config.
- *
- * @private
- * @param {Object} state - The actual or persisted redux state.
- * @returns {Object}
- */
-function _getFilteredState(state: Object) {
-    const filteredState = {};
-
-    for (const slice of Object.keys(persisterConfig)) {
-        filteredState[slice] = _getFilteredSlice(
-            state[slice],
-            persisterConfig[slice]
-        );
-    }
-
-    return filteredState;
-}
-
-/**
- *  Returns the persisted redux state. This function takes
- * the persisterConfig into account as we may have persisted something
- * in the past that we don't want to retreive anymore. The next
- * {@link #persistState} will remove those values.
- *
- * @returns {Object}
- */
-export function getPersistedState() {
-    let persistedState = window.localStorage.getItem(PERSISTED_STATE_NAME);
-
-    if (persistedState) {
-        try {
-            persistedState = JSON.parse(persistedState);
-        } catch (error) {
-            return {};
-        }
-
-        const filteredPersistedState = _getFilteredState(persistedState);
-
-        logger.info('Redux state rehydrated', filteredPersistedState);
-
-        return filteredPersistedState;
-    }
-
-    return {};
-}
-
-/**
- * Persists a filtered subtree of the redux state into {@code localStorage}.
- *
- * @param {Object} state - The redux state.
- * @returns {void}
- */
-export function persistState(state: Object) {
-    const filteredState = _getFilteredState(state);
-
-    window.localStorage.setItem(
-        PERSISTED_STATE_NAME,
-        JSON.stringify(filteredState)
-    );
-
-    logger.info('Redux state persisted');
-}
-
 /**
  * Sets a specific property of a specific state to a specific value. Prevents
  * unnecessary state changes (when the specified {@code value} is equal to the
diff --git a/react/features/base/redux/index.js b/react/features/base/redux/index.js
index aa4e6ec3e..f28de691b 100644
--- a/react/features/base/redux/index.js
+++ b/react/features/base/redux/index.js
@@ -1,5 +1,6 @@
 export * from './functions';
 export { default as MiddlewareRegistry } from './MiddlewareRegistry';
+export { default as PersistencyRegistry } from './PersistencyRegistry';
 export { default as ReducerRegistry } from './ReducerRegistry';
 
 import './middleware';
diff --git a/react/features/base/redux/middleware.js b/react/features/base/redux/middleware.js
index fb2645669..682868a32 100644
--- a/react/features/base/redux/middleware.js
+++ b/react/features/base/redux/middleware.js
@@ -1,8 +1,8 @@
 /* @flow */
 import _ from 'lodash';
 
-import { persistState } from './functions';
 import MiddlewareRegistry from './MiddlewareRegistry';
+import PersistencyRegistry from './PersistencyRegistry';
 
 import { toState } from '../redux';
 
@@ -16,7 +16,7 @@ const PERSIST_DELAY = 2000;
  * A throttled function to avoid repetitive state persisting.
  */
 const throttledFunc = _.throttle(state => {
-    persistState(state);
+    PersistencyRegistry.persistState(state);
 }, PERSIST_DELAY);
 
 /**
diff --git a/react/features/base/redux/persisterconfig.json b/react/features/base/redux/persisterconfig.json
deleted file mode 100644
index f3cc178f1..000000000
--- a/react/features/base/redux/persisterconfig.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-    "features/base/profile": {
-        "profile": true
-    }
-}
diff --git a/react/features/base/redux/readme.md b/react/features/base/redux/readme.md
index 6abd64f70..a7b7a1b63 100644
--- a/react/features/base/redux/readme.md
+++ b/react/features/base/redux/readme.md
@@ -1,30 +1,24 @@
 Jitsi Meet - redux state persistency
 ====================================
-Jitsi Meet has a persistency layer that persist a subtree (or specific subtrees) into window.localStorage (on web) or
+Jitsi Meet has a persistency layer that persists a subtree (or specific subtrees) into window.localStorage (on web) or
 AsyncStorage (on mobile).
 
 Usage
 =====
-If a subtree of the redux store should be persisted (e.g. ``'features/base/participants'``), then persistency for that
-subtree should be enabled in the config file by creating a key in
+If a subtree of the redux store should be persisted (e.g. ``'features/base/profile'``), then persistency for that
+subtree should be requested by registering the subtree (and related config) into PersistencyRegistry.
 
+E.g. to register the field ``profile`` of the Redux subtree ``'features/base/profile'`` to be persisted, use:
+
+```JavaScript
+PersistencyRegistry.register('features/base/profile', {
+    profile: true
+});
 ```
-react/features/base/redux/persisterconfig.json
-```
-and defining all the fields of the subtree that has to be persisted, e.g.:
-```json
-{
-    "features/base/participants": {
-        "avatarID": true,
-        "avatarURL": true,
-        "name": true
-    },
-    "another/subtree": {
-        "someField": true
-    }
-}
-```
-When it's done, Jitsi Meet will persist these subtrees/fields and rehidrate them on startup.
+
+in the ``reducer.js`` of the ``profile`` feature.
+
+When it's done, Jitsi Meet will automatically persist these subtrees/fields and rehidrate them on startup.
 
 Throttling
 ==========
diff --git a/react/features/recent-list/actionTypes.js b/react/features/recent-list/actionTypes.js
new file mode 100644
index 000000000..5df42cb9a
--- /dev/null
+++ b/react/features/recent-list/actionTypes.js
@@ -0,0 +1,11 @@
+// @flow
+
+/**
+ * Action type to signal a new addition to the list.
+ */
+export const STORE_CURRENT_CONFERENCE = Symbol('STORE_CURRENT_CONFERENCE');
+
+/**
+ * Action type to signal that a new conference duration info is available.
+ */
+export const UPDATE_CONFERENCE_DURATION = Symbol('UPDATE_CONFERENCE_DURATION');
diff --git a/react/features/recent-list/actions.js b/react/features/recent-list/actions.js
new file mode 100644
index 000000000..6c7dfe214
--- /dev/null
+++ b/react/features/recent-list/actions.js
@@ -0,0 +1,38 @@
+// @flow
+
+import {
+    STORE_CURRENT_CONFERENCE,
+    UPDATE_CONFERENCE_DURATION
+} from './actionTypes';
+
+/**
+ * Action to initiate a new addition to the list.
+ *
+ * @param {Object} locationURL - The current location URL.
+ * @returns {{
+ *      type: STORE_CURRENT_CONFERENCE,
+ *      locationURL: Object
+ * }}
+ */
+export function storeCurrentConference(locationURL: Object) {
+    return {
+        type: STORE_CURRENT_CONFERENCE,
+        locationURL
+    };
+}
+
+/**
+ * Action to initiate the update of the duration of the last conference.
+ *
+ * @param {Object} locationURL - The current location URL.
+ * @returns {{
+ *      type: UPDATE_CONFERENCE_DURATION,
+ *      locationURL: Object
+ * }}
+ */
+export function updateConferenceDuration(locationURL: Object) {
+    return {
+        type: UPDATE_CONFERENCE_DURATION,
+        locationURL
+    };
+}
diff --git a/react/features/recent-list/components/AbstractRecentList.js b/react/features/recent-list/components/AbstractRecentList.js
index 0d83c9183..968b6316a 100644
--- a/react/features/recent-list/components/AbstractRecentList.js
+++ b/react/features/recent-list/components/AbstractRecentList.js
@@ -1,12 +1,9 @@
 // @flow
 
 import { Component } from 'react';
-import { ListView } from 'react-native';
 
 import { appNavigate } from '../../app';
 
-import { getRecentRooms } from '../functions';
-
 /**
  * The type of the React {@code Component} props of {@link AbstractRecentList}
  */
@@ -18,19 +15,6 @@ type Props = {
     dispatch: Dispatch<*>
 };
 
-/**
- * The type of the React {@code Component} state of {@link AbstractRecentList}.
- */
-type State = {
-
-    /**
-     * The {@code ListView.DataSource} to be used for the {@code ListView}. Its
-     * content comes from the native implementation of
-     * {@code window.localStorage}.
-     */
-    dataSource: Object
-};
-
 /**
  * Implements a React {@link Component} which represents the list of conferences
  * recently joined, similar to how a list of last dialed numbers list would do
@@ -38,43 +22,7 @@ type State = {
  *
  * @extends Component
  */
-export default class AbstractRecentList extends Component<Props, State> {
-
-    /**
-     * The datasource that backs the {@code ListView}.
-     */
-    listDataSource = new ListView.DataSource({
-        rowHasChanged: (r1, r2) =>
-            r1.conference !== r2.conference
-                && r1.dateTimeStamp !== r2.dateTimeStamp
-    });
-
-    /**
-     * Initializes a new {@code AbstractRecentList} instance.
-     */
-    constructor() {
-        super();
-
-        this.state = {
-            dataSource: this.listDataSource.cloneWithRows([])
-        };
-    }
-
-    /**
-     * Implements React's {@link Component#componentWillMount()}. Invoked
-     * immediately before mounting occurs.
-     *
-     * @inheritdoc
-     */
-    componentWillMount() {
-        // The following must be done asynchronously because we don't have the
-        // storage initiated on app startup immediately.
-        getRecentRooms()
-            .then(rooms =>
-                this.setState({
-                    dataSource: this.listDataSource.cloneWithRows(rooms)
-                }));
-    }
+export default class AbstractRecentList extends Component<Props> {
 
     /**
      * Joins the selected room.
@@ -96,3 +44,19 @@ export default class AbstractRecentList extends Component<Props, State> {
         return this._onJoin.bind(this, room);
     }
 }
+
+/**
+ * Maps Redux state to component props.
+ *
+ * @param {Object} state - The redux state.
+ * @returns {{
+ *      _homeServer: string,
+ *      _recentList: Array
+ * }}
+ */
+export function _mapStateToProps(state: Object) {
+    return {
+        _homeServer: state['features/app'].app._getDefaultURL(),
+        _recentList: state['features/recent-list'].list
+    };
+}
diff --git a/react/features/recent-list/components/RecentList.native.js b/react/features/recent-list/components/RecentList.native.js
index 58526e0ad..71effacb1 100644
--- a/react/features/recent-list/components/RecentList.native.js
+++ b/react/features/recent-list/components/RecentList.native.js
@@ -2,19 +2,32 @@ import React from 'react';
 import { ListView, Text, TouchableHighlight, View } from 'react-native';
 import { connect } from 'react-redux';
 
-import { Icon } from '../../base/font-icons';
-
-import AbstractRecentList from './AbstractRecentList';
+import AbstractRecentList, { _mapStateToProps } from './AbstractRecentList';
 import styles, { UNDERLAY_COLOR } from './styles';
 
+import { getRecentRooms } from '../functions';
+
+import { Icon } from '../../base/font-icons';
+
 /**
  * The native container rendering the list of the recently joined rooms.
  *
  * @extends AbstractRecentList
  */
 class RecentList extends AbstractRecentList {
+    /**
+     * The datasource wrapper to be used for the display.
+     */
+    dataSource = new ListView.DataSource({
+        rowHasChanged: (r1, r2) =>
+            r1.conference !== r2.conference
+                && r1.dateTimeStamp !== r2.dateTimeStamp
+    });
+
     /**
      * Initializes a new {@code RecentList} instance.
+     *
+     * @inheritdoc
      */
     constructor() {
         super();
@@ -35,14 +48,18 @@ class RecentList extends AbstractRecentList {
      * @returns {ReactElement}
      */
     render() {
-        if (!this.state.dataSource.getRowCount()) {
+        if (!this.props || !this.props._recentList) {
             return null;
         }
 
+        const listViewDataSource = this.dataSource.cloneWithRows(
+            getRecentRooms(this.props._recentList)
+        );
+
         return (
             <View style = { styles.container }>
                 <ListView
-                    dataSource = { this.state.dataSource }
+                    dataSource = { listViewDataSource }
                     enableEmptySections = { true }
                     renderRow = { this._renderRow } />
             </View>
@@ -182,26 +199,4 @@ class RecentList extends AbstractRecentList {
     }
 }
 
-/**
- * Maps (parts of) the Redux state to the associated RecentList's props.
- *
- * @param {Object} state - The Redux state.
- * @private
- * @returns {{
- *     _homeServer: string
- * }}
- */
-function _mapStateToProps(state) {
-    return {
-        /**
-         * The default server name based on which we determine the render
-         * method.
-         *
-         * @private
-         * @type {string}
-         */
-        _homeServer: state['features/app'].app._getDefaultURL()
-    };
-}
-
 export default connect(_mapStateToProps)(RecentList);
diff --git a/react/features/recent-list/functions.js b/react/features/recent-list/functions.js
index eb4447b64..e59fb1a49 100644
--- a/react/features/recent-list/functions.js
+++ b/react/features/recent-list/functions.js
@@ -5,8 +5,6 @@ import moment from 'moment';
 import { i18next } from '../base/i18n';
 import { parseURIString } from '../base/util';
 
-import { RECENT_URL_STORAGE } from './constants';
-
 /**
  * MomentJS uses static language bundle loading, so in order to support dynamic
  * language selection in the app we need to load all bundles that we support in
@@ -36,76 +34,43 @@ require('moment/locale/tr');
 require('moment/locale/zh-cn');
 
 /**
- * Retreives the recent room list and generates all the data needed to be
+ * Retrieves the recent room list and generates all the data needed to be
  * displayed.
  *
- * @returns {Promise} The {@code Promise} to be resolved when the list is
- * available.
+ * @param {Array<Object>} list - The stored recent list retrieved from Redux.
+ * @returns {Array}
  */
-export function getRecentRooms(): Promise<Array<Object>> {
-    return new Promise((resolve, reject) =>
-        window.localStorage._getItemAsync(RECENT_URL_STORAGE).then(
-            /* onFulfilled */ recentURLs => {
-                const recentRoomDS = [];
+export function getRecentRooms(list: Array<Object>): Array<Object> {
+    const recentRoomDS = [];
 
-                if (recentURLs) {
-                    // We init the locale on every list render, so then it
-                    // changes immediately if a language change happens in the
-                    // app.
-                    const locale = _getSupportedLocale();
+    if (list.length) {
+        // We init the locale on every list render, so then it changes
+        // immediately if a language change happens in the app.
+        const locale = _getSupportedLocale();
 
-                    for (const e of JSON.parse(recentURLs)) {
-                        const location = parseURIString(e.conference);
+        for (const e of list) {
+            const location = parseURIString(e.conference);
 
-                        if (location && location.room && location.hostname) {
-                            recentRoomDS.push({
-                                baseURL:
-                                    `${location.protocol}//${location.host}`,
-                                conference: e.conference,
-                                conferenceDuration: e.conferenceDuration,
-                                conferenceDurationString:
-                                    _getDurationString(
-                                        e.conferenceDuration,
-                                        locale
-                                    ),
-                                dateString: _getDateString(e.date, locale),
-                                dateTimeStamp: e.date,
-                                initials: _getInitials(location.room),
-                                room: location.room,
-                                serverName: location.hostname
-                            });
-                        }
-                    }
-                }
+            if (location && location.room && location.hostname) {
+                recentRoomDS.push({
+                    baseURL: `${location.protocol}//${location.host}`,
+                    conference: e.conference,
+                    conferenceDuration: e.conferenceDuration,
+                    conferenceDurationString:
+                        _getDurationString(
+                            e.conferenceDuration,
+                            locale),
+                    dateString: _getDateString(e.date, locale),
+                    dateTimeStamp: e.date,
+                    initials: _getInitials(location.room),
+                    room: location.room,
+                    serverName: location.hostname
+                });
+            }
+        }
+    }
 
-                resolve(recentRoomDS.reverse());
-            },
-            /* onRejected */ reject)
-    );
-}
-
-/**
- * Retreives the recent URL list as a list of objects.
- *
- * @returns {Array} The list of already stored recent URLs.
- */
-export function getRecentURLs() {
-    const recentURLs = window.localStorage.getItem(RECENT_URL_STORAGE);
-
-    return recentURLs ? JSON.parse(recentURLs) : [];
-}
-
-/**
- * Updates the recent URL list.
- *
- * @param {Array} recentURLs - The new URL list.
- * @returns {void}
- */
-export function updateRecentURLs(recentURLs: Array<Object>) {
-    window.localStorage.setItem(
-        RECENT_URL_STORAGE,
-        JSON.stringify(recentURLs)
-    );
+    return recentRoomDS.reverse();
 }
 
 /**
@@ -142,8 +107,7 @@ function _getDateString(dateTimeStamp: number, locale: string) {
  * @returns {string}
  */
 function _getDurationString(duration: number, locale: string) {
-    return _getLocalizedFormatter(duration, locale)
-            .humanize();
+    return _getLocalizedFormatter(duration, locale).humanize();
 }
 
 /**
@@ -158,22 +122,23 @@ function _getInitials(room: string) {
 }
 
 /**
- * Returns a localized date formatter initialized with the
- * provided date (@code Date) or duration (@code Number).
+ * Returns a localized date formatter initialized with the provided date
+ * (@code Date) or duration (@code number).
  *
  * @private
- * @param {Date | number} dateToFormat - The date or duration to format.
+ * @param {Date | number} dateOrDuration - The date or duration to format.
  * @param {string} locale - The locale to init the formatter with. Note: This
  * locale must be supported by the formatter so ensure this prerequisite before
  * invoking the function.
  * @returns {Object}
  */
-function _getLocalizedFormatter(dateToFormat: Date | number, locale: string) {
-    if (typeof dateToFormat === 'number') {
-        return moment.duration(dateToFormat).locale(locale);
-    }
+function _getLocalizedFormatter(dateOrDuration: Date | number, locale: string) {
+    const m
+        = typeof dateOrDuration === 'number'
+            ? moment.duration(dateOrDuration)
+            : moment(dateOrDuration);
 
-    return moment(dateToFormat).locale(locale);
+    return m.locale(locale);
 }
 
 /**
diff --git a/react/features/recent-list/index.js b/react/features/recent-list/index.js
index 7ce19e0e6..daaa5d02a 100644
--- a/react/features/recent-list/index.js
+++ b/react/features/recent-list/index.js
@@ -1,3 +1,4 @@
 export * from './components';
 
 import './middleware';
+import './reducer';
diff --git a/react/features/recent-list/middleware.js b/react/features/recent-list/middleware.js
index a5f1562ac..9f9815620 100644
--- a/react/features/recent-list/middleware.js
+++ b/react/features/recent-list/middleware.js
@@ -3,8 +3,7 @@
 import { CONFERENCE_WILL_LEAVE, SET_ROOM } from '../base/conference';
 import { MiddlewareRegistry } from '../base/redux';
 
-import { LIST_SIZE } from './constants';
-import { getRecentURLs, updateRecentURLs } from './functions';
+import { storeCurrentConference, updateConferenceDuration } from './actions';
 
 /**
  * Middleware that captures joined rooms so they can be saved into
@@ -16,93 +15,47 @@ import { getRecentURLs, updateRecentURLs } from './functions';
 MiddlewareRegistry.register(store => next => action => {
     switch (action.type) {
     case CONFERENCE_WILL_LEAVE:
-        return _updateConferenceDuration(store, next, action);
+        _updateConferenceDuration(store, next);
+        break;
 
     case SET_ROOM:
-        return _storeJoinedRoom(store, next, action);
+        _maybeStoreCurrentConference(store, next, action);
+        break;
     }
 
     return next(action);
 });
 
 /**
- * Stores the recently joined room into {@code window.localStorage}.
+ * Checks if there is a current conference (upon SET_ROOM action), and saves it
+ * if necessary.
  *
- * @param {Store} store - The redux store in which the specified action is being
- * dispatched.
- * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
- * specified action to the specified store.
- * @param {Action} action - The redux action {@code SET_ROOM} which is being
- * dispatched in the specified store.
+ * @param {Store} store - The redux store.
+ * @param {Dispatch} next - The redux {@code dispatch} function.
+ * @param {Action} action - The redux action.
  * @private
- * @returns {Object} The new state that is the result of the reduction of the
- * specified action.
+ * @returns {void}
  */
-function _storeJoinedRoom(store, next, action) {
-    const result = next(action);
-
+function _maybeStoreCurrentConference(store, next, action) {
+    const { locationURL } = store.getState()['features/base/connection'];
     const { room } = action;
 
     if (room) {
-        const { locationURL } = store.getState()['features/base/connection'];
-        const conference = locationURL.href;
-
-        // If the current conference is already in the list, we remove it to add
-        // it to the top at the end.
-        const recentURLs
-            = getRecentURLs()
-                .filter(e => e.conference !== conference);
-
-        // XXX This is a reverse sorted array (i.e. newer elements at the end).
-        recentURLs.push({
-            conference,
-            conferenceDuration: 0,
-            date: Date.now()
-        });
-
-        // maximising the size
-        recentURLs.splice(0, recentURLs.length - LIST_SIZE);
-
-        updateRecentURLs(recentURLs);
+        next(storeCurrentConference(locationURL));
     }
-
-    return result;
 }
 
 /**
- * Updates the conference length when left.
+ * Updates the duration of the last conference stored in the list.
  *
- * @param {Store} store - The redux store in which the specified action is being
- * dispatched.
- * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
- * specified action to the specified store.
- * @param {Action} action - The redux action {@code CONFERENCE_WILL_LEAVE} which
- * is being dispatched in the specified store.
+ * @param {Store} store - The redux store.
+ * @param {Dispatch} next - The redux {@code dispatch} function.
+ * @param {Action} action - The redux action.
  * @private
- * @returns {Object} The new state that is the result of the reduction of the
- * specified action.
+ * @returns {void}
  */
-function _updateConferenceDuration({ getState }, next, action) {
-    const result = next(action);
+function _updateConferenceDuration(store, next) {
+    const { locationURL } = store.getState()['features/base/connection'];
 
-    const { locationURL } = getState()['features/base/connection'];
-
-    if (locationURL && locationURL.href) {
-        const recentURLs = getRecentURLs();
-
-        if (recentURLs.length > 0) {
-            const mostRecentURL = recentURLs[recentURLs.length - 1];
-
-            if (mostRecentURL.conference === locationURL.href) {
-                // The last conference start was stored so we need to update the
-                // length.
-                mostRecentURL.conferenceDuration
-                    = Date.now() - mostRecentURL.date;
-
-                updateRecentURLs(recentURLs);
-            }
-        }
-    }
-
-    return result;
+    next(updateConferenceDuration(locationURL));
 }
diff --git a/react/features/recent-list/reducer.js b/react/features/recent-list/reducer.js
new file mode 100644
index 000000000..d80ff7d85
--- /dev/null
+++ b/react/features/recent-list/reducer.js
@@ -0,0 +1,106 @@
+// @flow
+
+import {
+    STORE_CURRENT_CONFERENCE,
+    UPDATE_CONFERENCE_DURATION
+} from './actionTypes';
+import { LIST_SIZE } from './constants';
+
+import { PersistencyRegistry, ReducerRegistry } from '../base/redux';
+
+/**
+ * The initial state of this feature.
+ */
+const DEFAULT_STATE = {
+    list: []
+};
+
+/**
+ * The Redux subtree of this feature.
+ */
+const STORE_NAME = 'features/recent-list';
+
+/**
+ * Registers the redux store subtree of this feature for persistency.
+ */
+PersistencyRegistry.register(STORE_NAME, {
+    list: true
+});
+
+/**
+ * Reduces the Redux actions of the feature features/recent-list.
+ */
+ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
+    switch (action.type) {
+    case STORE_CURRENT_CONFERENCE:
+        return _storeCurrentConference(state, action);
+    case UPDATE_CONFERENCE_DURATION:
+        return _updateConferenceDuration(state, action);
+    default:
+        return state;
+    }
+});
+
+/**
+* Adds a new list entry to the redux store.
+*
+* @param {Object} state - The redux state.
+* @param {Object} action - The redux action.
+* @returns {Object}
+*/
+function _storeCurrentConference(state, action) {
+    const { locationURL } = action;
+    const conference = locationURL.href;
+
+    // If the current conference is already in the list, we remove it to re-add
+    // it to the top.
+    const list
+        = state.list
+            .filter(e => e.conference !== conference);
+
+    // This is a reverse sorted array (i.e. newer elements at the end).
+    list.push({
+        conference,
+        conferenceDuration: 0, // we don't have this data yet
+        date: Date.now()
+    });
+
+    // maximising the size
+    list.splice(0, list.length - LIST_SIZE);
+
+    return {
+        list
+    };
+}
+
+/**
+ * Updates the conference length when left.
+ *
+ * @param {Object} state - The redux state.
+ * @param {Object} action - The redux action.
+ * @returns {Object}
+ */
+function _updateConferenceDuration(state, action) {
+    const { locationURL } = action;
+
+    if (locationURL && locationURL.href) {
+        const list = state.list;
+
+        if (list.length > 0) {
+            const mostRecentURL = list[list.length - 1];
+
+            if (mostRecentURL.conference === locationURL.href) {
+                // The last conference start was stored so we need to update the
+                // length.
+                mostRecentURL.conferenceDuration
+                    = Date.now() - mostRecentURL.date;
+
+                return {
+                    list
+                };
+            }
+        }
+    }
+
+    return state;
+}