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.
$ npm install effector-apollo
# or
$ yarn add effector-apollo
# or
$ pnpm add effector-apolloNote that this library requires effector@23 and @apollo/client as peer dependencies, so make sure you have them installed in your project.
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:DocumentNodedescribing your querycontext?:DefaultContext | Store<DefaultContext>allows you to provide arbitrary context to your Apollo Link
Returns: a new Query instance
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:trueif theQueryhas not ever started$pending:trueif theQueryis currently fetching$failed:trueif theQueryhas failed with someApolloError$succeeded:trueif theQueryhas succeeded with data$finished:trueif theQueryhas finished with either success or failure
Query state events:
finished.success:Event<{ variables: Variables; meta: QueryMeta, data: Data }>is fired when yourQuerysucceeds, providingvariablesthat you called the query with, anddatathat the query has returnedfinished.failure:Event<{ variables: Variables; meta: QueryMeta, error: ApolloError }>is fired when yourQueryfails, providingvariablesand the correspondingerrorfinished.finally:Eventis fired when yourQueryfinishes with eitherstatus: "done"orstatus: "fail", and will provide you withdata/error
const query = createQuery({
client,
document: gql`
query user {
user {
id
name
}
}
`,
})
sample({
clock: appStarted,
target: query.start,
})Creates a new mutation to modify data on your GraphQL server.
Options:
client:ApolloClient | Store<ApolloClient>that the mutation will use to fetch datadocument:DocumentNodedescribing your mutationcontext?:DefaultContext | Store<DefaultContext>allows you to provide arbitrary context to your Apollo Link
Returns: a new Mutation instance
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:trueif theMutationhas not ever started$pending:trueif theMutationis currently fetching$failed:trueif theMutationhas failed with someApolloError$succeeded:trueif theMutationhas succeeded with data$finished:trueif theMutationhas finished with either success or failure
Mutation state events:
finished.success:Event<{ variables: Variables; data: Data }>is fired when yourMutationsucceeds withdatathat the GraphQL server has returnedfinished.failure:Event<{ variables: Variables; error: ApolloError }>is fired when yourMutationfails with the corresponding executionerrorfinished.finally:Eventthat's fired when yourMutationfinishes with eitherstatus: "done"orstatus: "fail", and will provide you withdata/error
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 data -
document:DocumentNodewith a singlefragmentdefinition you want to bind -
id: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 yourfragmentUnless you customize your cache ID,
idor_idare 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
__typenamein thisStore, as it is inferred from yourfragment.
-
-
variables?:Store<Variables>that yourfragmentrequires to resolve- You can omit this if your
fragmentdoes not require any variables ⚠️ If thefragmentdoes need variables, you must provide this option with correctVariables, otherwise you'll never receive data
- You can omit this if your
-
setup:Eventthat the binding will initialize on (upon trigger)- Usually, that would be your
appStartedevent, but you can also use any other trigger, likeQuery.finished.success
- Usually, that would be your
-
teardown:Eventtears down the binding, clearing$dataand stopping listening for updates -
optimistic?:booleancan be set tofalseto disable readingoptimisticcache
Returns: a new FragmentBinding
A live binding into ApolloCache for a specific fragment.
$data:Store<Data | null>containing yourfragmentdata (ornullif nofragmentwas found in Cache)$active:Store<boolean>marking if your binding is active or not
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:Querythat you want to subscribe to cacheoptimistic?:booleancan be set tofalseto disable readingoptimisticcacheclient?:ApolloClient | Store<ApolloClient>to use cache from. Will use the client fromcreateQueryif not provided
Enables automatic refreshes for a query, ensuring that the data stays up-to-date in response to specific events or triggers.
Options:
query:Queryyou 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 TriggerProtocolfrom any library that implements it- 💡
@withease/web-apiis a great package with triggers like tab visibility change or network status change patronum/intervalcan 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
Example usage:
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 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
mergefunction ofInMemoryCache. This allows to store paginated results in the cache - The
Querymust not be "idle" when you use pagination. Fetch the Query using.refreshor.startbefore paginating.
Triggering the event returned by paginate(query) will
- shallowly merge last used
queryvariables and new variables provided topaginate - execute the
queryto 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:Querythat will be paginated
Returns: EventCallable<Partial<Variables>> which you can call to fetch the next page
Example usage:
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 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:Mutationthat you want to define an optimistic response forfn:Variables => Datafunction that constructs an optimistic response for a mutationclient?:ApolloClient | Store<ApolloClient>to write response to. Will use the client fromcreateMutationif not provided
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.
Versions 0.x.x may contain breaking changes in minor releases, which will be documented.