React for Beginners
02 - Thinking and Understanding React Components
Components
Is a reusable piece of your site. Basically able to reuse tags throughout your site. They're not custom tags, even though they'll look like that in the react inspector, they're react components. The components pull on data and whenever it changes then it gets updated.
03 - Creating our first component
- Components need at least one method which for a component is
render()
- React needs to be imported in all component files
- You need to render out the html at least in one location (only one if it is only a react app)
- You import the renderer from
react-dim
- You import the renderer from
import { render } from 'react-dom'
import StorePicker from './components/StorePicker'
render(<StorePicker />, document.querySelector('#main'));
04 - Writing HTML with JSX
Writing HTML in javascript is a pain though that pain has been eased slightly with the introduction of javascript templates. JSX makes it easier by being able to write HTML/React components without the use of HTML. It would look like:
<p className="hey">Hello</p>
There is an alternative way to write the html:
React.createElement('p', {className: "hey"}, "Hello")
The above makes one want to claw their eyes out especially when you have to nest create elements:
React.createElement('p', {className: "hey"}, React.createElement('p', {className: "cool"}, "Yoyo"))
Javascript's automatic semicolon insertion is why you use the pair of parentheses: ()
Rendering and returning sibling elements
From a render method you can only ever return one element. Meaning that you can have multiple elements but there must be a root or parent element (you can't return siblings):
<div className="parent">
<p className="child1"></p>
<p className="child2"></p>
</div>
However, in React 16.2 you can return siblings if you wrap them in a React.Fragment
tag. It renders out to nothing and removes the need for needless dummy divs.
<React.Fragment>
<p className="child1"></p>
<p className="child2"></p>
</React.Fragment>
Soon blank tags may be allowed to achieve the same goal as React.Fragment
:
<>
<p className="child1"></p>
<p className="child2"></p>
</>
Commenting in JSX
In order to comment in JSX, you need to use {}
that basically tells the compiler that we're going to be writing in Javascript now. There are no regular html comments.
{/* my comment */}
Also, realize that you currently can't add a expression/comment before the html that is returned. Otherwise it will give a very vague error such as: Syntax Error: unexpected token , expected ,
. This will fail:
{/* my comment */}
<div className="parent">
<p className="child1"></p>
<p className="child2"></p>
</div>
This will not:
<div className="parent">
{/* my comment */}
<p className="child1"></p>
<p className="child2"></p>
</div>
05 - Loading CSS Into Our React Application
Componetized CSS
There's two different ways that you can handle the CSS. The first is to globally add it to your base HTML page. The second is to import the css into the react component which allows for the css to be coupled to the component and for it to be specific to that component.
When you create a react application using create react app
then it automatically creates a webpack configuration as well. It automatically adds hot reloading for a served development page.
07 - Passing Dynamic Data with Props
- Props is how the data gets to where it needs to go
- To pass in a string define props as a string:
<Header tagline="my tagline" />
- To pass in anything other than a string you need to use curly's:
<Header age={500} />
- To reference props, they live at
this.props.<prop-name>
inside of a component. Use a pair of{}
to resolve them in JSX<span>{this.props.tagline}</span>
- When you're using a stateless functional component, there is no
this
(as compared to when using an ES6 class) and a property ofprops
is passed to the function. You just removethis.
and access normally.
- To pass in a string define props as a string:
- State where the data lives
this
can refer to variables in the react dev tools such as $r
. $0
, $1
, etc refer to the most recent things that you clicked on in the react dev tools.
08 - Stateless Functional Components
For components that only have a render method that returns HTML, there's no need for it to be a full blown react component. In that situation you can use a stateless functional component instead. Supposedly they're more performant and saves a bit of code.
You can use a function to define stateless functional components:
function Header(props) {
return (
{/* html */}
);
}
// or arrow function syntax:
const Header = (props) => {
return (
{/* html */}
);
}
// or arrow function syntax with an implicit return:
const Header = (props) => (
{/* html */}
);
// or arrow function syntax with an implicit return and destructured props:
// tagline is pulled from props.tagline
const Header = ({tagline, age}) => (
{/* html */}
);
09 - Routing with React Router
Routing is not baked into react. Two most popular are React Router and Next.js. The router in react is a component.
The <Route>
tag works by sequentially going through the routes to see if it finds a match. <Route exact path="/" component={StorePicker}>
matches, exactly, the /
route.
<Route path="/stores/:storeId" component={App}>
matches /stores/*
and passes storeId
to the App
component at props.match.params.storeId
.
Route provides the route params to the component that is mentioned in the route
10 - Helper and Utility Functions
Named exports are exports in ES6 modules that are not default exports. In React, if you put a value on an input element then React will error out since doing so creates a read-only field.
11 - Events, Refs, and this Binding
Events in React are very similar to those say in jQuery, AngularJS and other FE frameworks. React wraps events with its own synthetic events class. For example, you can use the onClick
prop to pass a method for React when the element is clicked: <button onClick={this.handleClick}>Click Me!</button>
. Notice that the handleClick
function is not immediately invoked. It is invoked on every click so you must pass it a reference to the function and not a resolved function result.
Similarly, there's an onSubmit
event that you can use on forms.
Every event function is passed an event object: handleClick(event) {}
A golden rule in React is never to touch the DOM. However, there are some instances when you should but you should always default to not doing so.
To get the submitted form values you can use ref
on the input. A deprecated way is to use ref
with a string value.
<input
type="text"
required
ref="myInput"
placeholder="Store Name"
defaultValue={getFunName()}
/>
Another way is to use a function ref (though a bit difficult to understand and grok):
<input
type="text"
required
ref={(myInput) => this.myInput = myInput}
placeholder="Store Name"
defaultValue={getFunName()}
/>
The standard way is like the below now. You use this.myInput
to reference the input ref that you created at the top of the class.
class MyClass extends React.Component {
myInput = React.createRef();
render() {
<form>
<input
type="text"
required
ref={this.myInput}
placeholder="Store Name"
defaultValue={getFunName()}
/>
</form>
}
}
Component Lifecycle Events
componentDidMount()
will run as soon as the component mounts.componentDidUpdate()
Binding
Any methods that are added to a component, beyond what is provided in React.Component
, will not be bound to this in the context of the component. The solution is to bind your own methods as well. Add a constructor()
with a call to super()
. Inside of there, you call each of your methods with this.goToStore.bind(this)
.
Another solution is to use arrow functions which will be set to the instance instead of nothing:
class MyClass extends React.Component {
goToStore = (event) => {
// this is set to MyClass instance
}
}
12 - Handling Events
When a component is a child of the router (meaning it is used by the router), then it is provided the routes that it needs to manage and manipulate routes.
Change the route by:
this.props.history.push(`/store/${storeName}`);
13 - Understanding State
State is an object that lives inside of a component and stores data for that component and possibly some of its children. It is a single source of truth. If you update the state, the data, then it will react and update all of the different places that rely upon it.
Every component in React can have its own state. However, it is often that you have a parent state on a higher component and you pass that down to the children. You can't pass data up but you can pass it down.
You can initialize state in the constructor:
class App extends React.Component {
constructor() {
super();
this.state = {};
}
}
Or you can use a property which is the preferred way:
class App extends React.Component {
state = {
fishes: {},
order: {}
};
}
You can pass state using props:
class App extends React.Component {
state = {
fishes: {},
order: {}
};
addFish = fish => {
console.log("Adding a fish");
}
render() {
return (
<Inventory addFish={this.addFish} />
);
}
}
In order to update state in React you need to:
-
take a copy of the existing staten:
const fishes = {...this.state.fishes}
- You don't want to directly modify state since that is mutating the object and it causes issues with performance and with things updating out of order.
- Use an object spread:
...
. A deep clone is not necessary.
-
add the new object to the copy
-
set the new composite object to state
- Use
this.setState(fishes)
- This will trigger a change on the page
- You don't need to pass the entire state but just the individual elements
- Use
14 - Loading data into state onClick
Create state event functions in the same component in which the state lives.
15 - Displaying State with JSX
How to loop over fishes in JSX? JSX doesn't have logic. Use JS for logic.
When creating an array of tracked items in React you need to pass a unique key property: <li key={myRandomVariable}></li>
. Needs to be a string index (I believe).
16 - Updating Our State
You can disable a button by using the disabled prop: <button disabled={!isAvailable} >Add to Cart</button>
React does not give you access to the key when working with iterated items so you must use a different prop, in this case index
, in order to get access to the key.
<Fish key={key} index={key} details={this.state.fishes[key]} addToOrder={this.addToOrder} />
17 - Displaying Order State with JSX
To add all of the properties from a state to the component, you can use an object spread. In the below example, the Order
component would receive the props fishes
and order
. Doing this is not recommended since it is a best practice to explicitly define state when adding state to components.
state = {
fishes: {},
order: {}
};
<Order {...this.state}/>
When you're doing too much inside of your render component, that typically means that you should create a new component and delegate some of the functionality to it.
Another pattern is that you can add separate render functions to a component.
18 - Persisting our State with Firebase
Use lifecycle methods to add in things such as persistence.
componentDidMount() {
console.log('here');
}
We'll be using this.ref
though those refs are different than what we've been using in react. It refers to the firebase refs
.
When you switch from one "store" to another, you can create memory leaks with Firebase since the browser is still listening for changes. You need to use the componentWillUnmount()
method.
componentWillUnmount() {
base.removeBinding(this.ref);
}
19 - Persisting Order State with LocalStorage
Use localStorage setItem()
and getItem()
.
20 - Bi-directional Data Flow and Live State Editing
React doesn't like it when you put state into an editable area without a plan to update it. Don't use input value without adding an onChange()
event.
Computed property names (new in ES6):
handleChange = event => {
const updatedFish = {
...this.props.fish,
[event.currentTarget.name]: event.currentTarget.value
};
this.props.updateFish(this.props.index, updatedFish);
};
21 - Removing Items from State
To delete an item you first copy the state element and then you set the individual item to null and then you set the state again using the react function. You can't use the delete
keyword because of Firebase. It expects that you'll set the item to null. It's pretty much the same as the update except that you set it to null instead of the updated item.
deleteFish = (key) => {
const fishes = { ...this.state.fishes };
fishes[key] = null;
this.setState({ fishes });
};
22 - Animating React Components
To add React transitions to a ul
element, use the TransitionGroup
component with a prop of component
equal to ul
:
<TransitionGroup component="ul" className="order">
{ orderIds.map(this.renderOrder) }
</TransitionGroup>
To add in a React CSS transition to an li
, use the CSSTransition
component providing a classNames
, timeout
, and key
prop:
<CSSTransition classNames="order" key={key} timeout={{enter: 250, exit: 250}}>
<li>
// your code
</li>
</CSSTransition>
Setting max-height to 0 allows you to transition from a fixed height to a calculated height. It's a bit of a hack.
23 - Component Validation with PropTypes
The PropTypes package used to be built in to React and now it is an alternative to Flow or Typescript. Here's an example for a stateless functional component:
import React from 'react';
import PropTypes from 'prop-types';
const Header = props => {
render() {
return (
<header className="top">
<h3 className="tagline">
<span>{props.tagline}</span>
</h3>
</header>
);
}
}
Header.propTypes = {
tagline: PropTypes.string.isRequired,
};
export default Header;
For a regular react component, you can use a static property:
import React from "react";
import PropTypes from "prop-types";
class AddFishForm extends React.Component {
static propTypes = {
addFish: PropTypes.func
};
render() {
return (
// redacted
);
}
}
export default AddFishForm;
29 - Eject from create-react-app
Use eject from create react app when you need to add in custom build processes or compilation steps. It is a permament change so do it on a separate branch first to test. It adds several dependencies and you can edit the webpack file directly.