Custom stores
If your app uses a state management library such as Redux, it can be useful to have Uppy store its state there instead—that way, you could write custom uploader UI components in the same way as the other components in the application.
Uppy comes with two state management solutions (stores):
@uppy/store-default
, a basic object-based store.@uppy/store-redux
, a store that uses a key in a Redux store.
You can also use a third-party store:
- uppy-store-ngrx, keeping Uppy state in a key in an Ngrx store for use with Angular.
Using stores
To use a store, pass an instance to the
store
option in the Uppy constructor:
import DefaultStore from '@uppy/store-default';
const uppy = new Uppy({
store: new DefaultStore(),
});
DefaultStore
Uppy uses the DefaultStore
by default! You do not need to do anything to use
it. It does not take any options.
ReduxStore
The ReduxStore
stores Uppy state on a key in an existing Redux store. The
ReduxStore
dispatches uppy/STATE_UPDATE
actions to update state. When the
state in Redux changes, it notifies Uppy. This way, you get most of the benefits
of Redux, including support for the Redux Devtools and time traveling!
Checkout our Redux example for a working demo.
opts.store
Pass a Redux store instance, from Redux.createStore
. This instance should have
the Uppy reducer mounted somewhere already.
opts.id
By default, the ReduxStore
assumes Uppy state is stored on a state.uppy[id]
key. id
is randomly generated by the store constructor, but can be specified
by passing an id
option if it should be predictable.
ReduxStore({
store,
id: 'avatarUpload',
});
opts.selector
If you’d rather not store the Uppy state under the state.uppy
key at all, use
the selector
option to the ReduxStore
constructor to tell it where to find
state instead:
const uppy = new Uppy({
store: ReduxStore({
store,
id: 'avatarUpload',
selector: (state) => state.pages.profile.uppy.avatarUpload,
}),
});
Note that when specifying a custom selector, you must also specify a custom
store ID. The store id
tells the reducer in which property it should put
Uppy’s state. The selector must then take the state from that property. In the
example, we set the ID to avatarUpload
and take the state from the
[reducer mount path].avatarUpload
.
If your app uses reselect
, its selectors
work well with this!
Implementing Stores
An Uppy store is an object with three methods.
-
getState()
- Return the current state object. -
setState(patch)
- Merge the objectpatch
into the current state. -
subscribe(listener)
- Calllistener
whenever the state changes.listener
is a function that should receive three parameters:(prevState, nextState, patch)
The
subscribe()
method should return a function that “unsubscribes” (removes) thelistener
.
The default store implementation, for example, looks a bit like this:
function createDefaultStore() {
let state = {};
const listeners = new Set();
return {
getState: () => state,
setState: (patch) => {
const prevState = state;
const nextState = { ...prevState, ...patch };
state = nextState;
listeners.forEach((listener) => {
listener(prevState, nextState, patch);
});
},
subscribe: (listener) => {
listeners.add(listener);
return () => listeners.remove(listener);
},
};
}
A pattern like this, where users can pass options via a function call if necessary, is recommended.
See the @uppy/store-default package for more inspiration.