React is a front end UI library. It has nothing to do with backend.
The most important concept in React is "Component", which is similar to "Widget" in Flutter. It is the basic unit of UI element.
There are two style to write a component, one is functional and the other is class. I personally find class-based syntax is more intuitive.
All Component names must be capitalized (both file, class name, and function), otherwise they will be treated as regular html, not JXS.
React hooks are great, much better than Flutter's state management. But you need to be careful of differentiating immutable types in state and have a good design on where you put your states, or whether to merge states to prevent re-render and loops. See this good article if loops are annoying.
This video is great.
The following three code snippets are equivalent:
import React from "react";
const FunctionalComponent = () => {
return <h1>Hello, world</h1>;
};
import React from "react";
function FunctionalComponent() {
return <h1>Hello, world</h1>;
}
import React from "react";
class ClassComponent extends React.Component {
render() {
return <h1>Hello, world</h1>;
}
}
Note that reacts might automatically wrap what you
return
with<div>
tags. If you don't like that, use<>...
. Alsoclass
is a reserved keyword in Javascript, so useclassName
instead.
Export with Default Name (function name is the same as file name)
export default App;
Named Export
export {App}
You can import things other than Components.
import avatar form './avatar.png';
Or use require
<img src={require('./avatar.png')}>
The following two imports are equivalent:
import React from 'react'
React.cloneElement(...)
import {cloneElement} from 'react'
cloneElement(...)
An react element can look like this:
const element = <div />;
but it can also look like this: where name
is like field and Sara
is like arguments to pass into functions. We call {Sara}
props (think as a dictionary of arguments).
const element = <Welcome name="Sara" />;
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
The following codes are equivalent:
const FunctionalComponent = ({ name }) => {
return <h1>Hello, {name}</h1>;
};
const FunctionalComponent = (props) => {
return <h1>Hello, {props.name}</h1>;
};
class ClassComponent extends React.Component {
render() {
const { name } = this.props;
return <h1>Hello, { name }</h1>;
}
}
You should not modify its own props.
Events in javascript is written like onclick
whereas in react is written like onClick
. Also, you should not directly invoke functions in react:
html
<button onclick="foo()"></button>
react
<button onClick={foo}></button>
Although you can add onClick
directly in html, it is recommended to document.getElementById
, then .addEventListener
to html object. // TODO: why
Note that
onClick
can be passed into Components props.
The following two snippets are equivalent:
const FunctionalComponent = () => {
const [count, setCount] = React.useState(0);
return (
<div>
<p>count: {count}</p>
<button onClick={() => setCount(count + 1)}>Click</button>
</div>
);
};
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>
);
}
}
Since react will compare the old state with the new state to decide if DOM should be updated, you should never modify the state directly. Instead, if your state is, say, a dictionary, make a shallow copy of it. This can be achieved by
{...oldState, newKey: newValue}
or[...oldState, newElem]
useReducer
is a wrapper around useState
that allow you to customize complex logic that updates states.
Instead of children.map
, you can use React.Children.map(...)
that doesn't throw error when children
is null
.
When a parent component wants to add or modify the props of its children (for example, adding parent's ref
), use React.cloneElement
. Note that you can't directly change children without copying, since children is the prop
of parent, doing so will mutate prop
inside itself, which is bad.
If cloneElement
feels too ad-hoc, you can use render
prop instead:
class RenderPropComponent extends React.Component {
render() {
return (
{this.props.render('Hello, Render Prop!')}
);
}
}
function App() {
return (
<RenderPropComponent render={(data) => <p>{data}</p>} />
);
}
// Provider.js
import React from 'react';
const Context = React.createContext();
const data = 0;
export const Provider = ({children}) => {
const [state, setState] = React.useState(data)
return (
<Context.Provider value={{data}}>
{children}
</Context.Provider>
)
}
export const useDataContext = () => React.useContext(Context)
import {useDataContext} from "../providers/Provider.js"
const Component = () => {
const {data} = useDataContext();
return (<div>
<h1>data</h1>
</div>);
}
export default Component
const reducerFn = (value) => {
return {key: value}
}
function App() {
const initialState = {key: 0};
const [state, dispatch] = useReducer(reducerFn, initialState);
return (<button onClick={() => dispatch({key: 1})}></button>);
}
export default App;
To install react router: npm i react-router-dom
import {BrowserRouter} from "react-router-dom"
const root = ReactDOM.createRoot(document.getElement(...))
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
)
import {Routes, Route, Link} from "react-router-dom"
function App() {
return (
<Routes>
<Link to="/">link</Link>
<Route path="/about" element={<h1>about</h1>} />
<Route path="/index" element={<h1>index</h1>} />
<Routes>
)
}
See short Hooks API Reference
useEffect
hook is used to watch a state change and then call functions that generates side effects. It is good for http fetch. UseEffect should return a function that, once called, clean up stuffs.
For all hooks, don't use them in loops, conditionals, or subfunctions. Hook must be inside Components.
function App() {
const inputRef = React.useRef(null);
const focusInput = () => {
inputRef.current.focus();
}
return (
<input ref={inputRef} />
<button onClick={focusInput} />
);
}
useRef()
create a javascript object just like {current: ...}
, but the ref
stay the same for every render. useRef()
does not notify when its content changes (ie. mutation .current
will not cause re-render).
useCallback(fn, deps)
is equivalent to useMemo(() => fn, deps)
.
useCallback
will return a memoized version of the callback that only changes if one of the dependencies has changed. This is mostly for optimization.
Every value referenced inside the callback should also appear in the dependencies array
Don't do any sideeffect in useCallback
and useMemo
, because react may choose to forget some value for performance optimization. You should useEffect
to establish semantic guarantee.
The following two snippets are equivalent:
const FunctionalComponent = () => {
React.useEffect(() => {
console.log("Hello");
}, []);
return <h1>Hello, World</h1>;
};
class ClassComponent extends React.Component {
componentDidMount() {
console.log("Hello");
}
render() {
return <h1>Hello, World</h1>;
}
}
When the ref
attribute is used on an HTML element, the ref
callback receives the underlying DOM element as its argument.
render() {
return (
<div
style={{ width: '400px', height: '400px' }}
ref={(mount) => { this.mount = mount }}
/>
)
}
this.mount
will hold a reference to the actual <div>
the component is mounted to.
The following two snippets are equivalent:
const FunctionalComponent = () => {
React.useEffect(() => {
return () => {
console.log("Bye");
};
}, []);
return <h1>Bye, World</h1>;
};
class ClassComponent extends React.Component {
componentWillUnmount() {
console.log("Bye");
}
render() {
return <h1>Bye, World</h1>;
}
}
Let's deep dive into how react is rendered.
react is a "diffing" tool that compares two virtual DOM to determine what is the least operations to update the real DOM
react uses "functions that return JSX" to generate virtual DOM
When you need to call multiple setState()
in sequence, there is no guarantee that no render will be made in between where state might not be consistent for your application. We can observe that setState inside Promise may not be batched by react, creating problems.
Therefore, at the cost of re-render virtual DOM, any interdependent states should be grouped as one single state.
See this article
Consider:
const MyComponent({page, type}) {
const resolvedValue = getResolvedValue(page, type)
return <ExpensiveComponent resolvedValue={resolvedValue}/>
}
When resolvedValue
is a primitive and the value is the same as last render, ExpensiveComponent
won't gets re-rendered. But when resolvedValue
is not a primitive, without using useMemo
, the object reference changed, and ExpensiveComponent
will get re-rendered.
Consider:
const myComponent({page, type}) {
const resolvedValue = useMemo(() => {
return getResolvedValue(page, type)
}, [page, type])
return <ExpensiveComponent resolvedValue={resolvedValue}/>
}
So here we will update resolvedValue
's reference only when page
and type
's reference changes. Reference changes for primitives means difference in value, for objects means difference in memory address.
Table of Content