Loading react with initial data/user-side settings page

I’m creating a settings page to allow users to change their own: e.g. Email address/name.

Question with React - how do you go about loading the initial email address in the form input of a React controlled component? So that the user can go to domain.com/settings - and the email field has the current email address filled in already.

I’ve wired up a method so that when the save button is hit, the value updates the Meteor.users collection, just cannot load the initial data into the form. Thought to set state via passing props but that throws an error (and I believe is considered an anti-pattern)?

Thanks in advance to anyone who can point me to a tutorial/writeup/help with an example!

I would recommend using composeWithTracker from https://github.com/arunoda/react-komposer

Tutorial:
https://themeteorchef.com/snippets/using-react-komposer/

Cheers @mrmsupport

I’m currently using meteor-react-data as recommended in the guide, which also passes in data as props (which seems to be similar to React Komposer). How would I pass Komposer data in as initial state (for a controlled form input component)?

Komposer will only render the child components once the onData() function is called in the composer. while loading the data Komposer will let you render a loading component.

Komposer will only render the child components once the onData() function is called

Yup I get that. It’s the next step that I’m having trouble with. Once I have the data, what then?

  1. I can’t pass the data into initial state because that would be settings state via props which we are trying to avoid
  2. If I pass the data as the defaultValue of an input field, then I can’t use that field as a controlled component anymore

Setting state through props is regarded as an anti-pattern, unless you use it to prefill a form( like you are trying to do). See here.

1 Like

Awesome stuff thanks guys! @copleykj @eleventy

Apologies for the delay, been AWOL couple of days. Will go try it out now…

Hmm, the issue I seem to be having is that:

Props are being passed by react-container. Therefore, when the component initially renders, the props are not yet ready (the subscription is not yet ready) which causes a typeerror:

Uncaught TypeError: Cannot read property 'emailFromName' of undefined(…)

Anyone figure out/have suggestions on how to get round this?

Your render function should be able to handle this: check if the data property is defined, else return something like an empty string.

First render, the empty string is displayed, and when the data comes back from the server, the component rerenders with the username

Data seems to be properly defined. E.g. simplifying my code to test it out:

If in my code I type in this, it works and renders fine:

export default Settings;

class Settings extends Component {
	constructor(props) {
		super(props);
		this.state = {
			email: '',
		};
	}
	render() {
		//loading prop checks to see if subscription is ready
		if ( this.props.loading ) {
			return <p>loading...</p>
		}
		
		return (
			<div>this.props.email</div>
		)
	}
}

It works perfectly fine.

If I instead use this, it does not work:

class Settings extends Component {
	constructor(props) {
		super(props);
		this.state = {
			email: this.props.email,
		};
	}
	render() {
		//loading prop checks to see if subscription is ready
		if ( this.props.loading ) {
			return <p>loading...</p>
		}
		
		return (
			<div>this.state.email</div>
		)
	}
}

I use the following format for pre-populated inputs:

<input placeholder="some placeholder text" value={this.props.text || ""} onChange={this.handleTextChange.bind(this)}></input>

where handleTextChange(event) sends a change to the top-level state and the text value is passed back down as props. Seems to work pretty well for me.

Thanks @globalise

Issue is, though, that in your example, the default value is the same as the value being changed/updated by your onChange handler (i.e. this.props.text) . However, I am trying to load an initial value from Mongo, while the onChange handler is updating the state in the component (or a top level component, same principle applies).

Or to put it another way via the use of a specific example. I want to allow a user to update their email address from their end. When they go to their settings page, they need to see their current email address (via the default value in the input). When they edit it, the onChange handler needs to store the new value until the save button is clicked. (Unless we wire up the onChange handler to update Mongo directly, but that seems like it will be a lot of unnecessary Mongo queries for each character).

Does that make sense?

Instead of updating with onChange, could you use an ‘onBlur’ function which only updates Mongo when the user leaves the input (i.e. once for the whole change rather than for each character)? That at least has the benefit of not storing your props as state. It also eliminates the need for a save button.

Great idea mate - might do this actually!

But workarounds aside (even if in this case it is better than what I was trying), I would like to learn the flow on how to go about doing this. Never know what other scenario this may come up in.

We have an answer!! Thanks to @callum and @emminnn from the TMC Slack group for it.


You can set state in the lifecycle methods - either componentWillReceiveProps or componentDidUpdate. E.g:

componentWillReceiveProps(nextProps) {
			this.setState({
				email: nextProps.currentUser.emails[0].address
			})
	}

Thanks for all the help guys!

1 Like

Is there any specific reason you need to have the data available in both props and state? Setting state through the use of props is considered an anti-pattern, and in this case redundant as far as I can tell.

Re: Antipattern - I thought so too. See here: Loading react with initial data/user-side settings page

Specific reason - to set initial form value state for users to update their e.g. email address saved in Mongo

Hello, from where you are passing data to this form. Sorry I am asking this question as I am new to Meteor-React. Can you share your code. I’m new to React so I wasn’t aware of details. I know how to pass data when having array i.e using map function. But when there are only one document(findOne) i.e when we receive only one object in this case how to pre-populate it in form.Thanks in advance!

Hey mate. I’ve already shared my code above.

If you want to share your code, we can have a look and find a solution.

Note - Your subscriptions should be returning a cursor - I was under the impression that findOne will not work for that reason - though you can of course use findOne after subscribing.