GithubHelp home page GithubHelp logo

apollo-link-debounce's Introduction

apollo-link-debounce

npm version Build Status codecov

An Apollo Link that debounces requests made within a certain interval of each other.

Motivation

Sometimes it can be useful to debounce updates by the user before sending them to the server if all that matters is the final state, for example the value at which a slider comes to rest after being moved by the user. You could debounce the slider event at the component level, but that's not always an option when there are other parts of the UI that depend on having the most up-to-date information on the slider position.

Apollo-link-debounce can help in such situations by allowing you to debounce requests. Slider position, for example, could be debounced such that if multiple slider events happen within 100ms of each other, only the last position update (mutation) gets sent to the server. Once the server response comes back, all subscribers will receive the response to the last event. (Another option would be to immediately complete all but the last request. If you need that, feel free to make a PR implementing it!)

It is possible to debounce different events separately by setting different debounce keys. For example: if there are two sliders, they can use separate debounce keys (eg. the slider's name) to ensure that their updates don't get mixed up together.

Read more about debounce here. See a real-world example of using a debounce link here.

Installation

npm install apollo-link-debounce

or

yarn add apollo-link-debounce

Usage

import { gql, ApolloLink, HttpLink } from '@apollo/client';
import { RetryLink } from '@apollo/client/link/retry';

import DebounceLink from 'apollo-link-debounce';

const DEFAULT_DEBOUNCE_TIMEOUT = 100;
this.link = ApolloLink.from([
    new DebounceLink(DEFAULT_DEBOUNCE_TIMEOUT),
    new HttpLink({ uri: URI_TO_YOUR_GRAPHQL_SERVER }),
]);

const op = {
    query: gql`mutation slide($val: Float){ moveSlider(value: $val) }`,
    variables: { val: 99 }
    context: {
        // Requests get debounced together if they share the same debounceKey.
        // Requests without a debounce key are passed to the next link unchanged.
        debounceKey: '1',
    },
};

const op2 = {
    query: gql`mutation slide($val: Float){ moveSlider(value: $val) }`,
    variables: { val: 100 },
    context: {
        // Requests get debounced together if they share the same debounceKey.
        // Requests without a debounce key are passed to the next link unchanged.
        debounceKey: '1',
    },
};

// Different debounceKeys can have different debounceTimeouts
const op3 = {
    query: gql`query autoComplete($val: String) { autoComplete(value: $val) { value } }`,
    variables: { val: 'apollo-link-de' }, // Server returns "apollo-link-debounce"
    context: {
        // DEFAULT_DEBOUNCE_TIMEOUT is overridden by setting debounceTimeout
        debounceKey: '2',
        debounceTimeout: 10,
    },
};


// No debounce key, so this request does not get debounced
const op4 = {
    query: gql`{ hello }`, // Server returns "World!"
};

link.execute(op).subscribe({
    next(response) { console.log('A', response.data.moveSlider); },
    complete() { console.log('A complete!'); },
});
link.execute(op2).subscribe({
    next(response) { console.log('B', response.data.moveSlider); },
    complete() { console.log('B complete!'); },
});
link.execute(op3).subscribe({
    next(response) { console.log('C', response.data.autoComplete.value); },
    complete() { console.log('C complete!'); },
});
link.execute(op4).subscribe({
    next(response) { console.log('Hello', response.data.hello); },
    complete() { console.log('Hello complete!'); },
});

// Assuming the server responds with the value that was set, this will print
// -- no delay --
// Hello World!
// Hello complete!
// -- 10 ms delay --
// C apollo-link-debounce
// C complete!
// -- 100 ms delay --
// A 100 (after 100ms)
// A complete!
// B 100
// B complete!

apollo-link-debounce's People

Contributors

1v4nx avatar danistefanovic avatar helfer avatar hwillson avatar pl12133 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

apollo-link-debounce's Issues

add 'waiting' state

No idea if it's even possible. But currently, react-apollo is providing a loading state while the query is running. Would be nice if this link could add a waiting (or debouncing) state while the query is being delayed in the debounce.

Merge variables objects when flushing queue

Would you be ok with merging the variables object for all queued requests when flushing the queue? It seems to me that for cases where you're updating an object instead of a single value that would be the desired behaviour. Happy to take a crack at it if you're open.

set debounce time on query level

Would be nice if we could set the debounce time on the query level, instead of global scope. Or perhaps bound to a debounceKey?

My prefered way. Specify the delay in the query, and thereby close to the implementing components:

const op = {
    query: gql`mutation slide($val: Float){ moveSlider(value: $val) }`,
    variables: { val: 99 }
    context: {
        debounceKey: 'Slide',
        debounceDelay: 300,
    },
};

Alternative option that could be usefull. Supply a key-delay-map as second argument to the link constructor:

const DEBOUNCE_TIMEOUT = 100;
this.link = ApolloLink.from([
    new DebounceLink(DEBOUNCE_TIMEOUT, {
      Slide: 300,
    }),
    new HttpLink({ uri: URI_TO_YOUR_GRAPHQL_SERVER }),
]);

Apollo client with a DebounceLink server side

Hello,

I'm using a DebounceLink on the client and server, and if I understand it correctly the link is used to not send 2 same queries on a given interval.

Then, we figured out that the debounce on the server impacts the server response time. I would like to know your experiences if any, doing SSR with Apollo Client? Do you advice me to not use there?

@apollo/client compatibility

Hi! I tried apollo-link-debounce on @apollo/client, but the packages have slightly changed. In particular, ApolloLink is now part of @apollo/client (rather than apollo-link), and a few fields have been added (onError and setOnError, which seems to be very small functions - probably inheritable?).

Feature request: use abortController to cancel any running requests when a new one is queued

Currently, for mutations, when the debounce timeout is reached and a request is sent, if the the same request is queued again while the first's request is in network flight, then a mutation result will be returned that overrides the requests queued since.

The solution to this is to cancel any in-flight requests with an abortController whenever a new request is enqueued.

This article is a pretty decent write-up: https://evilmartians.com/chronicles/aborting-queries-and-mutations-in-react-apollo

Optimistic Response with debounce mutations

  • Hello I am updating user input with the real-time optimistic response, I had applied to debounce link for 1 second, optimistic works fine and also debounce but that will freeze the browser, if I remove the debounce it will work fine with zero lags, but I want to stop backend call while user typing so I have to use this debounce-link, any help would be appreciated

code example:

This is optimistic query

return {
    __typename: 'Mutation',
    updateBlockItemField: {
      __typename: 'Resume',
      ...resume,
      id: resumeId,
      updatesCount: resume.updatesCount + 1,
      details: {
        ___typename: 'ResumeDetails',
        ...resume.details,
      },
      settings: {
        ___typename: 'ResumeSettings',
        ...resume.settings,
      },
      blocks: resume.blocks,
    },
  };

This is Mutation:

save = debounce(value => {
    const { mutate, variables, optimisticResponse, update, isCoverLetter } = this.props;
    const options = {
      variables: {
        ...variables,
        value,
      },
      context: {
        debounceKey: generateDebounceKey(variables),
      },
    }

    if (optimisticResponse) options.optimisticResponse = optimisticResponse(value);
    mutate(options);
  }, 30);

And this is my init

return new ApolloClient({
    connectToDevTools: isBrowser,
    ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once)
    link: ApolloLink.from([
      onError(({ graphQLErrors, networkError }) => {
        if (graphQLErrors) {
          graphQLErrors.map(({ message, locations, path }) =>
            console.error(`[GraphQL error]: Message: ${message}, Path: ${path}`),
          );
        }

        if (networkError) {
          console.error(`[${networkError.name}]: Status: ${networkError.statusCode}: Message: ${networkError.message}`);
        }
      }),
      new DebounceLink(1000),
      authLink.concat(
        ApolloLink.split(
          operation => operation.getContext().client === 'coverLetter', // Routes the query to the proper client
          coverLetterLink,
          httpLink,
        ),
      ),
    ]),
    cache,
    resolvers: {},
  });

