Somewhere along the road of JavaScript fatigue, I believe I had developed a bad habit. I wanted to keep my React components free of any Meteor-related stuff, so I was relying on Redux as purely an action dispatcher:
class MyComponent extends Component {
handleSubmit(args) {
this.props.dispatch(updateUserProfile(args));
}
}
So then I’d have a whole bunch of Redux actions that were basically just thunks, and dispatched another action to report whether the Meteor method had succeeded or failed:
export const UPDATE_USER_PROFILE = 'UPDATE_USER_PROFILE';
export function updateUserProfile(args) {
return (dispatch) => {
updateProfile.call(args, (error) => {
if (!error) dispatch(updateUserProfileSucceeded());
else dispatch(updateUserProfileFailed(error.reason));
});
}
}
export const UPDATE_USER_PROFILE_SUCCEEDED = 'UPDATE_USER_PROFILE_SUCCEEDED';
export function updateUserProfileSucceeded() {
return {
type: UPDATE_USER_PROFILE_SUCCEEDED,
}
}
export const UPDATE_USER_PROFILE_FAILED = 'UPDATE_USER_PROFILE_FAILED';
export function updateUserProfileFailed(errorMessage) {
return {
type: UPDATE_USER_PROFILE_FAILED,
errorMessage, // but probably not best suited for a persistent state model
}
}
Looking back now, I don’t think this is a great way to keep components pure. So now I’m experimenting with another approach that I wanted to share, just in case it helps anyone, or if anyone who’s been down this road has feedback. Check it out:
So, in UserProfile.js:
processSubmission(formStatus: Object, fields: Object) {
if (!formStatus.valid) {
this.setState({
dialogMessage:
'Some fields in the form are invalid. Please correct them.',
});
} else {
this.props.updateUserProfile({
email: fields.email.value,
firstName: fields.firstName.value,
lastName: fields.lastName.value,
password: fields.password.value,
}, (error: Object) => {
if (!error) console.log('done');
else console.error(error);
});
}
}
And in UserProfileContainer.js (wraps UserProfile):
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
import UserProfile from '../components/UserProfile';
import { updateUserProfile as _updateUserProfile } from '../methods';
export default createContainer(() => {
const updateUserProfile = (args: Object, callback: Function) => {
_updateUserProfile.call({ ...args, userId: Meteor.userId() }, callback);
};
return {
updateUserProfile,
user: Meteor.user(),
}
}, UserProfile)
With this approach, UserProfile
is kept pure, and I’m free to stub the updateUserProfile
prop when doing testing or using React Storybook. UserProfileContainer
handles anything Meteor-oriented, and injects the userId
into the arguments to be passed to the method.
If anyone has any better/cleaner approaches, I definitely wanna hear about it!