FAQ and gotchas

Troubleshooting checklists, the questions people ask first, the mistakes people make first, and an honest look at what QueueKit is not.

Troubleshooting

Jobs are not running

Dump the queue first. It usually answers the question by itself:

Then walk this list in order:

  1. No handler. Was the module that calls queue.define() imported at startup? Jobs of a type with no handler wait forever. In a background task, did you import your job modules there too?
  2. Type name mismatch. The string passed to define() must equal what was saved. If you renamed it, add an alias handler (Renaming a job type).
  3. The queue is stopped. autoStart: false or a stop() without a later start().
  4. Offline. requiresNetwork defaults to true. Is your network monitor reporting what you think? Without one, the queue assumes online.
  5. Not due yet. startsInMs above is positive: a delayMs or a retry backoff. Check attempts and lastError.
  6. A limit. concurrency, limits[type].concurrency or a rate window is full. Jobs wait for a slot.
  7. Expired. A ttlMs elapsed: the job was dropped and an expired event fired.
  8. The app is suspended. JavaScript does not run in the background. See Background.
  9. Different storage. A different MMKV id or queue name than before looks like an empty queue.
  10. Expo Go. MMKV needs a development build.

Jobs run twice

Decorators do nothing or fail to compile

The wrong handler runs for a job

Two define() (or register, or decorated methods) with the same type string: the last one replaces the first, and every job of that type runs through it, with no error. Search your code for the string, and keep strict on (it is the default outside development builds) so a duplicate throws (Catching duplicate job names).

My payload came back different

Payloads are JSON. Dates become strings, undefined properties vanish, Maps and class instances flatten. See Payload rules.

Large uploads keep timing out

The default per-attempt timeout is 30 seconds. Raise timeoutMs, split slow work into its own queue, upload in chunks, or hand the transfer to a native uploader with detach().

Storage keeps growing

Failed jobs are kept until you act. Surface them in a sync issues screen, retry or discard them, and put ttlMs on low-value jobs so they expire by themselves.

Common questions

Can I get a job's result?

No. A job has no result: the return value of a handler is ignored. Write the outcome where your app already looks (a database, a cache) from inside the handler, and read it there.

Can jobs depend on each other?

Chain them: have a handler enqueue the next step when it finishes (Chaining), or use the compress-then-upload pattern (Pipeline). Give steps deterministic ids so a restart cannot duplicate them.

Does it keep order?

Priority, then due time, then creation time. Not across failures: a retrying job lets later ones pass. Use a sequence number on the server for strict order (Chat recipe).

Is it encrypted?

Not by default. Payloads are stored as plain JSON in your MMKV instance. Use an MMKV instance with an encryptionKey (Secrets and tokens), and do not put secrets such as tokens in payloads. Enqueue with sensitive: true to keep a payload out of event listeners and logs; it does not encrypt anything by itself.

How many jobs can it hold?

Hundreds to a few thousand is comfortable. Every job is loaded in memory at start-up, and the scheduler scans pending jobs when something changes. For tens of thousands of items, batch them into one job that carries a list or a reference to a local table.

Can I use it without React?

Yes. The core (@nijatk/react-native-queuekit) has no React dependency. Hooks live in @nijatk/react-native-queuekit/react.

Does it work on web or in tests?

The core is plain TypeScript, so it runs anywhere with a QueueStorage. On the web use a localStorage adapter (Other backends). In tests use memoryStorage().

Does the clock matter?

Delays, backoff and expiry use Date.now() with no protection against clock changes. If the user sets the device time back, waiting jobs wait longer; forward, they fire or expire early. Do not use ttlMs or delayMs for security-critical timing.

Can I schedule a job for 9am tomorrow?

With delayMs, yes, but it only runs if the app is alive then (or opens soon after). For exact times use local notifications.

Is the API stable?

This is version 0.1. Expect additions, and possibly small changes, before 1.0. Pin the version.

Why MMKV and not SQLite or AsyncStorage?

MMKVSQLiteAsyncStorage
AccessSynchronousAsynchronousAsynchronous
Saved when enqueue() returnsYesOnly if you awaitOnly if you await
Start-upInstant, no schemaOpen, migrate, queryAsync load
Fit for queue stateSmall, key-addressed recordsOverkill; great for large relational dataSlow and not designed for hot writes
EncryptionBuilt inExtra libraryNo

A job queue reads and writes small records by key, very often, and needs each write to be durable before it moves on. That is exactly MMKV's shape. Keep large binary data on disk and queue its path.

How it compares

Not a scoreboard, just a guide to choosing. Details change, so check each project's own docs.

OptionWhat it isChoose it when
QueueKitA persistent, network-aware job queue with retries, limits, hooks and native-task handoff.You want one reliable outbox for uploads, forms and background work, independent of your data layer.
TanStack Query mutationsCache and request library with paused, persistable mutations.Your app is already built around it and your offline needs are mostly "replay my mutations". Pairs well with QueueKit (recipe).
react-native-job-queueA React Native job queue library.You prefer its worker model. Compare maintenance activity and features first.
redux-offline, react-native-offlineRedux-centred offline helpers and action queues.You are all-in on Redux and want offline handled in reducers and middleware.
expo-background-task, react-native-background-fetchOS background wake-ups.Complementary: use them to give QueueKit time to drain().
Native uploaders (react-native-background-upload and similar)OS-level transfer services.Complementary: for big files, detach() to them and let QueueKit track the job.

What QueueKit is not