`Missing field ...` warnings are thrown when request is debounced

Some warnings like this are thrown when the last request is executed after some debounced requests:

capture d ecran 2019-01-09 a 01 11 37

When I remove the DebounceLink, these warnings disappear.

const link = ApolloLink.from([
  new DebounceLink(100),
  new HttpLink({ uri: process.env.GRAPHQL_URL! }),
])
export default class TaskListItem extends React.Component<TaskListItemProps> {
  public static fragment = gql`
    fragment TaskItemFragment on Task {
      id
      done
      message
    }
  `

  public render() {
    const { task } = this.props
    const debounceOptions = { debounceKey: task.id, debounceTimeout: 500 }

    if (task.done) {
      return (
        <Mutation<UncompleteTask, UncompleteTaskVariables>
          mutation={UNCOMPLETE_TASK}
          optimisticResponse={{
            uncompleteTask: {
              __typename: 'Task',
              done: false,
              id: task.id,
            },
          }}
          variables={{ id: task.id }}
          context={debounceOptions}
        >
          {uncompleteTask => (
            <Task
              checked
              actions={<DeleteTaskButton taskId={task.id} />}
              onCheck={() => uncompleteTask()}
            >
              <Text>{task.message}</Text>
            </Task>
          )}
        </Mutation>
      )
    }

    return (
      <Mutation<CompleteTask, CompleteTaskVariables>
        mutation={COMPLETE_TASK}
        optimisticResponse={{
          completeTask: {
            __typename: 'Task',
            done: true,
            id: task.id,
          },
        }}
        variables={{ id: task.id }}
        context={debounceOptions}
      >
        {completeTask => (
          <Task
            actions={<DeleteTaskButton taskId={task.id} />}
            checked={false}
            onCheck={() => completeTask()}
          >
            <Text>{task.message}</Text>
          </Task>
        )}
      </Mutation>
    )
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.