effector-apollo
v0.6.0
Published
Effector bindings for Apollo Client
Downloads
105
Readme
effector-apollo
A lightweight wrapper around Apollo Client to interoperate with ☄️ Effector. Create and manage your GraphQL Queries & Mutations declaratively, with Effector, while using powerful Apollo Client features, like normalized cache and Apollo Link.
🔗 Installation & use
$ npm install effector-apollo
# or
$ yarn add effector-apollo
# or
$ pnpm add effector-apollo
Note that this library requires effector@23
and @apollo/client
as peer dependencies, so make sure you have them installed in your project.
API
createQuery
Creates a new query that allows you to read data from GraphQL server or cache on request.
Options:
client
:ApolloClient | Store<ApolloClient>
that the query will use to fetch datadocument
:DocumentNode
describing your querycontext
:DefaultContext?
allows you to (optionally) provide arbitrary context to your Apollo Link
Returns: a new Query
instance
Query
Commands:
start
:EventCallable<Variables>
unconditionally starts your query, and will send a network requestrefresh
:EventCallable<Variables>
will refresh the query, using cache if possiblereset
:EventCallable<void>
resets your query to its initial state
Query state:
$data
:Store<Data | null>
containing your query data$error
:Store<ApolloError | null>
possibly containing query execution error$status
:Store<'initial' | 'pending' | 'done' | 'fail'>
reflecting current status of your query, which is also split into separate 'convenience' stores$idle
:true
if theQuery
has not ever started$pending
:true
if theQuery
is currently fetching$failed
:true
if theQuery
has failed with someApolloError
$succeeded
:true
if theQuery
has succeeded with data$finished
:true
if theQuery
has finished with either success or failure
Query state events:
finished.success
:Event<{ variables: Variables; meta: QueryMeta, data: Data }>
is fired when yourQuery
succeeds, providingvariables
that you called the query with, anddata
that the query has returnedfinished.failure
:Event<{ variables: Variables; meta: QueryMeta, error: ApolloError }>
is fired when yourQuery
fails, providingvariables
and the correspondingerror
finished.finally
:Event
is fired when yourQuery
finishes with eitherstatus: "done"
orstatus: "fail"
, and will provide you withdata
/error
Example usage
const query = createQuery({
client,
document: gql`
query user {
user {
id
name
}
}
`,
})
sample({
clock: appStarted,
target: query.start,
})
createMutation
Creates a new mutation to modify data on your GraphQL server.
Options:
client
:ApolloClient | Store<ApolloClient>
that the mutation will use to fetch datadocument
:DocumentNode
describing your mutationcontext
:DefaultContext?
allows you to (optionally) provide arbitrary context to your Apollo Link
Returns: a new Mutation
instance
Mutation
Commands:
start
:EventCallable<Variables>
unconditionally starts yourMutation
, and will send a network request immediatelyreset
:EventCallable<void>
resets your mutation to its initial state
Mutation state:
$status
:Store<'initial' | 'pending' | 'done' | 'fail'>
reflecting current status of yourMutation
, which is also split into separate 'convenience' stores$idle
:true
if theMutation
has not ever started$pending
:true
if theMutation
is currently fetching$failed
:true
if theMutation
has failed with someApolloError
$succeeded
:true
if theMutation
has succeeded with data$finished
:true
if theMutation
has finished with either success or failure
Mutation state events:
finished.success
:Event<{ variables: Variables; data: Data }>
is fired when yourMutation
succeeds withdata
that the GraphQL server has returnedfinished.failure
:Event<{ variables: Variables; error: ApolloError }>
is fired when yourMutation
fails with the corresponding executionerror
finished.finally
:Event
that's fired when yourMutation
finishes with eitherstatus: "done"
orstatus: "fail"
, and will provide you withdata
/error
createFragmentBinding
createFragmentBinding
sets up a light, read-only binding into Apollo Cache. This can come in handy to access entities from your Apollo Cache without requiring a specific Query
to do that.
Like useFragment
from Apollo Client, fragment binding does not trigger network requests on its own. Rather, it keeps data in sync between Apollo Cache and Effector.
Unlike useFragment
, however, createFragmentBinding
's primary purpose is to provide you with singleton-like entities that your business logic might requre. Common examples would be: currently logged in User
entity, a specific Setting
, or feature flags.
Options:
client
:ApolloClient | Store<ApolloClient>
that provides cached datadocument
:DocumentNode
with a singlefragment
definition you want to bindid
:Store<string> | Store<StoreObject>
to identify a specificfragment
When provided with
Store<string>
, binding will treat this as a ready-to-use Canonical Cache ID (likeUser:137
)When provided with
Store<StoreObject>
, binding treats this as an object with key fields that uniquely identify yourfragment
Unless you customize your cache ID,
id
or_id
are your key fields. So,Store<{ id: string }>
is usually the way to go!If you did customize cache ID for your fragment, provide all necessary key fields set in your type policy.
💡 You do not need to provide
__typename
in thisStore
, as it is inferred from yourfragment
.
variables?
:Store<Variables>
that yourfragment
requires to resolve- You can omit this if your
fragment
does not require any variables - ⚠️ If the
fragment
does need variables, you must provide this option with correctVariables
, otherwise you'll never receive data
- You can omit this if your
setup
:Event
that the binding will initialize on (upon trigger)- Usually, that would be your
appStarted
event, but you can also use any other trigger, likeQuery.finished.success
- Usually, that would be your
teardown
:Event
tears down the binding, clearing$data
and stopping listening for updatesoptimistic?
:boolean
can be set tofalse
to disable readingoptimistic
cache
Returns: a new FragmentBinding
FragmentBinding
A live binding into ApolloCache
for a specific fragment.
$data
:Store<Data | null>
containing yourfragment
data (ornull
if nofragment
was found in Cache)$active
:Store<boolean>
marking if your binding is active or not
watchQuery
watchQuery
allows you to subscribe a particular query to Apollo Cache. By default, Query
only reads data through a request, and does not read cache (unlike Apollo's React hooks).
This operator allows you to connect Query
to cache if you expect other parts of your app to request the same query. This will help you avoid extra requests.
Options:
query
:Query
that you want to subscribe to cacheoptimistic?
:boolean
can be set tofalse
to disable readingoptimistic
cacheclient?
:ApolloClient | Store<ApolloClient>
to use cache from. Will use the client fromcreateQuery
if not provided
keepFresh
Enables automatic refreshes for a query, ensuring that the data stays up-to-date in response to specific events or triggers.
Options:
query
:Query
you want to keep freshtriggers
:Array<Event | TriggerProtocol>
containing triggers that will invalidate the query and initiate a network request for fresh data. Trigger can be either:- any
Event
, or TriggerProtocol
from any library that implements it- 💡
@withease/web-api
is a great package with triggers like tab visibility change or network status change patronum/interval
can refresh your query on a timer
- 💡
- any
enabled?
:Store<boolean>
that controls whether the automatic refresh is enabled or disabled. If ommited, defaults to always enabled
Fetch the query when network connection is restored
import { keepFresh, createQuery } from "effector-apollo"
import { trackNetworkStatus } from "@withease/web-api"
const userQuery = createQuery({ client, document: USER_QUERY })
// If the connection is lost, fetch User as soon as it is restored
keepFresh(userQuery, {
triggers: [trackNetworkStatus],
})
Refetch a related query when a mutation fails
import { keepFresh, createQuery, createMutation } from "effector-apollo"
const userQuery = createQuery({ client, document: USER_QUERY })
const updateCountryMutation = createMutation({ client, document: UPDATE_COUNTRY_MUTATION })
// When update fails, data may become inconsistent,
// so we refetch User, ensuring data is most up to date
keepFresh(userQuery, {
triggers: [updateCountryMutation.finished.failure],
})
paginate
paginate
creates a fetchMore
-like function for your query, allowing for easy pagination powered by Apollo Cache.
Note that this operator requires two things from you to work properly:
- You must define a proper Field Policy on a paginated field using a
merge
function ofInMemoryCache
. This allows to store paginated results in the cache - The
Query
must not be "idle" when you use pagination. Fetch the Query using.refresh
or.start
before paginating.
Triggering the event returned by paginate(query)
will
- shallowly merge last used
query
variables and new variables provided topaginate
- execute the
query
to fetch the next page based on updated variables - merge pages using your policy (as defined in
InMemoryCache
) - store results in
query.$data
Read more about Pagination API in Apollo.
Options:
query
:Query
that will be paginated
Returns: EventCallable<Partial<Variables>>
which you can call to fetch the next page
Paginate a query using a cursor in a Relay-style pagination.
import { createQuery, paginate } from "effector-apollo"
import { gql } from "@apollo/client"
const document = gql`
query list($cursor: String) {
items(cursor: $cursor) {
nodes {
id
}
pageInfo {
endCursor
}
}
}
`
const listQuery = createQuery({ client, document })
const nextPageRequested = createEvent<void>()
sample({
// when the next page is requested
clock: nextPageRequested,
// take the end cursor of the list
source: listQuery.$data.map(({ items }) => items.pageInfo.endCursor),
// construct an update to query variables
fn: (cursor) => ({ cursor }),
// launch pagination
target: paginate(listQuery),
})
optimistic
optimistic
helps you define an optimistic response for your mutation. This will fill in data in Apollo Cache when running the mutation, so that your UI is responsive to user action. See more in Apollo Client "Optimistic results" documentation.
Options:
mutation
:Mutation
that you want to define an optimistic response forfn
:Variables => Data
function that constructs an optimistic response for a mutationclient?
:ApolloClient | Store<ApolloClient>
to write response to. Will use the client fromcreateMutation
if not provided
💬 Pick your library
When starting a new project from scratch, please, take a look at Farfetched, a great data fetching tool, before using effector-apollo
.
This library is an interoperability layer for projects that already use Apollo Client. It makes your life easier by giving you access to your GraphQL data from Effector.
This library strives to keep its API similar to Farfetched so that your future migration to this tool is simpler.
Releases policy
Versions 0.x.x
may contain breaking changes in minor releases, which will be documented.