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

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

{
  "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

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

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

  • library for forms
  • supports schema validation
    • uses library yup
  • docs
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

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