Skip to content

Instantly share code, notes, and snippets.

@dbismut
Last active January 25, 2022 09:02
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save dbismut/35b89185998d68bc18fd to your computer and use it in GitHub Desktop.
Save dbismut/35b89185998d68bc18fd to your computer and use it in GitHub Desktop.
React Redux Meteor middlewares
// loading user (compatible with react-router-ssr)
export function loadUserLoggingIn() {
return {
type: USER_LOGGING_IN,
meteor: {
get: () => Meteor.loggingIn()
}
};
}
export function loadUserData() {
return {
type: USER_DATA,
meteor: {
subscribe: {
name: 'userData',
get: () => Meteor.users.findOne(Meteor.userId())
}
}
};
}
// loading elements from collection
export function loadPosts() {
return {
type: POSTS,
meteor: {
subscribe: {
name: 'posts',
get: () => Posts.find().fetch()
}
}
}
}
// dispatching Meteor method
export function signIn(email, password) {
return {
type: USER_SIGNIN,
meteor: {
call: {
method: Meteor.loginWithPassword,
params: [email, password]
}
}
}
}
// dispatching Meteor call
export function checkUsername(username) {
return {
type: USER_CHECKUSERNAME,
meteor: {
call: {
method: 'users.usernameIsFree',
params: [{username}]
}
}
}
}
export function actionTypeBuilder(prefix) {
return {
type: actionType => `${prefix}/${actionType}`,
loading: actionType => `${actionType}/loading`,
ready: actionType => `${actionType}/ready`,
stopped: actionType => `${actionType}/stopped`,
changed: actionType => `${actionType}/changed`,
error: actionType => `${actionType}/error`,
success: actionType => `${actionType}/success`
};
}
export default actionTypeBuilder('@myApp');
import actionTypeBuilder from './actionTypeBuilder';
const comps = {};
export default store => next => action => {
if (!action.meteor || (!action.meteor.get && !action.meteor.unsubscribe)) {
return next(action);
}
const { get, unsubscribe } = action.meteor;
if (Meteor.isServer) {
const data = get();
next({ type: actionTypeBuilder.changed(action.type), data });
return Promise.resolve();
}
// If we already have an handle for this action
if (comps[action.type]) {
if (unsubscribe) {
comps[action.type].stop();
}
const data = get();
next({ type: actionTypeBuilder.changed(action.type), data });
return Promise.resolve();
}
return new Promise((resolve, reject) => {
comps[action.type] = Tracker.autorun(() => {
const data = get();
next({ type: actionTypeBuilder.changed(action.type), data });
return resolve();
});
comps[action.type].onStop(() => {
next({ type: actionTypeBuilder.stopped(action.type), name } );
delete comps[action.type];
});
});
};
import actionTypeBuilder from './actionTypeBuilder';
export default store => next => action => {
if (!action.meteor || !action.meteor.call) {
return next(action);
}
const { method, params, showError } = action.meteor.call;
const parameters = params || [];
const meteorMethod = typeof method === 'string' ? Meteor.call : method;
if (typeof method === 'string') {
parameters.unshift(method);
}
return new Promise((resolve, reject) => {
next({ type: actionTypeBuilder.loading(action.type) });
meteorMethod(...parameters, function(error, result) {
if (error) {
next({ type: actionTypeBuilder.error(action.type), error });
if(showError) {
next(showNotification(error));
}
return reject(error);
}
next({ type: actionTypeBuilder.success(action.type) });
return resolve(result);
});
});
};
import actionTypeBuilder from './actionTypeBuilder';
import { SUBSCRIPTIONS } from './subscriptionsActions';
const subs = {};
export default store => next => action => {
if (!action.meteor || (!action.meteor.subscribe && !action.meteor.unsubscribe)) {
return next(action);
}
const { name } = action.meteor.subscribe || action.meteor.unsubscribe;
const sub = subs[name];
if (action.meteor.unsubscribe) {
if (sub) {
delete sub.subscribedActions[action.type];
if (_.isEmpty(sub.subscribedActions)) {
sub.handle.stop();
}
}
return Promise.resolve();
}
const { params, get } = action.meteor.subscribe;
const parameters = params || [];
if (Meteor.isServer && action.meteor.subscribe) {
Meteor.subscribe(name, ...parameters);
next({ type: actionTypeBuilder.ready(SUBSCRIPTIONS), name } );
if (get) {
return next({ type: action.type, meteor : { get } });
}
return Promise.resolve();
}
const existing = sub && _.isEqual(sub.parameters, parameters);
if (existing && sub.handle.ready()) {
if (! sub.subscribedActions[action.type]) {
sub.subscribedActions[action.type] = 'ready';
}
if (get) {
return next({ type: action.type, meteor : { get } });
}
return Promise.resolve();
}
if (sub && sub.handle.ready()) {
sub.handle.stop();
}
return new Promise((resolve, reject) => {
next({ type: actionTypeBuilder.loading(SUBSCRIPTIONS), name });
const handle = Meteor.subscribe(name, ...parameters, {
onReady: () => {
next({ type: actionTypeBuilder.ready(SUBSCRIPTIONS), name } );
if (get) {
return next({ type: action.type, meteor : { get } }).then(resolve);
}
return resolve();
},
onStop: (error) => {
next({ type: actionTypeBuilder.stopped(SUBSCRIPTIONS), name } );
delete subs[name];
if (error) {
next({ type: actionTypeBuilder.error(SUBSCRIPTIONS), name, error } );
reject(error);
}
}
});
subs[name] = {
subscribedActions: {[action.type]: 'ready'},
parameters: _.clone(parameters),
handle: handle
};
});
};
export default function(state = [], action) {
const { type, data } = action;
switch (type) {
case actionTypeBuilder.changed(POSTS):
return data;
default:
return state;
}
}
import actionTypeBuilder from './actionTypeBuilder';
export const SUBSCRIPTIONS = actionTypeBuilder.type('SUBSCRIPTIONS');
export function subscribeTo(name, type, ...params) {
return {
type: type,
meteor: {
subscribe: {
name: name,
params: params
}
}
}
}
import actionTypeBuilder from './actionTypeBuilder';
import { SUBSCRIPTIONS } from './subscriptionsActions';
const initialState = {};
export default function(state = initialState, action) {
const { type, name } = action;
switch (type) {
case actionTypeBuilder.ready(SUBSCRIPTIONS):
return {...state, [name]: {ready: true} };
case actionTypeBuilder.loading(SUBSCRIPTIONS):
return {...state, [name]: {ready: false, loading: true} };
case actionTypeBuilder.stopped(SUBSCRIPTIONS):
return {...state, [name]: {ready: false, loading: false} };
case actionTypeBuilder.error(SUBSCRIPTIONS):
return {...state, [name]: {ready: false, error: true, loading: false} };
default:
return state;
}
}
@michaltakac
Copy link

Btw thank you very much for this gist!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment