Relay Helpers
Helpers to simplify and enhance Relay (https://facebook.github.io/relay/) including:
- Decorators to simplify the Renderer and Mutation APIs
- TTL on queries
- Simple way to reset the Relay store
- Simple server side rendering
and more!
Why?
We're waiting for Relay 2 but in the meantime we wanted to fill some gaps in the features and tidy up the API a bit.
We're hiring!
If you like working with React and GraphQL and you're interested in building the future of radio check out https://www.mixcloud.com/jobs/
Installation
npm install relay-helpers
RelayEnvProvider
RelayEnvProvider
is a component that provides the Relay Environment on context. It is used by everything else in
relay-helpers
.
;; const env = ; Component { return <RelayEnvProvider initialEnv=env> /* ... the rest of your app ... */ </RelayEnvProvider> ; }
Resetting the store
Under some circumstances you will want to reset the entire store (e.g. log in or log out). The context provided by
RelayEnvProvider
has a reset()
method:
;; Component static contextTypes = relayEnv: RelayEnvContextType; { // Log out... // This will create a new relay environment and refresh any renderers on the page that were created using // withRelayRenderer or withRelayQuery thiscontextrelayEnv; }; { return <div onClick=thisonClick>Logout</div>; } Component { // Pass a `createEnv` prop to RelayEnvProvider that will be called on init and when `reset()` is called return <RelayEnvProvider createEnv= > <LogoutButton /> </RelayEnvProvider> ; }
Higher Order Components
createRelayContainer
A simple wrapper for Relay.createContainer
so that it can be used like other higher order components and with tools
such as recompose.
e.g.
;;; const MyContainer = MyComponent; const MyWrappedComponent = MyComponent;
withRelayRenderer
withRelayRenderer
can be used to simplify the process of creating a Relay.Renderer. Instead of needing a Renderer
and a Route you can just decorate the component. In most circumstances you can probably use withRelayQuery
- this
is provided as a lower level alternative.
Note: withRelayQuery
or withRelayRenderer
must be used if you want to make use of context.relayEnv.reset()
.
; { // Note: unlike Relay.Renderer, withRelayRenderer will render your component // with `loading`, `error`, and `offline` props. // If this is not the behaviour you want a higher order component that only renders if !loading && !error if loading return <div>Loading...</div>; // `offline` is true if error is a TypeError with message "Network request failed" // the error is still passed in as `error` if offline return <div>Check your internet connection...</div>; if error return <div>Something went wrong...</div>; // Data props passed is as usual return <div>userusername</div>;} Username = Username; { // params are taken from props return <Username userId="user1" />;}
Changing the query based on props
Instead of providing a queryConfig
object you can provide a function:
{ return name: 'UsernameQuery' queries: Relay.QL` query { user(id: $userId) } ` params: userId: propsmyUserIdParam ;}
This will be called every time props changes.
withRelayQuery
withRelayQuery
is a higher level higher order component that enables you to create a component with a query
very quickly.
;
Options
query
EITHER a Relay.QL of a full query (not separated into queries and fragments) OR an array of Relay.QL (if you have more than one root)name
(optional) name the query - by default the component name will be usedparams
EITHER param definitions OR a function:(props) => ({paramName: 'value', ...})
initialVariables
as you would provide to Relay.createContainerwithHelpers
optionally wrap in thewithRelayHelpers
higher order componentforceFetch
andttl
(same aswithRelayRenderer
)
How it works
withRelayQuery
splits the Relay.QL into the parts required for the route and fragments. This might not work in all
circumstances.
Note about fragment variables
It is possible to have variables that aren't params (in the same way as you can with separate Route and Container) and it is possible to set those variable values from props. For example:
const Wrapper = TestComponent;
withRelayHelpers
Provides setVariables
and forceFetch
props equivalent to props.relay.setVariables
and props.relay.forceFetch
that return promises instead of taking callbacks.
; { { ; } return <div onClick=onClick>Load more</div>;} MyComponent = MyComponent;
Mutations
context.relayEnv
provides a mutate
function that wraps Relay.GraphQLMutation
:
contextrelayEnv;
withRelayMutations
If you define mutation functions like so:
const myMutation = ;
You can use withRelayMutations
to provide the mutation as a prop to the component:
; { { ; } return <div onClick=onClick>Click Me!</div>;} // myMutation from aboveMyComponent = MyComponent;
Server side (isomorphic) rendering
Server and Client Relay Environments are provided that, combined with RelayEnvProvider
and
withRelayRenderer
/withRelayQuery
, can enable isomorphic rendering.
Note: you can provide an `isomorphic: false` option to `withRelayRenderer` and `withRelayQuery` if you do not want them to be included in server side rendering
On the server:
; // Provide a function to get graphql query responses// getQueryResponse = (query: {query: string, variables: Object}) => Promise<{errors?: Array<Object>, data?: Object}> const env = getQueryResponse; { return <RelayEnvProvider initialEnv=env> /* the rest of the app */ </RelayEnvProvider> ;} // This will recursively find queries and run themenv;
On the client:
; const env = ; // env.injectNetworkLayer ... etc { return <RelayEnvProvider initialEnv=env> /* the rest of the app */ </RelayEnvProvider> ;} const serverData = ... // Get the data from the server (probably from the hidden DOM element) env;
Caching
querySubscriberDecorator
querySubscriberDecorator
is a helper that can be used, for example, to create an offline cache for Relay.
Usage:
var env = ; // A simple cache example - this could be persisted to localStorage, for examplevar cache = {}; // We listen for all queriesenv;
The result
that querySubscriberDecorator
passes in is compatible with the ServerData
used for isomorphic rendering. They
can be injected into the store in the same way (whether you are using server side rendering or not):
var env = ; // "cache" from the code above - for example loaded from localStorage const localResults = Object; // Concatenating serverData and localResults - just pass localResults if you're not using isomorphic renderingenv;
Tests
Some helpers are provided for testing.
Relay.Renderer mock
// Either;// or;
Usage (example using jest, but jest isn't required):
; jest; ;
createMockRelayEnv
This requires jest but will provide a mocked relayEnv context.
expect(relayEnvContext).toHaveMutated
Also requires jest.
const relayEnv = ;not; const wrapper = ; // ... do something that causes a mutation such as:relayEnv; ;;;