TL;DC, jump to the Notes, Tips & Tricks Section
Why
I admit, I totally missed the boat on the whole MVC library/framework movement (I’m just going to call them all frameworks from here on). I come from a mindset that says “your site should work just fine without JS” and, at that time, all of those frameworks certainly flew in the face of that mindset; without JS, you usually get nothing but a blank page…
Well, my opinion was clearly not shared by the masses, and now some of the frameworks do offer SSR… So now I find myself a bit behind the times, but nevertheless ready to dig in.
What
But before I could dig in, I had to make a monstrous decision: Which one??? Angular was certainly the first powerhouse and still is, it seems, with enterprises, but it certainly does not control the job market anymore. React is easily the biggest player right now, with jobs and jobs galore. Backbone and Ember are both still very viable options, though neither is very strong in the job market. And recently there have been some very interesting new kids on the block that have a slightly different take on things, like Vue, Svelte, Qwik, and, well, the list goes on and on and on…
But, figuring most people are probably learning a new tech to help find a new job or improve their standings at their current one, I went with the current king of the job boards, and that is definitively React…
So, let’s get to know React!
Getting Started
As with all new things, the first stab at learning is a massive Google search… And boy are there a lot of tutorials! Articles, videos, articles with videos, documentation…
Of course, the first place we should all start is the documentation. But man can that stuff be dry… And when I was starting my search, React’s documentation was all words and code samples, no videos, no interaction… And I definitely learn best by tinkering!
So I went looking for articles & videos. There are naturally a lot of “Learn React in 10 minutes!” types of articles/videos out there, and while they do at least expose you to some bits and pieces, you are never going to actually learn something in 10 minutes… Or 15, or 20…
So then I went looking for courses. Again, there are plenty! And I tried plenty!! But with each one, I found myself walking away… Either the code was really old (tech changes fast!), I had trouble understanding or following the presenter, or they just bounced around too much…
But finally, I found my way to Scrimba and found a course description that sounded perfect. But it was not free, which is fine with me, I definitely do not mind paying for someone’s time, but I was now quite gun-shy, and was afraid of paying for a 12-hour, 126-lesson course, only to find the presenter drove me nuts!
So I reached out to Scrimba support and asked if they had a sample video of the presenter. To my surprise, they did not… But they did refer me to a free intro course (you do have to create an account and login), on the same subject, by the same instructor! :-)
I immediately took that intro course and loved it and the presenter so much that I subscribed and took the full course! They are both awesome, and Bob Ziroll, for me, is an excellent instructor: He speaks slowly and calmly; he presents subjects methodically, some times deviating to a seemingly unrelated subject or example, only to bring it back around and incorporate that subject or example in what he was previously teaching; he breaks up the lessons with lots of “hands on the keyboard” time; and the Scrimba interface itself is fantastic, allowing you to sort of interact with what appears to be the video, editing and testing code right in the browser, then returning seamlessly to the lesson…
Between Tania’s initial article, a host of other articles and videos, and these two courses, I have collected the following, which will likely be updated as I continue to learn more… Have fun!
Notes, Tips & Tricks
-
Glossary
- Component
- Reusable function that returns HTML, could be a button, widget, module, etc.
- Controlled Components
- Form elements that are controlled by React, in order to assure a “single source of truth” regarding the “state” of the form and your app.
- Hooks
- Functions that add Lifecycle Methods to Functional Components, such as
useState
anduseEffect
. - JSX
- React markup language, stands for JavaScript XML.
- Lifecycle
- Order in which methods are called.
- Lifecycle Methods
- Methods called at certain points during the lifecycle, such as
componentDidMount
. - Lifting State
- Moving the state of a component to a parent component, to help coordinate state among similar components.
- Mount
- Refers to an element being inserted into the DOM.
- Props
- Properties, read-only data passed to a component as an
array
. - State
- Temporary, read/write data store representing the “state” of your app; think “To Do list” or “Shopping Cart” items, where adding, editing, removing items changes the “state” of your app and its components.
- Unmount
- Refers to an element being removed from the DOM (or not included in a re-render).
-
Primer
- Open source library, not framework
- Developed by Facebook in 2011
- Can be complete project, or added into existing pages/sites
- Used to build a frontend, the “View” in MVC
- Builds using “components”, or reusable HTML (elements, sections, widgets, modules)
- Entire app is inserted into a Root component, often using
id="root"
- All JSX tags must be explicitly closed:
<img ... />
, not<img ...>
- Switching to JS while within JSX requires the JS to be wrapped within
{}
and do not use""
around the value:<img src={image_url} />
<p>Status: {active ? 'On' : 'Off'}</p>
- Stores data as
state
andprops
- Version 16.8 introduced Hooks, so Functional Components can handle State and Lifecycle Methods
- State cannot be altered by a child component; in order for a child to affect the parent’s state, the parent must pass its “change” function to the child component as a
prop
- Use
this.setState()
or “set state” function to updatestate
- State change triggers a re-render of any component, and it’s children, that are affected by that state
-
Add React to an existing page/site, using ES6
React doesn’t have to be the entire site or app, and it doesn’t have to be installed and compiled!
It can be added to an existing page/site, even just a flat HTML page:
https://reactjs.org/docs/add-react-to-a-website.htmlAdd a React container to an existing HTML page, something like:
<div id="like_button_container"></div>
Along with the stand-alone React JS:
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
The latest CDN links can be found here:
https://reactjs.org/docs/cdn-links.htmlBe sure to replace “development.js” with “production.min.js” when deploying live.
And be sure to add your custom React component script as well:
<script src="my_like_button.js"></script>
Then, in addition to the JS magic you have written, your custom React component script would contain something like:
// create your component as a JS Class, extending `React.Component` class LikeButton extends React.Component { // receive any props and set the initial state constructor(props) { super(props) this.state = { liked: false } } // `render` the contents of your component render() { // if the `state` is true, return the "true" message if (this.state.liked) { return 'You liked this.' } // otherwise, create the actionable component return React.createElement( 'button', { onClick: () => this.setState({ liked: true }) }, 'Like' ) } } // cache the React component container in your DOM const container = document.querySelector('#like_button_container') // render the actual component into the component container ReactDOM.render(React.createElement(LikeButton), container);
When the above code loads, inside of your component container (
#like_button_container
), React will either display the “You liked this.” message, or create abutton
element, with a click event listener that changes the component’s state to true, and with the text of “Like”. -
Add React to an existing page/site, using JSX
Or step it up a notch and get to know some JSX:
Getting Started with React(There is a lot more about JSX below.)
Add a React component container to an existing HTML page, something like:
<div id="root"></div>
Along with the stand-alone React and Babel JS:
<script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/[email protected]/babel.js"></script>
The latest CDN links can be found here:
https://reactjs.org/docs/cdn-links.htmlBe sure to replace “development.js” with “production.min.js” when deploying live.
And be sure to add your custom React component script as well:
<script src="App.js"></script>
Your custom React component script would contain something like:
class App extends React.Component { render() { return <h1>Hello World!</h1> } } ReactDOM.render(<App/>, document.querySelector('#root');
When the above code loads, inside of your component container (
#root
), React will create anh1
element with the text of “Hello World!”. -
Create Complete React App
Static HTML is cool, but it is not very scalable…
Facebook created Created React App, a Node package to install a pre-configured dev environment with starter app code.
This app allows you to very easily get a sample React app up and running, so you can see, touch and play with real, working code.
- In Terminal, navigate to the directory where you app should live, for example, a parent directory like
/apps
- Install “Create React App”
npx create-react-app react-app-demo
Notes regarding the above installation:
- Yes, the command is
npx
, notnpm
react-app-demo
is the directory into which the app will be installednpx
will first create the directory, then install the app into it- The directory into which you are installing the app must be empty or
npx
will not continue - Node will confirm you want to install, then download and install the sample app and all of its dependencies
Once installation is complete, follow the on-screen instructions to navigate into the new directory and start a Node server:
cd react-app-demo && npm start
This should create a local server and open a default browser window for this app.
If you look at the default structure of this demo app, you will see something similar to this (there will be other files, but this is the important part):
/react-app-demo /node_modules /public /src .package.json
Anything that NPM installs will save into the
/node_modules
folder.Your starter
index.html
files and some other starter files will be in the/public
folder.And all of the components, custom JS, custom CSS, images, etc. that you add to your app will go into the
/src
folder.Feel free to click around, open files, dig through things. Get nosy, that’s how you’ll learn best!
And remember, if you mess something up, you can always delete the demo app folder and re-install as you did above!
Depending on your IDE and extensions, any changes you then make and save to the app files should auto-refresh in that browser tab.
If you do not have an extension to auto-refresh, simply refresh the tab manually after saving changes.
If you do something that breaks the app in the browser, you might have to manually refresh to get auto-refresh going again.
- In Terminal, navigate to the directory where you app should live, for example, a parent directory like
-
Add Dependencies to an Existing React Project
“Create React App” installs all of its dependencies automatically.
And any existing project you work on will likely have all of its dependencies installed too.
But suppose you want to add something new at some point?
- Look-up the new package name in NPM:
https://www.npmjs.com/, then use Search - In Terminal, navigate to the app directory
- Tell
npm
to install that package, replacingnew-package-name
with the actual new package name found in NPM:npm install new-package-name --save-dev
npm
will automatically install the new package and any dependencies it may have
- Look-up the new package name in NPM:
-
JSX
JSX is React’s custom markup language. It stands for “JavaScript XML”.
It looks a lot like HTML, but with a few caveats:
- All tags need to be explicitly closed:
<img ... />
instead of<img ...>
- Attributes, properties and methods are camelCase:
onClick
instead ofonclick
backgroundColor
instead ofbackground-color
- You need to use
className
instead ofclass
:<h1 className="page-title">Hello World!</h1>
- Do not use quotes around the JSX, as it is NOT a String:
const header = <h1>Hello World!</h1>
- Any JS, including variables, needs to be wrapped inside
{}
:<h1>Hello {name}</h1>
<h1>Status: {active ? 'On' : 'Off'}</h1>
- All tags need to be explicitly closed:
-
“Declarative” vs. “Imperative”
Before digging any further into code, one of the first bits to grok is that React is “declarative”, as opposed to “imperative”.
This means that we tell React “what we want done”, not “how to do it”.
In a way, using JSX feels a bit like using jQuery…
For example, in Vanilla JS, we would do something like this, telling JS every step it needs to take:
// create an H1 element const elem = document.createElement('h1') // add a class attribute elem.className = 'site-heading' // add some text inside of the element elem.innerHTML = 'Hello World!' // append the element to #root document.querySelector('#root').append(elem)
While with jQuery we could simply say:
// grab #root and put this stuff in there $('#root').append('<h1 class="site-heading">Hello World!</h1>')
We are not telling jQuery “how” to do all of that, just “do it”.
Similarly, without JSX, we would have to tell React:
ReactDOM.render( React.createElement( // create an H1 element 'h1', // add a class attribute {class: 'site-heading'}, // add some text inside of the element 'Hello World!' ), // append the element to #root document.querySelector('#root') )
While with JSX, we can tell React something like this:
// put this stuff inside #root ReactDOM.render( <h1 className="site-heading">Hello World!</h1>, document.querySelector('#root') )
And if you were to create a variable first, and log that to the console, you would see that it is not a String and is not an HTML node, but it is a JS Object:
const elem = <h1 className="site-header">Hello World!</h1> console.log(elem) { type: "h1", key: null, ref: null, props: { className: "site-header", children: "Hello World!" }, _owner: null, _store: {} }
This JS object tells React what we want done and it just does it.
-
render()
1 Line vs. Multiple LinesThe
render()
function can only have one parent element returned to it.That one parent element can have unlimited child elements, but everything that you want to return must be wrapped in one single element.
If
render()
is only returning 1 line, you can do it like this:class App extends React.Component { render() { return <h1>Hello World!</h1> } }
But if
render()
is returning multiple lines, you need to wrap the JSX in parentheses:class App extends React.Component { render() { return ( <header> <h1>Hello World!</h1> </header> ) } }
However, if you do have multiple elements, but don’t really need a parent element, you can use an “empty” element as your wrapper:
class App extends React.Component { render() { return ( <> <h1>Hello World!</h1> <h2>It's a beautiful day!</h2> </> ) } }
React will see the
<>
and</>
and ignore them when generating the actual output. -
Various Ways to
import
React and ComponentsYour initial JS file, usually
index.js
, will need something like this to get started:import React from 'react' import ReactDOM from 'react-dom'
You can also import custom components with something like:
import MyComponent from './MyComponent'
The code above would import a component called “MyComponent” from a local file called “MyComponent.js”.
Note that you do not need the “.js” in the
from
name; without a file extension, React will assume it is “.js”.React also allows you to import specific components via destructuring;
Fruits.js
might contain a component for every fruit known to mankind, but if you only need the Apple, Banana and Orange components, you can import only those:import {Apple, Banana, Orange} from './Fruits'
The code samples above import React then use
React.Component
:import React from 'react' class App extends React.Component { ... }
But you can also import Component itself, then use it directly:
import React, {Component} from 'react' class App extends Component { ... }
-
Components
Speaking so much about Components, a few quick notes, most elaborated on below:
- Nearly everything in React is a component
- Components can contain components
- Convention puts each component into its own JS file, but you can have multiple components in a single JS file
- Component names must be PascalCase:
Table
,TableHead
,TableBody
,TableRow
, etc. - Convention names the related JS file the same way, so:
Table.js
,TableHead.js
, etc. - Each Component JS file must export any Components that should be “importable” elsewhere
- If your project has more than one or two Components, convention puts all Components in a separate directory like
/components
; you then import them something like:import TableHead from './components/TableHead'
- Functional (Simple) Components are Function-driven, essentially just JS functions
- Class Component is Class-driven, meaning JS Classes
- Convention is moving away from Class Components, and toward Functional Components, for ease of writing, reading and debugging, as well as smaller code bases
Functional Components are just JS functions:
const FunctionalComponent = () => { return <div>Example</div> }
Class Components use a JS Class:
class ClassComponent extends Component { render() { return <div>Example</div> } }
Exporting Components can be done either as the component is created:
export default function FunctionalComponent() { return <div>Example</div> }
Or at the end of the component file:
function FunctionalComponent() { return <div>Example</div> } ... export default FunctionalComponent;
If done at the end of the component file, you can export multiple components with a single line of code, but you cannot use the
default
keyword, and you need to wrap the multiple component names with{ }
:function FunctionalComponent1() { return <div>Example 1</div> } function FunctionalComponent2() { return <div>Example 2</div> } ... export {FunctionalComponent1, FunctionalComponent};
Aside from the major difference of Functional Components being just a JS function and Class Components being a JS Class object:
- Functional Components use
return
:const FunctionalComponent = () => { ... return <div>Example</div> }
Class Components must
render
thereturn
:class ClassComponent extends Component { ... render() { return <div>Example</div> } }
- Functional Components are stateless, but can add state using Hooks; Class Components are stateful by default, via their
constructor()
method - Functional Components cannot use lifecycle methods, but can add most lifecycle functionality using Hooks; Class Components can use lifecycle methods (i.e.
componentDidMount
) - Functional Components have no constructor; Class Components do
Below are several code comparisons and conversions between Simple and Class Components...
- Basic Component construct:
-
import React from 'react' const FunctionalComponent = () => { return <h1>Hello, world</h1> }
vs.
import React, { Component } from 'react' class ClassComponent extends Component { render() { return <h1>Hello, world</h1> } }
Note that a Class Component must extend
React.Component
(or simplyComponent
, if destructured, as above) and must userender()
. - Using Props:
-
Pass props into a component:
<Component name="Aaron" />
Then consume them:const FunctionalComponent = props => { return <h1>Hello, {props.name}</h1> }
vs.
class ClassComponent extends React.Component { constructor(props) { super(props) this.state = { name: props.name } } render() { return <h1>Hello, {this.state.name}</h1> } }
Note that Class Components must import props via the
super()
function within theirconstructor()
, and they are then available throughout the class viathis.
. - Using State:
-
const FunctionalComponent = () => { const [ count, setCount ] = React.useState(0) return ( <div> <p>count: {count}</p> <button onClick={() => setCount(count + 1)}>Click</button> </div> ) }
vs.
class ClassComponent extends React.Component { constructor(props) { super(props) this.state = { count: 0 } } render() { return ( <div> <p>count: {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click </button> </div> ) } }
The Functional Component's
React.useState
Hook sets the initial value (0
), and returns the current value and a function to change the state, destructured intocount
andsetCount
above.While the Class Component's
constructor()
sets its initial state (count: 0
), inherits its props viasuper(props)
, and automatically hasthis.setState
.Because the Functional Component's
React.useState
runs with each re-render, if it is doing anything that could cause a performance problem, you can use "lazy initialization", by wrapping the init "value" with a function that returns the end value, so that something like:const [state, setState] = React.userState( JSON.parse(localStorage.getItem('notes')) )
becomes:
const [state, setState] = React.userState( () => JSON.parse(localStorage.getItem('notes')) )
- Lifecycle Methods:
-
const FunctionalComponent = () => { React.useEffect(() => { console.log("Hello") }, []) return <h1>Hello, World</h1>; }
vs.
class ClassComponent extends React.Component { componentDidMount() { console.log("Hello"); } render() { return <h1>Hello, World</h1>; } }
The Functional Component's
React.useEffect
Hook works as the Class Component'scomponentDidMount
does, receiving a function to perform state changes, but also receives an array of states to react to; an empty array (as above) says "only run on the initial render".componentDidMount
is automatically called when its component is mounted (added to the DOM).
-
Props
Short for Properties, props are used to pass data down to Components.
This data can be any valid JS data type: String, Number, Boolean, Function, Array, Object, etc.
You can pass unlimited props to a component, but all will be received by the component wrapped in a single JS Object.
Props cannot change! They are passed down and used as "read only" data.
For example, you can pass props like this:
<TableRow id={id} firstName={firstName} lastName={lastName} jobTitle={jobTitle} />
And they will be received inside a single JS Object, conventionally called
props
, and can be consumed from there:function TableRow(props) { const { id, firstName, lastName, jobTitle } = props ... }
A deeper example, using the following data:
const employees = [ { id: 12345, firstName: 'Charlie', lastName: 'Brown', jobTitle: 'CEO', }, { id: 23456, firstName: 'Linus', lastName: 'Van Pelt', jobTitle: 'CFO', }, { id: 34567, firstName: 'Peppermint', lastName: 'Patty', jobTitle: 'COO', }, { id: 45678, firstName: 'Snoopy', lastName: '', jobTitle: 'CTO', }, ];
The
employees
array could be passed from theTableBody
Component to theTableRow
Component like this:function TableBody() { return ( <tbody> <TableRow data={employees}/> </tbody> ) }
Note that we are passing the entire data array as a single prop (
data
), and do not have to use the same name for the prop and the prop value.The
TableRow
Component receives the data, inside a single JS Object, and can consume it via any JS method, such asmap
below:function TableRow(props) { // loop thru props, // build a `tr` for each employee const rows = props.data.map( employee => { return ( <tr key={employee.id}> <td>{employee.id}</td> <td>{employee.firstName}</td> <td>{employee.lastName}</td> <td>{employee.jobTitle}</td> </tr> ) }) // return `rows` back to `TableBody` return ( {rows} ) }
Note that you should always use a
key
on any iteration parent node to help React identify each "unique" iteration (it will complain in the console otherwise...).Note also that you can destructure props as they arrive in the component, then you no longer need the
props.
in the JSXreturn
:<Component prop1="foo" prop2="bar" />
function Component({ prop1, prop2 }){ return( <p>{prop1} {prop2}</p> ) }
You can also rename props as they arrive:
function Component({ prop1: newName, prop2: diffName }){ return( <p>{newName} {diffName}</p> ) }
Remember that a prop can be any valid JS data type, including Objects, Arrays, Functions, and combos of all of the above, but only a String should be wrapped in quotes
"..."
.If the prop value is not a String, then it must be wrapped in
{...}
, and should not have quotes.And if things start getting outta hand, it might be time to push each prop to a new line...
<Component prop1="foo" isCool={true} team={[ {name: 'Charlie', title: 'CEO'}, {name: 'Linus', title: 'CFO'}, {name: 'Peppermint', title: 'COO'}, {name: 'Snoopy', title: 'CTO'} ]} onClick={myOnClickFunction} />
And the above might be okay for a couple/few props, but if things get really outta hand, you can build all of your props into a JS Object, then pass the entire Object to the component:
<Component allEmployees={data} />
Then pull that data object apart within the component:
function Component(props){ const rows = props.allEmployees.map( (row, index) => { return ( <tr key={index}> <td>{row.name}</td> <td>{row.title}</td> ... </tr> ) }) return( <tbody>{rows}</tbody> ) }
Or, you can use the spread operator, so a single line creates all of the individual props for you:
<Component {...data} />
But then you need to go back to referencing the props via the
prop.
Object:function Component(props){ return( <p>{props.prop1} {props.prop2}</p> )
-
State
State can be a tricky one to conceptualize, because it is the "state" of your app.
Meaning, if there is something that "will change", then that is the "state" of your app.
For example, in a "To Do" list app, adding, removing, sorting, editing would all change the "state" of an app.
When the State changes, React will refresh the UI to reflect the data change(s).
State can be created and managed by a Component, but to be maintained between sessions or page refreshes, any values that should be stored permanently must be stored in some data storage, like a database or
localStorage
.State comes built-in with Class Components, but must be added to Functional Components, using the
useState
Hook.State must never be updated manually/directly, but rather through the "set state" functions.
If we have a Functional Component, we first have to
import
theuseState
Hook, like either this:import React from 'react' export default function Component(){ const [count, setCount] = React.useState(0); ... }
or this:
import React, {useState} from 'react' export default function Component(){ const [count, setCount] = useState(0); ... }
In either case above, note that
useState
can receive an optional starting value (0
in both examples above), and returns a two-item array that contains 1) the current state, and 2) a function to change the state; these usually get destructured into something like this:const [count, setCount] = useState(0);
Convention starts the state change function name with "set" followed by the variable name, so in the above case,
setCount
, because this will be used to update the state ofcount
.Then when we want to update the state, no matter where we need to do this, we can use the
setCount
function:setCount(1)
If the "set state" function needs to use the current state, we need to use a callback function:
setCount(prevCount => prevCount + 1)
Note the
prevCount
variable. This is the state before the update begins, and is automatically provided by the "set state" function.Convention starts the previous state name "prev" + the variable name, so in the above case,
prevCount
, as this represents the previous state ofcount
.It is very important never to try to update the previous state directly, with something like this, as this would directly affect the current state itself:
/** DO NOT DO THIS **/ setCount(prevCount => prevCount++)
Instead, create a new value and pass that back, as above.
Similarly, when the state is an array, we cannot do either of the below, as they would also alter the current state directly:
/** DO NOT DO THIS **/ setTasks(tasks.push(`Task ${tasks.length + 1}`)) /** DO NOT DO THIS **/ setTasks(prevTasks => prevTasks.push(`Task ${prevTasks.length + 1}`))
In the case of an array, we need to create a new array, using the spread operator to replicate it, then append to it:
setTasks(prevTasks => [...prevTasks, `Task ${prevTasks.length + 1}`])
Objects are similar to arrays, where we can still use the spread operator to replicate the old state into a new object, but then we overwrite the old value with the new:
setContact(prevContact => { return {...prevContact, phoneNumber: newPhoneNumber} })
In the above case,
phoneNumber
already exists in the state, but by adding the update at the end, it overwrites the previous value with the new.Note that if we want to use the implicit return by putting the object on a single line, we need to wrap the object in
( )
or React will interpret the object's{ }
as the function brackets, so it would look like:setContact(prevContact => ({...prevContact, phoneNumber: newPhoneNumber}))
If we have a Class Component, state is automatically included, and is used something like:
class App extends Component { // receive any props and set the initial state constructor(props) { super(props); this.state = { count: 0 }; } ... }
To update state, use the built-in
this.setState
function:this.setState({ state: (this.state + 1) })
Remember that child components cannot change parent state by themselves; they would need to receive the "set state" function from the parent, something like:
/* The App component receives `props`, defines `state`, and has a function that updates `state` */ class App extends Component { // receive `props` and set `state` constructor(props) { super(props) this.state = props.data } // method to remove item from array removeItem = ( index ) => { // get current state const { items } = this.state // update state by filtering out the `index` passed /* must use built-in `.setState` to alter `state` */ this.setState({ items: items.filter((item, i) => { return i !== index }) }) } render() { // pass `items` and `removeItem` to Table Component return ( <Table items={this.state.items} removeItem={this.removeItem} /> ) } } /* The Table component receives `props`, passes them to the `TableBody` component */ const Table = props => { // receive the `props` from `Table` const { items, removeItem } = props // pass them to the `TableBody` component return ( <table> <TableHeader /> <TableBody items={items} removeItem={removeItem} /> </table> ) } /* The TableBody component receives `props`, and consumes them */ const TableBody = props => { // map data set, return `tr` for each, // include the "set state" function as an `onClick` event const rows = props.items.map( (row, index) => { return ( <tr key={index}> <td>{row.name}</td> <td>{row.job}</td> <td><button onClick={()=>props.removeItem(index)}>×</button></td> </tr> ) }) return( <tbody>{rows}</tbody> ) }
This is the only way for a child component to affect the state of a parent component: the parent must pass down a function, if necessary as above from child-to-child, which can then use the parent's state change function, which will trigger the state change, which will trigger the UI to re-render, reflecting the new state.
Note that in the
Table
component example above, the props are destructured before being passed to theTableBody
component, individually. This could have been done more succinctly, though not as intuitively, by using the Spread Operator:const Table = props => { return ( <table> <TableHeader /> <TableBody {...props} /> </table> ) }
In the above case, the "spread" props would have arrived exactly the same in the
TableBody
component. -
Lifting/Raising/Derived State
Lifting or Raising State happens when you realize a sibling or parent component needs access to the state you created for some component.
Derived State happens when you find yourself initializing the state in a component based on incoming props. This can create "two sets of truth": 1) in the parent, 2) in the child(ren).
In all of these cases, it is probably better to set and maintain the state in a parent component, still keeping it as "local" as possible, then pass that state down as props to whatever components that need it.
-
Event Listeners
Event listeners get added right to elements, and use PascalCase in JSX:
<button onClick={myClickFunction}>Click me</button> <input onChange={myChangeFunction} value="Name" />
When you need to tell a parent to change state, you often have to pass some identifier, like
id
here:function toggle(id) { setState(prev => { return prev.map(user => { return user.id === id ? {...user, active: !user.active} : user }) }) }
But we cannot pass that identifier to the
toggle
function, as this would make the function fire on render:/** DO NOT DO THIS **/ <button onClick={toggle(id)}>Click me</button>
So we need to pass our function and parameter inside an anonymous function:
<button onClick={() => {toggle(id)}}>Click me</button>
You can read more about React Event Listeners here:
https://reactjs.org/docs/events.html -
Inline CSS
While it is possible to add a
style
attribute on an element in React, it is not exactly how you do it in HTML.In HTML, you could do something like this:
<div style="width: 100px">Hello World!</div>
In React, you need one set of
{ }
to tell JSX to "switch to JS", and another set to create a JS Object for the styles, and there are no "quotes" around the attribute value, so the example above would become:<div style={{width: 100px}}>Hello World!</div>
Convention often creates the JS Object before, especially if there are a multiple style declarations, then adds it to the JSX like this:
const styles = {width: 100px}
<div style={styles}>Hello World!</div>
Also note that, as this is JS, you need to use camelCase, and can use multiple rows for ease-of-reading:
const styles = { backgroundColor: "red", fontColor: "white" }
<div style={styles}>Hello World!</div>
-
Adding Assets (images, SVGs, videos, etc.)
If you look at the default structure of a React app, you will see something similar to this:
/my-react-app /node_modules /public /src .package.json
All of your Components and custom CSS and JS should go into the
/src
folder.You can choose to also put your images, etc., in
/src
, or you can put them in/public
.But how you reference those assets in your code will vary depending on which you choose...
If you put your assets in
/public
, you can refer to them like you normally would in your HTML:function Header() { return ( <header> <img src="/logo.png" ... /> </header> ) }
If you put your assets in
/src
, however, you first have toimport
that asset, as a Component, then you can refer to it:import logo from './logo.png'; function Header() { return ( <header> <img src={logo} ... /> </header> ) }
Note that
{logo}
above is the URL for the image, to be used as thesrc
value, and does not use quotes. Think oflogo
as a variable, pointing to that image location.And don't forget that
img
elements must be explicitly closed in JSX! -
Conditional Rendering
Sometimes your UI will "toggle" some items depending on the current state.
Of course you can use ternary statements:
<h1> Hello { firstName !== '' ? firstName : 'World' }! </h1>
This is okay for toggling small fragments.
But for larger sections of code, or even entire elements, kind of like when you are using an
if
statement, you can use&&
and do something like this:{ props.shouldShow && <h1>Hello</h1> }
The above will only show the
h1
ifprops.shouldShow
istrue
. -
Forms
Forms in React are another complicated topic...
Normal HTML forms do work just fine in React, but usually you want an Ajax submit.
Which means form elements need to control their own state (as the user enters data) and React already controls its state via
setState
, so we need to combine the two so that React is the "single source of truth".Form elements controlled by React are called a "controlled components".
Event handlers update the React state with every keystroke.
This process works for
input
,select
andtextarea
, although<input type="file" />
is considered an "uncontrolled component", as it is read-only.The basic idea is to setup state, create an event handler, and attach it to the form element:
const [firstName, setFirstName] = React.useState('') function handleChange(event){ setFirstName(event.target.value) }
<input type="text" onChange={handleChange} />
However, this can get pretty crazy, pretty quickly, with large forms, having to create state and an event handler for every form element...
Instead, we can save all of the form data in a single state, as a JS Object, then
handleChange
can be expanded to handle all form fields:const [formData, setFormData] = React.useState({ firstName: '', lastName: '' }) function handleChange(event){ setFormData(prevFormData => { return { ...prevFormData, [event.target.name]: event.target.value } }) }
<input name="firstName" onChange={handleChange} /> <input name="lastName" onChange={handleChange} />
Note that if the form element
name
attributes are the same as the state "key" name, then in thehandleChange
function we can use the spread operator to copy all existing object names & values, and use a dynamic variable name ([event.target.name]
) to update just the data that is being updated by the user.To take things a little further, and ensure that React is the "single source of truth", we can create "Controlled Inputs" by adding a
value
attribute to eachinput
, and letting React update it on render:<input value={formData.firstName} name="firstName" onChange={handleChange} />
Yes, this might seem a bit circular (the user types into the
input
, React takes theinput
value and pushes it to the state, then the state updates theinput
value), but this means we have a "single source of truth", and React controls it.And, in a way, this is not that dissimilar to how an old-school PHP-driven form works: the user types into the
input
, when the form submits PHP takes theinput
value and pushes into the database, then PHP updates theinput
value before sending back to the browser.Not dissimilar, just... slower.
Naturally, not all form elements are "so simple"...
The
select
element is still fairly straight forward, as thename
andonChange
work similarly to native HTML and the above examples.A small difference from native HTML is that in React we add a
value
, like theinput
examples above, and React automatically "selects" the correctoption
based on thatvalue
, which is actually quite nice (none of that "if the selected value is the same as this value, mark this option as selected" stuff):<select name="favColor" value={formData.favColor} onChange={handleChange} > <option value="">-- Choose --</option> <option value="Red">Red</option> <option value="Blue">Blue</option> ... </select>
The
textarea
follows a similar pattern, with one more slight change...While in HTML, the
textarea
element is not self-closing, and its "value" is whatever is between the opening and closing tags:<textarea>The comment is here</textarea>
In React, the
textarea
element is self-closing, and its "value" goes in avalue
attribute, just like all the examples above:<textarea value="The comment is here" />
Next up is the
checkbox
elements, and since it really represents Boolean states (either "checked" or "not checked"), React does not use a Stringvalue
attribute, but instead uses thechecked
attribute state, which must be eithertrue
orfalse
:<input type="checkbox" checked={formData.optIn} onChange={handleChange} />
Again, not bad, and actually kind of nice, but you might note that this breaks our
handleChange
function, which is looking for theevent.target.value
, so we need to update it a little...Convention typically destructures the
event.target
object to get thename
andvalue
parameters, and since we will also need a couple more parameters now, we can use a ternary to determine whether to use thevalue
orchecked
attribute, based on the element'stype
:function handleChange(event){ const {name, value, type, checkbox} = event.target setFormData(prevFormData => { return { ...prevFormData, [name]: (type === 'checkbox' ? checked : value) } }) }
Note that we are still using the spread operator to copy all previous form data, and, after destructuring, we are still using the dynamic variable
[name]
to determine which state property needs to be updated, but the ternary now determines whichevent.target
parameter we use as the new statevalue
: if thetype
is "checkbox", we will return either atrue
orfalse
Boolean; otherwise we will return whatever String is in thevalue
.Next, like regular HTML
radio
elements, multiple elements are used to indicate the value for a single state.All of these radio button have
name="employment"
, so only one can ever be checked, but each has a uniquevalue
:<input type="radio" name="employment" value="unemployed" onChange={handleChange} /> <input type="radio" name="employment" value="full-time" onChange={handleChange} /> <input type="radio" name="employment" value="part-time" onChange={handleChange} />
However, since the
value
attribute is needed to designate the element's actual value, React sort of bastardizes thecheckbox
'schecked
attribute: if the state matches that radio's value, then itschecked
attribute istrue
:<input type="radio" name="employment" value="part-time" checked={formData.employment === 'part-time'} onChange={handleChange} />
So if
formData.employment
is "part-time", then the above element'schecked
will betrue
, meaning, all of the other related elements will not be...And amazingly, since the above element's
type
is notcheckbox
, then this will also "just work" with our existinghandleChange
function, and it still store the form field'svalue
...Finally, the
submit
button is also fairly straight-forward!The
form
gets anonSubmit
attribute, which takes an event handler and function:<form onSubmit={handleSubmit}>
But instead of using a classic
input type="submit"
to submit the form, we use abutton
element (which will also submit a form just fine in standard HTML):<button>Submit</button>
And since we have been updating
formData
all along, the data is actually already ready to send, as a JS Object, to whatever data store we intend to use!function handleSubmit(event) { event.preventDefault() saveDataSomewhere(formData) }
Note that we do still have to
preventDefault
if the form is being submitted via Ajax.And lastly, similarly to how React needs to use
className
and notclass
, note that thelabel
element needs to usehtmlFor
instead offor
...:<label htmlFor="employment">Employment</label>
You can read more about React Forms here:
https://reactjs.org/docs/forms.htmlYou might also consider using pre-existing form handlers, some of which include validation, like:
https://jaredpalmer.com/formik
-
Side Effects
Considering that React's main tasks are:
- render the UI
- manage the state between renders
- keep the UI updated
That leaves-out a lot of things that are often found in apps, like:
- localStorage/database/API interactions
- subscriptions to things like web workers
- syncing multiple internal states
- and the list goes one...
This is where "Side Effects" come in; this is basically React's way of dealing with things that React doesn't do well, or at all, "out of the box".
Also know as "the Effect Hook", and spotted in code as
useEffect
, Side Effects are to Functional Components what Lifecycle Methods are to Class Components (mostly): things likecomponentDidMount
,componentDidUpdate
andcomponentWillUnmount
.Created by React, Side Effects allow us to interact with things that are "outside of React", syncing React state with outside systems: data fetching, setting up a subscriptions, and manually changing the DOM in React components, and more.
useEffect
tells React to do something after the render.The "Effect" is a function that you pass to
useEffect
:function Component() { const [count, setCount] = useState(0) useEffect(() => { // this will happen *after* the render, // so state will always be "current" document.title = `You clicked ${count} times` }) return ( <button onClick={ () => setCount(count + 1) }> Click me </button> ) }
So on initial load, the
document.title
will say "0", but with each click it will increment by 1.Because
useEffect
is inside the component, it is within the component scope, so it has access to all of the local variables and state, so we do not need to pass those around!useEffect
runs after every render (initial and updates, unlike its related Class-based counterparts), allowing you to do things after the state has been updated, and a re-render has completed.There are two common kinds of Side Effects in React:
- those that do not require an< type of cleanup, like network requests, manual DOM updates, logging, etc.,
- and those that do, like things that might cause memory leaks, like event listeners, web socket subscriptions, etc.
Cleaning up after a Side Effect can be handled by returning a "cleanup" function from
useEffect
:useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline) } ChatAPI.subscribe(id, handleStatusChange) // Specify how to clean up after this effect return function cleanup() { ChatAPI.unsubscribe (id, handleStatusChange) } })
React will store the cleanup function and use it when the component unmounts.
Note that the cleanup function doesn't have to be a "named" function, it can be anonymous, and is only named above for clarity.
If the cleanup/re-setup after each render could cause performance issues, you can also pass previous state into
useEffect
(known as a "Dependencies Array", seen as[count]
in the example below), which tells it to only process the effect if the state has changed.useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // Only re-run the effect if `count` changes
One concern to be aware of is the possibility of creating an infinite "refresh" loop...
Consider the following API call:
// after the UI is rendered... useEffect(() => { // fetch something from an API fetch('https://swapi.dev/api/people/1') // convert to JSON .then(res => res.json()) // and push into state .then(data => setData(data)) })
Now let's walk through what is happening here:
- the component renders
- after render,
useEffect
fetches data from the API - once received, updates the state
- state update triggers a re-render
- go back to step 2...
The solution is to pass a Dependencies Array (the
[count]
bit added above to avoid performance issues), but in this case, we want to pass an empty array:useEffect(() => { fetch('https://swapi.dev/api/people/1') .then(res => res.json()) .then(data => setData(data)) }, []);
Note the empty Dependencies Array, telling React that there are no Dependencies to watch (and trigger a re-render) for, therefore this effect should run only once, on the initial render.
You can read more about Side Effects here:
-
Deploy to Live Web Server
In order to deploy a React app to a live server, so the rest of the world can see it, the process is slightly more difficult than the old "FTP to server, refresh page".
Since I use Dreamhost as my web host, I found this article, but I do not see anything specific about Dreamhost in it, so hopefully it will work for you, too:
Deploying a Simple React Application using FTP, FileZilla, & DreamHostMy only notes from that article are:
- I had to rewrite all relative URLs to absolute URLs because my test app was nested in several directories on my site.
- The
<script src="bundle.js"...
did not get created and added to my build, not sure why, so I removed that from my app'sindex.html
.
Summary
I am super happy that I have finally dug into this MVC stuff! I really enjoyed learning what I have already learned, and definitely plan to keep learning more! I don't know if I will ever need it, as most things that I build are web "sites", not really web "apps", meaning, mostly just content, not a lot of interaction.
And while I know I could use React for a content-driven site, it also seems like the wrong tool for the job...
But I know that if I ever do need to do something "appy", I could crank out a React component or two and have a super fly add-on to any existing site!
Resources
Below is a very short list of resources that I found useful while getting to know React. I highly recommend you visit them all as well!
- React's current documentation
- React's beta documentation
- Tania Rascia's Getting Started with React article
- WebDevSimplified's Learn React In 30 Minutes video
- DevEd's React Tutorial For Beginners video
- Glitch's React Starter article & videos
- Scrimba's free Learn React course
- Scrimba's paid React Bootcamp course
Thanks a lot for following along, and please, if you think I missed anything or if you differ with my interpretation or understanding of something, let me know! My goal here is for everyone to read this and get to know this topic better! And you can help with that!
Happy reacting,
Atg
A great, lengthy “best practices” collection:
https://github.com/jsmapr1/react-best-practices