From b56f0f9431ee84554acdf37fc27bd2af23321bc4 Mon Sep 17 00:00:00 2001 From: Tamlyn Rhodes <tamlyn@tamlyn.org> Date: Wed, 22 Nov 2017 13:41:31 +0000 Subject: [PATCH] Create entity selectors Use reselect to aid with selector composition. Remove currentUser.isAuthenticated state property as it can be inferred. --- package.json | 3 ++- src/components/AuthenticatedComponent.jsx | 3 ++- src/components/UpdateSubscriber.jsx | 3 ++- src/helpers/Authorize.jsx | 6 ++--- src/helpers/withAuthsome.js | 27 ++++++++----------- src/reducers/currentUser.js | 18 +++++-------- src/selectors/index.js | 32 +++++++++++++++++++++++ test/reducers/currentUser.test.js | 6 ++--- test/selectors/index.test.js | 14 ++++++++++ yarn.lock | 4 +++ 10 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 src/selectors/index.js create mode 100644 test/selectors/index.test.js diff --git a/package.json b/package.json index 1e6df06..a9f522f 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "redux": "^3.6.0", "redux-form": "^7.0.3", "redux-logger": "^3.0.1", - "redux-thunk": "^2.2.0" + "redux-thunk": "^2.2.0", + "reselect": "^3.0.1" }, "devDependencies": { "babel-cli": "^6.26.0", diff --git a/src/components/AuthenticatedComponent.jsx b/src/components/AuthenticatedComponent.jsx index 9960140..dcb3df2 100644 --- a/src/components/AuthenticatedComponent.jsx +++ b/src/components/AuthenticatedComponent.jsx @@ -5,6 +5,7 @@ import { push } from 'react-router-redux' import PropTypes from 'prop-types' import actions from '../actions' +import { selectCurrentUser } from '../selectors' export class AuthenticatedComponent extends React.Component { componentWillMount() { @@ -39,7 +40,7 @@ AuthenticatedComponent.propTypes = { function mapState(state) { return { isFetching: state.currentUser.isFetching, - isAuthenticated: state.currentUser.isAuthenticated, + isAuthenticated: !!selectCurrentUser(state), } } diff --git a/src/components/UpdateSubscriber.jsx b/src/components/UpdateSubscriber.jsx index 9f483e2..9d2c31f 100644 --- a/src/components/UpdateSubscriber.jsx +++ b/src/components/UpdateSubscriber.jsx @@ -7,6 +7,7 @@ import _ from 'lodash/fp' import * as T from '../actions/types' import 'event-source-polyfill' import token from '../helpers/token' +import { selectCurrentUser } from '../selectors' const actionMap = { 'collection:create': T.CREATE_COLLECTION_SUCCESS, @@ -171,7 +172,7 @@ UpdateSubscriber.propTypes = { export default connect( state => ({ - currentUser: state.currentUser, + currentUser: selectCurrentUser(state), }), dispatch => ({ handleUpdate: (type, body) => dispatch({ type, ...body }), diff --git a/src/helpers/Authorize.jsx b/src/helpers/Authorize.jsx index 2d23985..9706550 100644 --- a/src/helpers/Authorize.jsx +++ b/src/helpers/Authorize.jsx @@ -6,6 +6,7 @@ import { connect } from 'react-redux' import { compose } from 'redux' import withAuthsome from './withAuthsome' +import { selectCurrentUser } from '../selectors' export class Authorize extends React.Component { constructor(props) { @@ -57,10 +58,7 @@ Authorize.propTypes = { function mapState(state) { return { - teams: state.teams, - collections: state.collections, - fragments: state.fragments, - currentUser: state.currentUser.user, + currentUser: selectCurrentUser(state), } } diff --git a/src/helpers/withAuthsome.js b/src/helpers/withAuthsome.js index 01ab39e..4e6941d 100644 --- a/src/helpers/withAuthsome.js +++ b/src/helpers/withAuthsome.js @@ -1,31 +1,26 @@ import Authsome from 'authsome' import { connect } from 'react-redux' import config from 'config' +import { + selectCollection, + selectFragment, + selectTeam, + selectUser, +} from '../selectors' const mode = require(config.authsome.mode) // higher order component to inject authsome into a component export default function withAuthsome() { - const authsome = new Authsome({...config.authsome, mode}, {}) + const authsome = new Authsome({ ...config.authsome, mode }, {}) function mapState(state) { authsome.context = { // fetch entities from store instead of database models: { - Collection: { - find: id => - state.collections.find(collection => collection.id === id), - }, - Fragment: { - find: id => state.fragments[id], - }, - Team: { - find: id => state.teams.find(team => team.id === id), - }, - User: { - find: id => { - return state.users.users.find(user => user.id === id) - }, - }, + Collection: { find: id => selectCollection(state, { id }) }, + Fragment: { find: id => selectFragment(state, { id }) }, + Team: { find: id => selectTeam(state, { id }) }, + User: { find: id => selectUser(state, { id }) }, }, } diff --git a/src/reducers/currentUser.js b/src/reducers/currentUser.js index a14eb84..92db56f 100644 --- a/src/reducers/currentUser.js +++ b/src/reducers/currentUser.js @@ -5,26 +5,23 @@ import { LOGOUT_SUCCESS, } from '../actions/types' -export default function( - state = { - isFetching: false, - isAuthenticated: false, - }, - action, -) { +const initialState = { + isFetching: false, + user: null, +} + +export default function(state = initialState, action) { switch (action.type) { case GET_CURRENT_USER_REQUEST: return { ...state, isFetching: true, - isAuthenticated: false, } case GET_CURRENT_USER_SUCCESS: return { ...state, isFetching: false, - isAuthenticated: true, user: action.user, } @@ -32,13 +29,12 @@ export default function( return { ...state, isFetching: false, - isAuthenticated: false, + user: null, } case LOGOUT_SUCCESS: return { isFetching: false, - isAuthenticated: false, user: null, } diff --git a/src/selectors/index.js b/src/selectors/index.js new file mode 100644 index 0000000..acc0aa1 --- /dev/null +++ b/src/selectors/index.js @@ -0,0 +1,32 @@ +import { createSelector } from 'reselect' + +export const selectCollections = state => state.collections +export const selectTeams = state => state.teams +export const selectUsers = state => state.users.users +export const selectFragments = state => state.fragments + +export const selectCollection = createSelector( + selectCollections, + (_, { id }) => id, + (collections, id) => collections.find(collection => collection.id === id), +) + +export const selectFragment = createSelector( + selectFragments, + (_, { id }) => id, + (fragments, id) => fragments[id], +) + +export const selectTeam = createSelector( + selectTeams, + (_, { id }) => id, + (teams, id) => teams.find(team => team.id === id), +) + +export const selectUser = createSelector( + selectUsers, + (_, { id }) => id, + (users, id) => users.find(user => user.id === id), +) + +export const selectCurrentUser = state => state.currentUser.user diff --git a/test/reducers/currentUser.test.js b/test/reducers/currentUser.test.js index de55c02..581ba79 100644 --- a/test/reducers/currentUser.test.js +++ b/test/reducers/currentUser.test.js @@ -22,7 +22,6 @@ describe('currentUser reducers', () => { ) expect(actual).toEqual({ isFetching: false, - isAuthenticated: true, user: mockuser, }) }) @@ -34,7 +33,7 @@ describe('currentUser reducers', () => { type: T.GET_CURRENT_USER_FAILURE, }, ) - expect(actual).toEqual({ isFetching: false, isAuthenticated: false }) + expect(actual).toEqual({ isFetching: false, user: null }) }) it('currentUser request', () => { @@ -44,7 +43,7 @@ describe('currentUser reducers', () => { type: T.GET_CURRENT_USER_REQUEST, }, ) - expect(actual).toEqual({ isFetching: true, isAuthenticated: false }) + expect(actual).toEqual({ isFetching: true }) }) it('logout success', () => { @@ -58,7 +57,6 @@ describe('currentUser reducers', () => { ) expect(actual).toEqual({ isFetching: false, - isAuthenticated: false, user: null, }) }) diff --git a/test/selectors/index.test.js b/test/selectors/index.test.js new file mode 100644 index 0000000..2d49d3f --- /dev/null +++ b/test/selectors/index.test.js @@ -0,0 +1,14 @@ +import { selectUser } from '../../src/selectors' + +describe('Selectors', () => { + it('selectUser', () => { + const user2 = { id: 2, foo: 'bar' } + const state = { + users: { + users: [{ id: 1 }, user2], + }, + } + + expect(selectUser(state, { id: 2 })).toEqual(user2) + }) +}) diff --git a/yarn.lock b/yarn.lock index 2860c32..4941383 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3936,6 +3936,10 @@ require-uncached@^1.0.3: caller-path "^0.1.0" resolve-from "^1.0.0" +reselect@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147" + resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" -- GitLab