Get rid of decorators and use Apollo GraphQL queries and mutations in the simple and readable way.
MIT License
This is opinionated replacement for graphql
decorator from react-apollo
package.
Under development, API can change
npm install --save react-apollo-graphql
It provides:
<ApolloProvider />
from react-apollo
import GraphQL from 'react-apollo-graphql';
import ApolloClient from 'apollo-client';
import ApolloProvider from 'react-apollo';
<ApolloProvider client={new ApolloClient(...)}>
<GraphQL render={(queries, mutations, props) => <div />} />
</ApolloProvider>
ApolloClient
directlyimport GraphQL from 'react-apollo-graphql';
import ApolloClient from 'apollo-client';
<GraphQL client={new ApolloClient(...)} render={(queries, mutations, props) => <div />} />
In order to use define and use queries, one has to initialize them.
// @flow
import type { QueryInitializerOptions } from 'react-apollo-graphql';
import type { ApolloClient, ObservableQuery } from 'react-apollo-graphql/lib/types';
const queries = {
// queryA will be resolved only once
queryA: (
client: ApolloClient,
props: Object
): ObservableQuery<{ id: number }> => client.watchQuery({
query: gql`{ id }`,
}),
// queryB will be resolved everytime the relevant props change
queryB: (
client: ApolloClient,
props: Object,
options: QueryInitializerOptions
): ObservableQuery<{ name: string }> => {
// add our function which will be called on every props change
options.hasVariablesChanged((currentProps, nextProps) => {
if (currentProps.name === nextProps.name) {
return false;
}
return { name: nextProps.name };
});
return client.watchQuery({
query: gql`query test($name: String!) { id(name: $name)}`,
variables: { name: props.name },
});
}
};
<GraphQL
queries={queries}
render={(initializedQueries) => {
console.log(initializeQueries.queryA.data);
console.log(initializeQueries.queryA.loading);
console.log(initializeQueries.queryA.error);
console.log(initializeQueries.queryA.networkStatus);
console.log(initializeQueries.queryA.partial);
}}
/>
In order to define and use mutations, one has to provided initializers. Initializers are called on every render so you have current props
available in the initializers.
// @flow
import type { ApolloClient, QueryResult } from 'react-apollo-graphql/lib/types';
const mutations = {
registerUser: (
client: ApolloClient,
props: Object
) => (): Promise<QueryResult<{ registerUser: boolean }>> => client.mutate({
mutation: gql`mutation registerUser($email: String!) { registerUser(email: $email) }`,
variables: {
email: props.email,
},
}),
};
<GraphQL
email="[email protected]"
mutations={mutations}
render={(queries, mutations, fetchers, props) => {
mutations.registerUser(props.email).then(
(data) => console.log(data.registerUser),
e => console.error(e),
);
}}
/>
In order to use fetchers (queries that run only when user invokes them), user has to first initialize them. Fetchers are initialized with client
and current props
on each render and passed to render()
function.
// @flow
import type { QueryInitializerOptions } from 'react-apollo-graphql';
import type { ApolloClient, QueryResult } from 'react-apollo-graphql/lib/types';
const fetchers = {
// queryA will be resolved only once
search: (
client: ApolloClient,
props: Object
) => (term: string): Promise<QueryResult<Array<{ id: number }>>> => client.query({
query: gql`query search($term: String!) { search(term: $term) { id } }`,
variables: { term },
}),
};
<GraphQL
fetchers={fetchers}
text="text"
render={(queries, mutations, fetchers, props) => {
fetchers.search(props.text).then(
(data) => console.log(data.search[0].id);
);
}}
/>
In order to use fragments (you can simulate partial results using fragments), user has to first initialize them. Fragments are initialized with client
, previous props
and current props
on componentWillMount
and every update if props
used by given fragment have changed. If props have not changed and you don't want fragment to fetch data on every update, return false
.
// @flow
import type { QueryInitializerOptions } from 'react-apollo-graphql';
import type { ApolloClient, FragmentResult } from 'react-apollo-graphql/lib/types';
const fragments = {
// user detail will be resolved on componentWillMount and on every update if props
// used as variables have changed
userDetail: (
client: ApolloClient,
previousProps: ?Object,
currentProps: Object
): FragmentResult<{ __typename: 'User', id: number, name: string }> => {
if (previousProps && previousProps.id === currentProps.id) {
return false;
}
return client.readFragment({
id: `User:${currentProps.id}`,
fragment: gql`fragment userDetails on User { __typename, id, name }`,
});
}
};
<GraphQL
fragments={fragments}
id={10}
render={(queries, mutations, fetchers, fragments, props) => {
expect(fragments.userDetail).toEqual({
__typename: 'User',
id: 10,
name: 'Fero',
});
}}
/>
For server side rendering you need to:
import { getDataFromTree } from 'react-apollo-graphql';
const view = <App />;
)await getDataFromTree(view);
ReactDOM.renderToString(view);
// example taken from react-router v4 docs
import { createServer } from 'http';
import ApolloClient from 'apollo-client';
import ApolloProvider from 'react-apollo';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router';
import { getDataFromTree } from 'react-apollo-graphql';
import App from './App';
createServer(async (req, res) => {
const context = {};
const client = new ApolloClient();
const view = (
<StaticRouter
location={req.url}
context={context}
>
<ApolloProvider client={client}>
<App/>
</ApolloProvider>
</StaticRouter>
);
await getDataFromTree(view);
const html = ReactDOMServer.renderToString(view);
if (context.url) {
res.writeHead(301, {
Location: context.url
});
res.end();
} else {
res.write(`
<!doctype html>
<div id="app">${html}</div>
`);
res.end();
}
}).listen(3000);
apollo-client
package. See documentation.// @flow
export type QueryInitializerOptions = {
// sets function to determine if there is a relevant change in props to compute new variables
// returns false if there is no change in props used for variables
// or returns new variables for query.setVariables()
hasVariablesChanged: (
(currentProps: Object, nextProps: Object) => boolean | { [key: string]: any },
) => void,
};
<GraphQL fetchers?={Fetchers} fragments?={Fragments} queries?={Queries} mutations?={Mutations} render={RenderFunction} />
Fragments = { [key: string]: (client: ApolloClient, previousProps: ?Object, currentProps: Object) => FragmentResult<any> }
optional
prop, object with fragments' initializerfalse
or result of client.readFragment()
(this means that it has to call the client.readFragment() method
)Fetchers = { [key: string]: (client: ApolloClient, props: Object) => (...args: any) => Promise<QueryResult<*>>}
optional
prop, object with fetchers' initializer(...args: any) => Promise<QueryResult<*>>
(this means that it has to call the client.query() method
)Queries = { [key: string]: (client: ApolloClient, props: Object, options: QueryInitializerOptions) => ObservableQuery<*> }
optional
prop, object with query initializers.ObservableQuery
(this means that it has to call the client.watchQuery() method
)Mutations = { [key: string]: (client: ApolloClient, props: Object) => () => Promise<QueryResult<*>>}
optional
prop, object with mutation initializers() => Promise<QueryResult<*>>
(this means that it has to call the client.mutate() method
)RenderFunction = (queries: InitializedQueries, mutations: InitializedMutations, fetchers: InitializedFetchers, props: Object) => React$Element<any>
queries
arg: result of each query initializer passed to the queries
prop on <GraphQL />
component will be mapped to it's result, plus additional methods like fetchMore(), refetch(), etc
see client.watchQuery() method
mutations
arg: each mutation initializer from the mutations
prop passed to the <GraphQL />
component will be called on render and the result will be passed under the same key
to the mutations
arg of render function.fetchers
arg: each fetcher initializer from the fetchers
prop passed to the <GraphQL />
component will be called on render and the returned function will be passed under the same key
to the fetchers
arg of render function.props
arg: current props passed to <GraphQL />
component