Skip to content

Commit

Permalink
Version v1.2.1 (#44)
Browse files Browse the repository at this point in the history
* Single item with iterable child population now supported with `populatedDataToJS`
* `getRedirectResult` no longer calls dispatches `LOGOUT` on null response (fixes refresh issue with redux-auth-wrapper).
* `isLoading` typo corrected to `isInitalizing` in logout reducer case
  • Loading branch information
prescottprue committed Jan 28, 2017
1 parent 62daf9c commit fc0cce8
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 82 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-redux-firebase",
"version": "1.2.0",
"version": "1.2.1",
"description": "Redux integration for Firebase. Comes with a Higher Order Component for use with React.",
"main": "dist/index.js",
"module": "src/index.js",
Expand Down
39 changes: 19 additions & 20 deletions src/actions/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,27 +184,26 @@ export const init = (dispatch, firebase) => {
if (firebase._.config.enableRedirectHandling) {
firebase.auth().getRedirectResult()
.then((authData) => {
if (!authData || !authData.user) {
return dispatch({ type: LOGOUT })
if (authData && authData.user) {
const { user } = authData

firebase._.authUid = user.uid
watchUserProfile(dispatch, firebase)

dispatchLogin(dispatch, user)

createUserProfile(
dispatch,
firebase,
user,
{
email: user.email,
displayName: user.providerData[0].displayName || user.email,
avatarUrl: user.providerData[0].photoURL,
providerData: user.providerData
}
)
}
const { user } = authData

firebase._.authUid = user.uid
watchUserProfile(dispatch, firebase)

dispatchLogin(dispatch, user)

createUserProfile(
dispatch,
firebase,
user,
{
email: user.email,
displayName: user.providerData[0].displayName || user.email,
avatarUrl: user.providerData[0].photoURL,
providerData: user.providerData
}
)
}).catch((error) => {
dispatchLoginError(dispatch, error)
return Promise.reject(error)
Expand Down
113 changes: 63 additions & 50 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,29 @@ export const dataToJS = (data, path, notSetValue) => {

return data
}

/**
* @private
* @description Build child list based on populate
* @param {Map} data - Immutable Map to be converted to JS object (state.firebase)
* @param {Object} list - Path of parameter to load
* @param {Object} populate - Object with population settings
*/
export const buildChildList = (data, list, populate) =>
mapValues(list, (val, key) => {
let getKey = val
// Handle key: true lists
if (val === true) {
getKey = key
}
// Set to child under key if populate child exists
if (dataToJS(data, `${populate.root}/${getKey}`)) {
return dataToJS(data, `${populate.root}/${getKey}`)
}
// Populate child does not exist
return val === true ? val : getKey
})

/**
* @description Convert parameter under "data" path of Immutable Map to a
* Javascript object with parameters populated based on populates array
Expand All @@ -188,71 +211,61 @@ export const dataToJS = (data, path, notSetValue) => {
* import { connect } from 'react-redux'
* import { firebaseConnect, helpers } from 'react-redux-firebase'
* const { dataToJS } = helpers
* const populates = [{ child: 'owner', root: 'users' }]
*
* const fbWrapped = firebaseConnect(['/todos'])(App)
* const fbWrapped = firebaseConnect([
* { path: '/todos', populates } // load "todos" and matching "users" to redux
* ])(App)
*
* export default connect(({ firebase }) => ({
* // this.props.todos loaded from state.firebase.data.todos
* // each todo has child 'owner' populated from matching uid in 'users' root
* todos: populatedDataToJS(firebase, 'todos', [{ child: 'owner', root: 'users' }])
* todos: populatedDataToJS(firebase, 'todos', populates)
* }))(fbWrapped)
*/
export const populatedDataToJS = (data, path, populates, notSetValue) => {
if (!data) {
return notSetValue
}

const pathArr = `/data${fixPath(path)}`.split(/\//).slice(1)

if (data.getIn) {
if (!toJS(data.getIn(pathArr, notSetValue))) {
return toJS(data.getIn(pathArr))
}
const populateObjs = getPopulateObjs(populates)
// reduce array of populates to object of combined populated data
return reduce(
map(populateObjs, (p, obj) =>
// map values of list
mapValues(toJS(data.getIn(pathArr)), (child, i) => {
// no matching child parameter
if (!child[p.child]) {
return child
}
// populate child is key
if (isString(child[p.child])) {
if (toJS(data.getIn(['data', p.root, child[p.child]]))) {
return {
...child,
[p.child]: toJS(data.getIn(['data', p.root, child[p.child]]))
}
const populateObjs = getPopulateObjs(populates)
// reduce array of populates to object of combined populated data
return reduce(
map(populateObjs, (p, obj) => {
// single item with iterable child
if (dataToJS(data, path)[p.child]) {
return {
...dataToJS(data, path),
[p.child]: buildChildList(data, dataToJS(data, path)[p.child], p)
}
}
// list with child param in each item
return mapValues(dataToJS(data, path), (child, i) => {
// no matching child parameter
if (!child[p.child]) {
return child
}
// populate child is key
if (isString(child[p.child])) {
if (dataToJS(data, `${p.root}/${child[p.child]}`)) {
return {
...child,
[p.child]: dataToJS(data, `${p.root}/${child[p.child]}`)
}
// matching child does not exist
return child
}
// populate child list
return {
...child,
[p.child]: mapValues(child[p.child], (val, key) => { // iterate of child list
let getKey = val
// Handle key: true lists
if (val === true) {
getKey = key
}
// Set to child under key if populate child exists
if (toJS(data.getIn(['data', p.root, getKey]))) {
return toJS(data.getIn(['data', p.root, getKey]))
}
// Populate child does not exist
return val === true ? val : getKey
})
}
})
), (obj, v) =>
// matching child does not exist
return child
}
// populate child list
return {
...child,
[p.child]: buildChildList(data, child[p.child], p)
}
})
}), (obj, v) =>
// reduce data from all populates to one object
Object.assign({}, v, obj),
)
}

return data
)
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export default (state = initialState, action = {}) => {
auth: null,
authError: null,
profile: null,
isLoading: false,
isInitializing: false,
data: {}
})

Expand Down
2 changes: 1 addition & 1 deletion src/utils/populate.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export const promisesForPopulate = (firebase, originalData, populatesIn) => {
})

// Return original data after population promises run
return Promise.all(promisesArray).then(d => results)
return Promise.all(promisesArray).then(() => results)
}

export default { promisesForPopulate }
25 changes: 19 additions & 6 deletions test/unit/helpers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ describe('Helpers:', () => {
it('exists', () => {
expect(helpers).to.respondTo('toJS')
})

it('handles non-immutable data', () => {
expect(helpers.toJS(exampleData)).to.equal(exampleData)
})

it('handles immutable data', () => {
expect(helpers.toJS(exampleState)).to.be.an.object
})
Expand All @@ -47,22 +49,26 @@ describe('Helpers:', () => {
it('exists', () => {
expect(helpers).to.respondTo('pathToJS')
})

it('passes notSetValue', () => {
expect(helpers.pathToJS(null, '/some', exampleData))
.to
.equal(exampleData)
})

it('gets data', () => {
expect(helpers.pathToJS(exampleState, '/some', exampleData))
.to
.equal(exampleData)
})

it('gets meta (string key)', () => {
expect(helpers.pathToJS(exampleState, 'timestamp/some/path'))
.to
.have
.keys('test')
})

it('returns state if its not an immutable Map', () => {
const fakeState = {}
expect(helpers.pathToJS(fakeState, 'asdf'))
Expand All @@ -75,17 +81,20 @@ describe('Helpers:', () => {
it('exists', () => {
expect(helpers).to.respondTo('dataToJS')
})

it('passes notSetValue', () => {
expect(helpers.dataToJS(null, '/some', exampleData))
.to
.equal(exampleData)
})

it('gets data from state', () => {
const path = 'some'
expect(helpers.dataToJS(exampleState, path, exampleData))
.to
.equal(exampleData.data[path])
})

it('returns state if its not an immutable Map', () => {
const fakeState = { }
expect(helpers.dataToJS(fakeState, 'asdf'))
Expand All @@ -98,22 +107,19 @@ describe('Helpers:', () => {
it('exists', () => {
expect(helpers).to.respondTo('populatedDataToJS')
})

it('passes notSetValue', () => {
expect(helpers.populatedDataToJS(null, '/some', [], exampleData))
.to
.equal(exampleData)
})

it('returns undefined for non existant path', () => {
expect(helpers.populatedDataToJS(exampleState, '/asdf', []))
.to
.equal(undefined)
})
it('returns state if its not an immutable Map', () => {
const fakeState = { }
expect(helpers.populatedDataToJS(fakeState, 'asdf'))
.to
.equal(fakeState)
})

it('returns unpopulated data for no populates', () => {
const path = '/projects'
expect(helpers.populatedDataToJS(exampleState, path, []))
Expand Down Expand Up @@ -185,16 +191,19 @@ describe('Helpers:', () => {
it('exists', () => {
expect(helpers).to.respondTo('customToJS')
})

it('handles non-immutable state', () => {
expect(helpers.customToJS(exampleData, '/some', 'some'))
.to
.equal(exampleData)
})

it('passes notSetValue', () => {
expect(helpers.customToJS(null, '/some', 'some', exampleData))
.to
.equal(exampleData)
})

it('passes custom data', () => {
expect(helpers.customToJS(exampleState, '/some', 'snapshot'))
.to
Expand All @@ -206,9 +215,11 @@ describe('Helpers:', () => {
it('exists', () => {
expect(helpers).to.respondTo('isLoaded')
})

it('defaults to true when no arguments passed', () => {
expect(helpers.isLoaded()).to.be.true
})

it('returns true when is loaded', () => {
expect(helpers.isLoaded('some')).to.be.true
})
Expand All @@ -218,9 +229,11 @@ describe('Helpers:', () => {
it('exists', () => {
expect(helpers).to.respondTo('isEmpty')
})

it('returns false when not loaded', () => {
expect(helpers.isEmpty('asdf')).to.be.false
})

it('returns true when is loaded', () => {
expect(helpers.isEmpty([{}])).to.be.false
})
Expand Down
5 changes: 2 additions & 3 deletions test/unit/reducer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ const exampleEmptyState = fromJS(emptyState)

describe('reducer', () => {
it('is a function', () => {
expect(firebaseStateReducer)
.to.be.a.function
expect(firebaseStateReducer).to.be.a.function
})

it('handles no initialState', () => {
Expand Down Expand Up @@ -109,7 +108,7 @@ describe('reducer', () => {
auth: null,
authError: null,
profile: null,
isLoading: false,
isInitializing: false,
data: {}
}))
})
Expand Down

0 comments on commit fc0cce8

Please sign in to comment.