3

I'm trying to get more familiar with best practices and design patterns using React, and I have a question about which of two similar solutions is the proper one:

The first solution uses a class not extending a component, and it's constructor returns an element set based off of an object, it seems to be a little cleaner looking and less overall code.

class PostEntryElement {
	constructor(postObjectArray) {
		return (
      postObjectArray.map((postObject, index) => {
        return (
          <li style={{marginTop: '20px'}}>
            Author: {postObject.author}, 
            Body: {postObject.body},
            Created: {postObject.created},
            Title: {postObject.title}
          </li>
        )
       })
     )
	}
}

class MyComponent extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			exampleAPIBlogPostsResponse: [
				{
					title  : 'Title 1',
					body   : 'Body 1',
					author : 'Author 1',
					created: 'Created 1'
				}, {
					title  : 'Title 2',
					body   : 'Body 2',
					author : 'Author 2',
					created: 'Created 2'
				}, {
					title  : 'Title 3',
					body   : 'Body 3',
					author : 'Author 3',
					created: 'Created 3'
				}
			]
		};
	}

	render() {
		return (
			<div>
      	See All Posts Below:
				<div>
        { 
          this.state.exampleAPIBlogPostsResponse && 
          new PostEntryElement(this.state.exampleAPIBlogPostsResponse)
        }
	      </div>
			</div>
		);
	}
}

React.render(<MyComponent />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

The second solution uses a custom component with properties, and passes them in dynamically from objects, but has more code and seems less 'clean'.

class PostEntryElement extends React.Component {
	constructor(props) {
		super(props);
	}

	render() {
		return (
          <li style={{marginTop: '20px'}}>
            Author: {this.props.author}, 
            Body: {this.props.body},
            Created: {this.props.created},
            Title: {this.props.title}
          </li>
		);
	}
}

class MyComponent extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			exampleAPIBlogPostsResponse: [
				{
					title  : 'Title 1',
					body   : 'Body 1',
					author : 'Author 1',
					created: 'Created 1'
				}, {
					title  : 'Title 2',
					body   : 'Body 2',
					author : 'Author 2',
					created: 'Created 2'
				}, {
					title  : 'Title 3',
					body   : 'Body 3',
					author : 'Author 3',
					created: 'Created 3'
				}
			]
		};
	}

	generatePosts() {
		return (this.state.exampleAPIBlogPostsResponse.map((postObject, index) => {
			return (
       <PostEntryElement
				title={postObject.title}
				body={postObject.body}
				author={postObject.author}
				created={postObject.created} 
       />
      )
		 })
    );
	}

	render() {
		return (
			<div>
        See All Posts Below:
				<div>
					{this.state.exampleAPIBlogPostsResponse && this.generatePosts()}
				</div>
			</div>
		);
	}
}

React.render(<MyComponent />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

3 Answers 3

1

In React your state should be managed by container components. Containers should generally be classes that extend Component. Presentational components should be stateless pure functions that render according to properties provided by their container.

If you're interested, Thinking in React is a great introduction to React, and how components should interact with each other.

Sign up to request clarification or add additional context in comments.

Comments

1

I'll approach this as if you are another developer on my team and I'm expected to work with your code. This is basically the same thing as you coming back to your code in a year and having to work with it again.

In short, solution #2 is better, though still not perfect. Here's why:

1) Debugging. If you're not using the React devtools, you should try them out. They'll give you a better idea of how React works. But in short: React expects to have full control over all components. It wants to be able to create instances of your classes internally (via new). Using new yourself to create any part of the view is usually a bad thing, making problems much harder to track down.

2) Related to #1. Prefer components over "floating markup". While in React, you can throw JSX anywhere, you should try to always make sure that markup belongs to a specific component. In your first example where you create a new type of class that isn't a component, I now have to learn how you expect me to use it before I can. More work for me, more surface area for bugs. This is why there are standards. Use components.

3) In your first example, the non-component returns a list of elements. But in React,

Components must return a single root element

(see https://facebook.github.io/react/docs/components-and-props.html). This is why @aeid's solution does not work as-is. And I don't think he even noticed. I don't really blame him. He was thrown off by this poor practice. This is what React expects, so it's what all React developers expect. Note that this problem never would have occurred if you'd used a real component in the first place and forced yourself to conform to the standards.

4) Prefer upgrading your tools to downgrading your code. I think you're hoping solution #1 is better because then you don't have to list out 'author', 'body', 'created', and 'title' twice. Guess what? Enabling ES2017 in Babel gives you the object spread "operator". So you can do this:

<PostEntryElement {...postObject} />

rather than listing out all the props. Though such power can certainly be abused, I still recommend it if it's a possibility for you.

Anyway, so much for the comparison. Now let's fix up example #2.

1) Back to "no floating markup". In particular, if you find yourself writing markup outside a component's render() method, it's a good sign you need to extract a new component. In this case, the MyComponent.generatePosts() method should be replaced with a new component entirely, e.g. PostEntryList. This is a little more boilerplate, perhaps, but much easier to read.

2) Remember to use key! As a basic rule of thumb, whenever you use Array.prototoype.map() in a render() method (or wherever you write markup if you insist on writing it outside the render() methods), you need to add a unique key property to each element.

3) As others have mentioned, consider using pure functions for presentational components. I'm not actually particular on this one. I find removing the constructor to be clear enough.

So all together:

class PostEntryList extends React.Component {
  render() {
		return (
      <ul>
        {this.props.elements.map((postObject, index) => (
          <li style={{marginTop: '20px'}} key={postObject.title}>
            Author: {postObject.author}, 
            Body: {postObject.body},
            Created: {this.props.created},
            Title: {this.props.title}
          </li>
        ))}
      </ul>
    );
  }
}

class MyComponent extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			exampleAPIBlogPostsResponse: [
				{
					title  : 'Title 1',
					body   : 'Body 1',
					author : 'Author 1',
					created: 'Created 1'
				}, {
					title  : 'Title 2',
					body   : 'Body 2',
					author : 'Author 2',
					created: 'Created 2'
				}, {
					title  : 'Title 3',
					body   : 'Body 3',
					author : 'Author 3',
					created: 'Created 3'
				}
			]
		};
	}

	render() {
		return (
			<div>
        See All Posts Below:
				<div>
					{this.state.exampleAPIBlogPostsResponse && <PostEntryList elements={this.state.exampleAPIBlogPostsResponse} />}
				</div>
			</div>
		);
	}
}

ReactDOM.render(<MyComponent />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

1 Comment

Wow, I can't thank you enough for such an incredibly comprehensive answer! Much appreciated.
0

if your component doesn't need to maintain any state , use stateless component they are pure functions that take props and render ui

PostEntryElement component as stateless component ,

const PostEntryElement = (props) => {
        return (
      postObjectArray.map((postObject, index) => {
        return (
          <li style={{marginTop: '20px'}}>
            Author: {postObject.author}, 
            Body: {postObject.body},
            Created: {postObject.created},
            Title: {postObject.title}
          </li>
        )
       })
     )
}

if for whatever reason your component need state then use a stateful component

read this

1 Comment

Ahhh, so keep it as a function instead of an extended custom component, but get rid of the constructor and initialization since it doesn't require state manipulation?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.