React overview
Init react
Commend
npx create-react-app .
npx means you use this package but don’t download it.
or with redux
npx create-react-app my-app --template redux
life cycle

Class component lifecycle

Function component method
shouldComponentUpdate()
Run before render()
to check this component need to render or not.
shouldComponentUpdate(nextProps, nextStates){
return boolean;
}
PureComponent
can automatically do a shallow comparison to determine need to update or not
export default React.memo(<component name>)
only compare props but it works for functional component
getSnapshotBeforeUpdate
it is invoked right before the most recently rendered output is committed
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('snapshot');
console.log(prevProps, prevState);
return 1;
}
getSnapshotBeforeUpdate
must pair with componentDidUpdate()
Context API
Doc: https://reactjs.org/docs/context.html#reactcreatecontext
Use a context:
- React.createContext({defaultValue})
- import the context your create. Warp you root component which you want to use this context in <ContextName.provider ></ContextName.provider>
- component under root context component will access to this context
- static contextType = MyContext
- Warp component like
<MyContext.Consumer>
{(value) => /_ render something based on the context value _/}
</MyContext.Consumer>
All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. The propagation from Provider to its descendant consumers (including .contextType and useContext) is not subject to the
shouldComponentUpdate
method, so the consumer is updated even when an ancestor component skips an update.
Context API is not for solve all state sharing problem. Think like Context provider change(even prevent update by shouldComponentUpdate
) all consumer will re-render.
The defaultValue argument is only used when a component does not have a matching Provider above it in the tree. This can be helpful for testing components in isolation without wrapping them. Note: passing undefined as a Provider value does not cause consuming components to use defaultValue.
Ref
React.createRef()
API introduced in React 16.3. If you are using an earlier release of React, we recommend using callback refs instead.
There are a few good use cases for refs:
- Managing focus, text selection, or media playback.
- Triggering imperative animations.
- Integrating with third-party DOM libraries.
create ref
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
access ref
const node = this.myRef.current;
Adding a Ref to a Class Component
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// create a ref to store the textInput DOM element
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
this.textInput.current.focus();
}
render() {
// tell React that we want to associate the <input> ref
// with the `textInput` that we created in the constructor
return (
<div>
<input type="text" ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
this.textInput.current.focusTextInput();
}
render() {
return <CustomTextInput ref={this.textInput} />;
}
}
Adding a Ref to a Functional Component
By default, you may not use the ref attribute on function components because they don’t have instances:
If you want to allow people to take a ref to your function component, you can use forwardRef
(possibly in conjunction with useImperativeHandle
), or you can convert the component to a class.
useImperativeHandle + ForwardRef
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
}));
return <input ref={inputRef} />;
}
FancyInput = React.forwardRef(FancyInpacut);
In this example, a parent component that renders <FancyInput ref={inputRef} />
would be able to call inputRef.current.focus()
.
React Hooks
useState
const [state, setState] = useState(initialState);
useEffect
useEffect(() => {
<effect>;
return () => {
<cleanup>;
};
}, [<input>]);
useContext
Given an Example for useContext + useReducer
In ContextProvider.jsx
import React, { useReducer, useEffect, createContext } from 'react';
const initialState = {
ID: '',
};
export const store = createContext(initialState);
const { Provider } = store;
function ContextProvider({ children }) {
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'setID':
return { ...state, currentUserID: action.payload };
default:
throw new Error();
}
}, initialState);
return <Provider value={{ state, dispatch }}>{children}</Provider>;
}
export default ContextProvider;
In ContextConsumer.jsx
:
import React, { useContext, useEffect } from 'react';
import { store } from './ContextProvider.jsx';
const ContextConsumer = () => {
// get field that you pass in provider value
const { state, dispatch } = useContext(store);
useEffect(() => {
dispatch({ type: 'setID', payload: 'ID1' });
});
return <div> {state.ID} </div>;
};
export default ContextConsumer;
useContext(MyContext)
is equivalent to static contextType = MyContext
in a class, or to <MyContext.Consumer>
But You still need a <MyContext.Provider>
above in the tree to provide the value for this context.
useReducer
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + action.payload };
case 'decrement':
return { count: state.count - action.payload };
case 'clean':
return { count: 0 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({ type: 'clean' });
}, []);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'decrement', payload: 1 })}>
-
</button>
<button onClick={() => dispatch({ type: 'increment', payload: 1 })}>
+
</button>
</>
);
}
Unlike Redux
dispatch
function doesn’t need to add inuseEffect
oruseCallback
dependence list sincedispatch
always be a same function
setState by useReducer
Usually we set a state by useState. But if the state is simple boolean,useReducer
is the shortest way to create a state for toggle a boolean value
const [switch, toggleSwitch] = useReducer((state) => !state, false);
// switch is a boolean
// toggleSwitch is a function
<button onClick={()=>{toggleSwitch()}} value={switch} />
// or add one. if you want.
const [num, addOne] = useReducer((state) => state + 1, 0);
relation with useState,
Theoretically, useReducer is a more general useState hook.
// this is exactly same with useState.
const [state, setSate] = useReducer((state) => state, initVal);
That is whydispatch
always be same. (setState always same in useState)
useRef
useRef()
creates a plain JavaScript object. The only difference betweenuseRef()
and creating a{current: ...}
object yourself is that useRef will give you the same ref object on every render.
useLayoutEffect
The only different between useLayoutEffect
and useEffect
is that the useLayoutEffect
is synchronous. Just same as componentDidMount
and componentDidUpdate
.
- useLayoutEffect()
- render
- useEffect()
useImperativeHandle
useImperativeHandle
customizes the instance value that is exposed to parent components when using ref. It is rare to use.
This is use for handle using ref to access functional component.
React.lazy()
The
React.lazy
function lets you render a dynamic import as a regular component.
Component only be loaded when it will be rendered
import React, { Suspense } from 'react';
// These lazy imports should put the end of other import code
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
You can even wrap multiple lazy components with a single
Suspense
component.
Class Component vs Functional Component
Class Component lifecycle ==> Functional Component hooks
Class component | Functional component |
---|---|
state + setState | useState() |
componentDidMount() | useEffect() with a empty input list |
componentDidUpdate() | useEffect() with a input list contained which you want to change |
componentWillUnmount() | useEffect() with a return callback function |
shouldComponentUpdate() | export default React.memo( |
Logic extraction
Suppose we have two components A
and B
that B
is depends on A
, the common solution is wrapping B
in A
. However,
when the logic in A
need to reuse, we cannot copy and parse A
to everywhere. Extracting the logic is essential.
const A = () => {
// some logic will reuse
return (
<div>
// ... other component
<B_depends_on_A />
</div>
);
};
// render <A/>
const App = () => {
return <A />;
};
This Blog is an example to extract logic by Three different way: Check this Blog
High-Order Component
Concretely, a higher-order component is a function that takes a component and returns a new component.
const withA = (ComponentB) => {
return (props) => {
// some logic will reuse
return (
<div>
// ... other component
<ComponentB {...props} />
</div>
);
};
};
const BWithA = withA(<B />);
const App = () => {
return <BWithA args={...}>;
};
Render props
Concretely, a render prop is a function prop that a component uses to know what to render.
const A = ({ render }) => {
// some logic will reuse
const [state, setState] = useState(1);
return (
<div>
// ... other component
{render(state)}
</div>
);
};
const App = () => {
return <A render={(args) => <B args={args} />} />;
};
Customize Hooks
Concretely, A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
const useA = () => {
// some logic calculation
return [reuseLogicResult];
};
const App = () => {
const [reuseLogicRes] = useA()
return <B args={reuseLogicRes} />} />;
};
Common Pitfall
Performance difference (SnapShot vs Current value)
When React introduce hooks for functional component, closure problem will be brought in as well. This will cause different performance between class component and function component with same logic.
These two component have same logic:
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
export class ClassProfilePage extends React.Component {
showMessage = () => {
alert('Followed ' + this.props.user);
};
handleClick = () => {
setTimeout(this.showMessage, 3000);
};
render() {
return (
<button onClick={this.handleClick}>
Get Current value (class component)
</button>
);
}
}
export function FunctionProfilePage(props) {
const showMessage = () => {
alert('Followed ' + props.user);
};
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return (
<button onClick={handleClick}>Get Snapshot (functional component)</button>
);
}
function App() {
const [state, setState] = useState(1);
return (
<div className="App">
<button
onClick={() => {
setState((x) => x + x);
}}
>
double
</button>
<div>state:{state}</div>
{/* snapshot */}
<FunctionProfilePage user={state} />
{/* current value */}
<ClassProfilePage user={state} />
</div>
);
}
Play with this example: Link
Click Get
button first and then click double
. Class component will alert current value, however function component will alert snapshot.
This is not a bug. This is a common question in Javascript due to the fact that every time you passing same reference or not.
See another example: closure loop problem
In React, Because props
is immutable(assign a new obj when you want to change it) in functional component, each rendering has different props
. Every click will generate a new props and setTimeout()
display the value when you click. However, in Class component. After class generated, props
is associated with class itself. Every rendering in class component have same this.props
and this.props.user
change overtime. Therefore, setTimeout()
use same this.props
and access this.props.user
which is current value.
This flowing code simulate this problem
let props = { count: 10 };
const fnA = ({ count }) => {
click = setTimeout(() => {
console.log(count);
}, 1000);
click;
};
class fnB {
constructor(input) {
this.props = input;
}
click = setTimeout(() => {
console.log(this.props.count);
}, 1000);
}
fnA(props);
let res2 = new fnB(props);
res2.click;
props.count--;
props.count--;
props.count--;
Solution
Since we know different props display snapshot and same reference display current value.
To get snapshot in class component, just assign the data that will alert to a new variable. (give several different value)
To get current value in functional component, add a ref(react) / create a value outside the component and store value to it. (give a same reference)
Initial state from props
Ref: https://react.dev/learn/choosing-the-state-structure#don-t-mirror-props-in-state
It is NOT RECOMMEND that initialized state from props directly. Update props won’t be reflected in the state. Because initialize state only run once
Only use this pattern if you intentionally want to ignore prop updates.
Class Base:
constructor(props) {
super(props);
// Don't do this!
this.state = { color: props.color };
}
Functional Base:
// Don't do this!
function Message({ messageColor }) {
const [state, setState] = useState(messageColor);
}
function Message({ messageColor }) {
const color = messageColor;
}
If really want to convert props to status. It should use be:
Class Base:
constructor(props) {
super(props);
this.state = {};
}
componentDidMount(){
this.setState({color: props.color})
}
Functional Base:
const { color } = props;
const [state, setState] = useState(null);
useEffect(() => {
setState(color);
}, [color]);
Trick
inject props to children
For example, we have a Wrapper component:
<Wrapper>{children}</Wrapper>
if we want to inject a props to children. Could use cloneElement()
to achieve.
const [isOpen, setIsOpen] = useState(false);
return <Wrapper>{cloneElement(children, { isOpen: isOpen })}</Wrapper>;
Reference from React.dev
React Posts Archive
Structure
package.json
react & react-dom is necessary for web app. For mobile app need react-native instead of react-dom.
public/index.html
signal page application which is index.html
Everything your do will inside <div id="root"></div>
src/index.js
Entry point for react
src/App.js
All the component.
Inside the class, the render() method is called life cycle method and to render the page.
In JSX, you cannot use HTML class attribute. you have to use className.
16.3 && before 16.3
Debugging hook
usePrevious
Save previous state
const usePrevious = <T>(value: T, initialValue: T) => {
const ref = useRef(initialValue);
useEffect(() => {
ref.current = value;
});
return ref.current;
};
useEffectDebugger
Get which field trigger useEffect from dependency list
export const useEffectDebugger = (
effectHook: EffectCallback,
dependencies: DependencyList,
dependencyNames = []
) => {
const previousDeps = usePrevious(dependencies, []);
const changedDeps = dependencies.reduce((prev, dependency, index) => {
if (dependency !== previousDeps[index]) {
const keyName = dependencyNames[index] || index;
return {
...prev,
[keyName]: {
before: previousDeps[index],
after: dependency,
},
};
}
return prev;
}, {});
if (Object.keys(changedDeps).length) {
console.log('[use-effect-debugger] ', changedDeps);
}
useEffect(effectHook, [...dependencies, effectHook]);
};
useCallbackDebugger
Get which field trigger useCallback from dependency list
export const useCallbackDebugger = <T extends (...args: any[]) => any>(
callback: T,
dependencies: DependencyList,
dependencyNames: string[] = [],
debuggerName = 'use-callBack-debugger'
) => {
const previousDeps = usePrevious(dependencies, []);
const changedDeps = dependencies.reduce((prev, dependency, index) => {
if (dependency !== previousDeps[index]) {
const keyName = dependencyNames[index] || index;
return {
...prev,
[keyName]: {
before: previousDeps[index],
after: dependency
}
};
}
return prev;
}, {});
if (Object.keys(changedDeps).length) {
console.log(`[${debuggerName}] `, changedDeps);
}
return useCallback(callback, [...dependencies, callback]);
};
useMemoDebugger
Get which field trigger useMemo from dependency list
export const useMemoDebugger = <T extends (...args: any[]) => any>(
callback: T,
dependencies: DependencyList,
dependencyNames: string[] = [],
debuggerName = 'use-memo-debugger'
) => {
const previousDeps = usePrevious(dependencies, []);
const changedDeps = dependencies.reduce((prev, dependency, index) => {
if (dependency !== previousDeps[index]) {
const keyName = dependencyNames[index] || index;
return {
...prev,
[keyName]: {
before: previousDeps[index],
after: dependency
}
};
}
return prev;
}, {});
if (Object.keys(changedDeps).length) {
console.log(`[${debuggerName}] `, changedDeps);
}
return useMemo(callback, [...dependencies, callback]);
};
useTraceUpdate
Get which props been pass to function component trigger rerender
export function useTraceUpdate<T extends Record<string, any>>(props: T) {
const prev = useRef(props);
useEffect(() => {
const changedProps = Object.entries(props).reduce<Record<string, any>>(
(ps, [k, v]) => {
if (prev.current[k] !== v) {
ps[k] = [prev.current[k], v];
}
return ps;
},
{}
);
if (Object.keys(changedProps).length > 0) {
console.log('Changed props:', changedProps);
}
prev.current = props;
});
}