@theclinician/ddp-connector
v0.9.6
Published
DDP client bindings for react-redux
Downloads
21
Readme
ddp-connector
DDP client bindings for react-redux
Installation
npm install --save @theclinician/ddp-connector
Basic usage
The execute the following examples you will need to install additional npm packages
npm install --save redux redux-thunk react-redux babel-preset-es2015 babel-preset-stage-3
Configuration
import DDPClient from '@theclinician/ddp-client';
import DDPConnector, { ddpReducer, DDPProvider } from '@theclinician/ddp-connector';
import { combineReducers, applyMiddleware, createStore } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
const ddpClient = new DDPClient({
endpoint: 'http://localhost:3000',
SocketConstructor: WebSocket,
});
const ddpConnector = new DDPConnector({
ddpClient,
// defaultLoaderComponent: ...
});
const rootReducer = combineReducers({
// NOTE: It has to be "ddp"
ddp: ddpReducer,
});
const store = createStore(
rootReducer,
{},
applyMiddleware(
thunk.withExtraArgument({ ddpConnector }),
),
);
// NOTE: This is crucial!
ddpConnector.bindToStore(store);
const RootContainer = () => (
<Provider store={store}>
<DDPProvider ddpConnector={ddpConnector}>
{/* ... */}
</DDPProvider>
</Provider>
);
Calling a method with dispatch
import { connect } from 'react-redux';
const callMethod = (method, ...params) => (dispatch, getState, { ddpConnector }) => ddpConnector.apply(name, params);
connect(
() => ({}),
dispatch => ({
onSubmit: (data) => dispatch(callMethod('api.methods.updateUser', data)),
}),
)(/* ... */);
Getting entities from store
import { connect } from 'react-redux';
import {
createStructuredSelector,
createSelector,
} from 'reselect';
class User {
constructor(doc) {
Object.assign(this, doc);
}
getName() {
return this.name;
}
}
User.collection = 'users'; // the Meteor default collection for storing users
User.selectors = {
...createEntitiesSelectors(User.collection, { Model: User }),
...createCurrentUserSelectors(User.collection, { Model: User }),
};
connect(
createStructuredSelector({
currentUser: User.selectors.getCurrent,
users: User.selectors.find(
// First, we define a predicate
(user, re) => !re || re.test(user.getName()),
// Next, we provide selectors for predicate arguments (after "user")
createSelector(
(state, props) => props.search,
search => (search ? new RegExp(search, 'i') : null),
),
)
}),
)
Connecting components
import { ddp } from '@theclinician/ddp-connector';
ddp({
subscriptions: props => [
{ name: 'api.subscriptions.currentUser', params: [] },
],
queries: props => ({
userNames: {
name: 'api.methods.getUserNames',
params: [],
},
}),
mutations: {
updateUser: ({ mutate }) => fields => mutate({
name: 'api.methods.updateUser',
params: [fields],
}).then(/* ... */),
},
}, {
// NOTE: If a function is provided here it will receive defaultLoaderComponent as the first parameter
// and it's expected to return a react node, to be displayed until subscriptions, mutations and queries are all ready.
// If you don't want loader to be rendered at all, the easiest way is to pass "null" instead of a function.
renderLoader: null,
})(({
subscriptionsReady,
mitationsReady,
queriesReady,
userNames, // when queries are ready this will containe the result of method call
}) => (
/* ... */
));
API
DDPConnector(options)
import DDPConnector from '@theclinician/ddp-connector';
/**
* @param {Object} options
* @param {Boolean} options.debug
* @param {DDPClient} options.ddpClient
* @param {Number} options.cleanupDelay
* @param {Number} options.entitiesUpdateDelay
* @param {Number} options.resourceUpdateDelay
* @param {Function} options.defaultLoaderComponent
*/
const ddpConnector = new DDPConnector(options);
// NOTE: By registering a model you can ensrue that all entities which you receive
// from store will me instances of the given model rather than pure js objects.
DDPConnector.registerModel(User); // User.collection needs to be set
ddp(options)
import { ddp } from '@theclinician/ddp-connector';
ddp({
subscriptions: (state, props) => [/* ... */],
queries: (state, props) => {/* ... */},
mutations: {},
});
Both subscriptions
and queries
are expected to return array or object (respectively) that contains elements of shape
{
name: 'methodOrSubscription',
params: [],
}
The mutations
object should define handlers, i.e. higher order functions that transform current props
to an actuall event handlers
(see recompose/withHandlers). One of the props is
a mutate
function that can be used to invoke a meteor method by passing a { name, params }
object to it.
createCurrentUserSelectors(collection, { Model })
import { createCurrentUserSelectors } from '@theclinician/ddp-connector';
createCurrentUserSelectors('users');
Returns a selectors
object with the following properties
selectors.getIsLoggingIn
selectors.getCurrent
selectors.getCurrentId
createEntitiesSelectors(collection)
import { createEntitiesSelectors as select } from '@theclinician/ddp-connector';
select('Todos').all()
select('Todos').one()
// find element by id
select('Todos').one.id('currentListId')
select('Todos').one.where(/* selectPredicate */)
select('Todos').all.where()
// prvide a custom sorter object
select('Todos').all.where().sort({
createdAt: -1,
})
// return a map id -> object rather than a list
select('Todos').all.where().byId()
// transform documents before returing the result
select('Todos').all.where().map('title')
select('TodoLists').all().lookup({
from: select('Todos').all(),
foreignKey: 'listId',
as: 'todos',
})
The following selectors are deprecated, please do not use them anymore:
select('Todos').findAndMap(predicate, transform, ...selectors);
select('Todos').findOne(predicate, ...selectors);
select('Todos').getOne(idSelector)
select('Todos').getAll
select('Todos').getAllById
Please note that .where()
accepts a predicate selector, not predicate function itself, so:
// NOTE: This is not going to work:
// select('Todos').where(todo => !todo.isReady())
const constant = x => () => x;
select('Todos').where(
constant(todo => !todo.isReady())
);
// or alternatively
select('Todos').all.satisfying(
todo => !todo.isReady()
);
This is important because you may need to do things like:
select('Todos').where(
createSelector(
selectCurrentListId
listId => todo => todo.listId === listId,
),
)