This set of helpers facilitates the use of GraphQL and Apollo with your React/Redux application.
It allows you to:
- Define queries and mutations using object notation for better code clarity
- Copy query results to redux store for easier optimistic callbacks
- Use higer order elements to display messages based on query easier
The server functionality has been moved to: apollo-modules
Client helpers exists in the apollo-mantra
module and focus on easy integration of apollo in redux projects.
Following is a list of helpers:
These helpers provide a possibility to define queries and mutations in object format, which facilitates the code readibility. Following is the structure of the query.
interface IQuery {
query: string;
variables?: Object;
pollInterval: number; // watch query only
returnPartialData: boolean; // watch query only
forceFetch: boolean;
optimisticCallback?: (dispatch: Function, state: () => any) => void;
thenCallback?: (data: any, dispatch: Function, state: () => any) => void;
errorCallback?: (errors: any, dispatch: Function, state: () => any) => void;
catchCallback?: (error: any, dispatch: Function, state: () => any) => void;
finalCallback?: (dispatch: Function, state: () => any) => void;
}
query(query: IQuery): void
create query using object definitionwatchQuery(query: IQuery): void
create watch query using object definiton and return an observermutation(query: IQuery): void
create mutation using object definition
The query returns an action that should be dispatched via redux store, watchQuery returns an observer.
createApp(context: any, options: IOptions): any
create a new apollo based application and expose context into all redux and apollo function calls
connect<T>(funcs: IConnectFunctions): (component: any) => React.StatelessComponent<T>
use as standard redux or apollo connect function, but all realted functions will now have application context as their first parameter:mapStateToProps(context: any, state: any, ownProps: any): void
mapDispatchToProps(context: any, dispatch: any, ownProps: any): void
mergeProps(context: any, state: any, ownProps: any): void
mapQueriesToProps(context: any, props: any): void
mapMutationsToProps(context: any, props: any): void
loadingContainer(component: any, keys?: string[]): any
higher order component that shows a loading control while queries are loadingloadingContainer(component: any, loading?: any, keys?: string[]): any
higher order component that shows a loading control while queries are loadingqueriesFinished(state: IApolloState): boolean
decides whether all queries currently finished loading
Please see the reducer example.
copyQuery(state: Object, stateKey: string, queryResult: Object[], queryKey?: string, overwrite?: boolean): Object
copies a query result into the storeisQuery(action: any, queryName: string): boolean
checks whether a given action represent a query call with a given namegetQuery<T>(action: any): string
obtains a result of a query with a specified nameisMutation(action: any, queryName: string): boolean
checks whether a given action represent a query call with a given namegetMutation<T>(action: any): string
obtains a result of a mutation with a specified name
List of examples of common uses of our helpers
This is how you can copy apollo query results to the store.
import { getQuery, copyQuery } from 'apollo-mantra';
import update from 'react-addons-update';
export interface IMarkingState {
showMarked: boolean;
showPending: boolean;
solutions: Cs.Entities.ISolution[];
current: { [index: string]: Cs.Entities.ISolution };
}
export default function reducer(state: IMarkingState = { showMarked: false, showPending: false, solutions: [], practical: {}, current: null }, action: any) {
// when we execute a specific query, we want to copy its values nto the store
switch (getQuery(action)) {
case 'practical':
// the copy query will copy the query reult into the key 'practical' and add a new
// field under its '_id'. e.g. practical.id1 = result
return copyQuery(state, 'practicals', action.result.data.practical, '_id');
case 'markingSolutions':
// we want to merge results with what is currently in the store
const res = action.result.data.markingSolutions;
const eliminateDuplicates = (s: Cs.Entities.ISolution) => !res.find((r: Cs.Entities.ISolution) => r._id === s._id);
if (res && res.length) {
let output = res.concat(state.solutions.filter(eliminateDuplicates));
return update(state, { solutions: { $set: output } });
}
return state;
}
switch (action.type) {
// other reducer stuff
}
return state;
}
const mutation = mutation({
query: `mutation requestResetPassword($email: String!) {
requestResetPassword(email: $email)
}`,
variables: {
email
},
errorCallback: (err) => {
if (err.message === 'User not found [403]') {
dispatch(actions.showError('accounts.error.emailNotFound'));
} else {
dispatch(actions.showError('accounts.error.unknownError'));
}
if (callback) { callback(); }
},
thenCallback: (data: any) => {
dispatch(actions.showError('accounts.messages.passwordResetEmailSent'));
if (callback) { callback(); }
}
});
dispatch(mutation);