Basic React Patterns
by John Vincent
Posted on May 24, 2018
This stuff ends up sprayed everywhere, so let's create a reference document.
Patterns
Starting snippet
import React from 'react';
import PropTypes from 'prop-types';
Stateless function
Highly reusable components. They do not hold state.
const Game = () => (
<div>Hello</div>
);
or
const Game = () => <div>Hello</div>
or
const Game = function() {
return <div>Hello</div>;
}
or
function Game() {
return (
<div>Hello</div>
);
}
Stateless with properties
Notice that property types are validated. This is a required part of the pattern.
import React from 'react';
import PropTypes from 'prop-types';
function GuessItem(props) {
return <div>{props.guess}</div>;
}
GuessItem.propTypes = {
guess: PropTypes.number.isRequired,
};
export default GuessItem;
Class
Basic structure
import React from 'react';
import PropTypes from 'prop-types';
class Tag extends React.Component {
constructor(props) {
super(props);
this.state = {
workingTags: []
};
this.something = this.props.something.bind(this);
this.handleAdd = this.handleAdd.bind(this);
}
handleAdd(tag) {
const { workingTags } = this.state;
workingTags.push({
id: tag,
text: tag
});
this.setState({ workingTags });
}
render() {
const { myvar } = this.props;
this.props.something();
return (
<MyTags
handleAdd={this.handleAdd}
/>
);
}
}
Tag.propTypes = {
...
something: PropTypes.func.isRequired,
};
Tag.defaultProps = {
...
};
const mapStateToProps = state => ({
data: state.dataReducer.data
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actions, dispatch)
});
export default Tag;
export default connect(mapStateToProps, mapDispatchToProps)(Tag);
or
import React from 'react';
import PropTypes from 'prop-types';
class Tag extends React.Component {
state = { search: '' };
handleSearchChange = e => {
this.setState({ search: e.target.value });
};
handleKeyPressed = event => {
if (event.keyCode === 13) {
this.handleSubmit();
}
};
handleSubmit = () => {
this.props.actions.searchUserData(this.state.search, this.props.goals);
};
render() {
const { myvar } = this.props;
this.props.something();
return (
<Input
id="search"
variant="text"
value={this.state.search}
onChange={this.handleSearchChange}
onKeyDown={this.handleKeyPressed}
tabIndex="0"
endAdornment={
<InputAdornment position="start">
<IconButton onClick={this.handleSubmit}>
<SearchIcon />
</IconButton>
</InputAdornment>
}
/>
);
}
}
Tag.propTypes = {
...
something: PropTypes.func.isRequired,
actions: PropTypes.shape({
searchUserData: PropTypes.func.isRequired
}).isRequired,
};
Tag.defaultProps = {
...
};
const mapStateToProps = state => ({
data: state.dataReducer.data
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actions, dispatch)
});
export default Tag;
export default connect(null, mapDispatchToProps)(Tag);
export default connect(mapStateToProps, mapDispatchToProps)(Tag);
Destructuring arguments
Pass object's properties as JSX attributes.
const Tag = ({ name }) => <div>{name}</div>
is the same as
const Tag = props => <div>{props.name}</div>
Rest Parameter
Collect remaining properties into a new object
const Tag = ({ name, ...props }) =>
<div {...props}>{name}</div>
Conditional Rendering
Use the ternary operator
if
{condition && <span>Rendered when `truthy`</span> }
if-else (tidy one-liners)
{condition
? <span>Rendered when `truthy`</span>
: <span>Rendered when `falsey`</span>
}
if-else (big blocks)
{condition ? (
<span>
Rendered when `truthy`
</span>
) : (
<span>
Rendered when `falsey`
</span>
)}
Callback
Allow the parent to handle the task.
class ListProjects extends React.Component {
constructor(props) {
super(props);
this.add = this.props.add.bind(this);
}
render() {
this.add();
}
}
ListProjects.propTypes = {
add: PropTypes.func.isRequired
};
or using arrow functions
class GoalDialog extends React.Component {
handleSubmit = () => {
this.add();
};
<Button onClick={this.handleSubmit}>
Array as Children
A common pattern
render() {
const div = projects.map((project, idx) => {
return (
<ListProject
key={`key_${project.id}`}
project={project}
idx={idx}
/>
);
});
return <div>{div}</div>;
}
}
or
<div>
{[projects.map((project, idx) => (
<ListProject
key={`key_${project.id}`}
project={project}
idx={idx}
/>
))}
</div>
Events
Notice the usage, to add
<GoalDialog />
to edit, passing the data object
<GoalDialog goal={goal} />}
Put properties into state.
class GoalDialog extends React.Component {
state = { ...this.props.goal };
The default properties are used if the goal object is null, but not if the goal object is not null, or in edit mode.
const goalType = PropTypes.shape({
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string,
status: PropTypes.number.isRequired,
});
GoalDialog.propTypes = {
goal: goalType // eslint-disable-line react/no-typos
};
GoalDialog.defaultProps = {
goal: {
id: 0,
title: '',
description: '',
status: 0
}
};
Use the following for all fields, for example
<TextField
required
label="Title"
value={this.state.title}
onChange={this.handleChange('title')}
/>
will all invoke
handleChange = name => ({ target: { value } }) => {
this.setState({
[name]: value
});
};
Then the submit
<Button className={classes.button} color="primary" variant="raised" onClick={this.handleSubmit}>
Done
</Button>
will call
handleSubmit = () => {
if (this.state.title.length < 1) {
this.setState({ errorTitle: true });
return;
}
const { id, title, description, status } = this.state;
...
};
Container Component
A container does data fetching and then renders its corresponding subcomponent.
A reusable component
const projectList = ({ projects }) =>
<ul>
{projects.map(project =>
<li>{project.id} {project.title}</li>
)}
</ul>
Fetch data and render the stateless component
class ProjectListContainer extends React.Component {
constructor() {
super()
this.state = { projects: [] }
}
componentDidMount() {
$.ajax({
url: "/app-projects.json",
dataType: 'json',
success: projects =>
this.setState({projects: projects});
})
}
render() {
return <ProjectList projects={this.state.projects} />
}
}
State Hoisting
Pass a callback from a parent container component to a stateless component.
const Name = ({ onChange }) =>
<input onChange={e => onChange(e.target.value)} />
class NameContainer extends React.Component {
constructor() {
super()
this.state = {name: ""}
}
render() {
return <Name onChange={newName => this.setState({name: newName})} />
}
}
Uncontrolled Input
Notice use of Ref
<input type="text" ref={element => {
this.textInput = element;
}} />
and accessing the value of the text input field
onButtonClick() {
console.log(this.textInput.value);
}
class InputWithButton extends React.Component {
constructor(props) {
super(props);
this.onButtonClick = this.onButtonClick.bind(this);
}
onButtonClick() {
console.log(this.textInput.value);
}
render() {
return (
<div>
<input type="text" ref={element => {
this.textInput = element;
}} />
<button type="button" onClick={this.onButtonClick}>
Click me!
</button>
</div>
)
}
}
Controlled Input
class ControlledNameInput extends React.Component {
constructor() {
super()
this.state = {name: ""}
}
render() {
return (
<input type="text"
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}
/>
);
}
}
or
class ControlledNameInput extends React.Component {
state = { name: ''};
handleChange = name => ({ target: { value } }) => {
this.setState({
[name]: value
});
};
render() {
return (
<input type="text"
value={this.state.name}
onChange={this.handleChange('name')}
/>
);
}
}
onSubmit(event) {
event.preventDefault();
const text = this.state.text;
console.log(text);
// TODO: Add the card or list
this.setState({
text: ''
});
}
or
handleChange = name => ({ target: { value } }) => {
this.setState({
[name]: value
});
};
which is based on, for example
const key = 12345;
const obj = { [key]: `Some value` };
console.log('obj ', obj);
which yields
obj { '12345': 'Some value' }
React
- Basic React
- Basic React Patterns
- Basic React Redux
- Basic React Redux App
- Basic React Testing with Jest and Enzyme
- Building and deploying MyTunes to johnvincent.io
- Building and deploying React Github Helper App to johnvincent.io
- Deploy React App to Heroku using Travis Continuous Integration
- Deploy TaskMuncher React App to AWS
- First time deploy TaskMuncher React App to Digital Ocean
- Gatsby and Client Only Components
- Gatsby Getting Started
- Gatsby React Icons
- Mac Visual Studio Code
- Material-UI
- Material-UI Pickers
- Material-UI Styling
- Optimizing TaskMuncher with Webpack 4
- Overview of React Gomoku
- Overview of React Hangman
- Overview of React Lights Out
- Overview of React Yahtzee
- React Material-UI
- React Production Issues
- React PropTypes
- React/Redux Node/Express Ecosystem
- Redux Dev Tools
- Responsive Material-UI
- Styling Material-UI components using Styled-Components
- TaskMuncher Performance
- Transforming Html with Webpack
- Update TaskMuncher for Lighthouse Findings
- Update TaskMuncher to be a Progressive Web App
- Update TaskMuncher to use React BrowserRouter
- Update TaskMuncher to Webpack v4, Babel v7, Material-UI v3
- Upgrading Babel and ESLint to use React Advanced Language Features
- Webpack Bundle Analyzer