React: Lifecycle
Can be loosely divided into three primary stages:
- Before a Component is mounted – constructor, state initialization, some hooks
- When a component is mounted – initial processing logic on
propsintostate(say in therender()method or call itself),componentDidMount()logic to fetch data say, hooks and side-effects - After a Component is mounted – rerendering/updating, prep for unmounting, responding to user inputs
Re-rendering included as needed based on the render operation called. (Consult the detailed Component API reference: https://reactjs.org/docs/react-component.html.) A more specific breakdown:
Mounting – key methods:
constructor()render()componentDidMount()
Updating – key methods, rerendering:
shouldComponentUpdate()render()componentDidUpdate()setState()forceUpdate()
Unmounting – key methods:
componentWillUnmount()
Resources and Links
Code samples:
React: Virtual and Shadow DOM
The Shadow DOM is now part of JavaScript's Web APIs:
A Virtual DOM is sometimes used in parallel with or on top of the common Shadow DOM:
React used to support a React-specific Virtual DOM as an additional React feauture:
- It now appears to be deprecated (in newer versions).
- https://legacy.reactjs.org/docs/faq-internals.html
- https://www.geeksforgeeks.org/reactjs-virtual-dom/
Vue and Angular support using the Shadow DOM:
- https://dev.to/zokizuan/angular-adventure-deep-dive-into-angulars-view-encapsulation-ml
- https://www.npmjs.com/package/vue-shadow-dom
- https://vuejs.org/guide/extras/web-components
Resources and Links
- https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM
- https://en.wikipedia.org/wiki/Virtual_DOM
- https://vuejs.org/guide/extras/rendering-mechanism
- https://legacy.reactjs.org/docs/faq-internals.html
- https://www.geeksforgeeks.org/reactjs-virtual-dom/
- https://dev.to/zokizuan/angular-adventure-deep-dive-into-angulars-view-encapsulation-ml
- https://www.npmjs.com/package/vue-shadow-dom
- https://vuejs.org/guide/extras/web-components
React: State, Props
- Props - immutable, settings, configuration, initialized values, etc. that are passed one-way (unidirectionally) from a wrapping parent element into a child element.
- State - the mutable, local Component state.
Props
Consider the following example. A component CustomFooter has several child Components namely CustomLink.
Values are supplied for url and label which are passed from CustomFooter into each CustomLink as Props.
// CustomFooter
import React from 'react'
import CustomLink from '../CustomLink'
import './CustomFooter.css'
export default () =>
<footer>
<ul>
<li><CustomLink url={'https://www.linkedin.com/in/adamintaegerard/'} label={'LinkedIn'}/></li>
<li><CustomLink url={'https://thoughtscript.io/landing.html'} label={'Thoughtscript.io'}/></li>
</ul>
</footer>
CustomLink then accesses the supplied Props through destructuring.
It then uses the supplied values for url and label to populate the href and text values for the rendered Anchor tag:
// CustomLink
import React from 'react'
export default ({url, label}) => <a href={url} rel="nofollow noopener noreferrer" target="_blank">{label}</a>
State
Consider the following example:
AccountSummariesinitializes itsstateObject within theconstructorcall.- A token is made via
makeToken()to authenticate against the endpointBUDGET_HELPER_API_URL. - An
asyncHTTP GET Request is made against the endpointBUDGET_HELPER_API_URL. - The Response Object is parsed and checked for a valid Status Code.
- The resultant data is then set into State, updating the Component
stateObject. - Setting or modifying State typically involves the Component re-rendering.
- Values in State are destructured
const { accounts, authorized } = this.stateand made available during Render.
import React from 'react'
import './AccountSummaries.css'
import { asyncGet } from '../../../Helpers/Xhr/Get'
import { BUDGET_HELPER_API_URL } from '../../../Constants'
import { makeToken, parseJson } from '../../../Helpers/Generic'
export class AccountSummaries extends React.Component {
constructor(props) {
super(props)
this.state = {
accounts: [],
authorized: true,
...this.props
}
}
componentDidMount() {
try {
asyncGet(`${BUDGET_HELPER_API_URL}accounts?auth=${makeToken()}`).then(accounts => {
if (JSON.parse(accounts).status === 200) {
this.setState({
accounts: parseJson(accounts),
authorized: true
})
})
} else {
this.setState({
authorized: false
})
}
})
} catch (ex) {
console.log(ex)
}
}
render() {
const { accounts, authorized } = this.state
}
}
Resources and Links
Code samples:
React: Hooks
Comparisons
- State :
- Holds
statein a single Component. - Doesn't persist data between Component Mounts.
- Persists data between Rerenders, Renders.
- Mutable.
- Can be used as Hook but can be used in fully-qualified Class syntax.
- Holds
- Reducer:
- Can be reused across Components.
- Doesn't persist data between Component Mounts.
- Persists data between Rerenders, Renders.
- Mutable.
- Must be a Hook.
- Context:
- Simplifies Prop-Drilling and allows top-level
stateto be shared with deeply nested child Components. - Persists and shares data between Rerenders, Renders.
- Mutable.
- Must be a Hook.
- Simplifies Prop-Drilling and allows top-level
- Web Storage API (
localStorage,sessionStorage) or Client-Side Database (lowdb, IndexedDB API):- Persists data between Rerenders, Renders, and Component Mounts / Component Unmounting.
- Can be subscribed to using the Hook
useSyncExternalStore.
State
One can now set State within a formerly Stateless Functional Component.
import React, { useState } from 'react'
export function App(props) {
const [stateVar, setStateVar] = useState(0)
return (
<div>
<button onClick={() => {
setStateVar(stateVar + 1)
console.log(stateVar); //0 1 2 3 ...
}}><code>stateVar</code> Counter</button>
</div>
);
}
Note that
stateVarmust be initializated inuseState.
Side Effects
Side Effects are run after a Components renders (hence, side-effect).
import React, { useEffect, useRef } from 'react'
export function App(props) {
const myRef = useRef(null)
useEffect(() => {
myRef.current.style.color = "Red"
myRef.current.className = "Example"
console.log("side-effect") // side-effect
console.log(myRef.current.className) // Example
})
return (
<div>
<h1 ref={myRef}>My Header</h1>
</div>
)
}
Useful to make succinct changes within Stateless Functional Components ("dummy components") post-render using terse/less verbose syntax (no Class constructor initialization, componentDidMount, etc.).
Reducers
Reducers are now supported out of the box and don't have to be associated with an underlying State Provider.
React continue to use this nomenclature to draw comparisons to the prior Map-Reduce naming convention (e.g. - converging sources to a single Reducer).
export const exampleStateInitialization = {
id: 0,
text: 'Hi!'
}
export function exampleReducer(state, action) {
switch (action.type) {
case 'action_type_a': {
return {
...state,
id: action.id,
text: action.text,
};
}
case 'action_type_b': {
return {
...state,
text: action.text,
};
}
case 'action_type_b': {
return {
...state,
text: 'LOREM_IPSUM',
};
}
default: {
throw Error('Unknown action type: ' + action.type);
}
}
}
import React, { useReducer } from 'react'
import { exampleStateInitialization, exampleReducer } from './exampleReducer'
export function App(props) {
// Define a Dispatcher function name to call the Reducer with a specific Event.
const [exampleReducerState, exampleReducerDispatch] = useReducer(exampleReducer, exampleStateInitialization)
return (
<div>
<h2 onClick = {(e) => {
exampleReducerDispatch({
id: 1,
type: "action_type_b",
text: "event_text"
})
}}>My Button Header</h2>
</div>
)
}
Context
import { createContext } from 'react'
// Create and export
export const ExampleContext = createContext("Me, Myself, and I")
import React, { createContext } from 'react'
import { ExampleComponent } from './ExampleComponent'
import { ExampleContext } from './ExampleContext'
// Import and wrap elments
export App(props) =>
<div>
<ExampleContext.Provider value={ "You and only you" }>
<ExampleComponent />
</ExampleContext.Provider>
</div>
import React, { useContext } from 'react'
import { ExampleContext } from './ExampleContext'
export function ExampleComponent(props) {
// Import and use the context without passing specific Props everywhere and in-between!
const exampleContextVal = useContext(ExampleContext)
return (
<div>
<h1>{ exampleContextVal }</h1>
</div>
)
}
Both updating a React context value and making a React context updateable (from within a sub-Component) involves associating State with the Provider value:
export function App(props) {
const [example, setExample] = useState("Me, Myself, and I")
return (
// Associate the state field here
<ExampleContext.Provider value={example}>
<Button onClick={() => { setExample("You and only you") }}>
I'm Button Text
</Button>
</ExampleContext.Provider>
)
}
Resources and Links
- https://reactjs.org/docs/hooks-intro.html
- https://reactjs.org/docs/hooks-effect.html
- https://react.dev/learn/extracting-state-logic-into-a-reducer
- https://react.dev/learn/passing-data-deeply-with-context
- https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
Code samples:
React: Refs
There are two primary ways to create refs (a reference to a DOM Element that's accessible within a React Component):
React.createRef()- primarily used when initializing refs within aReact.ComponentClass constructor:import React from 'react' export default class App extends React.Component { constructor(props) { super(props); this.header = React.createRef(); } render() { console.log(this.header) // current: <h1>My Header</h1> return ( <div className="wrapper"> <h1 ref={this.header} onClick={() => { this.header.current.className = 'active' this.header.current.innerText = "My New Header" this.header.current.style.color = "Red" console.log(this.header) // current: <h1 class="active" style="color: red;">My New Header</h1> }}>My Header</h1> </div> ) } }const myRef = useRef(null);- useful to both access a DOM Element that's accessible within a React Component with an initialized value while keeping the value around.Use with Stateless Functional Components ("dummy components").
import React, {useRef} from 'react'; export default () => { const myRef = useRef(null) console.log(myRef) // current: <h1>My Header</h1> return ( <div> <h1 ref={myRef} onClick = {(e) => { myRef.current.innerHTML = "My New Header" myRef.current.style.color = "Red" console.log(myRef) // current: <h1 style="color: red;">My New Header</h1> }}>My Header</h1> </div> ) }
Current
- Most refs will expose their attributes after the Component is rendered (e.g. - in a Side Effect or
onClick). - Use
currentto access the current Element State.
Resources and Links
Code samples:
React: Parcel
Build-tool and configuration notes.
npm run build-parcel-prod
npm run build-parcel
cd dist
npx serve
One gotcha: parcel-bundler/parcel#7636. If you add to package.json:
"engines": {
"node": "=16.17.0"
}
You'll get: @parcel/packager-js: External modules are not supported when building for browser. Remove the engines field.
By default, NPX and parcel will serve from: http://localhost:1234/
React: Redux
Overview
Redux provides asynchronous, multi-component, horizontal, State Management for complex single page React apps.
- Actions - defines the available operations on state storage.
- Container Component - binds Actions to Props. Call the bound Actions within the component to modify the Reducer state.
- Reducer - state storage.
Example
Actions:
'use strict'
/**
* Default actions for interacting with various Redux stores.
*
* @Author - Adam InTae Gerard - https://www.linkedin.com/in/adamintaegerard/
*/
export const UNSAFE_SAVE = 'UNSAFE_SAVE'
export const REMOVE = 'REMOVE'
export const GET = 'GET'
export const CLEAR = 'CLEAR'
export const SAFE_SAVE = 'SAFE_SAVE'
//Use for public info containing no sensitive information
export const unsafeSave = v => {
return {type: UNSAFE_SAVE, v}
}
//Use for secure or private info
export const safeSave = v => {
return {type: SAFE_SAVE, v}
}
export const remove = v => {
return {type: REMOVE, v}
}
export const get = v => {
return {type: GET, v}
}
export const clear = v => {
return {type: CLEAR, v}
}
Stateful Container Component:
'use strict'
/**
* Page Container.
*
* @Author - Adam InTae Gerard - https://www.linkedin.com/in/adamintaegerard/
*/
import { connect } from 'react-redux'
import { Page } from './Page'
import { clear, get, remove, safeSave } from '../../../Redux/Shared/DefaultActions'
const mapStateToProps = state => {
return {
...state
}
}, mapDispatchToProps = dispatch => {
return {
save: (key, s) => {
dispatch(safeSave({data: s, index: key}))
},
remove: key => {
dispatch(remove({index: key}))
},
clear: () => {
dispatch(clear())
},
get: key => {
dispatch(get({index: key}))
}
}
}
export const PageContainer = connect(
mapStateToProps,
mapDispatchToProps
)(Page)
Reducer:
'use strict'
/**
* Encapsulated state storage Reducer.
*
* @Author - Adam InTae Gerard - https://www.linkedin.com/in/adamintaegerard/
*/
import { CLEAR, GET, REMOVE, SAFE_SAVE } from '../Shared/DefaultActions'
let encapsulatedStateObj = {}
const set = (index, data) => {
encapsulatedStateObj[index] = data
return Object.assign({}, encapsulatedStateObj)
}, remove = index => {
delete encapsulatedStateObj[index]
return Object.assign({}, encapsulatedStateObj)
}, clear = () => {
for (let i = 0; i < Object.getKeys(encapsulatedStateObj).length; i++) {
remove(Object.getKeys(encapsulatedStateObj)[i])
}
return Object.assign({}, encapsulatedStateObj)
}
/**
* Default supplied reducer.
*
* Caches into state.
*
* Partition by key in state.
*
* @param state
* @param action
* @returns {*}
* @constructor
*/
export const SafeStorage = (state = encapsulatedStateObj, action) => {
const type = action['type']
switch (type) {
case SAFE_SAVE:
return set(action['v']['index'], action['v']['data'])
case REMOVE:
return remove(action['v']['index'])
case GET:
return Object.assign({}, encapsulatedStateObj[action['v']['index']])
case CLEAR:
return clear()
default:
return state
}
}
Resources and Links
Code samples: