Babel is a transpiler that converts our js code in any modern version (e.g. ES2016, ES2017) to ES5, which is universally supported by browsers. It also converts jsx to js.
There are 2 module systems in JS:
- JS ES2015 Modules use the
import
keyword. (this is the one we typically use) - Common Modules use the
require
keyword.
- Explained through an example:
import React from 'react'
looks for the 'react' folder insite thenode_modules
and assigns all the code to theReact
variable in my file (I can name it whatever I want). - We could also import from a path
import MyLibrary from 'a/path'
.
React is a library that contains how al components work. ReactDOM is a library that deals with putting those components on the DOM. If we were using React Native, then there would be a library to render the components inside the app.
- Use Functional Components for very simple content (e.g. only render HTML using some props passed to it).
- Use Class Components for everything else (e.g. complex logic, handling any user interaction).
Customization can be either to:
- Customize how a component looks.
- Customize how a user interacts with it.
Children cannot pass props to the parent (only parent to child).
You can use the create-react-app
command line tool. You install it using npm install -g create-react-app
.
- src: where all the code goes (js and css).
- There is a special file
index.js
which is the "root" of your react application.
- There is a special file
- public: where static HTML and assets go.
- node_modules: all of the dependencies of the project (we never want to manually go inside it).
- package.json: analogous to the gemfile
npm start
- React component files are named using CamelCase. For example
CommentDetails.js
for theCommentDetail
component.
- Custom inline styling
<!-- HTML -->
<div style="background-color: red;"></div>
// JSX - Note: 1) JS object for properties, 2) CamelCase for dashed properties.
// Outer curly braces represent JS interpolation, Inner {} represent a JS object
<div style={{backgroundColor: 'red'}}></div>
-
The HTML class in a tag
<label class="foo">...
turns into<label className="foo">...
-
Referencing JS variables in JSX
const App = () => {
const buttonText = 'Click Me!';
return (
<button>{buttonText}</button>
);
}
We can use {}
to interpolate any JS within JSX. The only limitation is: we cannot use a JS object to print it out as text. For example, the next snippet will result in a "Objects are not valid as a react child" error.
// Wrong
const App = () => {
const buttonText = {text: 'Click Me'};
return (
<button>{buttonText}</button>
);
}
// Right
const App = () => {
const buttonText = {text: 'Click Me'};
return (
<button>{buttonText.text}</button>
);
}
- There are other differences in property names like
<label for="foo">...
turns into<label htmlFor="foo">
. Many of these won't crash your app. As a general rule, if you see a did you mean X warning message in the browser console, it is because you are using an invalid property name from React's point of view.
Props can only be passed from the parent to a child (not the other way around).
// Passing: Just like custom HTML attributes
const App = () => {
return (
<div className="ui container comments">
<CommentDetail author='Sam' body='This is comment 1' avatarUrl={faker.image.avatar()}/>
</div>
);
};
// Receiving props in functional components: passed as an object as the first argument.
const CommentDetail = (props) => {
return (
<div className="comment">
<a className="avatar" href="/">
<img alt="avatar" src={props.avatarUrl}/>
</a>
<div className="content">
<a href="/" className="author">{props.author}</a>
<div className="text">{props.body}</div>
</div>
</div>
);
};
// Receiving props in class based component: added to a property this.props
class CommentDetail extends React.Component {
someFunction() {
console.log(this.props.author);
}
}
We will use the following example to illustrate:
// index.js
// ApprovalCard gets injected the custom CommentDetail component
const App = () => {
return (
<div className="ui container comments">
<ApprovalCard>
<CommentDetail author='Sam' body='This is comment 1' avatarUrl={faker.image.avatar()}/>
</ApprovalCard>
</div>
);
};
// ApprovalCard.js
// Injected children get assigned by react to props.chilren
const ApprovalCard = (props) => {
return (
<div className="ui card">
<div className="content">{props.children}</div>
{/*...*/}
</div>
);
};
Class based components must comply 3 rules:
- Must be a JS Class
- Must extend React.Component
- Must define a
render
method that returns some JSX. This method defines how the component renders itself.
-
Only usable with class components.
1.1 Note: Now it can technically by used with functional components through the 'hooks' system.
-
You will confuse props with state.
-
'State' is a JS object that contains data relevant to a singular component.
-
Updating 'state' on a component causes the component to (almost) instantly re-render. Re-rendering also triggers the re-rendering of ALL CHILD components.
-
State must be initialized when a component is created. This can be done in 2 equivalent ways (They are equivalent once Babel transpiles the code.)
// Option 1: Explicitly initialize inside the constructor method
class App extends React.Component {
// The constructor takes props as an argument
constructor(props) {
super(props); // We always have to call super inside the constructor
// This is the ONLY time we directly assign this.state (for any other time use setState)
this.state = { lat: null };
}
// ...
}
// Option 2: Use shorthand class property initialization
class App extends React.Component {
state = { lat: null };
// ...
}
- State can only be updated using the function
setState
.
class App extends React.Component {
myFunction() {
myAsyncFunction(
// On Success
(myArg) => {
// We use setState, NOT this.state.lat = ....
this.setState({ lat: myArg})
},
// On Failure
(err) => console.log(err)
);
}
}
The render
method is called by React VERY frequently. If we put any API calls or heavy logic inside it, we might
be doing unnecessary extra work.
Using a errorMessage
property inside the component's state is a simple and nice way to handle errors. For this
we also need to define how the component renders when the errorMessage
property is present. Here is a simple example
(note there are more elegant ways to do the conditional rendering):
class App extends React.Component {
constructor(props) {
super(props);
this.state = { lat: null, errorMessage: '' };
}
// ...
render() {
if (this.state.errorMessage && !this.state.lat) {
return <div>Error: {this.state.errorMessage}</div>
}
if (!this.state.errorMessage && this.state.lat) {
return <div>Latitude: {this.state.lat}</div>
}
return <div>Loading...</div>
}
}
constructor
: when the component is instantiated.render
: compulsory to define it.componentDidMount
: called ONE time when the component first shows on the screen.- Updates are caused by changes in state through the
setState
method. componentDidUpdate
: called every time the component gets updated. It is called AFTERrender
when the state changes.componentWillUnmount
: called when the component is going to be removed from the screen. Typically used for cleanup.
When we need to get some text, class names (etc) based on a condition, configuration objects are a better alternative
than if..else
statements. See example:
// This is the configuration object
const seasonConfig = {
summer: {
text: 'Let\'s hit the Beach',
iconName: 'sun'
},
winter: {
text: 'Burr, it is chilly',
iconName: 'snowflake'
}
};
// ...
const SeasonDisplay = (props) => {
const season = getSeason(props.lat, new Date().getMonth()); // returns either 'summer' or 'winter'
const {text, iconName} = seasonConfig[season]; // Destructurizing a JS object
return (
<div>
<i className={`icon ${iconName}`} />
<h1>{text}</h1>
<i className={`icon ${iconName}`} />
</div>
);
};
A common pattern is to create a dedicated stylesheet for each component which is in charge of defining the styles
of the component. For example, for the SeasonDisplay.js
, we create a SeasonDisplay.css
.
Inside the SeasonDisplay.js
we:
import SeasonDisplay.css
and Babel will make that the stylesheet gets correctly imported in the HTML.- Add a matching
season-display
class to the root of the returned JSX so that we can specifically target elements inside that component.
Example:
// SeasonDisplay.js
import './SeasonDisplay.css';
const SeasonDisplay = (props) => {
// ...
return (
// Note the season-display class
<div className={`season-display ${season}`}>
{/* ... */}
</div>
);
};
.season-display {
display: flex;
justify-content: center;
}
.season-display.winter i {
color: blue;
}
.season-display.summer i {
color: red;
}
Default props are used when we want a component to have a props with a default value in case the parent component does not specify a particular prop.
const Spinner = (props) => {
return(
<div className="ui active dimmer">
<div className="ui big text loader">{props.message}</div>
</div>
);
};
// Default Props is a JS object in the main body of the Spinner.js file
Spinner.defaultProps = {
message: 'Loading...'
};
As a general rule, we want to avoid having more than ONE return in the render
method or having any heavy logic in it.
To avoid this, we can create custom helper methods that abstract better the conditional logic.
class App extends React.Component {
//...
// This is a helper method that unloads the conditional logic from the render method
renderContent() {
if (this.state.errorMessage && !this.state.lat) {
return <div>Error: {this.state.errorMessage}</div>;
}
if (!this.state.errorMessage && this.state.lat) {
return <SeasonDisplay lat={this.state.lat} />
}
return <Spinner message = 'Determining your Location...'/>;
}
render() {
return (
<div className="some-class-that-is-always-needed">
{this.renderContent()}
</div>
);
}
}
We can detect user interaction through special props like onChange
. It is our job
to define a callback function that will handle the event with any custom logic we need.
The most used special properties are:
onClick
: any HTML element can be wired up withonClick
.onChange
: triggered when a user changes an input. Only input fields triggeronChange
.onSubmit
: only forms triggeronSubmit
.
Here is an example for handling onChange
on a Search Bar.
class SearchBar extends React.Component {
// Used as a callback function to handle the change
// "event" is passed by default to all event handlers
onInputChange(event) {
// Whatever logic I need
console.log(event.target.value);
}
render() {
return (
<form>
<label>Image Search</label>
{/* onChange is a special property name that gets triggered when the input changes. */}
{/* We provide the callback function to handle the change */}
{/* Note that this example shows an uncontrolled element for simplicity. We prefer controlled elements */}
<input type="text" onChange={this.onInputChange}/>
{/* An alternative frequently used syntax is using arrow functions when the handlers are small */}
<input type="text" onChange={ (e) => console.log(e.target.value) }/>
</form>
);
}
}
Depending on how we wire up them, HTML input elements can be classified as controlled or uncontrolled.
Uncontrolled elements are elements in which the "truth" of the data is sitting inside the HTML. In the following
example, if we wanted to find the text in the input field from an arbitrary point in the component (e.g. from
componentDidUpdate
), we have no choice but to reach out to the HTML input element and read the value.
class SearchBar extends React.Component {
onInputChange(event) { /* Whatever logic I need */ }
componentDidUpdate() {
// To find out the search term, we have no option but to reach out to the HTML input.
}
render() {
return (
<form>
<label>Image Search</label>
<input type="text" onChange={this.onInputChange}/>
</form>
);
}
}
If we wire things differently we can make sure that the JS Component contains all the data and drives the HTML (not the other way around). We always prefer controlled elements.
class SearchBar extends React.Component {
// We use component state to store the search term. In this way, we can reach for the state
// at any arbitrary point to figure out the term.
state = { term: ''};
render() {
return (
<form className="ui form">
<label>Image Search</label>
{/* We fix (control) the value of the input through the state to make sure that */}
{/* react DRIVES the HTML and not the other way around */}
<input
type="text"
value={this.state.term}
onChange={ (e) => this.setState({term: e.target.value}) } />
</form>
);
}
}
Note: this is a complex issue, to get a full understanding of the problem, re-watch videos "84. Understanding 'this' in Javascript" and "85. Solving Context Issues" from Stephen Grider's Udemy course.
The error is caused by how the context system works in JS (i.e. what value is assigned to this
at runtime).
The next code exemplifies the problem:
class SearchBar extends React.Component {
state = { term: ''};
render() {
return (
/* Here we 'rip out' onFormSubmit from the SearchBar instance and give it to `onSubmit` */
<form className="ui form" onSubmit={ this.onFormSubmit }>
<label>Image Search</label>
<input
type="text"
value={this.state.term}
onChange={ (e) => this.setState({term: e.target.value}) } />
</form>
);
}
onFormSubmit(event) {
event.preventDefault();
// Here `this` causes the problem because the function is called outside the scope of the
// SearchBar instance. In that context `this = undefined`.
console.log(this.state.term);
}
}
Rule of thumb: the value of this
inside a JS function
this
inside a function takes the value of the instance on which the function is being called
(i.e. what is left of the dot). For example, given an arbitrary function myFun
that contains this
inside it.
myCar.myFun(); // `this` refers to the myCar instance
myTruck.myFun(); // `this` refers to the myTruck instance
myFun2 = myCar.myFun; // Rip out myFun and assign it to myFun2.
myFun2(); //`this` refers to `undefined` since myFun2 is not being called on any instance.
Solutions to the this
context problem
There are many solutions to this problem, we name the most popular.
- Solution 1: Fix the value of
this
inside the problematic function by binding it inside theconstructor
class SearchBar extends React.Component {
state = { term: ''};
constructor(props) {
super(props);
// Bind returns a new version of the function with the `this` keyword fixed to the current SearchBar instance.
this.onFormSubmit = this.onFormSubmit.bind(this);
}
render() {
return (
<form className="ui form" onSubmit={ this.onFormSubmit }>
<label>Image Search</label>
<input
type="text"
value={this.state.term}
onChange={ (e) => this.setState({term: e.target.value}) } />
</form>
);
}
onFormSubmit(event) {
event.preventDefault();
// Once binded, `this` will always refer to the SearchBar instance
// (i.e. it is no longer context dependent).
console.log(this.state.term);
}
}
- Solution 2: Use ES6 arrow functions to declare instance methods.
Arrow functions automatically bind the value of
this
. This is the most common method.
class SearchBar extends React.Component {
state = { term: ''};
render() {
return (
<form className="ui form" onSubmit={ this.onFormSubmit }>
<label>Image Search</label>
<input
type="text"
value={this.state.term}
onChange={ (e) => this.setState({term: e.target.value}) } />
</form>
);
}
onFormSubmit = (event) => {
event.preventDefault();
// Here `this` would cause the problem. However, arrow functions automatically bind this to the instance.
console.log(this.state.term);
};
}
- Solution 3: Wrap callback inside an arrow function (that automatically binds
this
).
class SearchBar extends React.Component {
state = { term: ''};
render() {
return (
// Wrapping the callback with an arrow function automatically binds `this`
<form className="ui form" onSubmit={ (e) => this.onFormSubmit(e) }>
<label>Image Search</label>
<input
type="text"
value={this.state.term}
onChange={ (e) => this.setState({term: e.target.value}) } />
</form>
);
}
onFormSubmit(event) {
event.preventDefault();
console.log(this.state.term);
}
}
By design, React only allows data to be pushed DOWN from parents to children through the props system. If we want to communicate from a child to a parent, the parent needs to provide a callback function and the child will hold it as a prop to use it when necessary.
// Parent
class App extends React.Component {
// The callback function for communication
onSearchSubmit = (term) => {
console.log(`From app: ${term}`);
};
render() {
return (
<div className="ui container" style={{marginTop: '10px'}}>
{/* Pass down callback function as prop */}
<SearchBar onSearchSubmit={this.onSearchSubmit}/>
</div>
);
}
}
// Child
class SearchBar extends React.Component {
state = { term: ''};
onFormSubmit = (event) => {
event.preventDefault();
// Use Callback function to notify parent
this.props.onSearchSubmit(this.state.term);
};
}
React itself does NOT do network requests. For this we need to use an external library like axios
or
the built-in function fetch
.
fetch
is a built-in function to do network requests that does not add any overhead. However, it is very basic and requires coding some boilerplate code.axios
is a higher level 3rd party library that handles requests in a very predictable way. Axios is recommended to perform the API requests on react apps.npm install --save axios
A promise is an object that represents the eventual completion or failure of an asynchronous operation. Promises are frequently used in JS for network requests and it is up to the developers to code what should happen when the promise succeeds or fails.
In the context of API requests, there are 2 options for handling promises: 1) promise.then
, 2) async
, await
Note: more information can be found on video "92. Handling Requests with Async Await" from Stephen Grider's course.
The then
method allows us to give the promise a callback to execute once the promise succeeds.
class App extends React.Component {
// Callback for making a network request when the search term changes.
onSearchSubmit = (term) => {
axios.get('https://api.unsplash.com/search/photos', {
params: { query: term},
headers: {
Authorization: 'Client-ID someRandomToken'
}
}).then((response)=>{
// axios.get is async and returns a promise, so deal with it with `then` callback
console.log(response.data.results);
// Do whatever you need to do...
})
};
render() { /*...*/ }
}
This is a newer syntax that allows us to write simpler and cleaner code.
class App extends React.Component {
// Callback for making a network request when the search term changes.
// The network request is asynchronous, so we tag our function as `async` and
// `await` to allow the promise to resolve
onSearchSubmit = async (term) => {
// axios.get is async and returns a promise, so we need to deal with the promise.
const response = await axios.get('https://api.unsplash.com/search/photos', {
params: { query: term},
headers: {
Authorization: 'Client-ID someRandomToken'
}
});
this.setState({images: response.data.results});
};
render() { /*...*/ }
}
With axios, we can create a dedicated client that is configured to make requests with whatever configuration we set it up. This allows us to extract all the configuration, and authentication logic for a particular service into a dedicated file.
For example, for communicating with the unsplash API we could:
// 1. Create a file in src/api/unsplash.js that will hold the dedicated unsplash client
import axios from 'axios';
export default axios.create({
baseURL: 'https://api.unsplash.com',
headers: {
Authorization: 'Client-ID someRandomToken'
}
});
// 2. Then we only use this pre-configured client in all other places we need to communicate with unsplash
import unsplash from "../api/unsplash";
class App extends React.Component {
onSearchSubmit = async (term) => {
// Here we use the pre-configured unsplash client
const response = await unsplash.get('/search/photos', {
params: { query: term},
});
// ...
};
}
Rendering lists of information is very common in any application. The most common way of doing it is using
the JS map
function over an array of elements that contain the data of what we want to display. For example:
const ImageList = (props) => {
// In this example props.images = [
// {id: ..., url: ..., description: ...},
// {id: ..., url: ..., description: ...}
// ]
// The arrow function inside the map is making use of destructuring assignment of each image object.
// That is equivalent to (image) => {... image.id .... image.urls.regular ...}
const images = props.images.map(({id, urls, description}) => {
return <img key={id} src={urls.regular} alt={description} />;
});
return <div>{images}</div>;
};
Warning:"Each child in array or iterator should have a unique key
prop". This is a warning given by react
if we omit the 'key' prop on each element of the list.
- The
key
prop helps react figure out which changes need to be done on the DOM. - Giving a key increases the performance of the front-end, but it is solely a performance consideration.
- The
key
only needs to be assigned to the outer-most / root HTML tag of each of the elements in the list. - Only collections require the
key
prop.
- Refs give access to a single DOM element.
- We create refs in the constructor, store them in an instance variables and assign them to a particular
jsx as props.
- Theoretically, we could save a ref to the state, but that is not necessary because refs don't change.
- Only things that change should be stored in
state
.
this.myRef.current
is a handle to the HTML element that was rendered in the DOM.
class ImageCard extends React.Component {
// 1. Refs need to be created in the constructor
constructor(props) {
super(props);
// 2. Create a ref and store it in the `imageRef` instance variable
this.imageRef = React.createRef();
}
componentDidMount() {
// 4. Use ref to access the DOM element
console.log(this.imageRef.current);
}
render () {
const { description, urls } = this.props.image;
return (
<div>
{/* 3. Assign ref to jsx element */}
<img ref={this.imageRef}
src={urls.regular}
alt={description} />
</div>
)
}
}
If we want our React app to change some styling, we need to do it as inline styles in the JSX elements. For example:
class ImageCard extends React.Component {
// ...
render () {
// ...
return (
// Add CSS inline through React
<div style={{gridRowEnd: `span ${this.state.spans}`}}>
<img ref={this.imageRef}
src={urls.regular}
alt={description} />
</div>
)
}
}
Note: to edit any of the diagrams go to
https://www.draw.io/#Hserodriguez68%2Freact-cheatsheet-udemy-2019%2Fmaster%2Fdiagrams%2F{name of diagram}.svg