Hi All,
I’m trying to work out a new pattern, but was having some trouble. Wanted some input on the below questions (react) -
What pattern do you use for a complex SPA component? This is what I use:
Routes → Container → Higher Order Component (initialize store here) → Misc Lower Level Components.
(1) Routes: I have a module ‘auth’ which exports a number of functions for authentication, starting from:
export const handleAuthUser = () => {
// User does not exist
if (!Meteor.user()) {
browserHistory.push('/');
}
};
Question: Can someone let me know how to test this? What i want to do is to run a unit test which (a) under Meteor.isServer, want to initialize user; (b) under Meteor.isClient want to test this function; (c) under meteor.isClient, want to shallow evaluate component with this function in it? Any advice appreciated. A whole example (I think the way to test is as a whole app, but this is currently causing me trouble.
(2) Container: Containers can get pretty complex, but they all follow the exact same pattern:
import { Meteor } from 'meteor/meteor';
import { composeAll, composeWithTracker } from 'react-komposer';
import { Loading } from '../components/utility/loading.js';
// Inject into here...
import { User } from '../components/user/user.js';
const composerUsers = (params, onData) => {
const subscription = Meteor.subscribe('user.users');
if (subscription.ready()) {
const users = Meteor.users.find().fetch();
onData(null, { users });
}
};
const composerUser = (params, onData) => {
onData(null, { user: Meteor.user() });
};
export default composeAll(
composeWithTracker(composerUsers, Loading, null, { pure: false }),
composeWithTracker(composerUser, Loading, null, { pure: false }),
)(User);
Question: Is there any benefit to passing Meteor.user() down in a prop in react, as opposed to simply import { Meteor } from 'meteor/meteor';
everywhere and using Meteor.user() in lower level components as needed? My thinking is yes, cause we do not need to add in all of the meteor package in a lower level component, but not sure.
(3) Higher Order Component: This is where the bulk of my questions are. How do people arrange this? My preferred pattern is to create a store, and then use the store in lower level components. As an example:
import React, { Component, PropTypes } from 'react';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import Paper from 'material-ui/Paper';
import {List, ListItem, MakeSelectable } from 'material-ui/List';
import Divider from 'material-ui/Divider';
import IconButton from 'material-ui/IconButton';
import ActionHelp from 'material-ui/svg-icons/action/help';
import { handleAuthUser } from '../../../modules/auth/auth.js';
import { getUserName } from '../../../api/users/helpers.js';
import { MyAccountIcon } from '../../parts/myAccount/myAccountIcon.js';
import { styles } from '../styles.js';
const SelectableList = MakeSelectable(List);
@observer
export class User extends Component {
@observable uiStore = {
paneSelection: String('news'),
itemSelection: String(''),
};
componentWillMount() {
handleAuthUser();
}
handleChange(event, value) {
this.uiStore = {
paneSelection: value,
itemSelection: '',
};
}
handleClick() {
this.uiStore.paneSelection = 'help';
}
handleChangeUIStore(option, newValue) {
if (option === 'paneSelection') {
this.uiStore.paneSelection = newValue;
}
if (option === 'itemSelection') {
this.uiStore.itemSelection = newValue;
}
}
renderPane() {
switch (this.uiStore.paneSelection) {
case 'library': return (
<span>Library</span>
);
case 'news': return (
<span>News</span>
);
case 'myAccount': return (
<span>My Account</span>
);
case 'help': return (
<span>Help</span>
);
default: return (
<span>Library</span>
);
}
}
render() {
return (
<div className="container-fluid">
<div className="row">
<div className="col-12">
<div style={styles.help}>
<MyAccountIcon
user={this.props.user}
changeDataStore={this.handleChangeUIStore.bind(this)}
/>
<IconButton
tooltip="Help"
onClick={this.handleClick.bind(this)}
>
<ActionHelp />
</IconButton>
</div>
</div>
</div>
<div className="row">
<div className="col-2">
<Paper style={styles.nav}>
<SelectableList
onChange={this.handleChange.bind(this)}
>
<ListItem
value="news"
primaryText={getUserName(this.props.user._id)}
style={styles.textBold}
/>
<Divider/>
<ListItem
value="library"
primaryText="Library"
/>
</SelectableList>
</Paper>
</div>
<div className="col-10">
<Paper style={styles.pane}>
{ this.renderPane() }
</Paper>
</div>
</div>
</div>
);
}
}
User.propTypes = {
users: PropTypes.array.isRequired,
user: PropTypes.object.isRequired,
};
PIC: 1
PIC: 2 (older version of same component)
Question: So what I am trying to do is to come up with a pattern for the higher order components to put a store intelligently into a lower order component.
Right now, it involves passing the handleChangeUIStore
function down to every component. This is terribly inefficient as it means (i think) that every component in the hierarchy gets re-rendered when the lowest-most component changes something simple. There is also a lot of boilerplate making this function prop go all the way down. The boilerplate looks like (think the second pic):
import React, { Component, PropTypes } from 'react';
import { observer } from 'mobx-react';
import { RenderPath } from './renderPath.js';
import { RenderElements } from './renderElements.js';
import { RenderActions } from './renderActions.js';
@observer
export class NavPart extends Component {
// this bit, needs to be replicated in every lower component that may change the store.
handleChangeStore(option, newValue) {
this.props.changeStore(option, newValue);
}
// this bit, needs to be replicated in every lower component that may change the store.
handleChangeUIStore(option, newValue) {
this.props.changeUIStore(option, newValue);
}
funcRenderPath(item) {
return this.props.funcRenderPath(item);
}
funcRenderElements(item) {
return this.props.funcRenderElements(item);
}
render() {
return (
<span>
<RenderPath
uiStore={this.props.uiStore}
changeUIStore={this.handleChangeUIStore.bind(this)}
store={this.props.store}
changeStore={this.handleChangeStore.bind(this)}
funcRenderPath={this.funcRenderPath.bind(this)}
/>
<RenderElements
uiStore={this.props.uiStore}
changeUIStore={this.handleChangeUIStore.bind(this)}
store={this.props.store}
changeStore={this.handleChangeStore.bind(this)}
funcRenderElements={this.funcRenderElements.bind(this)}
/>
<RenderActions
uiStore={this.props.uiStore}
changeUIStore={this.handleChangeUIStore.bind(this)}
store={this.props.store}
changeStore={this.handleChangeStore.bind(this)}
/>
</span>
);
}
}
NavPart.propTypes = {
uiStore: PropTypes.object.isRequired,
changeUIStore: PropTypes.func.isRequired,
store: PropTypes.object.isRequired,
changeStore: PropTypes.func.isRequired,
funcRenderPath: PropTypes.func.isRequired,
funcRenderElements: PropTypes.func.isRequired,
};
Question: I know i can use {…props} and send them down. That is not what I am asking. It is that when a lower level component changes, the change prop function handler is triggered up the chain until it reaches the top of the house and so all components are re-rendered (i think).
What I want to do is below, but i cannot get it to work. Please have a look at this, it is roughly what I am trying to replicate.
It should be possible to create a store class like so similar to the example:
import { observable, computed, action } from 'mobx';
class FooStore {
@observable foo; // some property
constructor() {
this.foo = foo;
}
@computed get foo() {
return foo;
}
@action setFoo = (someFoo) => {
this.foo = someFoo;
}
}
const fooStore = new FooStore();
export default fooStore;
export { FooStore };
And now it should be possible to inject this store into the UI, in the higher order component:
const stores = { fooStore };
<Provider { ...stores }>
//Lower order components
</Proivder>
And lastly, use in the lower order component as (example from here):
// This is from the example on the website. Obviously would inject the fooStore here...
// and not inject the userStore and trackStore
const StreamContainer = inject('userStore', 'trackStore')(observer(({ userStore, trackStore }) => {
return (
<Stream
me={userStore.me}
tracks={trackStore.tracks}
activeTrack={trackStore.activeTrack}
clientId={CLIENT_ID}
onAuth={auth}
onPlay={trackStore.onPlay}
/>
);
}))
The idea is, inject the relevant UI store into the higher order component. Then any change to the store is a simple onPlay={trackStore.onPlay} and I don’t have to port the function manually up and down the hierarchy.
I can’t get this pattern to work. It seems it would simplify the component hierarchy quite a bit, so the only prop going down is the store itself. The singleton of the store has all the information which can be picked up using helper type functions (@computed’s) or method type functions (@actions). It should be pointed out I am looking to use this more for UIStores than DataStores, but hey, save the uiStore somewhere and now you have a user settings page right? Whatever.
It should be pointed out, that the other attraction is that lets say we were working on a collection. Then, the store would include @action functions which include the collection helpers. This means alongside my collection simple schema I have a set of methods to manipulate the collection and associated helpers. As a file org issue, I can now refactor the code to handle horizontal scaling (multiple instances of app with single mongo db) easily as all my helper and methods are in one place (near the definition of the collection)
Has anyone tried anything similar? The major attraction is that the state changes in the store should be automagically handled by MobX, which frees me from caring about its efficiency. It also seems a far less boilerplate solution than a Redux store.
Any advice would be appreciated. Thanks in advance.
Tat
P.S. I’m using SimpleGrid for grids here. And Material-UI for other stuff…
tl,dr: How do you guys organise function helpers in stores?