This is guide to getting started with React and, later, Redux. it will use a Node.js, Express.js, and MongoDB back end, so familiarity with that tech is ideal.
Introduction to Create React App
CSS and Assets in Create React App
Importing and Exporting Modules
Designing State: Minimizing Components, Downward Data Flow
It is a popular, powerful front-end framework and library developed and sponsored by Facebook. It started on the web but, thanks to React Native, we can use it on mobile. React has a very strong community of developers.
It makes it easy to make reusable "view components"; a combination of HTML, CSS, and JavaScript. These encapsulate logic and HTML into a class. It makes it easier to build modular applications.
Previously, we would separate HTML, CSS, and JavaScript into their own tasks. With React, we combine all three to create components, so it is important to consider them in tandem.
Because Facebook is backing it, it is not likely to go away anytime soon. It is in demand. It is incredibly easy to create modular web applications.
It is hotly debated whether or not React is a framework, library, or both.
Frameworks require you to follow certain rules in order to produce a result. Libraries offer tools that enable developers to produce a result. React, at its base, is a library. However, when we introduce other tools like React Router and Redux, we create more of a framework because those common tools introduce rules that we must abide by.
Ultimately, React can be considered both a library and a framework depending on the stack.
This section covers components, props, state, JSX, and the component lifecycle. Create React is a tool utilized elsewhere but, for the sake of creating a component for the first time, one could use this boilerplate and create "components/index.js":
Note: It is especially important that the index.js component be of type"text/jsx". Components are written in JSX format, a HTML-like format that is actually JavaScript. We use this to interpret React components in the DOM.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>First Component</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone"></script>
<script src="components/index.js" type="text/jsx"></script>
</script>
</body>
</html>This is the core React package.
This works in tandem with React Development. It is required for React work in the browser.
Babel is a transpiler that translates our modern JavaScript into ES 2015 and ES5 JavaScript so that it is compatible with older browsers.
Components are the main building block of React. React describes components: "[they] let you split the UI into independent, reusable pieces, and think about each piece in isolation". One of the most challenging elements of React is determining what should be a component.
Components are:
- The building blocks of React
- Pieces of UI & View logic
- Classes that know how to render themselves into HTML
Here's a pseudocode example of how components work in ES 2015 style JavaScript:
class Dog {
constructor(name, color) {
this.name = name;
this.color = color;
}
render() {
return '<p>${this.name}</p>';
}
}Each 'Dog' has a name and a color. It knows how to render itself in the DOM. Again, this is not real React, but it illustrates how React performs.
We can either declare Class Components or Function Components. There are uses for each.
Note: It is standard to capitalize the declared names of React components.
- The traditional React component
- Write logic in a JavaScript class
- Must include a 'render' method
- Must 'extend' (inherit) the React.Component class
Here is a basic Component that renders as a child of 'div#root'.
class Hello extends React.Component {
render() {
// Can only return one element.
// You may return one element that contains others, though.
return (
<div>
<h1>Hello!</h1>
<h3>How's it going?</h3>
</div>
);
}
}
ReactDOM.render(<Hello />, document.getElementById('root'));- Used for simpler, "dumb" components
- Write logic in a JavaScript function
- No render method needed; just returns the content/markup
Here is the same Component from the class Component example as a function Component:
function Hello {
return (
<div>
<h1>Hello!</h1>
<h3>How's it going?</h3>
</div>
);
}- Both can accept 'props' and render content
- Historically, function components couldn't use important features like:
- State
- Lifecycle Methods
- With the introduction of 'Hooks' we can now write full-featured function components
Depending on who you ask, JSX stands for JavaScript Syntax Extension or JavaScript Syntax XML. Allows us to type HTML-looking code in JavaScript. It allows us to combine our UI with our JavaScript logic.
Note: You do not need JSX to write React, but it is much uglier.
JSX is more strict than HTML, elements must either:
- Have an explicit closing tag ...
- Be explicitly self-closed:
You cannot leave the '/' off or else it will syntax error.
JSX "fundamentally just provides syntactic sugar for the React.createElement(component, props, ...children) function". Babel will transpile our JSX into standard JavaScript.
If we have a component that returns some JSX:
<div id="jsxDemo">
<section>
<h1>Some picture</h1>
<img src="image.png" />
</section>
</div>Babel will transpile this into:
React.createElement("div", {
id: "jsxDemo"
}, React.createElement("section", null,
React.createElement("h1", null, "Some picture"),
React.createElement("img", {
src: someImage
})
)
);Again, JSX is not required to create React apps, but having to write nested React.createElement calls is far less readable and more difficult to manage.
Using curly braces {} we can embed JavaScript in our JSX:
render() {
return (
<section>
<h1>Embedded JS: 2 * 4 = {2 * 4}</h1>
<img src={getImageUrl()} /> // Quotes will be added
</section>
);
}We can use our embedded JavaScript syntax to use conditional logic:
const getNum = () => {
return Math.floor(Math.random() * 10) + 1;
};
class NumPicker extends React.Component {
render() {
let yesImgUrl = "someImage.gif"
let num = getNum();
return (
<div>
<h2>Your number is {num}</h2>
<p>{num === 7 ? 'Lucky!' : 'An acceptable number.'}</p>
{num === 7 ? <img src={yesImgUrl} /> : null}
</div>
);
}
}It is common to use array.map() to output loops in JSX. Below, for each message, we create a list item. Then, an unsorted list is returned. This pattern is very common.
class Messages extends React.Component {
render() {
const messages = [
{id: 1, text: "Greetings!"},
{id: 2, text: "Goodbye!"}
];
return (
<ul>
{ messages.map(m => <li>{m.text}</li>) }
</ul>
)
}
}Components should be stored in separate files; one ComponentName.js file per Component.
We have one Component that renders all of the other components. This is traditionally named 'App.js'. It combines all of our other components into a single element. It is the one Component we render into the DOM. This way, code readers know exactly where to start. This is usually the only thing rendered in index.js.
It is important to load scripts in order. We load index.js last and all other components before it.
Styling with React is similar to without React; we can use our own stylesheet or import a framework. A key difference is that it is standard to use CSS class names that match the Component names. If I have a Component, 'Machine', I would have a matching CSS class '.Machine'; complete with a capital M.
However, we cannot simply add this class to our Component using the 'class' attribute. Instead, we use 'className', because class is a reserved keyword, and we are working in a JavaScript (JSX) context.
To reiterate:
- Class names match Component names and are capitalized
- To add a class to a Component, use the className attribute, because 'class' is a reserved keyword
.Machine {
border: 2px solid black;
}Below, in Machine Component's render:
return (
<div className="Machine"> // 'className' instead of 'class'
<span>
{props.s1} {props.s2} {props.s3}
</span>
<p>{areSame ? 'You win!' : ''}</p>
</div>
);Note: The 'for' attribute for the label element is also reserved (for loops). We would need to use 'htmlFor' instead. Be careful regarding standard HTML attribute names and conflicts with JavaScript.
We can also use inline CSS styles but 'style' takes a JavaScript object:
return (
// We now use an object to pass inline styles
const styles = {
border: 1px solid black
}
<div style={styles}>
<span>
{props.s1} {props.s2} {props.s3}
</span>
<p>{areSame ? 'You win!' : ''}</p>
</div>
);We can also use JavaScript to set style properties. We have to wrap an object in an object and use camel case for our CSS properties e.g. nstead of font-size, we write fontSize.
return (
// Note the double curly braces and camelCase
<div style={{fontSize: '24px', backgroundColor: 'purple'}}>
<span>
{props.s1} {props.s2} {props.s3}
</span>
<p>{areSame ? 'You win!' : ''}</p>
</div>
);
---
## Props
### Introduction to Props
[TOC Shortcut](#table-of-contents)
Props, a.k.a. Properties, make components more reusable through configuration and customization. This is similar to the relationship between class Objects and their constructors; we use constructors to fill out classes, which serve as templates.
Properties are **immutable**; they do not change. If we want to interact with the data in props we need to do so elsewhere. We cannot assign new values to it.
Here, we have a Component, HelloWithProps, that contains data in this.props, which is inherited from React.Component. In the App's Component render syntax, we assign the 'to' and 'from' properties using HTML-like attribute syntax.
```js
class HelloWithProps extends React.Component {
render() {
return (
// this.props contains all property information
<div>
<h1>Hello {this.props.to}!</h1>
<h3>How's it going?</h3>
<h4>From {this.props.from}</h4>
</div>
);
}
}
class App extends React.Component {
render() {
return (
<section>
// Properties are assigned like HTML attributes
<HelloWithProps to="Ringo" from="Paul" albumYear={1969} areBeatles={true} hobbies={['drumming', 'guitar']}/>
</section>
);
}
}
ReactDOM.render(<App />, document.getElementById('hello'));To add default props to a Component we create a static function called defaultProps.
Note: It must be called defaultProps.
Here, if we only pass a recipient (props.to), we default to an anonymous sender (props.from).
class Hello extends React.Component {
static defaultProps = {
from: "Anonymous"
}
render() {
return(
<p>
Hi {this.props.to} from {this.props.from}
</p>
);
}
}React is a front-end library; you don't need server-side stuff. You can get react.js and react-dom.js from a CDN. You can transpile JSX in the browser at runtime. However, we can enhance our process.
Create-React-App is a utility script that:
- Creates a skeleton React project
- Sets it up so that JS files are run through Babel automatically
- Lets us use super-modern JavaScript features/idoms
- Makes testing and deployment much easier
It is an official tool that is associated with React and Facebook.
Create React App can be installed with Node.js's npm or npx:
npm is used to manage packages but does not execute packages. npx is used to execute node packages:
npx create-react-app my-app
Even though create-react-app is not installed, it will get and execute the latest version of the package. This package is not something we need to execute more than once, so npx is most appropriate.
We can start the server with the command:
npm start
- src/Car.js for a 'Car' Component
- src/House.js for a 'House' Component
The name of the file should match the Component name. Components are capitalized.
The Component class is imported from React. It exports the 'Component' class as the default object. We can import and extend Component using two different styles:
import React, { Component } from 'react';
// OR
import React from 'react';
class someComponent extends Component { ... }
// OR
class someComponent extends React.Component { ... }The CRA skeleton assumes the top object is named App and it is exported from App.js. This is attached to the #root element. It is best to maintain this as there is no reason to change it and it is the standard.
To include images ancd CSS you can import them in JavaScript files:
import logo from "./logo.svg";
import "./App.css";
// ...
<img src={logo} className="App-logo" alt="logo" />It is common to make a CSS file for each Component e.g. House.css for a House Component. We import it at the top of the Component script. CRA will automatically load the CSS.
There is also a conventional naming hierarchy. The class name should match the Component name and the CSS file. Child items use kebab-case to specify child styles:
<div className="House">
<p className="House-title>{title}</p>
<p className="House-address>{address}</p>
</div>Images are typically stored in the 'src' folder with the components. However, if we have many images, we can store them in a subfolder. Load them where needed and use the imported name where path should go:
import puppy from "./puppy.jpg";
<img src={puppy} alt="puppy" />CRA is built on webpack. It's a JavaScript utility that:
- Enables module importing/exporting
- Packages up all CSS/images/JS into a single file for the browser
- Reduces the number of HTTP requests needed
- Hot Reloading: When you change a source file it automatically reloads
- It only reloads relevant files
- Enables easy testing and deployment
CRA does all the hard work for us.
CRA uses ES2015 'modules'. This is a newer, standardized version of Node's require(). You can use this to export and import classes/data/functions between JavaScript files.
Imagine we have two files: index.js and helper.js. Here, we only choose to export the helpful() function out of this file:
// helper.js
const helpful = () => {
console.log("Import me!");
}
// Note: No parentheses, because we don't want to call it
export default helpful; We import the file, by name, with ./ notation, because it is a file. If we do not use ./ notation, it means we are attempting to import a node module from the node_modules folder.
// index.js
import React from 'react'; // Node Package
import ReactDOM from 'react-dom'; // Node Package
// Note: The name does not have to match the file or function.
import helpfulFunction from "./helper"; // Not a Node Package
helpfulFunction();We often want to export multiple things from a module. We can use object notation to export multiple functions.
// helpers.js
const helpful = () => {
// ...
}
const mostHelpful = () {
// ...
}
const reallyHelpful = () {
// ...
}
// We use object syntax to export each function
export default helpful;
export {mostHelpful, reallyHelpful};Our import function must compliment this object export function. This time, the name does matter, because we can pick and choose what exports we want to use in the current file.
// index.js
import {mostHelpful, reallyHelpful} from "./helpers";If we choose to import a module that has both export default and export, but do not specify functions, we will only import the default function. If we specify, we can import more of the functions from the file.
Conventionally, default exports are used when there is a "most likely thing" to export. For example, in a React component file, it is common to have the Component be the default export.
You never need to make a default export, but it can be helpful to indicate the most important functionality in a module.
Using the previous example we can import all three functions using this syntax:
import helpful, {reallyHelpful, mostHelpful} from "./helpers";In any sufficient advanced web app, the user interface has to be stateful; there is information that is likely to change. State is designed to constantly change in response to events. Every time an action is taken the state can change.
There are two types of things state can track on the front end:
The changing state of the interface.
- Logged in users see a different screen than logged out hsers
- Clicking Edit Profile opens a modal
- Sections of a site expand and collapse
The changing state of the data.
- Inbox messages
- Events registered for
- Account status
With vanilla JavaScript in jQuery, we relied heavily on style and visibility to demonstrate state. Elements would be shown, hidden, colored, disabled, and more.
Internal data is Component specific. Each Component is responsible for its own State. Data changes over time.
With React, we can reload part of the DOM to reflect state changes. React is "smart" enough to know what has changed and what has not.
State is an instance attribute on a Component. It is always a plain-old JavaScript object (POJO), since you will want to keep track of several keys and values.
// What is the current state of my Component?
console.log(this.state);
/*
Would output:
{
playerName: "Whiskey",
score: 100
}
*/Unlike props, we have to initialize state on the components we want to use state.
If your Component is stateless, you can omit the constructor function. Otherwise, we need to use a constructor. State should be initialized as soon as the Component is created. We set it in the constructor function.
Constructor takes a single argument: props. 'super(props)' is necessary boilerplate that can sometimes be omitted. 'super' is a reference to the Component constructor.
Inside instance methods, one may refer to 'this.state' just like 'this.props'.
class Game extends Component {
constructor(props) {
super(props);
this.state = {
// Values we want to track:
score: 0,
gameOver: false
};
render() {
return(
<div>
<h3>Your score is {this.state.score}</h3>
</div>
);
}
}
export default Game;There is an alternate, experimental state initialization syntax. However, the other syntax is not part of JavaScript or the JavaScript standard; it relies on Babel. It omits the constructor and this.state syntax.
Babel will take the invalid JavaScript code and convert it into code that includes the constructor and this.state syntax.
class Game extends Component {
state = {
// Values we want to track:
score: 0,
gameOver: false
};
render() {
return(
<div>
<h3>Your score is {this.state.score}</h3>
</div>
);
}
}
export default Game;Super allows us to access parent properties. Even if we choose not to use state, constructor() is part of Component, and we need to call super(); to indicate that we want to use Component's constructor. If we don't, we get an error.
Also, if we want to use the derived class's props, we need to pass it to super so that the constructor can interact with those props.
In React, you must never directly manipulate the state. Accessing the state on a component and making changes outside of the constructor is very poor design.
class Game extends Component {
state = {
// Values we want to track:
score: 0,
gameOver: false
};
this.state.score += 1; // THIS IS BAD
render() {
return(
<div>
<h3>Your score is {this.state.score}</h3>
</div>
);
}
}
export default Game;We can use this.setState(), a build-in React method, to change a component's state. We can use this to call any instance method except the constructor. You can only call setState on a Component that has been mounted.
- It takes an object describing the state changes
- It patches the state object. Unspecified keys do not change
- It's asynchronous! The component state will eventually update
Components will re-render when you change their state.
We do not set state manually because of all of these React specific features.
this.setState({playerName: "Brandon", score: 0});Here, given a Component with a prop, maxNum, we can change the displayed number every second. When the state is updated, it re-renders the Component:
// Given the inclusion: <Rando maxNum={7} />
import React, { Component } from 'react';
export default class Rando extends Component {
constructor(props) {
super(props);
this.state = { num: 0 };
this.makeTimer();
}
makeTimer() {
setInterval(() => {
let rand = Math.floor(Math.random() * this.props.maxNum);
this.setState({ num: rand });
}, 1000);
}
render() {
return <div className="Rando">{this.state.num}</div>;
}
}After Components, State and Props are the most important things in React.
| Term | Structure | Mutable | Purpose |
|---|---|---|---|
| state | POJO {} | Yes | Stores changing component data |
| props | POJO {} | No | Stores component configuration |
A common pattern is a stateful, "smart" parent component that passes down its state values as props to stateless, "dumb" child components.
class CounterParent extends Component {
constructor(props) {
super(props);
this.state = {count: 5};
}
render() {
// Passing down parent state as a prop to the child component
return (
<div>
<CounterChild count={this.state.count} />
</div>
);
}
}This idea is g eneralized in React as "downward data flow". It means that components get simpler as you go down the component hierarchy, and parents tend to be more stateful than their children.
Thus far, it has been common practice to overwrite values, but we often want to update existing values. We can do this by using the current state, operating on it, and setting it.
Imagine we have two buttons that call these functions.
You may consider the following in order to accomplish this, but it is incorrect:
// INCORRECT EXAMPLE
singleIncrementation() {
this.setState({ score: this.state.score + 1 });
}
tripleIncrementation() {
this.setState({ score: this.state.score + 1 });
this.setState({ score: this.state.score + 1 });
this.setState({ score: this.state.score + 1 });
}Remember, the setState operation is asynchronous, so if we are depending on current State information, that State may or may not be up to date. Also, React will batch calls to setState together for performance reasons.
Instead, we can use a callback function to assure State is up to date for our operation. Instead of passing an object, pass a callback with the current state as a parameter. The callback returns an object representing the new state:
// CORRECT EXAMPLE
singleIncrementation() {
this.setState(st => {
return { score: this.state.score + 1 }
});
}
tripleIncrementation() {
this.setState(st => {
return { score: this.state.score + 1 }
});
this.setState(st => {
return { score: this.state.score + 1 }
});
this.setState(st => {
return { score: this.state.score + 1 }
});
}Because we pass callback functions in order to update State, it makes sense to create functions that can be passed instead of writing them inline. This is called functional setState. We abstract away State updates to specific function calls:
incrementScore(st) {
return { score: st.score + 1 }
}
singleIncrementation() {
this.setState(st => incrementScore(st));
}
tripleIncrementation() {
this.setState(st => incrementScore(st));
this.setState(st => incrementScore(st));
this.setState(st => incrementScore(st));
}This pattern is very popular in Redux.
We need to be able to manage state updates for mutable data structures. Up until now, examples have primarily focused on primitives like numbers and strings.
// INCORRECT EXAMPLE
completeTodo(id) {
const theTodo = this.state.todos.find(t => t.id === id);
theTodo.done = true;
this.setState({ todos: this.state.todos });
}Mutating nested data structures in your state can cause problems with React.
Instead, we should make a new copy of the data structure. We can use any pure function to do this. A pure function's return value is based only on its inputs and has no other dependencies or effects on the overall program:
completeTodo(id) {
const newTodos = this.state.todos.map(todo => {
if(todo.id === id) {
// The 'spread' op unpacks the existing array into a brand new array
return {...todo, done: true}; // Return a matching todo with the updated state
}
return todo; // Don't manipulate other todos
});
this.setState({
todos: newTodos // We use our copy, with the updated info, to update the state
});
}Immutable state means that there is an old state object and a new state object. We copy the old state, add updates, and return our new state as the update. This is a good habit for React application building but is a necessity for Redux.
We can use Array's pure functions like:
- .map(): Creates a copy of an array with modifications, if we like
- .filter(): Returns an array with filtered values from the source array
- .reduce(): Returns a single accumulator data structure using the source array
It is worth the O(n) space/time required to make a copy because we want to ensure that our app has the most up to date state. It also reduces the chances of difficult bugs.
There are best practices for modeling state and designing components. It takes a lot of practice to learn React design:
- What should be a Component?
- What part of the Component should manage State?
We want to put as little data in the state as possible. We can ask simple questions to determine if some data should be a state or a prop:
"Does x change?" If not x should not be part of the state; it should be a prop.
"Is x already captured by some other value y in state or props?" If so, derive it from there instead.
A person's name does not change often. This should be a prop.
A birthday does not change. This should be a prop.
You may think that age changes, and it does, but age can be derived from Birthday, and so we can calculate this elsewhere. It should be a prop.
A person's mood does change often. This is suitable as a State property.
We want to support the "downward data flow" philosophy of React. That is, Parent Components should be "smarter" or more stateful than their "dumber", stateless child Components. The former relies heavily on State, the latter on Props.
If a current Component is Stateless, find out what is rendering it. That is where the State lives.
State most commonly changes in direct response to some kind of event. In React, every JSX element has built-in attributes representing every kind of browser event. They are camel-cased, such as onClick, and take callback functions as event listeners.
<button onClick={function(e) { alert("Clicked!"); } }>
Click Me
</button>We can also bind specific functions, but we must also bind 'this' to those functions so that, when we call 'this', we know what 'this' is referring to, because we specifically said what it should refer to:
import React, { Component } from 'react';
export default class Rando extends Component {
constructor(props) {
super(props);
this.state = { num: 0 };
// IMPORTANT:
// When we refer to 'this', we're specifically saying
// use this instance of this component.
this.setRandomNumber = this.setRandomNumber.bind(this);
}
getRandomNumber() {
return Math.floor(Math.random() * this.props.maxNum);
}
setRandomNumber(e) {
this.setState({ num: this.getRandomNumber() });
}
render() {
return (
<div className="Rando">
<div>{this.state.num}</div>
// Assign the function, but don't call it with ()
<button onClick={this.setRandomNumber}>Random Number</button>
</div>
);
}
}If we omit the bind() syntax, and intead use an arrow function, Babel will bind the function to 'this' automatically. Below, getRandomNumber() has been converted to this. We no longer need a constructor:
import React, { Component } from 'react';
export default class Rando extends Component {
state = { num: 0 };
setRandomNumber() {
return Math.floor(Math.random() * this.props.maxNum);
}
// This will bind getRandomNumber to 'this' automatically
getRandomNumber = (e) => {
this.setState({ num: this.getRandomNumber() });
}
render() {
return (
<div className="Rando">
<div>{this.state.num}</div>
// Assign the function, but don't call it with ()
<button onClick={this.setRandomNumber}>Random Number</button>
</div>
);
}
}Similar to vanilla JavaScript, we can handle mouse events. We can also use various other mouse events: Mouse enter, on mouse over, on mouse exit, etc.
Form events, like onSubmit, etc., are used for form interaction and submission.
Similar to vanilla JavaScript, React allows us to use keyboard events:
- onKeyDown
- onKeyUp
- onKeyPress
Clipboard events, pointer events, and much more are available to React, just as they are available to vanilla JavaScript.
They keyword 'this' relies on context. When we create Component methods we need to specifiy what 'this' is.
This is a method inside a component. When we refer to 'this' we receive an error because 'this' was never defined.
printHaiku() {
let { haikus } = this.props; // An array, named haikus, in props
let i = Math.floor(Math.random() * haikus.length);
console.log(haikus(i));
}So how do we make sure 'this' refers to the Component? We need to fix the binding.
In order to use 'this' in the method we need to define what 'this' is referring to. We can do this with one of three tools:
This is what is used in earlier examples. We bind 'this' to this method so that this method knows what 'this' refers to when 'this' is used in the method.
This method of binding is a little less clean, as, for every method call, we need to add an additional line to our constructor. However, it has many benefits, and may be the preferred way of binding.
- You only need to bind once
- It is more performant
- The intent is clear
Here is a familiar example:
constructor() {
this.printHaikus = this.printHaikus.bind(this);
}When we specify how an event is handled we can include an inline binding. This is very explicit. However, if we need to pass 'this.printHaiku' to multiple components, this will not work. Also, a new function is created on every render, which is not as resource intensive as one may think, but it is less organized. At scale, it can create performance issues.
// Instead of just assigning the function, we include bind(this)
<div className="Haiku" onClick={this.printHaiku.bind(this)}>
// Some content
</div>We call an anonymous arrow function that calls, not just assigns, the method we want.
- No mention of bind
- Intent is less clear
- What if you need to pass the function to multiple components?
- New function created every render
// Note: We do call the function with '()'
<div className="Haiku" onClick={ () => this.printHaiku() }>
// Some content
</div>We can use ES 2015 function declaration syntax to bind this function. On transpile, Babel will handle the actual binding by moving it to the constructor. As this becomes polished it may become standardized. It is common in many React applications.
Here is an example:
handleClick = () => {
console.log(`This is ${this}`);
}In the previous section, methods were not bound with arguments. But what happens when we need to pass arguments as function parameters? We can accomplish this by passing the argument in our bind:
We can use the inline or arrow function bind approaches. The key is that we pass in our arguments; after 'this' if it's an inline bind, or as an arrow function argument:
onClick={this.changeColor.bind(this, c)}
// OR
onClick={ () => changeColor(e) }class ButtonList extends Component {
colors = ['red', 'green', 'blue'];
constructor(props) {
super(props);
this.state = { color: 'white' }; // Initial
}
changeColor(newColor) {
this.setState({ color: newColor });
}
render() {
return (
// For every color, create a button with that color
// That button will change the background color of the page
<div className="ButtonList" style={{ backgroundColor: this.state.color }} >
{this.props.colors.map{c => {
const colorObj = { backgroundColor: c };
return <button
onClick={this.changeColor.bind(this, c)}
style={colorObj}>Click on me!</button>;
})}
</div>
);
}This is a very common pattern in React. Children are often not stateful but need to tell parents to change state. How do we send data "back up" to a parent component?
- A parent component defines a function
- The function is passed as a prop to a child component
- The child component invokes the prop
- The parent function is called, usually setting a new state
- The parent component is re-rendered, and so are the children
- The higher the better; don't bind in the child component if possible
- If you need a parameter, pass it down to the child as a prop, then bind in parent and child
- Avoid inline arrow functions / binding if possible
- No need to bind in the constructor and make an inline function
We can pass an inline reference to the function and an argument using a prop. However, every time we re-render we create a new function:
// Every NumberItem gets a reference to remove with a specific argument
let nums = this.state.nums.map(n => {
<NumberItem value={n} remove={() => this.remove(n) } />
});
return (
<div>
<ul>{nums}</ul>
</div>
)Instead, we can use our constructor to bind 'this'. But, we need to include a helper that can pass remove the argument. This is the preferred method.
constructor(props) {
super(props);
this.state = { nums: [1, 2, 3, 4, 5] };
this.remove = this.remove.bind(this);
}
remove(num) {
// Returns all but matching num
let this.setState(st => {
st.nums.filter(n => n !== num)
}
));
}
// Every NumberItem gets a reference to remove with a specific argument
let nums = this.state.nums.map(n => {
<NumberItem value={n} remove={this.remove} />
});
return (
<div>
<ul>{nums}</ul>
</div>
)In the child, we add a handleRemove function that will call the remove function, from props, with our argument. However, like with any function that requires 'this', we need to bind 'this' to handleRemove in the constructor.
class BetterNumberItem extends Component {
constructor(props) {
super(props);
this.handleRemove = this.handleRemove.bind(this);
}
handleRemove(e) {
this.props.remove(this.props.value);
}
render() {
return(
<li>
{this.props.value}
<button onClick={this.handleRemove}>X</button>
</li>
);
}
}It is good to follow some pattern when naming methods. A recommended method is using the action() and handleAction() pattern, where action is a method name.
When mapping over data and returning components, you get a warning about keys for list items. key is a special string attribute to include wehn creating lists of elements.
- Keys should be unique: Usually UID's in a data set
- Keys help React identify which items have changed, are added, or are removed
- Keys should be given to repeated elements to provide a stable identity
Note: If you do not have a stable, unique ID for items, you may use the iteration index as a key. However, there may still be issues. We can use packages like shortid and uuid to create unique identifiers.
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map(n =>
<li key={n.toString()}>
{n}
</li>
);