Client
Server
Database
HTTP
browser
request
html page
Client
Server
Database
HTTP
React in browser, mobile app...
API
request
data
Single page application
Web server
html, js
npx create-react-app my-app --template typescript
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
const label = React.createElement('a', {
href: 'https://google.com'
}, 'Go to Google.com');
<a href="https://google.com">Go to Google.com</a>
children
props
type
import React from 'react';
function App() {
return (
<div className="App">
Hello
</div>
);
}
function App() {
return React.createElement('div', { className: 'App' }, 'Hello');
}
import React from 'react';
function App() {
return (
<div className="App">
Hello
</div>
);
}
function App() {
return React.createElement('div', { className: 'App'}, 'Hello');
}
import React from 'react';
function App() {
return (
<div>
Yes
</div>
<div>
No
</div>
);
}
function App() {
return ????????
}
import React from 'react';
function App() {
return (
<>
<div>
Yes
</div>
<div>
No
</div>
</>
);
}
function App() {
return <h1>Hello</h1>;
}
function App() {
let something = 'hello';
return <div>{something}</div>;
}
function Array() {
let array = [1,2,3];
return <div>
{array.map((item, index) => <span key={index}>{item}</span>)}
</div>;
}
type Props = {
name: string;
};
function NameComponent(props: Props) {
return <h1>Hi, my name is {props.name}!</h1>;
}
ReactDOM.render(
<NameComponent name="Martin" />,
document.getElementById('root')
);
Component
Component
Component
Component
Component
Component
Component
User info
ArticleList
Article
Today Weather
Article
I am smart 💡
function NameComponent(props) {
return <h1>{props.name}</h1>;
}
function App() {
return <NameComponent name="Martin" />
}
Hello | Hello | Hello | Hello |
---|---|---|---|
Hello | Hello | Hello | Hello |
Hello | Hello | Hello | Hello |
<Table columns={4} rows={3} />
<Table columns={5} rows={2}>
<h1>Hello</h1>
</Table>
function Table(props) {
return (
<table>
<tr>
<td>
{props.children}
</td>
</tr>
</table>
)
}
<button type="button" onClick={() => console.log('Hello')}>
Hello world
</button>
import React, { useState } from 'react';
function Counter() {
const [name, setName] = useState('nobody');
function handleGiveName(name: string) {
setName(name);
}
return <div>
My name is {name}.
<button onClick={() => handleGiveName('Martin')}>
Give me name
</button>
</div>
}
initial value
import React from 'react';
type State = {
counter: number;
}
export class MyComponent extends React.Component<{}, State> {
state = {
counter: 0
};
increment() {
this.setState({ counter: this.state.counter + 1 });
}
render() {
const { counter } = this.state;
return <div>
Counter: {counter}
<button type="button" onClick={() => this.increment()}>Increment</button>
</div>
}
}
type Props = {
pregeneratedCount: number
}
type State = {
generatedNumbers: number[];
}
export class NumberGeneratorClass extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
for (let i = 0; i < props.pregeneratedCount; i++) {
this.state = {
generatedNumbers: [...this.state?.generatedNumbers || [], Math.random()]
};
}
}
generateNew() {
this.setState({ generatedNumbers: [...this.state.generatedNumbers, Math.random()] });
}
render() {
const { generatedNumbers } = this.state;
return <div>
{generatedNumbers.map((num, index) => <div key={index}>{num}</div>)}
<button type="button" onClick={() => this.generateNew()}>Generate new</button>
</div>
}
}
function MyComponent() {
const random = Math.random();
if (random < 0.5) {
return <span>lower</span>
} else {
return <span>higher</span>
}
}
function MyComponent() {
const random = Math.random();
return <span>
{random < 0.5 ? 'lower' : 'higher'}
</span>
}
function MyComponent() {
const condition = true;
return <>{condition && <span>It's true</span></>
}
import './App.css';
function Component() {
return <div className="red">Hello</div>
}
.red {
color: red;
}
App.css
App.tsx
import styles from './App.module.css';
function Component() {
return <div className={styles.red}>Hello</div>
}
.red {
color: red;
}
App.module.css
App.tsx
import cn from 'classnames';
function ValidationState() {
const [invalid, setInvalid] = useState(false);
return <div className={cn({ red: invalid })}>
Status
</div>
}
import cn from 'classnames';
import styles from './App.module.css';
function ValidationState() {
const [invalid, setInvalid] = useState(false);
return <div className={cn({ [styles.invalid]: invalid })}>
Status
</div>
}
export const MyMouse = () => {
const [mousePosition, setMousePosition] = useState({x: 0, y: 0});
useEffect(() => {
const onMouseMove = event => {
setMousePosition({
x: event.clientX,
y: event.clientY
});
};
window.addEventListener('mousemove', onMouseMove);
return () => {
window.removeEventListener('mousemove', onMouseMove);
};
}, []);
const {x, y} = mousePosition;
return (
<div>My mouse x position is {x} and y position is {y}</div>
);
};
type Props = {
onTrigger: () => void;
};
function ChildComponent(props: Props) {
return <button onClick={props.onTrigger}>emit event</button>;
}
<ChildComponent onTrigger={() => console.log('triggered')} />
parent component:
child component:
www.google.com
www.instagram.com
www.facebook.com
Toggle button
emit click
DropdownComponent
shows
function Component() {
const [name, setName] = useState('nobody');
const [inputName, setInputName] = useState(name);
function handleGiveName() {
setName(inputName);
}
return <>
My name is {name}.
<input
value={inputName}
onChange={(e) => setInputName(e.target.value)} />
<button onClick={() => handleGiveName()}>Save</button>
</>
}
import axios from 'axios';
const payload = { name: 'Martin' };
const response = await axios.post('/api/users', payload);
console.log(response);
GET https://api.chucknorris.io/jokes/random
Component
User info
JokeFetcher
Joke
I am smart 💡
data down
const useMouseMove = () => {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const onMouseMove = (event: MouseEvent) => {
setMousePosition({
x: event.clientX,
y: event.clientY,
});
};
window.addEventListener('mousemove', onMouseMove);
return () => {
window.removeEventListener('mousemove', onMouseMove);
};
}, []);
return { x: mousePosition.x, y: mousePosition.y };
};
debugger;
type ContextValue = boolean;
const MyContext = React.createContext<ContextValue>(false);
function App() {
return <MyContext.Provider value={true}>
<Component />
</MyContext.Provider>;
}
function Component() {
const value = useContext(MyContext);
return <div>{value}</div>;
}
type ContextValue = {
value: boolean;
changeValue: (newValue: boolean) => void;
}
// avoid the need to specify initial value
const MyContext = React.createContext<ContextValue>({} as unknown as ContextValue);
type Props = {
initialState: boolean;
children: React.ReactNode
}
export function MyContextProvider({initialState, children}: Props) {
const [state, setState] = useState(initialState);
const contextValue = {
value: state,
changeValue: (newValue: boolean) => setState(newValue)
};
return <MyContext.Provider value={contextValue}>
{children}
</MyContext.Provider>;
}
// used to read value from comopnent
export const useMyContext = () => useContext(MyContext);
const { value, changeValue } = useMyContext();
{
jokeSlice: {
currentJoke: "Chuck Norris can speak Braille.",
isLoading: false
},
counterSlice: {
count: 5
},
uiSlice: {
dropdownVisible: false
}
}
View
Reducer
Store
dispatch an action
update the store
view reads data from the store
increment = (state) => {
state.count += 1;
}
// /src/store/counterSlice.ts
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
export type CounterState = {
count: number;
};
const initialState: CounterState = {
count: 0,
};
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state, action) => {
state.count += 1;
}
},
});
export const { increment } = counterSlice.actions;
export const counterReducer = counterSlice.reducer;
// src/store/store.ts
import { configureStore } from '@reduxjs/toolkit';
import { jokeReducer } from './jokeSlice';
import { counterReducer } from './counterSlice';
import { uiReducer } from './uiSlice';
export const store = configureStore({
reducer: {
jokeSlice: jokeReducer,
counterSlice: counterReducer,
uiSlice: uiReducer
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// index.tsx
import { store } from './store/store'
import { Provider } from 'react-redux'
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
// src/store/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
import { useAppSelector } from '../store/hooks';
function Counter() {
const count = useAppSelector(state => state.counterSlice.count);
return <div>Current count: {count}</div>
}
import { useAppDispatch } from '../store/hooks';
import { increment } from "../store/counterSlice";
function IncrementButton() {
const dispatch = useAppDispatch();
return (
<button onClick={() => dispatch(increment())}>
Increment
</button>
);
}
const jokeSlice = createSlice({
name: 'joke',
initialState,
reducers: {
jokeFetchingStarted: (state) => {
state.isLoading = true;
},
jokeLoaded: (state, action: PayloadAction<string>) => {
state.joke = action.payload;
state.isLoading = false;
},
},
});
export const { jokeLoaded, jokeFetchingStarted } = jokeSlice.actions;
export const jokeReducer = jokeSlice.reducer;
// src/actions/fetch-joke.ts
import axios from 'axios';
import { jokeFetchingStarted, jokeLoaded } from '../store/jokeSlice';
import { AppDispatch } from '../store/store';
type ResponseType = {
value: string;
};
export const fetchJoke = () => async (dispatch: AppDispatch) => {
dispatch(jokeFetchingStarted());
const response = await axios.get<ResponseType>(
'https://api.chucknorris.io/jokes/random'
);
dispatch(jokeLoaded(response.data.value));
};
import { fetchJoke } from '../actions/fetch-joke';
import { useAppDispatch } from '../store/hooks';
export const NextJokeButton = () => {
const dispatch = useAppDispatch();
return (
<button type="button" onClick={() => dispatch(fetchJoke())}>
Fetch next joke
</button>
);
}
function Counter({children}) {
const [counter, setCounter] = useState(0);
function increment() {
setCounter(counter + 1);
}
return <>{children({counter, increment})}</>
}
function MyComponent() {
return <div>
<Counter>
{({counter, increment}) => <>
<div>Counter value: {counter}</div>
<button onClick={increment}>INC</button>
</>}
</Counter>
</div>
}
function Component() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return <div>
<input ref={inputRef} />
<button onClick={handleClick}>Focus the input</button>
</div>
}
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Link to="/">Home</Link>
<Link to="/categories">About</Link>
<Link to="/categories/animals">Joke about animals</Link>
<Link to="/categories/history">Joke about history</Link>
<Routes>
<Route index path="/" element={<Home />} />
<Route path="/categories" element={<JokeCategories />} />
<Route path="/categories/:category" element={<Joke />} />
</Routes>
</BrowserRouter>
);
}
const navigate = useNavigate();
navigate('/categories');
import { useParams } from "react-router-dom";
function Joke() {
const params = useParams();
return (
{params.category}
);
}
const JokeMemoized = React.memo(function Joke() {
...
});
<JokeMemoized />
const useFibonacci = (n) => {
const result = useMemo(() => fibonacci(n), [n]);
return result;
}
function fibonacci(n) {
return n < 1 ? 0
: n <= 2 ? 1
: fibonacci(n - 1) + fibonacci(n - 2)
}
type Props = {
me: string;
}
function Component({me}: Props) {
const handleClick = useCallback(
(name) => console.log(`Hello ${name} and ${me}`)
, [me]);
return <ExpensiveComponent onClick={handleClick} />;
}
const schema = yup.object().shape({
email: yup.string().required().email(),
age: yup.number().required().positive().integer()
})
const initialValues = {
email: '',
age: 0
}
export function MyForm() {
return (
<Formik
initialValues={initialValues}
validationSchema={schema}
onSubmit={values => console.log(values)}
>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field type="email" name="email" />
<ErrorMessage name="email" component="div" />
<Field type="number" className="error" name="age" />
<ErrorMessage name="age" className="error" component="div"/>
<button type="submit">
Submit
</button>
</form>
)}
</Formik>
);
}