An open-source Facebook project.
React/ReactJS was invented to manipulate website DOM faster than Javascript Core, using a virtual DOM model.
React/ReactJS is a Javascript library.
React/ReactJS can run on the front or back end.
React Native was built after React, as a platform for building native apps using Javascript. (As opposed to using Java on Android and Objective-C on iPhone)
React Native is built on ReactJS.
To run a React Native app, you'll need XCode (Mac) or Android Studio (Android).
React Native does not use HTML to render the app; instead it uses native UI components.
Prerequisites: Javascript, HTML
Continuation of notes apply to React 16/17/18
React is a JS library for building UIs
A React UI is a composition of components
Create hierarchies of reusable components
Rendered output changes when the state changes
Reconciliation will only update the parts of the UI that need to be updated
When a component function is invoked, that is "rendering" - it generates the JSX which will be converted into HTML DOM elements.
When React updates what the browser is displaying, that is "reconciliation".
Babel is a Javascript compiler. It can convert markup and programming languages into Javascript.
ReactJS uses Babel to convert JSX into Javascript.
Using in-browser compiler, which will be slow for your clients:
<html>
<head>
<meta charset="UTF-8">
<!-- Load React. -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<!-- If using JSX -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
<script type='text/babel'>
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
</script>
Compile JSX to Javascript before serving pages to your clients.
1) Install Node.js on your computer.
2) In terminal: npm init -y
3) In terminal: npm install babel-cli@6 babel-preset-react-app@3
4) In terminal: npx babel --watch source --out-dir . --presets react-app/prod
Step (4) starts a "watcher" application.
Now when you create a file with JSX in it in the "source" directory, it will be automatically processed into a plain Javascript file, with the same file name, in the output directory.
JSX stands for Javascript XML. It is a language extension for Javascript.
JSX is an easy-to-read syntax that is compiled into "React.createElement(component, properties, ...children)" commands.
JSX expressions produce React elements.
The aim is to keep tightly coupled business logic and display logic together, while separating the concerns of different components.
So this:
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
Will be compiled into this:
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'Click Me'
)
You can write the "React.createElement" commands directly, if you prefer them.
[You can test JSX in this Babel compiler]
JSX does not use quotes (" or ') around HTML elements.
var element = <h1>Hello World!</h1>
Expressions inside curly braces { } will be interpreted as Javascript in the current context.
<div id="myDiv">Hello Someone</div>
<script type="text/babel">
var name = 'John Doe';
ReactDOM.render(
<h1>Hello {name}</h1>,
document.getElementById('myDiv')
);
</script>
Do not use quotes (" or ') around curly braces. If the result of the expression is a string, it will be stored correctly.
JSX uses camel-case attributes names.
JSX avoids using Javascript reserved words as attribute names.
HTML attribute "class" => JSX "className"
HTML attributes "tabindex" => JSX "tabIndex"
Capitalized tag names refer to React components.
Ex: "<Foo />" refers to an in-scope variable named "Foo".
It is safe to insert user-data directly into JSX, because React automatically escapes all special characters.
Next.js is a toolchain
it is a React server-side framework
it connects the React library (a javascript library) to what is needed for debugging and publishing an app
these toolchains are not required but are a huge time saver
Next.js is not based on Node.js, Next.js runs on top of Node.js
the root component of a Next.js app will be in App.js
How to use:
- install Node.js
- run "npx create-next-app@latest" to get a basic app initialized
- if you answer "No" to using the App Router, then you will be using Page Router in your App instead
if you use App Router, then all your components will be server components (rendered on the server) and will therefore not be able to manage state in the client's browser
You can use a little React in your Javascript, or a lot.
Loading ReactJS into webpage:
<!DOCTYPE html>
<html lang="en">
<!-- Load React API -->
<script src= "https://unpkg.com/react@16/umd/react.production.min.js"></script>
<!-- Load React DOM-->
<script src= "https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<!-- Load Babel Compiler -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<body>
<script type="text/babel">
// JSX Babel code goes here
</script>
</body>
</html>
For *.js pages:
import React from 'react';
"Like" Button example.
likeButton.html
<html>
<head>
<meta charset="UTF-8">
<!-- Load React. -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
</head>
<body>
<div id="like_button_container"></div>
<!-- Load our React component. -->
<script src="likeButton.js"></script>
</body>
</html>
The "likeButton.js" must be loaded at the end of the "body" element.
If you load it in the "head" element, before the "div" is defined, you'll get a "target container is not a dom element" error.
likeButton.js (contains React component "LikeButton")
'use strict';
const e = React.createElement;
class LikeButton extends React.Component
{
constructor(props)
{
super(props);
this.state = { liked: false };
}
render()
{
if (this.state.liked)
{
return 'You liked this.';
}
return e(
'button',
{ onClick: () => this.setState({ liked: true }) },
'Like'
);
}
}
const domContainer = document.querySelector('#like_button_container');
ReactDOM.render(e(LikeButton), domContainer);
For Windows:
[Setup React Native on Windows]
1) Install Node.js. This includes "npm".
[Download Node.js]
2) Install JDK (Java SE Development Kit)
[Download JDK]
3) Install Android Studio
[Download Android Studio]
4a) Add Java SDK path to Windows Environment Variables
- Add New
- Name: JAVA_HOME (this name is expected)
- Path: C:\Program Files\Java\jdk-11.0.2 (or whatever your path is)
4b) Add Android Studio path to Windows Environment Variables
- Add New
- Name: ANDROID_HOME (this name is expected)
- Path: C:\Program Files\Android\Android Studio (or whatever your path is)
5) Configure an Android Virtual Device
a) Open Android Studio
b) Start a new project (maybe not this? create react native project in step 6)
c) Create an Android Virtual Device: Tools menu > ADV Manager > Create
-- the ADV Manager option will not appear until the application is fully loaded
-- you'll need to create a virtual device profile for every platform your application will run on
d) Boot the ADV
-- click the "Launch" arrow
6) Command Prompt
a) Open command prompt
b) Navigate to project folder
c) Install React Native globally
npm install -g react-native-cli
d) Create new react native project
react-native init MyProject
This will create a folder "MyProject" containing all the files for a new react native application.
7) Configure build.gradle
Change "android/app/build.gradle" file to use the correct version number of Android Studio SDK
??????????
Continue from here:
http://www.ntu.edu.sg/home/ehchua/programming/android/android_howto.html
To see the Android SDK version you have installed:
- Open Android Studio
- Tools menu > Android SDK
- select Appearance & Behavior > System Settings > Android SDK
You'll see a list of available SDKs, and which are installed.
8) Start application
a) Navigate into project folder in Command Prompt
b) Run application
react-native run-android
Wait awhiiiiiiile for Gradle to start up.
c) Start the packager
react-native start
TODO: complete these steps to test that they work
React elements are returned by "React.createElement" statements.
React elements are plain Javascript objects.
React DOM handles keeping the HTML DOM up to date with the React elements.
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
//becomes
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
//becomes something like
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
React elements are immutable.
For each change to the DOM, you'll need to create a new React element and render it.
This update process is greatly simplified by using React components.
Render React elements to the HTML DOM.
Example:
<div id="id01">Hello World!</div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello React!</h1>,
document.getElementById('id01')
);
</script>
React DOM compares the React element(s) with the current state of the HTML DOM. The minimum of necessary changes are made to update the HTML DOM.
The idea is to specify how the UI should look at each moment in time, instead of focusing on the exact series of changes being made.
React components are independent, reusable, auto-updating pieces of a web application.
A component is an instruction for how the GUI should be rendered.
React components are structured as Javascript functions that accept properties and return React elements (JSX).
JSX syntax resembled HTML but it is javascript, and extension of javascript.
JSX must be transformed to Javascript with a tool like Babel.
example: one component
const Banner = () => <h1>This is a banner</h1>;
//the above JSX is translated to the below Javascript
const Banner = () => React.createElement('h1', null, 'This is a banner');
Each component must return JSX with a single parent element.
If you don't want, like, an extra div messing up your layout:
const B = () => {
<React.Fragment> //Fragment
<div></div>
<div></div>
<React.Fragment/>
};
const A = () => {
<> //Empty Node is shorthand for a Fragment
<div></div>
<div></div>
</>
};
example: component composition
const Greeting = () => (
<div> //invoking a built-in component
<Banner /> //invoking that previous component
<h2 className='highlight'>Greetings</h2>
</div>
);
example: the component is a function, it doesn't have to return immediately
const Component = () => {
var x = 5;
var jsx = <span>Hello World</span>;
return (
<div>
{x}
{jsx}
</div>
);
};
Javascript Expressions
var imgSrc = "string";
var divStyles = {
fontStyle: "italic",
fontSize: "x-large",
};
const MyComponent = () => {
<div className='container'> //"className" means "class" but class is a keyword in JS
<img src={imgSrc} /> //the {} here is a Javascript Expression within JSX
<div style={divStyles}>
</div>
<div style={{fontStyle:"italic"}}> //{{}} means an Inline Object inside the Javascript Expression
</div>
</div>
};
Always start component names with a capital letter.
PascalCase
JSX will interpret capitalized tag names as references to components.
The built-in components are camelCase, the first letter being lowercase.
Mounting is when a component's state is initialized in memory, the component is rendered, and the html is added to the browser DOM.
Unmounting is when the component's html is removed from the DOM and the component's memory is released.
Component state is lost.
Function components are the simplest examples.
Component functions should be pure functions - the same input always produces the same output.
function Welcome(props)
{
return <h1>Hello, {props.name}</h1>;
}
Usage:
//component
function Welcome(props)
{
return <h1>Hello, {props.name}</h1>;
}
//element references component
//all attributes are passed to "Welcome()" as "props"
const element = <Welcome name="Sara" />;
//render
ReactDOM.render(
element,
document.getElementById('root')
);
If your component returns "null" instead of a React element, nothing will be rendered for it. This can be used to hide components.
Pass arguments into a component:
const Component = (props) => {
<div>
{props.myprop}
</div>
};
<Component myprop="value" /> //passing a prop into a component
"props" are to be treated as read-only, always.
Consider "pure" functions, which do not change their inputs and always return the same output when given the same input.
All React components must be pure in respect to their "props".
const Component = (props) => {
<div className="header">
{props.children}
</div>
};
<Component>Hello World</Component> //the innerHTML is auto-passed as props.children
detail: this is destructuring "props" as it comes into the component function
const Component = ({children}) => {
<div className="header">
{children}
</div>
};
<Component>Hello World</Component> //the innerHTML is auto-passed as props.children
deeper destructuring:
var widget = {
name: 'steve',
age: 5,
};
const Component = ({widget: { name, age}}) => {
<div className="header">
{name}
</div>
};
<Component widget={widget} />
//or
const Component = ({{ name, age}}) => {
//or use spread to pass in the properties of widget as individual props
<Component {...widget} /> //but this can impact performance as unneeded props are passed in
or use spread to pass in the properties of widget as individual props
Best practice: use destructuring syntax for receiving props in a component, to ensure that the component cannot accidentally display data that was passed to it but it should not have:
//calling the component
const data = {
favColor: "red",
age: 55,
ssn: "000-000-0000",
};
return (<MyComponent {...data} />);
//declaring component
function MyComponent({favColor, age}) {
console.log(JSON.stringify(props));
return(<div></div>);
}
A hook is a function with a name starting "use".
It encapsulates complexity.
There are many built-in hooks.
First Rule Of Hooks: Hooks should only be called at the top level.
- do not call them conditionally, like inside an IF statement
- do not call them after an optional RETURN statement in a function
This is so they will always be called in the same order each time the component executes.
Second Rule Of Hooks: Hooks should only be called from inside function components.
- exception: a custom hook may call other hooks
hook structure
useCustomHook(() => {
//this code runs when the component renders
//having a return is optional
return () => {
//this code executes only when the component is leaving the screen
};
}, []); //dependency array
A null dependency array means the "when component renders" code runs every time the component renders.
An empty ([]) dependency array means the "when component renders" code is only run on the first render.
A filled dependency array means the "when component renders" code is run on the first render, and any time an element of that array is edited.
You can create custom hooks to separate concerns (such as fetching a list of data and displaying it)
//put custom hooks under a ./project/hooks/ directory
//hook functions start with "use" prefix
import { useState, useEffect } from "react";
export const useHouses = () => {
const [houses, setHouses] = useState([]);
useEffect(() => {
//fetch house data
//call setHouses(data)
}, []);
return { houses, setHouses };
};
//in the houses component
import useHouses from "../hooks/useHouses";
const HouseList = () => {
const { houses, setHouses } = useHouses();
//display houses
};
Hooks do not return JSX.
Hooks may call other hooks.
When a hook's state changes, any component that calls on that hook will re-render.
When a hook is called from multiple components, each component keeps a separate instance of the hook's state - they do not share data or affect each other.
Basic hooks: useState, useEffect, useContext
Additional hooks: useReducer, useCallback, useMemo, useRef, useImperativeHandle, useLayoutEffect, useDebugValue, useDeferredValue, useTransition, useId
Library hooks (for building libraries): useSyncExternalStore, useInserationEffect
useEffect cannot be called async, but it can call an async function
useEffect(() => {
async function myAsync() {
}
myAsync();
}, []);
State is data held internally and privately by a component.
this.state
Every time the state of a component changes, the component function is run again. The "useState" lines will be skipped on re-runs.
The reconciliation step means only the parts of the JSX that changes will be updated in the browser.
Register variables within a component, and when they are edited the component will refresh.
var integers = [1,2,3];
const Component = () => {
const [_integers, setIntegers] = useState(integers); //hook useState returns the internal object and a function for updating it
const addInteger = () => {
setIntegers([..._integers, (_integers.Length+1)]); //do NOT edit "_integers" directly
};
return (
<>
{_integers.map((i) => "Number "+i+"<br/>")}
<button onClick={addInteger}>Add</button>
</>
);
};
Call "setIntegers" to tell React to re-render the component.
You can use "setState" multiple times within a component.
For reference-type variables (like arrays), React will refresh when the reference is changed. For primitive-types (like int), React will refresh when the value is changed. In both cases, the refresh will only occur if you use the variable somewhere in your JSX.
For rapid updates to primitive state values:
const [counter, setCounter] = useState(0);
setCounter(current => counter + 1);
React will batch the calls to setCounter. Using "current" will ensure that each call uses the updated value from the previous call.
Reserve this object for state information about your component.
You can only assign directly to "this.state" in the constructor.
Changing "this.state" directly will not cause the element to be re-rendered.
Use "this.SetState(object)" instead.
Reset the entire state of the component:
this.setState(object);
"setState" will merge the provided object into the current state, so you can update single variables at a time without losing the others.
Updates to "this.props" and "this.state" are managed by React asynchronously, and may be paused or batched.
If the "next" state values are calculated based on the "current" values, you must use this:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
useReducer is similar to useState
const [state, dispatch] = useReducer(reducer, initialArg, init);
state: local component state managed by the reducer
dispatch: a function - the first param passed to dispatch will be the second param passed to reducer
reducer: function(state, action) returns a new state - should be pure but is not required
initialArg: starting value of the state
init: function() returns the starting state
example with useState
import { useState } from "react";
export default function demo() {
const [cnt, setCnt] = useState(10);
return (
<button onClick={() => setCnt(cnt + 1)}>
{cnt}
</button>
);
}
same functionality with useReducer
import { useReducer } from "react"'
function reducer(state, action) {
switch(action.type) {
case "increment": return state + action.incrementValue;
default: return action;
}
}
export default function demo() {
const [state, dispatch] = useReducer(reducer, 10);
return (
<button onClick={() => dispatch({
type: "increment",
incrementValue: 1,
})}>
{state}
</button>
);
}
example of updating a complicated state
function reducer(state, action) {
switch(action.type) {
case "loaded": return { ...state, loading: false, speakers: action.speakers };
case "setLoadingStatus": return { ...state, loading: true };
case "update":
const speakersUpdated = state.speakers.map((rec) =>
action.speaker.id === rec.id ? action.speaker : rec
);
return { ...state, speakers: speakersUpdated };
default: throw new Error(`case failure: action type {action.type}`);
}
}
memo-ize
to make a memo, which here means to cache a value
a type of hook
With pure functions, you can cache input-to-output pairs to improve performance.
Wrap the component in React.Memo
import { memo } from "react";
const Component = (props) => {
return (
<div>{props.value}</div>
);
};
export const ComponentMemo = memo(Component);
The caching will now be handled automatically.
Do not wrap all components in memo - memory IS limited, and React is already pretty time-efficient.
Use profiling tools to determine where using memo is actually faster.
Look for component functions that
- are pure functions
- return non-trivial JSX
- are often called for the same inputs
memoize a long-running calculation
const result = useMemo(() => {
return longRunningCalculation(data);
}, [data]);
advanced memo: pass in the second argument that determines if a re-render is needed
import { memo } from "react";
const memoizedComponent = memo(
originalComponent,
(previousProps, newProps) => {
if(previousProps.criticalValue != newProps.criticalValue) return true;
return false;
}
);
This is one way to handle functions being passed to a child component. A new version of the functions will be passed in each time the parent re-renders, but maybe that doesn't matter to the child.
In React, side effects are just called "effects".
Side effects are when functions are not pure - they rely on or change the state outside of themselves. They do not give consistent outputs based on their inputs.
Ex: calling an API (could throw an error), using time outs, using the browser's document or window
Effect hook: this part of a component function will be run AFTER the pure part of the function has run and the browser has been reconciled.
useEffect(() => {
//do a not-pure function
});
import React, {useEffect, useState } from "react";
const Component = () => {
const [integers, setIntegers] = useState([]);
useEffect(() => {
//useEffect cannot be marked async itself, so wrap await calls in local async functions
const fetchData = async () => {
const response = await fetch("api/data");
const data = await response.json();
setIntegers(data);
};
fetchData();
});
return (
<>
{integers.map(i => "Number "+i+"<br/>")}
</>
);
};
So on the first invocation, this component will render an empty node, then the effect will run, the effect will update the state of the component, that will trigger the component to run again and render the data.
Does the effect run again the second time? Yes. Yes, it does, every time the component is invoked.
To avoid that, you need extra logic to tell the effect to not run every time.
const [counter, setCounter] = useState(0);
useEffect(() => {
document.title = counter; //just an example for illustration
}, [counter]); //this is a Dependency Array, the function will only run when a value in this array is edited
Specify an empty dependency array to mark a useEffect function as "run once only"
useEffect(() => {
//load data from api
}, []);
You can have multiple useEffect calls in one component, and they can have different dependency arrays.
Return a "cleanup" function from useEffect
The cleanup will be run (1) when the component is unmounted (removed from the UI), but also (B) before each time the useEffect is invoked.
useEffect(() => {
//subscribe to an event stream
return () => {
//cleanup
//unsubscribe from event stream
};
}, []);
Ref hook persists values between re-render calls without triggering a re-render.
So it is like state, but does not cause re-rendering.
const Component = () => {
const counter = useRef(0); //count number of renderings without causing more to happen
counter.current++;
};
Can be used to get a direct reference to a DOM element
so you can operate on it with plain javascript
but this only lasts until a re-render that replaces the DOM element
import { useRef } from "react";
export default function demo() {
const imgRef = useRef();
return (
<img src='image.png' ref={imgRef}
style={{filter: "grayscale(100%)"}}
onMouseOver={() => { imgRef.current.style.filter = "grayscale(0%)"; }}
/>
);
}
Components can refer to other components.
It is common to start an entire web application from one "App" component.
function Welcome(props)
{
return <h1>Hello, {props.name}</h1>;
}
function App()
{
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Design components the way you would an object oriented model. Each component should be reusable and do just its own job.
A higher level component accepts a component argument instead of a props argument.
It will return an enhanced version of the component that was passed in.
It is used to pass extra values into the argument-component as props.
import { useState } from 'react';
const App = ({darkTheme, toggleTheme}) => {
return (
<div data-theme={darkTheme ? "dark" : "light"} onClick={() => toggleTheme()}>
Toggle Dark Theme
</div>
);
};
const withTheme = (Component) => {
function Func(props) {
const [darkTheme, setDarkTheme] = useState(true);
return (
<Component {...props}
darkTheme={darkTheme}
toggleTheme={() => setDarkTheme(!darkTheme)}
/>
);
}
return Func; //returns a named function rather than an anonymous one to keep linter from complaining
};
export default withTheme(App);
Every component in the app that needs to know the theme can be called as "withTheme(Component)".
(Code sample above is not a complete solution)
No component should care if another component is stateful or stateless.
No component should care if another component is a function or class component.
A component can pass its state "down" as props to a child component.
Data is never passed "up" from child to parent component.
aka Top down data flow
aka Unidirectional data flow
Modules are not specific to React, they are a part of Javascript.
Modules enables reuse of code and enclosure of privates.
It is good practice to have only one component per module file.
(According to the video I'm watching, at work we've got lots of things being exported from each module)
ex module file: DoSomething.js
const doSomething = () => {
//...
};
export { doSomething };
Other module files can now import "doSomething".
Anything that a variable can hold can be exported.
export {
one,
two,
three
};
The module name is its filename without an extension.
import { doSomething } from "./DoSomething";
doSomething();
Each module can export one default item.
//file A
export default doSomething;
//file B
Import do from "./A"; //aliases the default export
do();
React events are named in camelCase instead of lowercase.
Comparison:
//HTML passes a string
<button onclick="activateLasers()">
Activate Lasers
</button>
//JSX passes a function
<button onClick={activateLasers}>
Activate Lasers
</button>
You can add all event listeners to elements when they initialized, instead of calling "addEventListener" after the element is created.
In Javascript, you can return "false" from an event handler to cancel default behavior.
In React, you must call "event.preventDefault()" to do this.
function ActionLink()
{
function constructor(props)
{
super(props);
// This binding is necessary to make "this" work in the callback
this.handleClick = this.handleClick.bind(this);
}
//your event handlers will usually be methods in the component class
function handleClick(e)
{
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={this.handleClick}>
Click me
</a>
);
}
React passes synthetic events to your event handlers. They are cross-browser compatible.
To pass additional arguments to an event handler:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
//OR
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
In both examples, the event handler will expect arguments (id, event).Example: only display the "You have X unread messages" text if there are unread messages.
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
If the condition is "true", the expression will be evaluated.
If the condition is "false", the expression will be skipped.
This is based on core Javascript syntax.
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
Generate a list of React elements.
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number, index) =>
<li key={number.toString()}>{number}</li>
);
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
Or
return (
<ul>
{numbers.map((number, index) =>
<li key={number.toString()}>{number}</li>
)}
</ul>
);
The "key" attribute is added to help React identify DOM elements that have been edited.
Keys should be unique within their list (among their siblings).
Usually you'll use data ids as your keys.
Use the list index if nothing else is available. (Not recommended if elements will change order in the list.)
If you create a component for an element that had a key on it, the key should be moved to the component tag.
function ListItem(props)
{
//no key here
return <li>{props.value}</li>;
}
function NumberList(props)
{
const numbers = props.numbers;
const listItems = numbers.map((number) =>
//key is here, but is not passed in "props" to the component
<ListItem key={number.toString()} value={number} />
);
return (
<ul>{listItems}</ul>
);
}
The key needs to be set where the array is created by map, not at a lower level.
Using template literal for if/else
<td className={`${house.price >= 500000 ? "highlight" : ""}`}>
{house.price}
</td>
Using lazy evaluation for if/else
The TD will only be rendered if "price" is "truthy" (not 0 or undefined)
{house.price && (
<td>{house.price}</td>
)}
Conditionally Rendering Components
const [selectedHouse, setSelectedHouse] = useState();
return (
{selectedHouse ? <House house={selectedHouse} /> : <HouseList />}
);
If a house is selected, display its details. Otherwise show the whole list of houses.
This also needs: a way for HouseList to update the higher-level setSelectedHouse() hook:
Shared State
const [selectedHouse, setSelectedHouse] = useState();
return (
{selectedHouse ?
<House house={selectedHouse} /> :
<HouseList selectHouse={setSelectedHouse}/>} //pass the setter into the lower-level module, it can be called from there
);
Conditionally including one element in an array:
<MyComponent listItems=[
{
value: 1,
},
...((condition) ? [] : [{
value: 2,
}]),
{
value: 3,
},
] />
//in HouseList, click a row to select it
const HouseRow = ({house, selectHouse}) => {
return (
<tr onClick={() => selectedHouse(oldHouse => house)}>
//...display house row
</tr>
);
};
If it is too risky to let the child component run the setter, use a wrapper function:
//top level
const [selectedHouse, setSelectedHouse] = useState();
const setSelectedHouseWrapper = (house) => { //pass this to child components
//can put checks here, verifying "house" is the correct data type
setSelectedHouse(house);
};
This does create a new instance of the wrapper function when the component is re-rendered, and if that wrapper instance is passed into a memoized component, then it can cause more accidental re-rendering. Similar issue if the wrapper ends up in a dependency array.
Can avoid these issues by using hook useCallback instead. This will preserve the same function reference across re-renders.
This does add overhead, so don't use it everywhere.
const setHouseWrapper = useCallback((house) => {
setSelectedHouse(house);
}, []); //this here is why is only creates one new function on the first render, and not again on each re-render
You don't have to pass setters through props to share state (prop drilling: passing props down and down and down through components, not even used by the intermediate components)
you can also use Context
Each context is available in any descendant component of the component that created it
const context = React.createContext("default");
return (
<context.Provider value="x"> //"x" is passed, if no value is defined then that "default" is passed
//children components
</context.Provider>
);
With this, both direct child components and all lower descendent components can read the Context.
const value = useContext(context);
//or
<context.Consumer>
{ value => /*render with the provided value*/ }
</context.Consumer>
When the context value changes, all components that use it will re-render.
Example usage
//this is exported so any other component can import it
export const navigationContext = React.createContext(defaultNavValues.start);
const Component = () => {
const navigate = useCallback(
(navTo) => setNav({ current: navTo, navigate }),
[]
);
const [nav, setNav] = useState({ current: defaultNavValues.start, navigate });
return (
<navigationContext.Provider value={nav}> //when nav state is changed, it will trigger re-renders
//children
</navigationContext.Provider>
);
};
//in child component
const ChildComponent = () => {
const { navigate } = useContext(navigatorContext); //can be used to change the state
};
So we're still using State to share data and trigger re-renders.
The Context is just making it so we don't have to explicitly pass the State variables into every child component.
HTML elements "input", "textarea", and "select" usually maintain their own state.
But React expects all state to maintained internally.
Controlled components are these HTML elements where React controls the state.
const submit = (e) => {
e.preventDefault();
//submit form data to api
};
return (
<form onSubmit={submit}>
</form>
);
const [firstName, setFirstName] = useState("Susan");
return (
<input type='text' value={firstName}
onChange={(e) => setFirstName(e.target.value)}/>
);
more complicated example
const [person, setPerson] = useState({ firstName: 'Susan', lastName: 'Smith' });
const change = ((e) => setPerson({...person, [e.target.name]: e.target.value });
return (
<form>
<input type='text' name='firstName' value={person.firstName} onChange={change} />
<input type='text' name='lastName' value={person.lastName} onChange={change} />
</form>
);
Computed Property Name: this part "[e.target.name]: e.target.value"
React's textarea component makes textarea similar to input.
<textarea value={val} onChange={change} />
React's select component makes select similar to input.
<select value={val} onChange={change} >
<option value='option 1'>1</option>
</select>
The form data is handled by the DOM as usual.
Example:
class NameForm extends React.Component
{
constructor(props)
{
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event)
{
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render()
{
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" defaultValue="Bob" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
File inputs must be uncontrolled components because the value can only be set by the user, not a program.
Tag "<input type='file'>".
Example:
class FileInput extends React.Component
{
constructor(props)
{
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef();
}
handleSubmit(event)
{
event.preventDefault();
alert(
`Selected file - ${this.fileInput.current.files[0].name}`
);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input type="file" ref={this.fileInput} />
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}
ReactDOM.render(
<FileInput />,
document.getElementById('root')
);
When more than one component needs to use the same state, lift the state to the nearest shared ancestor.
Recommended so you have a single place each piece of data is stored, which will greatly help debugging and changing features.
Example:
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
function toCelsius(fahrenheit)
{
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius)
{
return (celsius * 9 / 5) + 32;
}
function tryConvert(temperature, convert)
{
const input = parseFloat(temperature);
if (Number.isNaN(input))
{
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
class Calculator extends React.Component
{
constructor(props)
{
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature)
{
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature)
{
this.setState({scale: 'f', temperature});
}
render()
{
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
class TemperatureInput extends React.Component
{
constructor(props)
{
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e)
{
this.props.onTemperatureChange(e.target.value);
}
render()
{
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
It is recommended to use composition over inheritance to reuse code between components.
For container-type components (don't know exactly what their children will be) it is recommended to use the special "children" prop. Other components can easily pass children to the container with nested JSX.
//container
function FancyBorder(props)
{
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
//another component passes "h1" and "p" into "props.children"
function WelcomeDialog()
{
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
If you need to specify more than one collection of children, you'll need to roll your own solution.
function SplitPane(props)
{
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App()
{
return (
<SplitPane
left={<Contacts />}
right={<Chat />}
/>
);
}
Say you have a "Dialog" and a more specific "WelcomeDialog".
In object oriented programming, object "WelcomeDialog" would inherit from object "Dialog".
In React, "WelcomeDialog" will render "Dialog", with whatever specific settings it needs.
So it's actually like methods in any paradigm: the specific method calls on the general method.
You should not need any React components to inherit from each other.
function WelcomeDialog()
{
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />
);
}
function Dialog(props)
{
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
Hooks let you use state without writing a class.
Hooks let you keep related code together (such as Start/Stop actions) and keep unrelated code separate (such as a StatusChange and an ActionCountChange).
UseEffect example that runs after each Render.
import * as React from 'react';
function example() {
const initialCount = 0;
const [count, setCount] = React.useState(initialCount);
React.useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<button onClick={() => setCount(count + 1)}>
Click Me
</button>
</div>
);
}
UseEffect example that runs after Renders when one of the specified variables had changed value:
React.useEffect(() => {
doSomething();
}, [count, anotherCount]);
Pass in an empty array [] to run the UseEffect exactly once.
UseEffect does not block the browser from updating the screen.
Some operations do not require cleanup: anything that does not alter the DOM.
To specify a cleanup function, return it:
React.useEffect(() => {
startAThing();
return function cleanup() {
stopAThing();
};
});
This cleanup is performed when the component unmounts.
This cleanup is run if the same useEffect is called again, to cleanup before the next one runs.
In C#, I'd often write builder functions for unit tests, which would instantiate a new object and fill it with default data. Each test case would set just the properties that mattered for that test case.
React coding has a strong preference for pure functions, which includes not editing objects that are passed into the function. While this is not an exact match for that rule, it is similar. Anyway, this is the preferred pattern to use instead of a builder function.
interface MyProps {
someText: string | null;
someNumber: number;
};
//instantiate one constant object with default values
const defaultProps: MyProps = {
someText: '',
someNumber: 0,
};
//in each test case
//create a new copy of it, overwriting selected properties
const testCaseProps: MyProps = {
...defaultProps,
someNumber: 999,
};
You can always throw "console.log('message');" lines into your code.
Install and open browser React Developer Tools
this will add tabs Components and Profiler to the normal browser developer tools
in Components:
- click on a Component to see what props were passed into
- the props are also editable
- click on a Component's "rendered by" to step up to its ancestor Component
- click on the "{ }" button to view the source code of the Component
- click the eye-icon to see the DOM elements rendered by the Component
- click the bug-icon to open a console pane focused on the Component
- click the gear-icon to filter the Components tree
in Profiler:
- on Flamegraph, click "reload and start proofing", then click "stop" once the page has re-rendered
- this will display how long it took each nested component to render, and how many times they rendered
- to really dig into Flamegraph, click the gear-icon > "record why each component rendered while profiling" (this will slow down your app)
useDebugValue can be used in custom hooks
import { useDebugValue } from "react";
function useMyHook() {
const [var1, setVar1] = useState(0);
useDebugValue(`var1:${var1}`);
return { var1, setVar1 };
}
The output of useDebugValue will be displayed in the browser React Dev Tools when the component using this hook is selected.
advanced option
import { useDebugValue } from "react";
function useMyHook() {
const [var1, setVar1] = useState(0);
useDebugValue(`${var1}`, (value) => {
if(var1 > 10) return "XYZ";
if(var1 > 50) return "ABC";
return "DEF";
});
return { var1, setVar1 };
}
If you need more complex display logic than the single-parameter useDebugValue allows
then you can give a function as the second parameter.
The input argument of the function will be the result of the first parameter.
? might need to copy the ErrorBoundary code into your app yourself ?
import React from "react";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
//for logging error to a reporting service
logErrorToMyService(error, errorInfo);
}
render() {
if(this.state.hasError) {
if(this.props.errorUI) {
//custom error message
return this.props.errorUI;
}
// default error message
return <h1>Error Occurred</h1>;
}
//render the normal nested component
return this.props.children;
}
}
const Component = (props) => {
return (
<ErrorBoundary
errorUI={<div>{props.id} Errored</div>}>
<InnerComponent {...props} />
</ErrorBoundary>
);
};
The error boundary will catch errors/exceptions and will render an error message instead of crashing the whole app.
Only optimize when you witness an efficiency problem. React is efficient and computers are fast, so most code will work just fine even if it isn't optimized.
Over-rendering is when a component re-renders a lot when it does not need to.
Most common solution is useMemo (see above).
This can solve the issue: child component re-rendered only because the parent re-rendered. No props passed to the child changed.
example use case:
- you have a search input field and as the user types, you update the filtering of a list of items based on the search text
- if the list to filter is large, re-rendering it many times rapidly could slow down the app
useDeferredValue can tell the list of items to filter/refresh less often, so the UI remains responsive.
import { useDeferredValue, useState } from "react";
const App = () => {
const [searchText, setSearchText] = useState("");
const searchTextDeferred = userDeferredValue(searchText);
return (
<div>
//skip over display of search input field
<ItemList searchText={deferredSearchText} />
</div>
);
};
similar to useDeferredValue
it tells React that some components are a higher or lower priority when re-rendering
function App() {
const [searchText, setSearchText] = useState("");
const [searchTextSlow, setSearchTextSlow] = useState("");
const [isPending, startTransition] = useTransition();
return (
<div>
<input value={searchText} onChange={(e) =>{
setSearchText(e.target.value);
startTransition(() => setSearchTextSlow(e.target.value));
}
} />
<DisplayList searchText={searchTextSlow} />
</div>
);
}
"isPending" boolean is updated when all the state updates run in this component have finished
"startTransition" marks state function setters as low priority
In this example, the search input field gets immediate updates while the filtered list gets eventual updates.