Just started using React in Meteor for a new app, and this thread was very useful.
I’ve modified things to reduce the amount of boilerplate I’d have to write, and this might be a useful addition to the conversation. (Or I might learn something about how I should be doing things).
Basically, I can write components that consume context like this (the context is added to props) and write providers concentrating on just getting the data its schema:
some-component.jsx
import React from 'react';
import PropTypes from 'prop-types';
import { InjectConnectionStatusContextInto, connectionStatusPropTypes } from '/imports/client/providers/connection-status-provider';
export class ConnectionStatusInfo extends React.Component {
render() {
const { connected, status, retryCount } = this.props.connectionStatus;
return (
<React.Fragment>
<h3>Connection Information</h3>
<p>Status: <code>{status}</code></p>
<p>Connected: <code>{connected.toString()}</code></p>
</React.Fragment>
);
}
}
ConnectionStatusInfo.propTypes = {
connectionStatus: PropTypes.shape(connectionStatusPropTypes).isRequired,
};
export default InjectConnectionStatusContextInto(ConnectionStatusInfo);
The provider:
import PropTypes from 'prop-types';
import { Meteor } from 'meteor/meteor';
import createReactiveContext from './create-reactive-context';
export const connectionStatusPropTypes = {
retryCount: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
connected: PropTypes.bool.isRequired,
};
const propTypes = {
connectionStatus: PropTypes.shape(connectionStatusPropTypes).isRequired,
};
function getProps() {
return {
connectionStatus: Meteor.connection.status()
};
}
export const {
ConnectionStatusProvider, ConnectionStatusConsumer,
ConnectionStatusContext, InjectConnectionStatusContextInto
} = createReactiveContext('ConnectionStatus', propTypes, getProps);
I’ve dumped all the boilerplate into a function createReactiveContext
which is shown here:
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'underscore';
import { withTracker } from 'meteor/react-meteor-data';
function injectContext(ContextConsumer, WrappedComponent, preferContext) {
// context overwrites props if preferContext = true
return props => {
return (
<ContextConsumer>
{context => preferContext ? <WrappedComponent {...props} {...context}/> : <WrappedComponent {...context} {...props}/>}
</ContextConsumer>
);
};
}
export default function createReactiveContext(contextName, propTypes, reactivePropFunction,
{ preferContext = true, namedComponents = true } = {}
) {
const Context = React.createContext(contextName);
class ReactiveProvider extends React.Component {
render() {
return (
<Context.Provider value={this.props}>
{this.props.children}
</Context.Provider>
);
}
}
ReactiveProvider.propTypes = {
children: PropTypes.node.isRequired
};
_.extend(ReactiveProvider.propTypes, propTypes);
const Provider = withTracker(reactivePropFunction)(ReactiveProvider);
const Consumer = Context.Consumer;
const InjectContextInto = Component => injectContext(Consumer, Component, preferContext);
if (namedComponents) {
return {
[`${contextName}Provider`]: Provider,
[`${contextName}Consumer`]: Consumer,
[`${contextName}Context`]: Context,
[`Inject${contextName}ContextInto`]: InjectContextInto,
};
} else {
return { Provider, Consumer, Context, InjectContextInto };
}
}
… that’s as currently written in my codebase.
Comments very much welcome. (Actually, comments sought… because I’m not sure if this is the best way.)