diff --git a/package.json b/package.json index 1e6df0634be07151e810449841e965bfc1ad74d5..a9f522f636bfea82c0f7b6a0fdc16cd9d60c3fe8 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 996014060f574e630f1f5ca1812eafc39dd73d81..dcb3df2dea7f0b989171f29e9ebc5aa77e89b27d 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 9f483e24f67f90331404033947f7ff20bff94d9e..9d2c31fe7f5a83f98c4fecdc1041b54e08bf08dc 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 2d239859786a5dc67cbceb6b3674352c94b59ce7..97065508adea66613dc7386c51d0630320126d2d 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 01ab39ee4f3affd6f201aec3ad078177199952e0..4e6941d633b3dbdf2839b93390dfa70785293291 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 a14eb84953ef93b62cab41a64474a2d4b13ffa3c..92db56fbcb4780c9deb2cf1cf06b230910865567 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 0000000000000000000000000000000000000000..acc0aa170ff48668947886f8153928595e34a8bd --- /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 de55c028c7faf919ebc2a7ee6b3d95d7a82845bc..581ba790d1c3753ff7b389dce235ed54bea0c405 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 0000000000000000000000000000000000000000..2d49d3f28be03a5e32b87c6f2ecfcc136500719c --- /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 2860c32ad9120581836b3365919e9540073b4357..49413833db7352a93bea1face994a8eec4f412f6 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"