React Basics

21/10/21

Summary

  • What is React?
  • Live coding
  • Bonus
    • Redux
    • Router

What is react?

A JavaScript library for building user interfaces

As described by reactjs.org

Declarative

component-based

Learn once - Write everywhere

JSX

render() {
  return (
    <div>
      Seconds: {this.state.seconds}
    </div>
  );
}
render() {
  return React.createElement(
    'div',
    null,
    'Seconds: ',
    this.state.seconds
  );
}

Some history: class components

class Timer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { seconds: 0 };
  }

  tick() {
    this.setState(state => ({
      seconds: state.seconds + 1
    }));
  }

  componentDidMount() {
    this.interval = setInterval(() => this.tick(), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  render() {
    return (
      <div>
        Seconds: {this.state.seconds}
      </div>
    );
  }
};
import { useEffect, useState } from 'react';

const Timer = () => {
  const [seconds, setSeconds] = useState(0);

  const tick = () => setSeconds((s) => s + 1);

  useEffect(() => {
    const interval = setInterval(tick, 1000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div>
      Seconds: {seconds}
    </div>
  );
};

Component props

class Welcome extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.user.name}</h1>
    );
  }
}
const Welcome = ({ user }) => (
  <h1>Hello, {user.name}</h1>
);
const user = { name: 'Toto' }
<Welcome user={user} />
// => <h1>Hello, Toto</h1>

Lifecycle hooks

componentDidMount();

shouldComponentUpdate(nextProps, nextState);

render();

componentDidUpdate(prevProps, prevState, snapshot);

componentWillUnmount();

Hooks - The new way

import { useState } from 'react';

// Convention: keep the same name between state and setState
// Only argument is the default value
const [count, setCount] = useState(0);
import { useEffect } from 'react';

useEffect(
  // function executed each time one of the dependency changes
  () => {    
    const timer = setInterval(() => console.log('Toto'), 1000);

    // cleanup function
    return () => {
      clearInterval(timer):
    }
  },
  [] // dependencies array
)

Let's play!

$ npx create-react-app my-app

Want more?

Redux

The big picture

const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // Check to see if the reducer cares about this action
  if (action.type === 'counter/increment') {
    // If so, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      value: state.value + 1
    }
  }
  // otherwise return the existing state unchanged
  return state
}
import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())
// {value: 0}
store.dispatch({ type: 'counter/increment' })

console.log(store.getState())
// {value: 1}
const increment = () => {
  return {
    type: 'counter/increment'
  }
}

store.dispatch(increment())

console.log(store.getState())
// {value: 2}
const selectCounterValue = state => state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2

The easy way

export const store = {
  // initial state
  state: {
    count: 0
  },
  reducers: {
    // handle state changes with pure functions
    increment: (state, payload) => ({ ...state, count: state.count + payload }),
    decrement: (state, payload) => ({ ...state, count: state.count - payload })
  },
  effects: (dispatch) => ({
    // handle state changes with impure functions.
    // use async/await for async actions
    async incrementAsync(payload, rootState) {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      dispatch.count.increment(payload); // Or this.increment(payload);
    }
  })
};
import { init } from "@rematch/core";
import * as models from "./models";

const store = init({ models });

export default store;
$ yarn add -E @rematch/core
const { dispatch } = store;

// state = { count: 0 }

// reducers
dispatch({ type: "count/increment", payload: 1 });
// state = { count: 1 }
dispatch.count.increment(1);
// state = { count: 2 }

// effects
dispatch({ type: "count/incrementAsync", payload: 1 });
// state = { count: 3 } after delay
dispatch.count.incrementAsync(1);
// state = { count: 4 } after delay
import React from "react";
import ReactDOM from "react-dom";
import { Provider, connect } from "react-redux";

import store from "./store";

const Count = ({ count, increment, incrementAsync }) => (
  <div>
    The count is {count}
    <button onClick={increment}>increment</button>
    <button onClick={incrementAsync}>incrementAsync</button>
  </div>
);

const mapState = (state) => ({
  count: state.store.count,
});

const mapDispatch = (dispatch) => ({
  increment: () => dispatch.store.count.increment(1),
  incrementAsync: () => dispatch.store.count.incrementAsync(1),
});

export default connect(mapState, mapDispatch)(Count);

Router

$ yarn add -E react-router-dom
import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from "react-router-dom";

import { About, Home, Users } from './pages';

const App = () => (
  <Router>
    <div>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/users">Users</Link>
      </nav>
      <Switch>
        <Route path="/about">
          <About />
        </Route>
        <Route path="/users">
          <Users />
        </Route>
        <Route path="/">
          <Home />
        </Route>
      </Switch>
    </div>
  </Router>
);

React Basics

By Simon Roussel