Skip to content

Instantly share code, notes, and snippets.

@aqwert
Last active May 22, 2021 17:23
Show Gist options
  • Save aqwert/f80088cc77be5b2b856346cdca5d9339 to your computer and use it in GitHub Desktop.
Save aqwert/f80088cc77be5b2b856346cdca5d9339 to your computer and use it in GitHub Desktop.
Example of React-native with Redux and GraphQL

User -> UserTeam -> Team (A user can have many teams)

Each GraphQL type is a separate reducer in the app which captures the state for each instance.

  • Assumes the user is logged in and result stored in the authStore (authReducer)
  • Assumes that the boilerplate app setup is completed
type Team implements Node {
createdAt: DateTime!
id: ID! @isUnique
name: String!
updatedAt: DateTime!
userTeams: [UserTeam!]! @relation(name: "UserTeamOnTeam")
}
type User implements Node {
createdAt: DateTime!
email: String @isUnique
id: ID! @isUnique
password: String
userTeams: [UserTeam!]! @relation(name: "UserOnUserTeam")
updatedAt: DateTime!
}
type UserTeam implements Node {
createdAt: DateTime!
id: ID! @isUnique
user: User! @relation(name: "UserOnUserTeam")
updatedAt: DateTime!
team: Team @relation(name: "UserTeamOnTeam")
}
import { combineReducers } from 'redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import authStore from './authReducer';
import userStore from './userReducer';
import userTeamStore from './userTeamReducer';
import teamStore from './teamReducer';
const combined = combineReducers({
authStore,
userStore,
userTeamStore,
teamStore,
});
export default function configureStore() {
let store = createStore(combined, applyMiddleware(thunk));
return store;
}
function postQL(queryMutation, variables) {
return request('https://api.graph.cool/simple/v1/qwertyuiop', queryMutation, variables);
}
const createUserTeamMutation = `
mutation createUserTeam($userId: ID, $teamName: String!) {
createUserTeam(
userId: $userId,
team: {
name: $teamName
}) {
id,
team {
id
}
}
}
`
export function createUserTeam(team) {
team = { ...team }
return (dispatch, getState) => {
const userId = getState().authStore.userId;
const variables = {
userId: userId,
teamName: team.name
}
postQL(createUserTeamMutation, variables)
.then(data => {
dispatch({
type: "COMPOSITE", //so it can update multiple reducers with only the data it needs
"TEAM_RESULT": createTeamResult(team, data.createUserTeam),
"USERTEAM_RESULT": createUserTeamResult(team, data.createUserTeam),
"USER_RESULT": updateUserResult(userId, team, data.createUserTeam),
})
})
.catch(error => {
console.log("createUserTeam ERROR", error);
//todo: better handling here
})
}
}
function createTeamResult(team, result) {
return {
team: {
id: result.team.id,
name: team.name,
}
}
}
function createUserTeamResult(team, result) {
return {
userTeam: {
id: result.id,
teamId: result.team.id,
}
}
}
function updateUserResult(userId, team, result) {
return {
user: {
id: userId,
},
modify: {
userTeams: {
insert: [result.id]
}
}
}
}
var _ = require('lodash');
export function reduceIfAction(state, action, actionType, func) {
if (action.type === "COMPOSITE") {
if (action[actionType]) {
const data = action[actionType];
state = func(state, data);
}
} else {
if (action.type === actionType) {
state = func(state, action);
}
}
return state;
}
export function reduceStateIds(stateIds, item) {
let data = {};
if (_.isNil(stateIds[item.id])) {
data = {
...item,
}
} else {
data = {
...stateIds[item.id], //could be other data already on the object
...item,
}
}
return {
...stateIds[item.id], //copy first,
...data,
}
}
export function reduceState(state, ids) {
return {
...state,
ids: ids,
}
}
import PropTypes from 'prop-types';
import React from 'react';
import { View, Text } from 'react-native'
const TeamCard = props => {
const { team } = props;
if (team.meta.noTeam) {
//todo, allow clicking card to delete
return (
<View>
<Text style={styleSheet.disabledText}>(This team has been deleted) Tap to remove.</Text>
</View>
)
}
else {
return (
<View>
<Text>{team.name}</Text>
</View>
)
}
}
TeamCard.defaultProps = {
}
TeamCard.propTypes = {
team: PropTypes.any.isRequired
}
export default TeamCard;
//component to render a list of teams... (slimmed down)
import React, { Component, } from 'react'
import { ScrollView, View } from 'react-native';
import { connect } from 'react-redux';
import TeamCard from './teamCard';
var _ = require('lodash');
const ios = Platform.OS === 'ios';
class TeamList extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
const { userTeamIds, userTeams, teams } = this.props;
const displayTeams = userTeamIds.map(id => {
const ut = userTeams[id];
const utId = ut.teamId;
const team = teams[utId];
if (_.isNil(team)) {
return {
meta: {
noTeam: true,
}
}
}
return {
name: team.name,
meta: {
noTeam: false,
userTeam: ut,
team: team,
}
}
})
return (
<View>
<ScrollView>
{
displayTeams.map((team, i) => {
return (
<TeamCard team={team} key={i} />
)
})
}
</ScrollView>
</View>
)
}
}
function mapStateToProps({ authStore, userStore, teamStore, userTeamStore }) {
const userId = authStore.userId;
return {
userTeamIds: userStore.ids[userId].userTeams,
userTeams: userTeamStore.ids,
teams: teamStore.ids,
}
}
function mapDispatchToProps(dispatch) {
return {
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TeamList)
import { reduceIfAction, reduceStateIds, reduceState } from '../reducerHelper'
var _ = require('lodash');
const initialState = {
ids: {
/* schema
'{id-of-team}': {
id: '{id-of-team},
name: '{name-of-team}'
}
*/
}
}
export default function team(state = initialState, action) {
state = reduceIfAction(state, action, "TEAM_RESULT", teamsUpdateCompleted);
return state;
}
function teamsUpdateCompleted(state, action) {
let data = { ...state.ids }; //keep all ids intact via copy
//list
if (!_.isNil(action.teams)) {
action.teams.forEach(t => {
data[t.id] = reduceStateIds(state.ids, t);
})
}
//single
if (!_.isNil(action.team)) {
const id = action.team.id;
data[id] = reduceStateIds(state.ids, action.team);
}
return { ...reduceState(state, data) };
}
import { reduceIfAction, reduceStateIds, reduceState } from '../reducerHelper'
var _ = require('lodash');
const initialState = {
ids: {
/* schema
'{id-of-user}': {
id: '{id-of-user}'
username: '{email}' //for now will be actual name
userTeams: [ {id-of-userTeam} ]
},
*/
}
}
export default function userReducer(state = initialState, action) {
state = reduceIfAction(state, action, "USER_RESULT", userUpdatedCompleted);
return state;
}
function userUpdatedCompleted(state, action) {
const userId = action.user.id;
let data = { ...state.ids }; //keep all ids intact via copy
if (!_.isNil(action.user.userTeams)) {
action.user._userTeamCount = action.user.userTeams.length;
}
data[userId] = modifyItem(reduceStateIds(state.ids, action.user), action);
return {
...reduceState(state, data),
}
}
function modifyItem(item, action) {
if (!_.isNil(action.modify)) {
if (!_.isNil(action.modify.userTeams)) {
if (!_.isNil(action.modify.userTeams.insert)) {
item.userTeams = [...action.modify.userTeams.insert, ...item.userTeams]; //new items at front
item._userTeamCount = item.userTeams.length;
}
//todo more: upsert, update, delete
}
}
return item;
}
import { reduceIfAction, reduceStateIds, reduceState } from '../reducerHelper'
var _ = require('lodash');
const initialState = {
ids: {
/* schema
'{id-of-userTeam}': {
id: '{id-of-userTeam},
teamId: '{id-of-team}'
},
*/
}
}
export default function userTeam(state = initialState, action) {
state = reduceIfAction(state, action, "USERTEAM_RESULT", userTeamsUpdateCompleted);
return state;
}
function userTeamsUpdateCompleted(state, action) {
let data = { ...state.ids }; //keep all ids intact via copy
//list of things
if (!_.isNil(action.userTeams)) {
action.userTeams.forEach(ut => {
data[ut.id] = reduceStateIds(state.ids, ut);
})
}
//single thing
if (!_.isNil(action.userTeam)) {
const id = action.userTeam.id;
data[id] = reduceStateIds(state.ids, action.userTeam);
}
return { ...reduceState(state, data) };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment