My notes on React For Beginners by Wes Bos
Published on Jan 10 2021
The Shadow DOM
React doesn't update all elements on the screen when there is a change but it only rerenders the elements that have changed. This is part of what makes React feel fast (after initial load). React does this with the help of the "Shadow DOM". This is basically a copy of the actual DOM. They apply the changes to the Shadow DOM and then compare this to the actual DOM to see what elements have changed and what elements therefor have to be rerendered.
Basic component template
import React from 'react';
class ComponentName extends React.Component {
render() {
return (
<p>JSX HTML</p>
)
}
}
export default ComponentName;
Importing React parts
import React from 'react';
This will require you to concatenate everything with React.Whatever
.
Like React.Component
or React.Fragment
.
import React, { Component, Fragment } from 'react';
With this import you don't have to concatenate Component
and Fragment
with React.
.
class ComponentName extends Component {
render() {
return (
<Fragment>
</Fragment>
)
}
}
React.Fragment
Similar to ng-container in Angular. Compiles to nothing, meaning it will not show up in the HTML.
Inside the render()
method you can only return a single item. No siblings. So all JSX (HTML) that you return will have to be wrapped in a single other element. React.Fragment
can be used in this case.
render() {
return (
<React.Fragment>
<p>Sibling 1</p>
<p>Sibling 2</p>
</React.Fragment>
)
}
Writing JSX
You can't write <p class="paragraph">Paragraph</p>
as the name class
is reserved in JavaScript, meaning it is already used in JavaScript. Instead you have to use className
. <p className="paragraph">Paragraph</p>
You can add Javascript by wrapping it in curly braces.
<p>{ person.name }</p>
For writing comments in JSX you can't use HTML comments or double slashes. It have to be block quotes.
{ /* comment */ }
You write a piece of JavaScript in JSX so you wrap it in curly braces. You use /*
instead of //
because the double slashes will also comment out the closing curly brace.
Props
You can name them whatever as long as they don't overwrite any existing HTML attributes (or JSX variations).
<ComponentName anythingAtAll="burp" />
When you want to pass anything but a string you'll have to wrap it in curly braces.
<ComponentName age={31} updatePerson={functionName} />
Stateless Functional Component
A component that doesn't do anything but render some HTML and most likely some props.
const ComponentName = ( props ) => (
<p>{ props.name }</p>
)
You can't use this.props
as it no longer extends React.Component
. But the props are still passed to the component but then as a parameter to the function.
Notice the implicit return of the arrow function. No return
or curly brackets necessary.
You could also destructure the props like this:
const componentName = ( { name } ) => (
<p>{ name }</p>
)
React Routing
You'll need to import BrowserRouter
(along with Router
and Switch
) from react-router-dom
.
import { BrowserRouter, Router, Switch } from 'react-router-dom';
const Router = () => (
<BrowserRouter>
<Switch>
<Route exact path="/" component={ComponentName} />
<Route exact path="/:productId" component={ProductComponent} />
</Switch>
</BrowserRouter>
)
- Stateless Functional Component
BrowserRouter
is the parent componentSwitch
will make sure allRoute
components inside of it are looked at to search for a match for the path- You pass a
path
and acomponent
to theRoute
. If the path matches (matches exactly when usingexact
) the mentioned component will be used - A variable path can be defined by passing an id like this:
products/:productId
(note the colon) - If you don't use exact and have the path
products
then a path forproducts-collection
will also match
Changing route from component
If we take the above Router into account, our component ComponentName now has access to some Router specific props that have been passed through. These are history
, location
and match
.
(In match
you can find the productId
if we're in the ProductComponent.)
In history
are several methods that allow you to manipulate the routing. For changing a route you'll need this.props.history.push()
.
this.props.history.push( '/about-us' );
Adding initial value to input
React doesn't like it when you pass the value
attribute to an input without also defining onChange
on the input.
To pass an intial value to input you use the defaultValue
attribute (or prop?)
<input type="text" defaultValue={getValue()} />
<input type="text" defaultValue="This is not like a placeholder" />
When you do pass only a value to an input, React will kind of disable the input where it will catch any keypress and undo the action.
<input type="text" value={this.props.value} />
React want you to add onChange
where you will handle input changes and change the state. Once you've added the onChange
attribute, the error will be gone, but it will still not be possible to type in the input box because React will see that the value does not change in the state and it will still undo the action. You have to actually change the value that you put into the input, in state.
<input type="text" value={this.props.value} onChange={this.handleValueChange} />
Inline events
Inline events are events on a component itself in the template which you would normally add in a JavaScript file with an addEventListener
.
<button onClick="handleClick">Click me</button>
The handleClick()
function doesn't have paranthesis (round brackets) because that would invoke the method immediately when the component mounts. The click event is passed automatically to the handleClick()
function.
When you do have to use parenthesis to pass some data, you can wrap the function in an arrow function.
<button onClick="() => handleClick( value )">Click me</button>
If you also want to pass the click event to the handleClick()
function, this is now passed to the wrapping arrow function, so you'll have to pass it through from there.
<button onClick="( event ) => handleClick( event, value )">Click me</button>
Binding
React has it's own way of doing binding. This is of importance when trying to reference this
within the React component that you are trying to create.
You can reference this
within the different React lifecycle hooks like render()
and componentDidMount()
. In any other functions that you add, this
will be undefined
.
class ComponentName extends React.Component {
componentDidMount() {
// this is instance of component
}
myFunction() {
// this is undefined
}
render() {
// this is instance of component
}
}
You can bind your own functions to the component by using a constructor.
class ComponentName extends React.Component {
constructor() {
super(); // create the extended component first
this.myFunction = this.myFunction.bind( this );
}
myFunction() {
// this is now an instance of component and no longer undefined
}
}
The issue with this is the fact that if you add multiple functions, you'll have to create multiple lines in the constructor to bind this
to those functions.
A different, and arguably a more elegant solution, is to create a property that is set to an arrow function. As a property, the arrow function is bound to the instance of the component.
class ComponentName extends React.Component {
myFunction = (event) => {
// this is now an instance of component and not undefined
}
}
DOM References
React doesn't want you to directly touch the DOM to get like a value from an input, for example. There are several React specific ways of doing this, one of which is adding a reference.
class ComponentName extends React.Component {
// create a reference
myInput = React.createRef();
myFunction = ( event ) => {
// get value from input
const inputValue = this.myInput.value.value;
}
render() {
return (
<input type="text" ref={this.myInput}>
)
}
}
Passing data
React only allows you to pass data down into components. It doesn't allow you to pass data up. This is all fine with static data that doesn't change while inside the deeper child components. But it gets trickier when a child component is supposed to change the data. The data in question will most likely be the state that lives in a higher level parent component. The state can only be actually changed inside that parent component. In order to change the state from the child component you can pass a function down into the child component that will be then invoked with a certain set of new data. The function in the parent component will then run with this data where it can alter the state.
class ParentComponent extends React.Component {
state = {
product: {
name: '',
price: 0
}
}
const changeProduct = (product) => {
// change the state here
// see next chapter
}
render() {
<ChildComponent changeProduct={this.changeProduct} />
}
}
class ChildComponent extends React.Component {
const handleSubmit = (event) => {
this.props.changeProduct( { name: 'Product 1', price: 100 } )
}
render() {
<form onSubmit={handleSubmit}>
{ /* Form content here */ }
</form>
}
}
The ChildComponent will have access to the function in the ParentComponent via props
.
Changing state
The state inside a React component looks like a normal JavaScript object. But you're not allowed to update it as you would normally update an object in JavaScript. You're not allowed to directly mutate it.
class ComponentName extends React.Component {
state = {
product: {
name: 'Product 1',
price: 100
}
}
const changeState = () => {
// this is NOT allowed
this.state.product.name = 'Product 2';
}
}
Instead you have to use the built in setState
hook and pass it a copy of the part of the state you wish to update.
class ComponentName extends React.Component {
state = {
product: {
name: 'Product 1',
price: 100
},
collection: 'New arrivals'
}
const changeState = () => {
// create a copy of the part of the state we wish to update
const product = { ...state.product }
// update our copy of the state
product.name = 'Product 2';
// we could do that in one line with
// const product = { ...state.product, name: 'Product 2' }
// call the `setState` hook
this.setState( { product: product } )
// or: this.setState( { product } )
}
}
The key
attribute
When displaying state in JSX, to display a list of something or just a single item, React requires a unique value in a key
attribute on all items.
With the key
attribute, React can directly reference the item to update in the (Shadow) DOM instead of having to fuzzy search for the element by going over all elements in the DOM.
render() {
<p key={ person.id }>{ person.name }</p>
}
Lifecycle hooks
Lifecycle hooks are methods in React that you can call in your components that trigger at certain stages in the component. When it renders and when something in the component updates.
render()
The moment a component starts rendering.
componentDidMount()
The moment a component has rendered (has been mounted). It's the first time the component is done rendering.
componentWillUnmount()
The moment just before the component is removed from the DOM.
More lifecycle methods can be found in the React documentation.
Animating React
The react-transition-group
package is a animation package for React. You can import it as such:
import { TransitionGroup, CSSTransition } from 'react-transition-group';
You can use the TransitionGroup
as a wrapper around the elements that will contain the animation. But instead of adding it as another element, you can swap it for an existing element.
Let's say you have an unordered list.
<ul className="unordered-list">
<li>List item 1</li>
{ /* ...etc */ }
</ul>
You can change the ul
to TransitionGroup
, but because you still want all the traits of the ul
, you can pass ul
as a component property. When compiled to actual HTML, the TransitionGroup
will still render out an actual ul
element.
<TransitionGroup component="ul" className="unordered-list">
<li>List item 1</li>
{ /* ...etc */ }
</TransitionGroup>
You can then wrap the list items in the other thing that we imported, the CSSTransition
.
<TransitionGroup component="ul" className="unordered-list">
<CSSTransition key={key} classNames="item" timeout={ { enter: 250, exit: 250 } }>
<li>List item 1</li>
</CSSTransition>
</TransitionGroup>
The CSSTransition
component requires a key
and a timeout
. In this timeout
property we can pass an object that will contain information about how long the animation duration will be when animating in (enter
) and how long the animation duration will be when animating out (exit
).
The CSSTransition
component will add classes to whatever it wraps so you can animate it. For this is requires classNames
input. Notice the "s" on the end. Although not mentioned explicitely in the course, this seems to be a prop for the CSSTransition
component and will most likely be named as such because the className
attribute is reserved for JSX.
<li class="item-enter item-enter-active"></li>
<li class="item-exit item-exit-active"></li>
The classes item-enter
and item-exit
are added to the items when the list item is generated and when it is about to be removed from the DOM.
The classes item-enter-active
and item-exit-active
are added a split second after the first two I mentioned. This allows you to animate between two states.
.item-enter {
background-color: red;
transition: background-color .25s ease-in-out;
}
.item-enter-active {
background-color: blue;
}
After the 250 milliseconds as determined in the timeout
prop, the classes will be removed from the list item.
Prop types
You can determine the type of the value that needs to be passed into a prop. You can use a typed language like TypeScript, but you could also use PropTypes
. You can import these from the prop-types package.
import PropTypes from "prop-types";
You can define the type of the props inside the component itself. In case of a functional component you declare the prop types after the component.
const ComponentName = props => (
// JSX layout
)
ComponentName.propTypes = {
propName: PropTypes.string.isRequired,
otherPropName: PropTypes.number
}
In case of a class component you declare the prop types inside the class.
class ComponentName extends React.Component {
static propTypes = {
propName: PropTypes.string.isRequired,
otherPropName: PropTypes.number
}
render() {
...
}
}
With this bit of code we say the prop propName
is of type string
but also is required. The console will now issue an error when the prop is not added on the component and when anything but a string is passed into the prop. These errors will not show in the console in production, only in development.
The PropTypes documentation will show you all the different types that are possible. Notable is that the prop type for booleans and functions are bool
and func
respectively.
You can also determine what values an array holds with PropTypes.arrayOf(PropTypes.number)
.
The same goes for an object with fields of a certain type of value: PropTypes.objectOf( PropTypes.string )
.
When your objects holds fields with different type of values, you can use a shape
.
PropTypes.shape( {
name: PropTypes.string,
age: PropTypes.number
} )
Another component can also be passed into a prop. For this you can use instanceOf
: PropTypes.instanceOf( ComponentName )
.
When your prop can be multiple things you can use oneOfType
.
PropTypes.oneOfType( [
PropTypes.string,
PropTypes.number
] )
Or when your prop takes only specific values you can use oneOf
.
PropTypes.oneOf( [ 'CREATE', 'UPDATE', 'DELETE' ] )
PropTypes.oneOf( [ 100, 250, 500 ] )
Again, the PropTypes documentation has more information.
Ejecting from create-react-app
Running create-react-app gives you a React boilerplate where it has handled all of the difficult stuff like webpack and babel. If you would look into your package.json file, you would only a few dependencies. A whole lot of other dependencies have been tucked away in the react-scripts.
But you might run into a situation where you need some more control over the difficult stuff. For this you can eject from the create-react-app.
In you package.json you'll find an eject script which you can run with npm run eject
.
It will ask for confirmation and after that it will have added all the dependencies it had previously hidden from the package.json file, back into the package.json file.
You'll now also have two extra folders called config
and scripts
.