Intro
🙋♂️
Frontend engineer
- 9 years FE experience
- AngularJS, Angular, Ember, React
- currently at Productboard
❤️ 📸
Slides
Remind me
- look at your project
- breaks!
- lunch?
Setup IDE
Install
- nodejs
- Visual Studio Code
- Quokka plugin
- VS Code ➡ extensions ➡ search for quokka
Let's talk about React! 👍
React
- library for managing view
- component based
- helps split the app into small pieces
- used to create SPA
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
Create React app
CRA
- tool for scaffolding react app
npx create-react-app my-app --template typescript
Important parts
package.json
- describes the package
- dependecies list
- npm scripts
tsconfig.json
- settings for typescript compiler
- "target" = build for version of JS
/public folder
- contains assets
- index.html
index.tsx
- renders React into HTML element
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
App.tsx
- the main component
NPM
Dependency management
Node package manager
- packaging system
- large repository 3rd party libs
- https://www.npmjs.com
{
"name": "my-package",
"version": "1.0.0",
"description": "This is just description of my awesome package",
"main": "index.js",
"scripts": {
"dev": "nodemon --exec npm run start",
"start": "tsc && node dist/index.js",
"test": "mocha --opts mocha.opts"
},
"author": "Martin Nuc",
"license": "ISC",
"dependencies": {
"@types/chai": "4.0.4",
"@types/mocha": "2.2.43",
"@types/node": "8.0.28",
"@types/sinon": "2.3.4",
"chai": "4.1.2",
"mocha": "3.5.3",
"nodemon": "1.12.1",
"sinon": "3.2.1",
"ts-node": "3.3.0",
"typescript": "2.5.2"
}
}
package.json
Scripts
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
- used to execute commands
- npm dependencies executables resolution (from node_modules/.bin/*)
npm run <name>
Shortcut for start and test scripts only. For others you have to use npm run
Runs any script from npm.
npm start
npm test
👉
➡️ Start the React app
Dependencies
npm install lodash
installs lodash library:
npm install -D @types/lodash
installs lodash type definitions:
Use library in your code
import lodash from 'lodash';
lodash.difference([1, 2, 3], [2, 3]);
Dependencies and GIT
- we don't commit dependencies
- put node_modules folder in .gitignore
- npm install to install dependencies

{
"name": "my-package",
"version": "1.0.0",
"description": "This is just description of my awesome package",
"main": "index.js",
"scripts": {
"dev": "nodemon --exec npm run start",
"start": "tsc && node dist/index.js",
"test": "mocha --opts mocha.opts"
},
"author": "Martin Nuc",
"license": "ISC",
"dependencies": {
"@types/chai": "4.0.4",
"@types/mocha": "2.2.43",
"@types/node": "8.0.28",
"@types/sinon": "2.3.4",
"lodash": "4.17.5",
"chai": "4.1.2",
"mocha": "3.5.3",
"nodemon": "1.12.1",
"sinon": "3.2.1",
"ts-node": "3.3.0",
"typescript": "2.5.2"
}
}
package.json
Semantic versioning
6.11.2
patch
minor version
major version
Semantic versioning
6.11.2
patch
minor version
major version
- major changes, breaks API
Semantic versioning
6.11.2
patch
minor version
- new features
- doesn't break API
major version
- major changes, breaks API
Semantic versioning
6.11.2
patch
- only bugfixes
minor version
- new features
- doesn't break API
major version
- major changes, breaks API
➡️ Use lodash
- install lodash library (https://lodash.com)
- work in index.tsx
- use difference function from lodash find how are these array different:
- [1,2,3,4]
- [3,4,5,6]
JSX
Elements
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
What is JSX?
- syntactic sugar around createElement
- almost like HTML
- transpiled to Javascript
- example in App.tsx:
import React from 'react';
function App() {
return (
<div className="App">
Hello
</div>
);
}
function App() {
return React.createElement('div', { className: 'App' }, 'Hello');
}
Q: Why className?
import React from 'react';
function App() {
return (
<div className="App">
Hello
</div>
);
}
function App() {
return React.createElement('div', { className: 'App'}, 'Hello');
}
Q: What happens now?
import React from 'react';
function App() {
return (
<div>
Yes
</div>
<div>
No
</div>
);
}
function App() {
return ????????
}
Solution: React Fragment
- like empty element
- when you want to return multiple elements - wrap them in fragment
import React from 'react';
function App() {
return (
<>
<div>
Yes
</div>
<div>
No
</div>
</>
);
}
➡️ Before we continue
- remove everything in the body of App.tsx component
- notice the browser reloads
function App() {
return <h1>Hello</h1>;
}
Print a variable
function App() {
let something = 'hello';
return <div>{something}</div>;
}
Print an array
function Array() {
let array = [1,2,3];
return <div>
{array.map((item, index) => <span key={index}>{item}</span>)}
</div>;
}
Components
Component
- reusable unit
- just a function
-
input
- ="props"
-
output
- React element
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 tree

- split big problems to smaller ones
Component tree
Component
Component
Component
Component
Component
Component
Component tree
- Stateful components (smart)
- used to fetch data
- data manipulation
- Stateless components (dumb)
- only display data
- pass data down, emit events up
Component tree
Component
User info
ArticleList
Article
Today Weather
Article
I am smart 💡
Stateless component
- everything to display is received via props
- just a function
- input: props (=properties)
- output: React element
- easy to test
function NameComponent(props) {
return <h1>{props.name}</h1>;
}
How to use a component?
- pass data down
function App() {
return <NameComponent name="Martin" />
}
➡️ Dynamic table
- create a component which displays a table using JSX
- receives number of columns and rows as parameter
Hello | Hello | Hello | Hello |
---|---|---|---|
Hello | Hello | Hello | Hello |
Hello | Hello | Hello | Hello |
<Table columns={4} rows={3} />
Children props
Children props
- you might pass HTML as body of element:
<Table columns={5} rows={2}>
<h1>Hello</h1>
</Table>
- Table component receives react element via children prop:
function Table(props) {
return (
<table>
<tr>
<td>
{props.children}
</td>
</tr>
</table>
)
}
Event handling
- React unifies API of events (link)
<button type="button" onClick={() => console.log('Hello')}>
Hello world
</button>
State
useState
- hook for storing data
- instead of declaring variable
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
➡️ Create counter
- create button with counter as text
- start from 0
- everytime you click the button the counter is increased
Conditions
- use if statement
- use ternary operator
render() {
const random = Math.random();
if (random < 0.5) {
return <span>lower</span>
} else {
return <span>higher</span>
}
}
render() {
const random = Math.random();
return <span>
{random < 0.5 ? 'lower' : 'higher'}
</span>
}
Styling app
CSS modules
- scoped CSS
- can use preprocessors (SCSS, SASS)
import styles from './App.module.css';
function Component() {
return <div className={styles.red}>Hello</div>
}
.red {
color: red;
}
App.module.css
App.tsx
Conditional styling
- classnames library
- npm i classnames @types/classnames
- key = class to be applied
- value = condition
import cn from 'classnames';
import styles from './App.module.css';
...
<div className={cn({ [styles.invalid]: this.state.invalid })}>
</div>
useEffect
- hook for side effects
- second argument say when it runs
- empty - on every render
- [ ] - only at the begining (=on mount)
- [ variable ] - only when variable changes
- should return cleanup function
useEffect example
- tracks mouse position
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>
);
};
Try useEffect
- show text with mouse position
- when the mouse position is
- on the left to the last click - change text to green color
- on the right to the last click - change text to red color
Create automatic counter
- create component which increases counter every second
- in parent component create button which shows/hides this component
Creating own event
- component emits event up
type Props = {
doWork: () => void;
};
function ChildComponent(props: Props) {
handleClick = () => {
this.props.doWork();
}
return <button onClick={this.handleClick}>emit event</button>;
}
<ChildComponent doWork={() => console.log('triggered')} />
parent component:
child component:
Create dropdown
- What is dropdown?
- button which opens menu when clicked
Controlled input
- use component state as the only source of truth
funciton 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()}>Give me name</button>
</>
}
Create input
- input of type number
- how much the counter will increment
API request
Axios library
- used to make http requests
-
npm install axios @types/axios
- import in code:
import axios from 'axios';
➡️ Let's make http request
- open API request in browser to see structure of response
- display joke in the component
- create a button to load another joke
GET https://api.chucknorris.io/jokes/random
Component tree
Component
User info
JokeFetcher
Joke
I am smart 💡
data down
Custom hooks
Custom hooks
- separate logic from view
- no render
- named use*
- hooks to component lifecycle
- clear API
useMouseMove
const useMouseMove = () => {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const onMouseMove = (event: any) => {
setMousePosition({
x: event.clientX,
y: event.clientY,
});
};
window.addEventListener('mousemove', onMouseMove);
return () => {
window.removeEventListener('mousemove', onMouseMove);
};
}, []);
return { x: mousePosition.x, y: mousePosition.y };
};
- mouse position example
- no input
- outputs x, y of mouse
Fetch joke component
- encapsulate fetching joke logic into custom hook
- think about API first
Debugging
Main tools
- console.log
- React dev tools
- Chrome debugger
debugger;
Chrome dev tools
- Network
- Source
- Performance
- Application
- React dev tools
- Components
- Profiler
Logging
- Sentry.io
- TrackJS
React Context
Context
- "global" state for subtree of components
- avoids passing lot of props
- Provider + Consumer
const MyContext = React.createContext(false);
function App() {
return <MyContext.Provider value={true}>
<Component />
</MyContext.Provider>;
}
function Component() {
const value = useContext(MyContext);
return <div>{value}</div>;
}
Encapsulate context
- Provider component
const MyContext = React.createContext(false);
export function MyContextProvider({initialState, children}) {
const [state, setState] = useState(initialState);
const api = {
value: state,
changeValue: (newValue) => setState(newValue)
};
return <MyContext.Provider value={api}>
{children}
</MyContext.Provider>;
}
// used to read value from comopnent
export const useMyContext = () => useContext(MyContext);
const { value, changeValue } = useMyContext();
➡️ Dark & Light theme 🌗
- style your components to support dark & light theme
- create button to switch the theme
Render props
Render props
- pass function as children
- composition pattern
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>
}
Formik
Formik
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>
);
}
➡️ Create form
- think about any form and create it using formik
- include validations
useRef
useRef
- manipulate with DOM elements
- object with mutable current property
function Component() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return <div>
<input ref={inputRef} />
<button onClick={handleClick}>Focus the input</button>
</div>
}
useRef
- expose something from component to parent
- used to expose API by libraries
function Counter(props, ref) {
const [counter, setCounter] = useState(0);
useImperativeHandle(ref, () => ({
reset: () => setCounter(0)
}));
function handleIncrement() {
setCounter(counter+1);
}
return <div onClick={handleIncrement}>{counter}</div>;
}
const ResetableCounter = forwardRef(Counter);
function Component() {
const counterRef = useRef();
return <>
<ResetableCounter ref={counterRef} />
<button onClick={() => counterRef.current.reset()}>Reset</button>
</>
}
Routing
React router
- used to create multiple pages
- install react-router-dom + type definitions
- docs
Define pages and links
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>
);
}
- everything must be inside of <Router>
Navigate
const navigate = useNavigate();
navigate('/categories');
- either using <Link>
- or using useNavigate() hook
Reading url parameters
import { useParams } from "react-router-dom";
function Joke() {
const params = useParams();
return (
{params.category}
);
}
Try routing
- Create routes
- / -> categories list
- /categories/:category -> joke from category
- load joke based on category
Performance optimizations
Problem
- lot of rerenders
- every render creates new function, object etc
- DOM operations are expensive
React.memo
- rerenders component only on prop change
const JokeMemoized = React.memo(function Joke() {
...
});
<JokeMemoized />
useMemo
- precompute value
- for computation-expensive values
- avoids main thread lock
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)
}
useCallback
- used to retain a single function reference
- avoids problem with recreating handler every render
function Component({me}) {
const handleClick = useCallback(
(name) => console.log(`Hello ${name} and ${me}`)
, [me]);
return <ExpensiveComponent onClick={handleClick} />;
}
➡️ Optimize Joke
- won't get rerendered on theme change (=on context change)
- no waste = useCallback for handlers
🎉
Simplea React
By Martin Nuc
Simplea React
- 291