Skip to content

Instantly share code, notes, and snippets.

@HenrikJoreteg
Created July 23, 2018 03:55
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HenrikJoreteg/b7a97a5daf465d87b1973c6dfc2f5220 to your computer and use it in GitHub Desktop.
Save HenrikJoreteg/b7a97a5daf465d87b1973c6dfc2f5220 to your computer and use it in GitHub Desktop.
Light graphQL / redux-bundler example
import gqlFetch from '../helpers/gql-fetch'
import config from '../config'
export default {
name: 'extraArgs',
getExtraArgs: store => {
return {
gqlFetch: gqlFetch({
apiUrl: config.apiUrl,
getToken: () => store.selectAuthToken(),
handleBadStatusResponse: () => {
store.doSignOut()
}
})
}
}
}
const defaults = {
apiUrl: null,
handleBadStatusResponse: response => response,
firstCatch: err => {
throw err
},
getToken: () => null
}
export default spec => (query, variables = null) => {
const opts = Object.assign({}, defaults, spec)
const token = opts.getToken()
const encodedQuery = encodeURIComponent(query.trim())
const encodedVars = variables
? `&variables=${encodeURIComponent(JSON.stringify(variables))}`
: ''
const tokenQuery = token ? `&token=${token}` : ''
return fetch(
`${opts.apiUrl}/graphql?query=${encodedQuery}${encodedVars}${tokenQuery}`,
{
method: 'post'
}
)
.then(response => response.json())
.then(parsed => {
if (parsed.errors) {
parsed.errors.forEach(error => {
if (
(error.message === 'NO_AUTH' || error.message === 'NO_USER') &&
opts.handleBadStatusResponse
) {
opts.handleBadStatusResponse()
}
})
throw parsed.errors
}
return parsed
})
}
import { createSelector } from 'redux-bundler'
import { setItem } from '../helpers/ls'
import omit from '../helpers/omit'
const subscriptionTransform = sub =>
Object.assign({}, sub, {
cancelledAt: Number(sub.cancelledAt),
currentPeriodEnd: Number(sub.currentPeriodEnd),
currentPeriodStart: Number(sub.currentPeriodStart),
start: Number(sub.start)
})
export const userResponseSnippet = `{
id
name
email
emailVerified
token
// lots more stuff here
...
}
`
const actions = [
'USER_FETCH_STARTED',
'USER_FETCH_FINISHED',
'USER_FETCH_FAILED',
]
const STARTED = actions[0]
const FINISHED = actions[1]
const FAILED = actions[2]
export default {
name: 'user',
// we don't want to include START because we don't want the app
// to ever think it's starting in "loading" mode.
persistActions: [FAILED, FINISHED],
getReducer: () => {
const initialState = {
data: null,
loading: false,
error: null
}
return (state = initialState, { payload, type }) => {
if (type === STARTED) {
return Object.assign({}, state, { loading: true })
}
if (type === FAILED) {
return Object.assign({}, state, { loading: false, error: payload })
}
if (type === FINISHED) {
return Object.assign({}, state, {
error: null,
loading: false,
data: omit(payload, ['inhalationAgents', 'ivDrugs', 'token'])
})
}
if (type === CLEARED) {
return initialState
}
return state
}
},
doFetchUser: () => ({ gqlFetch, dispatch }) => {
dispatch({ type: STARTED })
return gqlFetch(`{ user ${userResponseSnippet} }`)
.then(res => {
store.doReceiveUser(res.data.user)
})
.catch(err => dispatch({ type: FAILED, payload: err }))
},
doUpdateUser: properties => ({ gqlFetch, dispatch }) => {
dispatch({ type: STARTED })
return gqlFetch(
`mutation updateUser($properties: UserInput!) {
updateUser(properties: $properties) ${userResponseSnippet}
}`,
{ properties }
)
.then(res => {
store.doReceiveUser(res.data.updateUser)
})
.catch(err => dispatch({ type: FAILED, payload: err }))
},
doUpdatePaymentMethod: token => ({ gqlFetch }) => {
return gqlFetch(`
mutation {
addPaymentMethod(stripeToken:"${token}") ${userResponseSnippet}
}
`)
.then(res => {
store.doReceiveUser(res.data.addPaymentMethod)
})
.catch(err => {
const first = err[0].message
if (first) {
const [type, receivedMessage] = first.split('|')
if (receivedMessage) {
throw Error(receivedMessage)
}
throw Error(type)
}
})
},
doCancelSubscription: () => ({ gqlFetch, store }) => {
const subscription = store.selectUserSubscription()
const id = subscription && subscription.id
if (!id) {
return
}
return gqlFetch(`
mutation {
cancelSubscription(id:"${id}") ${userResponseSnippet}
}
`).then(res => {
store.doReceiveUser(res.data.cancelSubscription)
})
},
doReceiveUser: user => {
setItem('token', user.token)
return { type: FINISHED, payload: user }
},
selectUserRaw: state => state.user,
selectUser: state => state.user.data,
selectUserIsLoading: state => state.user.loading,
selectUserSubscription: createSelector('selectUser', user => {
const sub = user && user.subscription
return sub && subscriptionTransform(sub)
}),
selectUserPaymentMethod: createSelector(
'selectUser',
user => user && user.paymentMethod
),
reactShouldFetchUser: createSelector(
'selectUserRaw',
'selectIsSignedIn',
(userRaw, isSignedIn) => {
if (!userRaw.loading && !userRaw.data && isSignedIn) {
return { actionCreator: 'doFetchUser' }
}
}
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment