Reactjs: Difference between revisions

From Training Material
Jump to navigation Jump to search
 
(31 intermediate revisions by the same user not shown)
Line 319: Line 319:


=== Exercise 3 ===
=== Exercise 3 ===
Yup, you know what to do now, for sure (-'
# Yup, you know what to do now, for sure (-'
* You do, right? (-,
#* You do, right? (-,
* Function, obviously!
#* Function, obviously!
# Oh well, let's "kill one stone with many birds", shall we?
#* Create short training course materials: "How to kill one stone with many birds?"
#* Keep it really simple as a presentation - just a couple of slides (2 or 3)
#* Use this extension - <small>https://github.com/rexxars/react-markdown</small>
<!-- Solution
<!-- Solution
const MarkdownEditor = () => {
const MarkdownEditor = () => {
Line 2,640: Line 2,644:
* <small>https://redux.js.org/introduction</small>
* <small>https://redux.js.org/introduction</small>


=== Example and exercises ===
=== Examples and exercises ===
# ''reactExamples''_
# ''reduxExamples''_
#* ''redux-crud''
#* ''crud'', ''jwt'', ''hooks''
# ''redux-toolkit-crud'' - fix me, again (-;
# Rewrite our '''dynamic form''' example with Redux
# Rewrite our '''dynamic form''' example with Redux
# Rewrite our '''FTJ''' app with Redux
# Rewrite our '''FTJ''' app with Redux
<!-- exercise with toolkit
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import TutorialDataService from "../services/TutorialService";
const initialState = [];
export const createTutorial = createAsyncThunk(
  "tutorials/create",
  async ({ title, description }) => {
    const res = await TutorialDataService.create({ title, description });
    return res.data;
  }
);
export const retrieveTutorials = createAsyncThunk(
  "tutorials/retrieve",
  async () => {
    const res = await TutorialDataService.getAll();
    return res.data;
  }
);
export const updateTutorial = createAsyncThunk(
  "tutorials/update",
  async ({ id, data }) => {
    const res = await TutorialDataService.update(id, data);
    return res.data;
  }
);
//// Moriarty's back - agaaaaaaaaain!
// - deleting one tutorial is gone, fix it!
//
export const deleteAllTutorials = createAsyncThunk(
  "tutorials/deleteAll",
  async () => {
    const res = await TutorialDataService.removeAll();
    return res.data;
  }
);
export const findTutorialsByTitle = createAsyncThunk(
  "tutorials/findByTitle",
  async ({ title }) => {
    const res = await TutorialDataService.findByTitle(title);
    return res.data;
  }
);
const tutorialSlice = createSlice({
  name: "tutorial",
  initialState,
  extraReducers: {
    [createTutorial.fulfilled]: (state, action) => {
      state.push(action.payload);
    },
    [retrieveTutorials.fulfilled]: (state, action) => {
      return [...action.payload];
    },
    [updateTutorial.fulfilled]: (state, action) => {
      const index = state.findIndex(tutorial => tutorial.id === action.payload.id);
      state[index] = {
        ...state[index],
        ...action.payload,
      };
    },
    [deleteAllTutorials.fulfilled]: (state, action) => {
      return [];
    },
    [findTutorialsByTitle.fulfilled]: (state, action) => {
      return [...action.payload];
    },
  },
});
const { reducer } = tutorialSlice;
export default reducer;
-->


== Managing state with Mobx ==
== Managing state with Mobx ==
Line 2,764: Line 2,854:
* we write '''custom''' react library (-:
* we write '''custom''' react library (-:
* ... ? (your ideas, please!)
* ... ? (your ideas, please!)
* '''''Example''''' - ''react-ts-api''
* '''''Example''''' - let's look at '''useNavigate()''' in ''react-router'' lib
* '''''Example/Exercise''''' - ''react-ts-api''
<br/>
<br/>
<!-- TODO: prep better exercise here (custom hook)
import { useState, useEffect } from "react";
import axios, { AxiosRequestConfig } from "axios";
//// Setup 'AxiosRequestConfig'


== React Query ==
 
* Most state management libraries (incl ''Redux'')
 
** good for working with '''client state'''
 
//// Your hook definition (-;
// - should use 'AxiosRequestConfig' as argument
// - should fetch asynchronously tutorials
// --- should handle data, error, loading (for now implement only 'data' and 'error' and default loading to false)
// - later on make sure our components will use it instead of 'service'
// - hints: use 'useState', 'useEffect'
 
 
 
 
 
 
-->
 
== React Query ==
* Most state management libraries (incl ''Redux'')
** good for working with '''client state'''
** not for server state
** not for server state
*** persists remotely in a location the client side cannot control
*** persists remotely in a location the client side cannot control
Line 2,782: Line 2,897:
* make our application feel '''faster and more responsive'''
* make our application feel '''faster and more responsive'''
* save bandwidth and increase memory '''performance'''
* save bandwidth and increase memory '''performance'''
* '''''Example''''' - ''reactq-axios-ts''
* '''''Example/Exercise''''' - ''reactq-axios-ts''
<br />
<br />
<!-- Exercise
import React, { useState, useEffect } from "react";
import { useQuery, useMutation } from "react-query";
import "./App.css";
import Tutorial from "./types/Tutorial"
import TutorialService from "./services/TutorialService"


== React Hook Form ==
const App: React.FC = () => {
* '''Less code''', more performant
  const [getId, setGetId] = useState("");
** reduces the amount of code we need to write
  const [getTitle, setGetTitle] = useState("");
** removes unnecessary re-renders
* '''Isolate''' re-renders
** ability to isolate component re-renders -> better performance on our page or app
* '''Subscriptions''' - better performance
** ability to '''subscribe to individual input''' and form State update without re-rendering the entire form
* Faster '''mounting''' (than Formik, ReduxForm, etc)
* More here <small>https://react-hook-form.com/</small>
* '''''Example''''' - ''react-hook-form-ts''
<br />


== MUI ==
  const [postTitle, setPostTitle] = useState("");
* '''MUI System''' - CSS utilities
  const [postDescription, setPostDescription] = useState("");
** <small>https://mui.com/system/getting-started/</small>
* '''BaseUI''' - blank canvas
** <small>https://mui.com/base-ui/</small>
* '''MaterialUI''' -
** <small>https://mui.com/material-ui/</small>
* '''JoyUI''' -
** <small>https://mui.com/joy-ui/getting-started/</small>


=== MUI System ===
  const [putId, setPutId] = useState("");
* Set of '''utilities''' for quickly '''applying styles''' to components from: ''BUI, MUI, JUI''
  const [putTitle, setPutTitle] = useState("");
** or to any '''third-party''' components
  const [putDescription, setPutDescription] = useState("");
* Helps to '''build''' custom designs more '''efficiently'''
  const [putPublished, setPutPublished] = useState(false);
** and to '''rapidly lay out''' custom designs
 
* Set of flexible, generic '''wrapper''' components (''Box, Container, Grid, Stack'')  
  const [deleteId, setDeleteId] = useState("");
** can be quickly customized using the <big>'''sx'''</big> ''prop''
 
*** styles can be '''defined directly''' within the components themselves
  const [getResult, setGetResult] = useState<string | null>(null);
*** '''simplifies''' bulky and redundant definitions of ''styled-components''
  const [postResult, setPostResult] = useState<string | null>(null);
*** ensures '''consistency''' in '''one-off''' styles too
  const [putResult, setPutResult] = useState<string | null>(null);
  const [deleteResult, setDeleteResult] = useState<string | null>(null);
 
  const fortmatResponse = (res: any) => {
    return JSON.stringify(res, null, 2);
  };
 
  const { isLoading: isLoadingTutorials, refetch: getAllTutorials } = useQuery<Tutorial[], Error>(
    "query-tutorials",
    async () => {
      return await TutorialService.findAll();
    },
    {
      enabled: false,
      onSuccess: (res) => {
        setGetResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setGetResult(fortmatResponse(err.response?.data || err));
      },
    }
  );
 
  useEffect(() => {
    if (isLoadingTutorials) setGetResult("loading...");
  }, [isLoadingTutorials]);
 
  function getAllData() {
    try {
      getAllTutorials();
    } catch (err) {
      setGetResult(fortmatResponse(err));
    }
  }
 
  const { isLoading: isLoadingTutorial, refetch: getTutorialById } = useQuery<Tutorial, Error>(
    "query-tutorial-by-id",
    async () => {
      return await TutorialService.findById(getId);
    },
    {
      enabled: false,
      retry: 1,
      onSuccess: (res) => {
        setGetResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setGetResult(fortmatResponse(err.response?.data || err));
      },
    }
  );
 
  useEffect(() => {
    if (isLoadingTutorial) setGetResult("loading...");
  }, [isLoadingTutorial]);
 
  function getDataById() {
    if (getId) {
      try {
        getTutorialById();
      } catch (err) {
        setGetResult(fortmatResponse(err));
      }
    }
  }


=== BaseUI ===
  const { isLoading: isSearchingTutorial, refetch: findTutorialsByTitle } = useQuery<Tutorial[], Error>(
* '''Library''' of "unstyled" ''React'' components (standalone)
    "query-tutorials-by-title", // ["query-tutorials-by-title", getTitle],
* Set of foundational '''"headless"''' components  
    async () => {
** can be built with using '''any styling''' solution
      return await TutorialService.findByTitle(getTitle);
    },
    {
      enabled: false,
      retry: 1,
      onSuccess: (res) => {
        setGetResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setGetResult(fortmatResponse(err.response?.data || err));
      },
    }
  );
 
  useEffect(() => {
    if (isSearchingTutorial) setGetResult("searching...");
  }, [isSearchingTutorial]);
 
  function getDataByTitle() {
    if (getTitle) {
      try {
        findTutorialsByTitle();
      } catch (err) {
        setGetResult(fortmatResponse(err));
      }
    }
  }
 
  const { isLoading: isPostingTutorial, mutate: postTutorial } = useMutation<any, Error>(
    async () => {
      return await TutorialService.create(
        {
          title: postTitle,
          description: postDescription
        });
    },
    {
      onSuccess: (res) => {
        setPostResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setPostResult(fortmatResponse(err.response?.data || err));
      },
    }
  );
 
  useEffect(() => {
    if (isPostingTutorial) setPostResult("posting...");
  }, [isPostingTutorial]);
 
  function postData() {
    try {
      postTutorial();
    } catch (err) {
      setPostResult(fortmatResponse(err));
    }
  }
 
  const { isLoading: isUpdatingTutorial, mutate: updateTutorial } = useMutation<any, Error>(
    async () => {
      return await TutorialService.update(
        putId,
        {
          title: putTitle,
          description: putDescription,
          published: putPublished
        });
    },
    {
      onSuccess: (res) => {
        setPutResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setPutResult(fortmatResponse(err.response?.data || err));
      },
    }
  );
 
  useEffect(() => {
    if (isUpdatingTutorial) setPutResult("updating...");
  }, [isUpdatingTutorial]);
 
  function putData() {
    if (putId) {
      try {
        updateTutorial();
      } catch (err) {
        setPutResult(fortmatResponse(err));
      }
    }
  }
 
  const { isLoading: isDeletingTutorials, mutate: deleteAllTutorials } = useMutation<any, Error>(
    async () => {
      return await TutorialService.deleteAll();
    },
    {
      onSuccess: (res) => {
        setDeleteResult(fortmatResponse(res));
      },
      onError: (err: any) => {
        setDeleteResult(fortmatResponse(err.response?.data || err));
      },
    }
  );
 
  useEffect(() => {
    if (isDeletingTutorials) setDeleteResult("deleting...");
  }, [isDeletingTutorials]);
 
  function deleteAllData() {
    try {
      deleteAllTutorials();
    } catch (err) {
      setDeleteResult(fortmatResponse(err));
    }
  }
 
  //// Moriarty's back! Ha Ha ha haaaaaaaaaaaa !!!
  // 1. mutate deletion of one 'tutorial'
  // 2. 'effect' it (-;
  // 3. add missing handler
 
 
 
 
 
 
 
 
 
 
 
 
  const clearGetOutput = () => {
    setGetResult(null);
  };
 
  const clearPostOutput = () => {
    setPostResult(null);
  };
 
  const clearPutOutput = () => {
    setPutResult(null);
  };
 
  const clearDeleteOutput = () => {
    setDeleteResult(null);
  };
 
  return (
    <div id="app" className="container my-3">
      <h3>React Query Axios Typescript example</h3>
 
      <div className="card mt-3">
        <div className="card-header">React Query Axios Typescript GET - BezKoder.com</div>
        <div className="card-body">
          <div className="input-group input-group-sm">
            <button className="btn btn-sm btn-primary" onClick={getAllData}>
              Get All
            </button>
 
            <input
              type="text"
              value={getId}
              onChange={(e) => setGetId(e.target.value)}
              className="form-control ml-2"
              placeholder="Id"
            />
            <div className="input-group-append">
              <button className="btn btn-sm btn-primary" onClick={getDataById}>
                Get by Id
              </button>
            </div>
 
            <input
              type="text"
              value={getTitle}
              onChange={(e) => setGetTitle(e.target.value)}
              className="form-control ml-2"
              placeholder="Title"
            />
            <div className="input-group-append">
              <button
                className="btn btn-sm btn-primary"
                onClick={getDataByTitle}
              >
                Find By Title
              </button>
            </div>
 
            <button
              className="btn btn-sm btn-warning ml-2"
              onClick={clearGetOutput}
            >
              Clear
            </button>
          </div>
 
          {getResult && (
            <div className="alert alert-secondary mt-2" role="alert">
              <pre>{getResult}</pre>
            </div>
          )}
        </div>
      </div>
 
      <div className="card mt-3">
        <div className="card-header">React Query Axios Typescript POST - BezKoder.com</div>
        <div className="card-body">
          <div className="form-group">
            <input
              type="text"
              value={postTitle}
              onChange={(e) => setPostTitle(e.target.value)}
              className="form-control"
              placeholder="Title"
            />
          </div>
          <div className="form-group">
            <input
              type="text"
              value={postDescription}
              onChange={(e) => setPostDescription(e.target.value)}
              className="form-control"
              placeholder="Description"
            />
          </div>
          <button className="btn btn-sm btn-primary" onClick={postData}>
            Post Data
          </button>
          <button
            className="btn btn-sm btn-warning ml-2"
            onClick={clearPostOutput}
          >
            Clear
          </button>
 
          {postResult && (
            <div className="alert alert-secondary mt-2" role="alert">
              <pre>{postResult}</pre>
            </div>
          )}
        </div>
      </div>
 
      <div className="card mt-3">
        <div className="card-header">React Query Axios Typescript PUT - BezKoder.com</div>
        <div className="card-body">
          <div className="form-group">
            <input
              type="text"
              value={putId}
              onChange={(e) => setPutId(e.target.value)}
              className="form-control"
              placeholder="Id"
            />
          </div>
          <div className="form-group">
            <input
              type="text"
              value={putTitle}
              onChange={(e) => setPutTitle(e.target.value)}
              className="form-control"
              placeholder="Title"
            />
          </div>
          <div className="form-group">
            <input
              type="text"
              value={putDescription}
              onChange={(e) => setPutDescription(e.target.value)}
              className="form-control"
              placeholder="Description"
            />
          </div>
          <div className="form-check mb-2">
            <input
              type="checkbox"
              name="putPublished"
              checked={putPublished}
              onChange={(e) => setPutPublished(e.target.checked)}
              className="form-check-input"
            />
            <label className="form-check-label" htmlFor="putPublished">
              Publish
            </label>
          </div>
          <button className="btn btn-sm btn-primary" onClick={putData}>
            Update Data
          </button>
          <button
            className="btn btn-sm btn-warning ml-2"
            onClick={clearPutOutput}
          >
            Clear
          </button>
 
          {putResult && (
            <div className="alert alert-secondary mt-2" role="alert">
              <pre>{putResult}</pre>
            </div>
          )}
        </div>
      </div>
 
      <div className="card mt-3">
        <div className="card-header">
          React Query Axios Typescript DELETE - BezKoder.com
        </div>
        <div className="card-body">
          <div className="input-group input-group-sm">
            <button className="btn btn-sm btn-danger" onClick={deleteAllData}>
              Delete All
            </button>
 
            <input
              type="text"
              value={deleteId}
              onChange={(e) => setDeleteId(e.target.value)}
              className="form-control ml-2"
              placeholder="Id"
            />
            <div className="input-group-append">
              <button
                className="btn btn-sm btn-danger"
                onClick={deleteDataById}
              >
                Delete by Id
              </button>
            </div>
 
            <button
              className="btn btn-sm btn-warning ml-2"
              onClick={clearDeleteOutput}
            >
              Clear
            </button>
          </div>
 
          {deleteResult && (
            <div className="alert alert-secondary mt-2" role="alert">
              <pre>{deleteResult}</pre>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}
 
export default App;
-->
 
== React Hook Form ==
* '''Less code''', more performant
** reduces the amount of code we need to write
** removes unnecessary re-renders
* '''Isolate''' re-renders
** ability to isolate component re-renders -> better performance on our page or app
* '''Subscriptions''' - better performance
** ability to '''subscribe to individual input''' and form State update without re-rendering the entire form
* Faster '''mounting''' (than Formik, ReduxForm, etc)
* More here <small>https://react-hook-form.com/</small>
* '''''Example/Exercise''''' - ''react-hook-form-ts''
<br />
<!-- Exercise
import './App.css';
import React from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as Yup from 'yup';
 
type UserSubmitForm = {
  fullname: string;
  username: string;
  email: string;
  password: string;
  confirmPassword: string;
};
 
const App: React.FC = () => {
  const validationSchema = Yup.object().shape({
    fullname: Yup.string().required('Fullname is required'),
    username: Yup.string()
      .required('Username is required')
      .min(6, 'Username must be at least 6 characters')
      .max(20, 'Username must not exceed 20 characters'),
    email: Yup.string()
      .required('Email is required')
      .email('Email is invalid'),
    password: Yup.string()
      .required('Password is required')
      .min(6, 'Password must be at least 6 characters')
      .max(40, 'Password must not exceed 40 characters'),
    confirmPassword: Yup.string()
      .required('Confirm Password is required')
      .oneOf([Yup.ref('password'), ''], 'Confirm Password does not match')
  });
 
  const {
    register,
    handleSubmit,
    reset,
    formState: { errors }
  } = useForm<UserSubmitForm>({
    resolver: yupResolver(validationSchema)
  });
 
  const onSubmit = (data: UserSubmitForm) => {
    console.log(JSON.stringify(data, null, 2));
  };
 
  return (
    <div className="register-form">
      <form onSubmit={handleSubmit(onSubmit)}>
        <div className="form-group">
          <label>Full Name</label>
          <input
            type="text"
            {...register('fullname')}
            className={`form-control ${errors.fullname ? 'is-invalid' : ''}`}
          />
          <div className="invalid-feedback">{errors.fullname?.message}</div>
        </div>
 
        <div className="form-group">
          <label>Username</label>
          <input
            type="text"
            {...register('username')}
            className={`form-control ${errors.username ? 'is-invalid' : ''}`}
          />
          <div className="invalid-feedback">{errors.username?.message}</div>
        </div>
 
        <div className="form-group">
          <label>Email</label>
          <input
            type="text"
            {...register('email')}
            className={`form-control ${errors.email ? 'is-invalid' : ''}`}
          />
          <div className="invalid-feedback">{errors.email?.message}</div>
        </div>
 
        <div className="form-group">
          <label>Password</label>
          <input
            type="password"
            {...register('password')}
            className={`form-control ${errors.password ? 'is-invalid' : ''}`}
          />
          <div className="invalid-feedback">{errors.password?.message}</div>
        </div>
        <div className="form-group">
          <label>Confirm Password</label>
          <input
            type="password"
            {...register('confirmPassword')}
            className={`form-control ${
              errors.confirmPassword ? 'is-invalid' : ''
            }`}
          />
          <div className="invalid-feedback">
            {errors.confirmPassword?.message}
          </div>
        </div>
 
        <div className="form-group">
          <button type="submit" className="btn btn-primary">
            Register
          </button>
          <button
            type="button"
            onClick={() => reset()}
            className="btn btn-warning float-right"
          >
            Reset
          </button>
        </div>
      </form>
    </div>
  );
};
 
export default App;
-->
 
== MUI ==
* '''MUI System''' - CSS utilities
** <small>https://mui.com/system/getting-started/</small>
* '''BaseUI''' - blank canvas
** <small>https://mui.com/base-ui/</small>
* '''MaterialUI''' - Google's Material Design
** <small>https://mui.com/material-ui/</small>
* '''JoyUI''' - MUI's own design
** <small>https://mui.com/joy-ui/getting-started/</small>
* ''MUI X'' - free + paid pro-options
** <small>https://mui.com/x/react-data-grid/demo/</small>
* ''Store'' with '''paid''' goodies
** <small>https://mui.com/store/</small>
 
=== MUI System ===
* Set of '''utilities''' (''Flexbox, Shadows, Typography, etc'')
** for quickly '''applying styles''' to components from: ''BUI, MUI, JUI''
** or to any '''third-party''' components
* Helps to '''build''' custom designs more '''efficiently'''
** and to '''rapidly lay out''' custom designs
* Set of flexible, generic '''wrapper''' components (''Box, Container, Grid, Stack'')
** can be quickly customized using the <big>'''sx'''</big> ''prop''
*** styles can be '''defined directly''' within the components themselves
*** '''simplifies''' bulky and redundant definitions of ''styled-components''
*** ensures '''consistency''' in '''one-off''' styles too
* ''CSS tricks'' - <small>https://css-tricks.com/snippets/css/a-guide-to-flexbox/</small>
 
=== BaseUI ===
* '''Library''' of "unstyled" ''React'' components (standalone) and '''low-level''' hooks
* Set of foundational '''"headless"''' components  
** can be built with using '''any styling''' solution
** no need to '''override''' any default style engine or theme
** no need to '''override''' any default style engine or theme
*  "skeletal" version of ''Material UI''
=== MaterialUI ===
* Uses this under the bonnet - <small>https://m2.material.io/</small>
* '''Comprehensive''' and can be used '''in production''' out of the box
** comes packaged with '''default styles'''
** is '''optimized''' to work with
*** '''Emotion''' (default CSS engine, in v6) - <small>https://emotion.sh/docs/introduction</small>
*** or '''styled-components''' - <small>https://styled-components.com/</small>
* ''Templates'' - <small>https://mui.com/material-ui/getting-started/templates/</small>
** Customizing - <small>https://mui.com/material-ui/customization/how-to-customize/</small>
*** Theming - <small>https://mui.com/material-ui/customization/theming/</small>
*** Tools/Builders
**** '''Theme Creator''' - <small>https://zenoo.github.io/mui-theme-creator/</small>
**** '''Color Palette Generator''' - <small>https://m2.material.io/inline-tools/color/</small>
* '''''Example''''' - ''material-ui-vite-ts''
=== MaterialUI Exercise ===
# Here choose the '''environment'''
#* <small>https://mui.com/material-ui/getting-started/example-projects/</small>
#* we will use that one
#** <small>https://github.com/mui/material-ui/tree/master/examples/material-ui-vite-ts</small>
# Next decide about the '''template''' (or not)
#* <small>https://mui.com/material-ui/getting-started/templates/</small>
#* let's take this one - ''Blog''
#** <small>https://github.com/mui/material-ui/tree/v6.1.1/docs/data/material/getting-started/templates/blog</small>
#** make it working
# Finally try to use this '''builder'''
#* <small>https://zenoo.github.io/mui-theme-creator/</small>
#** create custom theme settings with it - apply them
=== JoyUI ===
* List Example - <small>https://mui.com/joy-ui/react-list/</small>
* Template Example - <small>https://mui.com/joy-ui/getting-started/templates/order-dashboard/</small>


== React with Typescript ==
== React with Typescript ==

Latest revision as of 14:11, 31 October 2024


Reactjs

Reactjs Training Materials

Introduction

  • Developed by engineers at Facebook
    • "A JavaScript library for building user interfaces"
  • React enables websites to display
    • Complex animations
    • Large volumes of data
    • Other memory-heavy tasks without slowing down

Intro Con't

  • Declarative
  • Component-Based
  • Reusable and independent

Declarative

  • Declarative views make the code more predictable and easier to debug
  • Very easy way to create interactive UIs
  • Simple views for each state in the application
  • Efficient update and render of the right components when the data changes

Component-Based

  • Encapsulated components that manage their own state
    • Compose them to make complex UIs
  • Component logic is written in JavaScript instead of templates
    • Easy to pass rich data through the app
    • In the same time keeps state out of the DOM

Reusable

  • Independent from technology stack used in the whole project
  • We can develop new features without rewriting existing code
  • Can render on the server (for example using Nodejs)
  • Can power mobile apps using React Native

Design principles behind React

  • JSX
  • Rendering Elements
  • Components and props

JSX

Rendering Elements

Building your first component

  • Component lets split the UI into independent, reusable and isolated pieces
  • Components are like JavaScript functions (conceptually)
    • Accept arbitrary inputs (called "props")
      • Props are read-only (function can't change it's own props)
    • Return React elements describing what should appear on the screen
  • The simple one-page app example
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <script crossorigin>
        // const process = { 'env': { 'NODE_ENV' : 'development' } };
    </script>
    <script crossorigin src="https://unpkg.com/react@18.3.1/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
  </head>
  <body>
    <div id="rooty"></div>
    <script type="text/babel">

      ReactDOM.createRoot(
        document.getElementById('rooty')
      ).render(<h1>Hi Universe!</h1>);

    </script>
  </body>
</html>

How components work in React

  • Components implement a render() method
    • Takes input data and returns what to display
    • Often uses an XML-like syntax called JSX
    • Can access input data passed into the component via this.props
    • Input data passed into the component can be accessed by render() via this.props

Example 1

        //// JSX code
        class HelloMessage extends React.Component {
            render() {
            // JSX element treated as JavaScript expression:
            // can be saved in a variable, passed to a function, stored in an object or array, etc
                return <div>Hello {this.props.name}</div>;
            }
        }

        ReactDOM.createRoot(document.getElementById('rooty')).render(<HelloMessage name="Robert" />);


        //// COMPILED JS
        // class HelloMessage extends React.Component {
        //     render() {
        //         return React.createElement(
        //         "div",
        //         null,
        //         "Hello ",
        //         this.props.name
        //         );
        //     }
        // }

        // ReactDOM.createRoot(document.getElementById('rooty')).render(React.createElement(HelloMessage, { name: "Robert" }));

Exercise 1

Rewrite it into React function (-:

The component life cycle

  • In application with many components
    • Components are destroyed
    • Resources taken by the components have to be free up
  • Mounting
    • Whenever the component is rendered to the DOM for the first time
  • Unmounting
    • Whenever the DOM produced by the component is removed
  • Special(our own) methods called lifecycle hooks
  • setState() schedules updates to the component local state
  • "Common lifecycles"

Handling state in React

  • State is similar to props
    • But it is private and fully controlled by the component
    • Local state is a feature available only to classes
  • Component
    • Takes input data (accessed via this.props)
    • Can maintain internal state data (accessed via this.state)
    • When a component's state data changes, the rendered markup will be updated by re-invoking render()

Example 2

//// JSX part
class Timer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {secondsElapsed: 0};
  }

  tick() {
    this.setState((prevState) => ({
      secondsElapsed: prevState.secondsElapsed + 1
    }));
  }
  // Lifecycle hooks
  componentDidMount() {
    this.interval = setInterval(() => this.tick(), 1000);
  }

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

  render() {
    return (
      <div>Seconds Elapsed: {this.state.secondsElapsed}</div>
    );
  }
}

//// 'createRoot()' replaced 'ReactDOM.render()', below previous way (for React 17 and older)
// ReactDOM.render(<Timer />, document.getElementById('rooty'));
ReactDOM.createRoot(document.getElementById('rooty')).render( <Timer /> );

//// COMPILED JS, rendering part only
// ( ... )
//   render() {
//     return React.createElement(
//       "div",
//       null,
//       "Seconds Elapsed: ",
//       this.state.secondsElapsed
//     );
//   }
// ( ... )

Exercise 2

Guess what? (-!

  • Write function'ish version of it - of course!

Managing state

  • React on its own does not provide built-in support for state management
  • The React community uses libraries
    • Redux
    • MobX
  • React state vs. Redux state

Redux

  • Relies on synchronizing data through a centralized and immutable store of data
  • Updates to that data will trigger a re-render of the application
  • State is updated in an immutable fashion
    • Sending explicit action messages which must be handled by functions called reducers
  • Because of the explicit nature, it is often easier to reason about how an action will affect the state of our program

MobX

  • Relies on functional reactive patterns
    • State is wrapped through observables and passed through as props
    • Keeps state fully synchronized for observers
      • Simply marks state as observable.
  • Already written in TypeScript

React state vs. Redux state

  • Use React state for things that don’t matter globally
    • The form is one-off
    • The form’s data isn’t needed outside of it
    • The form’s state is so complex, managing all of that with Redux would be even worse
  • Use Redux for state that does matter globally
    • We want the input value to outlive the component
    • We have to have several instances of the form, synced
    • We want to share particular input between different forms
    • We want certain form state to have the impact on the rest of our app

Integrating React with other frameworks and plugins

Example 3, with ext plugin

// <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/remarkable/1.7.1/remarkable.js"></script>
// 
// JSX code
class MarkdownEditor extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {value: 'Type some *markdown* here!'};
  }

  handleChange(e) {
    this.setState({value: e.target.value});
  }

  getRawMarkup() {
    var md = new Remarkable();
    return { __html: md.render(this.state.value) };
  }

  render() {
    return (
      <div className="MarkdownEditor">
        <h3>Input</h3>
        <textarea
          onChange={this.handleChange}
          defaultValue={this.state.value} />
        <h3>Output</h3>
        <div
          className="content"
          dangerouslySetInnerHTML={this.getRawMarkup()}
        />
      </div>
    );
  }
}

ReactDOM.createRoot(document.getElementById('rooty')).render(<MarkdownEditor />);

Exercise 3

  1. Yup, you know what to do now, for sure (-'
    • You do, right? (-,
    • Function, obviously!
  2. Oh well, let's "kill one stone with many birds", shall we?
    • Create short training course materials: "How to kill one stone with many birds?"
    • Keep it really simple as a presentation - just a couple of slides (2 or 3)
    • Use this extension - https://github.com/rexxars/react-markdown

Bringing it all together - an application

  • Using props and state usually is enough to create the whole application
  • Below TODO list example
    • uses state to track the current list of items
    • Also tracks the text that the user has entered
  • Event handlers appear to be rendered inline
    • But they will be collected and implemented using event delegation

Example 4, app example

// JSX code
class TodoApp extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.state = {items: [], text: ''};
  }

  render() {
    return (
      <div>
        <h3>TODO</h3>
        <TodoList items={this.state.items} />
        <form onSubmit={this.handleSubmit}>
          <input onChange={this.handleChange} value={this.state.text} />
          <button>{'Add #' + (this.state.items.length + 1)}</button>
        </form>
      </div>
    );
  }

  handleChange(e) {
    this.setState({text: e.target.value});
  }

  handleSubmit(e) {
    e.preventDefault();
    var newItem = {
      text: this.state.text,
      id: Date.now()
    };
    this.setState((prevState) => ({
      items: prevState.items.concat(newItem),
      text: ''
    }));
  }
}

class TodoList extends React.Component {
  render() {
    return (
      <ul>
        {this.props.items.map(item => (
          <li key={item.id}>{item.text}</li>
        ))}
      </ul>
    );
  }
}

ReactDOM.createRoot(document.getElementById('rooty')).render(<TodoApp />);

Exercise 4

Yeah, again - I know (---;

  • Make it hooked!

Setting up your development environment

  • Create new app
  • Add React to an existing app

Create new app

Create new app Con't

  • Later we can adjust it and move to production
    • change the configuration
    • provide security (handling users via login and passwd, etc)
    • setup the db (mysql, mongodb, etc)
    • wire our existing web services, write new

Add React to an Existing App

  • No need to rewrite your app to start using React.
  • Start with small part of application (small widget), and see if it works well for our use case
  • React can be used without a build pipeline
  • It's recommended to set it up to be more productive
  • Build pipeline
    • Package manager (Yarn, npm, etc)
      • Vast ecosystem of third-party packages, easy to install or update them
    • Bundler (webpack, Browserify, etc).
      • Lets to write modular code and bundle it together into small packages to optimize load time
    • Compiler (Babel, etc)
      • Lets to write modern JavaScript code that still works in older browsers

Exercise 5, Full envi

We will create simple application with webpack and nodejs.
We will do it in steps, to cover most important elements of Reactjs.
Each step will be explained separately.

Envi preparation steps (command line, in linux you might gonna need also 'sudo ' before the 'npm')
 npx create-react-app my-app
 cd my-app
 npm start

How to use webpack with nodejs goodies

# enabling app in dev envi
npm start 
 
# run tests
npm test

# builds the app for production to the `build` folder
npm run build
  1. Step 1
    • Looking at the template code
  2. Step 2
    • change the file src/App.js
      import './App.css';
      
      function App() {
        return (
          <div className="App">
            <p>Hi Universe!</p>
          </div>
        );
      }
      
      export default App;
      
  3. Step 3
    • Create new component here components/Text.js
      const Text = () => {
          return (
              <p className="text">Lorem ipsum</p>
          ); 
      }
      
      export default Text;
      
  4. Step 4
    • Modify main component src/App.js
      import './App.css';
      import Text from './components/Text';
      
      function App() {
        return (
          <div className="App">
            <p>Hi Universe!</p>
            <Text />
          </div>
        );
      }
      
      export default App;
      
  5. Step 5
    • Understanding state. Change main component file again src/App.js
      import './App.css';
      import { React, useState } from 'react';
      import Text from './components/Text';
      
      function App() {
        const [text, setText] = useState('Not clicked!');
      
        function onButtonClick() {
          setText('Clicked!');
        }
      
        return (
          <div className="App">
            <p>Hi Universe!</p>
            <Text />
            <p>{text}</p>
            <button onClick={onButtonClick}>Click</button>
          </div>
        );
      }
      
      export default App;
      
  6. Step 6
    • Passing parameters(also state) to components.
      • Step 6.1. Modify file src/components/Text.js
        import PropTypes from 'prop-types';
        
        const Text = (props) => {
            return (
                <div>
                    <p className="text">{props.staticText}</p>
                    <p className="text">
                        {`Text from parent: ${props.clickText}`}
                    </p>
                </div>
            ); 
        }
        
        Text.propTypes = {
            staticText: PropTypes.string.isRequired,
            clickText: PropTypes.string.isRequired
        }
        
        export default Text;
        
      • Step 6.2. Modify main component file src/App.js
        import './App.css';
        import { React, useState } from 'react';
        import Text from './components/Text';
        
        function App() {
          const [text, setText] = useState('Not clicked!');
        
          function onButtonClick() {
            setText('Clicked!');
          }
        
          return (
            <div className="App">
              <p>Hi Universe!</p>
              {/* <Text /> */}
              <Text
                // Comment out the line below and check the 'console'
                staticText="Text from child component" 
                clickText={text} 
              />
              <p>{text}</p>
              <button onClick={onButtonClick}>Click</button>
            </div>
          );
        }
        
        export default App;
        

Defining our components' parent/child relationships

Lifting state up

  • A single "source of truth" for any data that changes
    • The state is first added to the component that needs it for rendering
    • Then, if other components also need it, we can lift it up to their closest common ancestor
    • Don't try to sync the state between different components, rely on the top-down data flow
  • More "boilerplate" code than in two-way binding approaches
    • But it takes less work to find and isolate bugs
      • any state "lives" in some component and that component alone can change it
      • the surface area for bugs is greatly reduced
    • We can also implement any custom logic to reject or transform user input
  • If something can be derived from either props or state, it probably shouldn’t be in the state

Containment

  • To reuse code between components
    • Composition model is recommended instead of inheritance
  • For not known children AOT in generic components
    • pass children elements directly with {props.children}
      • by nesting the JSX
    • For multiple generic "barrels" in a component use our own convention
      • pass our own React elements as props like any other data

Specialization

  • Components as special cases of other components
    • PublicCourseEvent and ConsultancyEvent are special cases of CourseEvent
  • Use composition
    • more "specific" component renders a more “generic” one
    • and configures it with props
  • To reuse non-UI functionality between components
    • extract it into a separate JavaScript module
    • the components may import it and use its constructs without extending it

Event handling and conditional rendering

Container vs Presentational Components

"Remember, components don’t have to emit DOM. They only need to provide composition boundaries between UI concerns"
  Dan Abramov (Co-author of Redux and Create React App)

Presentational Components

Dan Abramov's presentational components:

  • Are concerned with how things look
  • May contain both presentational and container components** inside, and usually have some DOM markup and styles of their own
  • Often allow containment via this.props.children
  • Have no dependencies on the rest of the app, such as Flux actions or stores
  • Don’t specify how the data is loaded or mutated
  • Receive data and callbacks exclusively via props
  • Rarely have their own state (when they do, it’s UI state rather than data)
  • Are written as functional components unless they need state, lifecycle hooks, or performance optimizations
  • Examples: Page, Sidebar, Story, UserInfo, List

Container Components

Dan's container components:

  • Are concerned with how things work
  • May contain both presentational and container components inside
    • but usually don’t have any DOM markup of their own except for some wrapping divs,
    • and never have any styles
  • Provide the data and behavior to presentational or other container components
  • Call Flux actions and provide these as callbacks to the presentational components
  • Are often stateful, as they tend to serve as data sources
  • Are usually generated using higher order components
    • such as connect() from React Redux, createContainer() from Relay, or Container.create() from Flux Utils,
    • rather than written by hand
  • Examples: UserPage, FollowersSidebar, StoryContainer, FollowedUserList

What are the benefits

  • Better separation of concerns
    • We understand our app and our UI better by writing components this way
  • Better reusability
    • We can use the same presentational component with completely different state sources
    • and turn those into separate container components that can be further reused
  • Presentational components are essentially our app’s palette
    • We can put them on a single page and let the designer tweak all their variations without touching the app’s logic
    • We can run screenshot regression tests on that page
  • This forces us to extract layout components such as Sidebar, Page, ContextMenu and use this.props.children
    • instead of duplicating the same markup and layout in several container components

Designing our React app

  • Prepare dry dummy interface
  • Break the UI into a Component Hierarchy
  • Build a static version in React (just props, no state yet)
  • Identify complete and minimal representation of UI state
  • Identify where our state should Live
  • Add inverse data Flow

Identify the minimal representation of UI state

Criteria for each piece of data (yes = probably not state)

  • Passed in from a parent via props?
  • Remain unchanged over time?
  • Computable based on any other state or props in our component?

Identify where our state should Live

For each piece of state in our application

  • Identify every component that renders something based on that state
  • Find a common owner component
    • a single component above all the components that need the state in the hierarchy
  • Either the common owner or another component higher up in the hierarchy should own the state
  • If we can’t find a component where it makes sense to own the state
    • create a new component simply for holding the state
    • and add it somewhere in the hierarchy above the common owner component

Main Exercises

  1. Pesel app
    • Migrate from jQuery to React
      • do it with React functions (-----:
      • Separate validations into JS module
  2. Jewel app
    1. First migrate it from Angular to React - yes, functioooooooons ruuuleeeeezzzzzz (--;
      • follow the best practices - design first, then code it (check again this Approach)
    2. Improve it with form element
    3. Extend it with adding new jewel to model
      • Add custom validation - adding same jewel not allowed
    4. Make sure gamers can see the list of available jewels
    5. Wrap it into simple navigation - use react-router module
      • Home page - shows only description of the game
      • Play it - redirects to the game itself
      • Show jewels - view with all the gems
      • Add Jewel - new jewel form
      • Pesel - form from the previous exercise
      • Simple - our first full example (clicked or not clicked)
    6. Make a view with single jewel
      • Improve our jewel - add description field
    7. Allow to remove one jewel
      • Extend it - removal of all jewels
    8. Make one jewel editable

Handling Forms in React

  • Controlled vs uncontrolled inputs
    • From uncontrolled to controlled
  • Field level validations
  • Disabling submit button
  • Instant fields validation
  • Validation on submit
  • Validating several fields
  • Default values
  • Wizard form
  • Migrating to RedUX
  • Dynamic form inputs
  • Context vs props

Controlled vs uncontrolled

Feature/descr Parameters One-time value retrieval Validating on submit Field validation Disabling submit button Enforcing input format Several inputs Dynamic inputs
Uncontrolled ref Yes Yes No No No No No
Controlled props + state Yes Yes Yes Yes Yes Yes Yes

Uncontrolled inputs

  • They are like traditional HTML form inputs
  • They remember what we typed
  • Their value can be reached via ref
  • We have to 'pull' the value from the field when we need it

Uncontrolled inputs example

class myFormy extends Component {
  handleSubmitClick = () => {
    const myName = this._myName.value;
    // use somehow the `myName`
  }

  render() {
    return (
      <div>
        <input type="text" ref={input => this._myName = input} />
        <button onClick={this.handleSubmitClick}>Give me fever</button>
      </div>
    );
  }
}

Controlled inputs

  • Accept their current value as a prop
    • also a callback to change that value
  • It’s a more "React way" of approaching this
  • The value of input has to live in the state somewhere
    • The form component that renders the input saves that in its state
    • It also can be in the state of another component
    • or even in the separate state store, like Redux

Controlled inputs example

class myFormy extends Component {
  constructor() {
    super();
    this.state = {
      myPropy: '',
    };
  }

  handleMyPropyChange = (event) => {
    this.setState({ myPropy: event.target.value });
  };

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.myPropy}
          onChange={this.handleMyPropyChange}
        />
      </div>
    );
  }
}

From uncontrolled to controlled

Steps

  1. Identify all form controls — textboxes, selects, checkboxes
  2. Initialize the state for each of them
  3. Make them "get" their value from this.state.
  4. And "set" new values to this.state (onChange prop)
  5. Change submit handler to get values from the state, instead of refs

Before

class UncontrForm extends Component {
  handleMySubmit = () => {
    const emailAddr = this._emailAddrInp.value;
    const yesCheckx = this._yesCheckx.checked;
    // ...
  };

  render() {
    return (
      <form onSubmit={this.handleMySubmit}>
        <input type="email" ref={inset => this._emailAddrInp = inset} />
        <input type="checkbox" ref={inset => this._yesCheckx = inset} />
      </form>
    );
  }
}

After

class ContrForm extends Component {
  constructor() {
    super();
    this.state = {
      emailA: '',
      yesOrNo: false,
    };
  }

  handleMySubmit = () => {
    const emailAddr = this.state.emailA;
    const yesCheckx = this.state.yesOrNo;
    // ...
  };

  handleEmailaChange = (e) => {
    this.setState({ emailA: e.target.value });
  };

  handleYesornoChange = (e) => {
    this.setState({ yesOrNo: e.target.checked });
  };

  render() {
    return (
      <form onSubmit={this.handleMySubmit}>
        <input onChange={this.handleEmailaChange} />
        <input type="email" value={this.state.emailA} onChange={this.handleEmailaChange} />
        <input type="checkbox" checked={this.state.yesOrNo} onChange={this.handleYesornoChange} />
      </form>
    );
  }
}

No Field level validations

  • Validation status isn’t "local" to fields
  • Not all validations are field-centric
  • Data flow is reversed (see later examples with instant and redux validations)
  • All fields have to be on-screen (multi-step forms, etc)

Disabling submit button

class SignUpForm extends React.Component {
  constructor() {
    super();
    this.state = {
      email: '',
      password: '',
    };
  }
  
  handleEmailChange = (evt) => {
    this.setState({ email: evt.target.value });
  }
  
  handlePasswordChange = (evt) => {
    this.setState({ password: evt.target.value });
  }
  
  handleSubmit = (evt) => {
    if (!this.canBeSubmitted()) {
      evt.preventDefault();
      return;
    }
    const { email, password } = this.state;
    alert(`Signed up with email: ${email} password: ${password}`);
  }
  
  canBeSubmitted() {
    const { email, password } = this.state;
    return (
      email.length > 0 &&
      password.length > 0
    );
  }
  
  render() {
    const isEnabled = this.canBeSubmitted();
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type="text"
          placeholder="Enter email"
          value={this.state.email}
          onChange={this.handleEmailChange}
        />
        <input
          type="password"
          placeholder="Enter password"
          value={this.state.password}
          onChange={this.handlePasswordChange}
        />
        <button disabled={!isEnabled}>Sign up</button>
      </form>
    )
  }
}

Instant fields validation

function validate(email, password) {
  // true means invalid, so our conditions got reversed
  return {
    email: email.length === 0,
    password: password.length === 0,
  };
}

class SignUpFormInstVal extends React.Component {
  constructor() {
    super();
    this.state = {
      email: '',
      password: '',
      
      everFocusedEmail: false,
      everFocusedPassword: false,
      inFocus: '',
    };
  }
  
  handleEmailChange = (evt) => {
    this.setState({ email: evt.target.value });
  }
  
  handlePasswordChange = (evt) => {
    this.setState({ password: evt.target.value });
  }
  
  handleSubmit = (evt) => {
    if (!this.canBeSubmitted()) {
      evt.preventDefault();
      return;
    }
    const { email, password } = this.state;
    alert(`Signed up with email: ${email} password: ${password}`);
  }
  
  canBeSubmitted() {
    const errors = validate(this.state.email, this.state.password);
    const isDisabled = Object.keys(errors).some(x => errors[x]);
    return !isDisabled;
  }
  
  render() {
    const errors = validate(this.state.email, this.state.password);
    const isDisabled = Object.keys(errors).some(x => errors[x]);
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          className={errors.email ? "error" : ""}
          type="text"
          placeholder="Enter email"
          value={this.state.email}
          onChange={this.handleEmailChange}
        />
        <input
          className={errors.password ? "error" : ""}
          type="password"
          placeholder="Enter password"
          value={this.state.password}
          onChange={this.handlePasswordChange}
        />
        <button disabled={isDisabled}>Sign up</button>
      </form>
    )
  }
}

CSS file

* {
  box-sizing: border-box;
}

body {
  padding-top: 0;
  font-family: Helvetica Neue, Helvetica;
  background: #f7f7f7;
}

h3 {
  font-size: 18px;
  margin-top: 20px;
  margin-bottom: 5px;
}

form {
  padding: 0 40px;
}

form input {
  display: block;
  width: 100%;
  font-size: 20px;
  padding: 5px 10px;
  margin: 10px 0;
  border-radius: 5px;
  border: 1px solid #ddd;
}

form input.error {
  border-color: red;
}

form button {
  display: block;
  width: 100%;
  appearance: none;
  border-radius: 5px;
  background-color: #84c00c;
  color: #fff;
  border: none;
  font-size: 16px;
  height: 40px;
  margin-top: 30px;
}

form button:hover {
  background-color: #669509;
}

form button:disabled {
  background-color: #dbf99f;
  color: #333;
}

Instant Fields Exercises

  • Make it more user-friendly and wait for the user's first try, before it shows any error

(Hint: use on-blur event)

  • Force display of errors on all fields,
    • regardless of whether they have been in focus,
    • when the user hovers or clicks a disabled submit button

Validation on submit

function validate(name, email, password) {
  // we are going to store errors for all fields
  // in a signle array
  const errors = [];

  if (name.length === 0) {
    errors.push("Name can't be empty");
  }

  if (email.length < 5) {
    errors.push("Email should be at least 5 charcters long");
  }
  if (email.split('').filter(x => x === '@').length !== 1) {
    errors.push("Email should contain a @");
  }
  if (email.indexOf('.') === -1) {
    errors.push("Email should contain at least one dot");
  }

  if (password.length < 6) {
    errors.push("Password should be at least 6 characters long");
  }

  return errors;
}


class SignUpFormSubmVal extends React.Component {
  constructor() {
    super();
    this.state = {
      errors: [],
    };
    
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(e) {
    e.preventDefault();
    
    const name = ReactDOM.findDOMNode(this._nameInput).value;
    const email = ReactDOM.findDOMNode(this._emailInput).value;
    const password = ReactDOM.findDOMNode(this._passwordInput).value;

    const errors = validate(name, email, password);
    if (errors.length > 0) {
      this.setState({ errors });
      return;
    }

    // submit the data...
  }

  render() {
    const { errors } = this.state;
    return (
      <form onSubmit={this.handleSubmit}>
        {errors.map(error => (
          <p key={error}>Error: {error}</p>
        ))}
        <input
          ref={nameInput => this._nameInput = nameInput}
          type="text"
          placeholder="Name"
        />
        <input
          ref={emailInput => this._emailInput = emailInput}
          type="text"
          placeholder="Email"
        />
        <input
          ref={passwordInput => this._passwordInput = passwordInput}
          type="password"
          placeholder="Password"
        />
        
        <button type="submit">Submit</button>
      </form>
    );
  }
}

Validation Submit Exercise

  1. Rewrite it in a controlled way
  2. Improve validation function and the component
    • Change them in such a way that we would know which error is about which field

Validating several fields

function validate(email, password, passwordConfirm) {
  // true means invalid, so our conditions got reversed
  return {
    email: email.length === 0,
    password: password.length === 0,
    passwordConfirm: passwordConfirm !== password,
  };
}

class SignUpFormSev extends React.Component {
  constructor() {
    super();
    this.state = {
      email: '',
      password: '',
      passwordConfirm: '',
      
      touched: {
        email: false,
        password: false,
        passwordConfirm: false,
      },
    };
  }
  
  handleEmailChange = (evt) => {
    this.setState({ email: evt.target.value });
  }
  
  handlePasswordChange = (evt) => {
    this.setState({ password: evt.target.value });
  }
  
  handlePasswordConfirmChange = (evt) => {
    this.setState({ passwordConfirm: evt.target.value });
  }
  
  handleBlur = (field) => (evt) => {
    this.setState({
      touched: { ...this.state.touched, [field]: true },
    });
  }
  
  handleSubmit = (evt) => {
    if (!this.canBeSubmitted()) {
      evt.preventDefault();
      return;
    }
    const { email, password } = this.state;
    alert(`Signed up with email: ${email} password: ${password}`);
  }
  
  canBeSubmitted() {
    const errors = validate(this.state.email, this.state.password, this.state.passwordConfirm);
    const isDisabled = Object.keys(errors).some(x => errors[x]);
    return !isDisabled;
  }
  
  render() {
    const errors = validate(this.state.email, this.state.password, this.state.passwordConfirm);
    const isDisabled = Object.keys(errors).some(x => errors[x]);
    
    const shouldMarkError = (field) => {
      const hasError = errors[field];
      const shouldShow = this.state.touched[field];
      
      return hasError ? shouldShow : false;
    };
    
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          className={shouldMarkError('email') ? "error" : ""}
          type="text"
          placeholder="Enter email"
          value={this.state.email}
          onChange={this.handleEmailChange}
          onBlur={this.handleBlur('email')}
        />
        <input
          className={shouldMarkError('password') ? "error" : ""}
          type="password"
          placeholder="Enter password"
          value={this.state.password}
          onChange={this.handlePasswordChange}
          onBlur={this.handleBlur('password')}
        />
        <input
          className={shouldMarkError('passwordConfirm') ? "error" : ""}
          type="password"
          placeholder="Confirm password"
          value={this.state.passwordConfirm}
          onChange={this.handlePasswordConfirmChange}
          onBlur={this.handleBlur('passwordConfirm')}
        />
        <button disabled={isDisabled}>Sign up</button>
      </form>
    )
  }
}

Default values

  1. Make the form itself fully controlled with regard to its parent (less usual one)
    • It means instead of keeping its own state, the form is always going to receive field values, as well as callbacks to change them
  2. Initialize the form state via props (preferred method)
    • The form will still have its own state with input values. It will simply use the passed field values as a starting point.
class PostForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      title: props.title || '',
      body: props.body || '',
    };
  }
}

// Used as
<PostForm
  title={someTitle}
  body={someBody}
  onSubmit={(newTitle, newBody) => { ... }}
/>

Wizard form

Case with a discussion:

  • Do you see anything problematic in here?
  • Is this wizard more like Harry or Neville? (--:
  • What would've Albus say about it?
class CheckoutFormPersonal extends React.Component {
  constructor() {
    super();
    this.state = {
      name: '',
      email: '',
    };
    
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  
  handleChange(field) {
    return (evt) => this.setState({ [field]: evt.target.value });
  }

  handleSubmit(evt) {
    evt.preventDefault();
    this.props.onSubmit();
  }
  
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <h3>Personal info</h3>
        <input
          type="text"
          placeholder="Enter your name"
          value={this.state.name}
          onChange={this.handleChange('name')}
        />
        <input
          type="text"
          placeholder="Enter email"
          value={this.state.email}
          onChange={this.handleChange('email')}
        />
        
        <button>Next</button>
      </form>
    );
  }
}

class CheckoutFormShipping extends React.Component {
  constructor() {
    super();
    this.state = {
      shipping_line: '',
      shipping_city: '',
      shipping_zip: '',
    };
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  
  handleChange(field) {
    return (evt) => this.setState({ [field]: evt.target.value });
  }

  handleSubmit(evt) {
    evt.preventDefault();
    this.props.onSubmit();
  }
  
  render() {
    return (
      <form onSubmit={this.handleSubmit}>        
        <h3>Shipping</h3>
        <input
          type="text"
          placeholder="Address line"
          value={this.state.shipping_line}
          onChange={this.handleChange('shipping_line')}
        />
        <input
          type="text"
          placeholder="City"
          value={this.state.shipping_city}
          onChange={this.handleChange('shipping_city')}
        />
        <input
          type="text"
          placeholder="ZIP"
          value={this.state.shipping_zip}
          onChange={this.handleChange('shipping_zip')}
        />
        
        <button>Next</button>
      </form>
    );
  }
}

class CheckoutFormBilling extends React.Component {
  constructor() {
    super();
    this.state = {
      billing_line: '',
      billing_city: '',
      billing_zip: '',
    };
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  
  handleChange(field) {
    return (evt) => this.setState({ [field]: evt.target.value });
  }

  handleSubmit(evt) {
    evt.preventDefault();
    this.props.onSubmit();
  }
  
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <h3>Billing</h3>
        <input
          type="text"
          placeholder="Address line"
          value={this.state.billing_line}
          onChange={this.handleChange('billing_line')}
        />
        <input
          type="text"
          placeholder="City"
          value={this.state.billing_city}
          onChange={this.handleChange('billing_city')}
        />
        <input
          type="text"
          placeholder="ZIP"
          value={this.state.billing_zip}
          onChange={this.handleChange('billing_zip')}
        />
        
        <button>Checkout</button>
      </form>
    );
  }
}

class CheckoutForm extends React.Component {
  constructor() {
    super();
    this.state = {
      step: 1,
    };
    this.goToNext = this.goToNext.bind(this);
  }
  
  goToNext() {
    const { step } = this.state;
    if (step !== 3) {
      this.setState({ step: step + 1 });
    } else {
      alert('Submitting');
    }
  };
  
  render() {
    switch (this.state.step) {
      case 1:
        return <CheckoutFormPersonal key="personal" onSubmit={this.goToNext} />;
      case 2:
        return <CheckoutFormShipping key="shipping" onSubmit={this.goToNext} />;
      case 3:
        return <CheckoutFormBilling key="billing" onSubmit={this.goToNext} />;
    }
  }
}

Wizard Exercise

  1. Rewrite it in a controlled way (--:
    • Make sure that on the submit moment we will have all the data from all the steps
  2. Redesign it with hooks (make them React function components)

Migrating to RedUX

  • Requirements change and apps evolve
  • We can’t always know in advance
  • What was perfectly fine to store in the local state might need to be moved to Redux
    • It can be painful to migrate to Redux
  • How to make it smooth?
    • Make small action reducer functions
    • Create a local reducer in our component

Small action reducers

  • They would take current state and some details about the action, and return the new state
  • They can be literally used with the functional form of setState
function changeEmail(state, newEmail) {
  return { ...state, email: newEmail }
}

changeEmail(state, 'new@email.com') // => returns state with the new value of email
handleEmailChange = (evt) => {
  this.setState(state => changeEmail(state, event.target.value);
};

Small action reducers con't

Migrating it to Redux

  • Instead of calling setState(state => changeEmail(state, ...)) dispatch this.props.onChangeEmail(...);
  • Getting the current values of fields: this.state goes to this.props
  • Function becomes a branch in the reducer
function loginFormReducer(state, action) {
    switch (action.type) {
      case 'changeEmail': {
        return { ...state, email: action.newEmail };
      }
    }
  }

Local reducer

function changeEmail(newEmail) {
  return { type: 'changeEmail', email: newEmail };
}

class Form extends Component {
 // ...
  reduce = action => this.setState(state => this.reducer(state, action));

  reducer = (state, action) => {
    switch (action.type) { // note we're switching by action.type
      case 'changeEmail': return { ...state, email: action.email };
    }
  };

  render() {
    // ...
    <input
      ...
      onChange={evt => this.reduce(changeEmail(evt.target.value))}
    />
  }
}

Local reducer con't

Switching it from local state to Redux

  • changeEmail(...) would be left without changes, and would be connected by react-redux
  • Instead of having reducer in the class, it would become a Redux reducer
  • this.reduce(changeEmail(...)) would be replaced by this.props.onChangeEmail(...)

Dynamic form inputs

Dealing with array inputs

  • add a new item
  • delete an existing one
    • we need to identify it
  • change details of it
    • have a generic function which accepts a field name
      • to reduce boilerplate if there are several fields
    • have several specialized functions, one for each field
      • allow to execute different logic depending on the field

Dynamic form inputs example

class IncorporationForm extends React.Component {
  constructor() {
    super();
    this.state = {
      name: '',
      shareholders: [{ name: '' }],
    };
  }
  
  handleNameChange = (evt) => {
    this.setState({ name: evt.target.value });
  }
  
  handleShareholderNameChange = (idx) => (evt) => {
    const newShareholders = this.state.shareholders.map((shareholder, sidx) => {
      if (idx !== sidx) return shareholder;
      return { ...shareholder, name: evt.target.value };
    });
    
    this.setState({ shareholders: newShareholders });
  }
  
  handleSubmit = (evt) => {
    const { name, shareholders } = this.state;
    alert(`Incorporated: ${name} with ${shareholders.length} shareholders`);
  }
  
  handleAddShareholder = () => {
    this.setState({ shareholders: this.state.shareholders.concat([{ name: '' }]) });
  }
  
  handleRemoveShareholder = (idx) => () => {
    this.setState({ shareholders: this.state.shareholders.filter((s, sidx) => idx !== sidx) });
  }
  
  render() {    
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type="text"
          placeholder="Company name, e.g. Magic Everywhere LLC"
          value={this.state.name}
          onChange={this.handleNameChange}
        />
      
        <h4>Shareholders</h4>
      
        {this.state.shareholders.map((shareholder, idx) => (
          <div className="shareholder">
            <input
              type="text"
              placeholder={`Shareholder #${idx + 1} name`}
              value={shareholder.name}
              onChange={this.handleShareholderNameChange(idx)}
            />
            <button type="button" onClick={this.handleRemoveShareholder(idx)} className="small">-</button>
          </div>
        ))}
        <button type="button" onClick={this.handleAddShareholder} className="small">Add Shareholder</button>
        <button>Incorporate</button>
      </form>
    )
  }
}

Using context instead of props

  • Passing props can become inconvenient if
    • we need to pass through many components
    • many components need the same info
  • Context makes some information available
    • to any component in the tree below the parent
    • no matter how deep it is
    • no need to explicitly passing through props
  • The context itself does not hold the information
    • it only represents the kind of information we can provide or read from components
  • Related docs:
  • Exercise
    • Try to re-design our Wizard form with context
      • What can you say?
      • What could be in the custom context there and what not (or shouldn't)?

Routing in React

  • Static Routing
    • configuration happens before our app renders
  • Dynamic Routing
    • routing that takes place as our app is rendering
  • Nested routes
  • Responsive routes
  • https://reactrouter.com/en/main

Adding Router lib

npm install react-router-dom

Dynamic routing

  • Routers
  • Route Matching
  • Main Route Props
  • Navigation

Routers

  • A router component at the core
  • Creates a specialized history object
  • For web projects ('react-router-dom' lib)
    • <BrowserRouter> - a server that responds to requests
    • <HashRouter> - a static file server
  • For mobiles ('react-router-native' lib)
    • <NativeRouter>

Route Matching

  • Comparing a <Route>'s path prop to the current location’s pathname
  • When matches it will render its content, otherwise null
  • A <Route> with no path will always match
  • Route matching components
    • <Route>
      • include to render content based on the location
      • list a number of possible <Route>s next to each other
    • <Routes>
      • used to group <Route>s together

Main Route Props

  • element - should be used for an existing element/component
    • React.Component, functional component, just element
  • path - the url's part

Navigation

  • <Link> - component to create links
  • <NavLink> - a special type of <Link>
    • can style itself as "active" when its to prop matches the current location
  • <Navigate> - to force navigation

Router example

import { Routes, Route, Link } from "react-router-dom";

const Home = () => (
  <div>
    <h2>Home</h2>
    <p>Navigation example</p>
    {/* <YourHomeComponent /> */}
  </div>
);

const About = () => (
  <div>
    <h2>About</h2>
    <p>What are we doing here?</p>
  </div>
);

const MyApp = () => {
    return (
        <div>
          <nav>
            <li>
              <Link to={"/"}>Home</Link>
            </li>
            <li>
              <Link to={"/about"}>About</Link>
            </li>
          </nav>
          <hr />
          <Routes>
              <Route path="/" element={<Home />} />
              <Route path="/about" element={<About />} />
          </Routes>
        </div>
    );
}

export default MyApp;

Nested Routes

  • Router has no 'nesting' API
  • Route is just a component (like div, etc)

Nested routes example

import { Routes, Route, Link, Outlet } from "react-router-dom";
import Timer from "./Timer";

function Home() {
    return ( <div>HOME page</div> );
}

function Messages() {
    return ( <div><p>Message 1</p><p>Message 2</p></div> );
}

function Todos() {
    return (
        <>
            <div>
                <h4>Todo 1</h4>
                <p>Fix the car</p>
            </div>
            <div>
                <h4>Todo 2</h4>
                <p>Spend some time with wife</p>
            </div>        
        </>
    );
}

function Menu() {
    return (
      <div>
        <h2>Menu</h2>
        <nav>
            <li><Link to={"messages"}>Messages</Link></li>
            <li><Link to={"todos"}>Todos</Link></li>
        </nav>
        {/* This element will render either <Messages> when the URL is
            "/messages" or <Todos> at "/todos"
        */}
        <Outlet />
      </div>
    );
}
  
function Nested() {
    return (
        <div>
            <h1>Main Menu</h1>            
            <nav>
                <li><Link to={"/"}>Home</Link></li>
                <li><Link to={"/sub"}>Sub</Link></li>
            </nav>            
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="sub" element={<Menu />}>
                    <Route path="messages" element={<Messages />} />
                    <Route path="todos" element={<Todos />} />
                </Route>
            </Routes>
        </div>        
    );
}

export default Nested;
Router Exercises
  1. Replace todos component with our Todos from this Example
  2. Add another link on the parent level
  3. Create 2nd level sub-menu
    • It should start from Messages
    • Put one link there only - showing some Contact details


Managing state with Redux

  • State of our app is stored in an object tree inside a single store
  • To change the state tree we emit an action, an object describing what happened
  • To specify how the actions transform the state tree, we write reducers
  • Doesn't support many stores
    • instead of adding stores, we split the root reducer into smaller reducers
    • independently operating on the different parts of the state tree
  • https://redux.js.org/introduction

Examples and exercises

  1. reduxExamples_
    • crud, jwt, hooks
  2. redux-toolkit-crud - fix me, again (-;
  3. Rewrite our dynamic form example with Redux
  4. Rewrite our FTJ app with Redux

Managing state with Mobx

  • Mobx is not a container for the state (not like Redux)
  • To make objects trackable for MobX
    • Use the @observable decorator or observable(object or array) functions
  • To create functions that can automatically derive their value from the state
    • Use the @computed decorator
  • To automatically run functions that depend on some observable state
    • Use autorun
    • useful for logging, making network requests, etc
  • To make React components truly reactive
    • Use the @observer decorator from the mobx-react package
    • They will update automatically and efficiently (also in large complex applications with large amounts of data)
  • https://mobx.js.org/

Example

// Define your state and make it observable
import {observable} from 'mobx';

var appState = observable({
    timer: 0
});


// Create a view that responds to changes in the State
import {observer} from 'mobx-react';

@observer
class TimerView extends React.Component {
    render() {
        return (<button onClick={this.onReset.bind(this)}>
                Seconds passed: {this.props.appState.timer}
            </button>);
    }

    onReset () {
        this.props.appState.resetTimer();
    }
};

ReactDOM.render(<TimerView appState={appState} />, document.body);


// Modify the State
appState.resetTimer = action(function reset() {
    appState.timer = 0;
});

setInterval(action(function tick() {
    appState.timer += 1;
}), 1000);

Mobx Exercise

Hooks

  • Let use: props, state, context, refs, lifecycle
    • without writing a class
    • provide new way to combine them
  • Optional and backwards-compatible
    • Work side-by-side with existing code - can be adopted gradually
  • Functions that:
    • let "hook into" React state and lifecycle features from function components
    • don’t work inside classes

Hooks Con't

  • Allow to reuse stateful logic without changing component hierarchy
  • Let split one component into smaller functions based on what pieces are related
    • Examples: setting up a subscription or fetching data
  • Common: useState, useEffect, useContext, useRef
  • Custom hooks - reusing stateful behavior between different components
  • Rules about calls
    • only at the top level (not in: loops, conditions, nested f)
    • only from function components (not from regular js) or custom hooks
  • Examples

Custom Hook

We can define our own hooks to use state and other React features without writing a class

  • As a function, it takes input and returns output
  • name starts with use (useQuery, useMedia.., etc)
  • returns a normal, non-jsx data (not like in functional component)
  • can use other hooks such as useState, useReF.. and other custom hooks (-;
  • some libraries also provide hooks - useForm (React Hook Form), useMediaQuery (MUI), useQuery (React Query), etc

Custom Hook Con't

Benefits:

  • Completely separate logic from user interface
  • Reusable in many different components with the same processing logic
    • Therefore, the logic only needs to be fixed in one place if it changes
  • Share logic between components
  • Hide code with complex logic in a component, make the component easier to read

When to use:

  • piece of code (logic) is reused in many places
  • logic is too long and complicated
  • we write custom react library (-:
  • ... ? (your ideas, please!)
  • Example - let's look at useNavigate() in react-router lib
  • Example/Exercise - react-ts-api


React Query

  • Most state management libraries (incl Redux)
    • good for working with client state
    • not for server state
      • persists remotely in a location the client side cannot control
      • can become outdated - we need to make asynchronous APIs for fetching and updating
  • React Query - one of the best libraries for managing server state
    • It helps to fetch, cache, synchronize and update data without touching any global state

React Query Con't

Helps:

  • to remove complicated and misunderstood code and replace with several React Query logic
  • easier to maintain and build new features without worrying about wiring up new server state data sources
  • make our application feel faster and more responsive
  • save bandwidth and increase memory performance
  • Example/Exercise - reactq-axios-ts


React Hook Form

  • Less code, more performant
    • reduces the amount of code we need to write
    • removes unnecessary re-renders
  • Isolate re-renders
    • ability to isolate component re-renders -> better performance on our page or app
  • Subscriptions - better performance
    • ability to subscribe to individual input and form State update without re-rendering the entire form
  • Faster mounting (than Formik, ReduxForm, etc)
  • More here https://react-hook-form.com/
  • Example/Exercise - react-hook-form-ts


MUI

MUI System

  • Set of utilities (Flexbox, Shadows, Typography, etc)
    • for quickly applying styles to components from: BUI, MUI, JUI
    • or to any third-party components
  • Helps to build custom designs more efficiently
    • and to rapidly lay out custom designs
  • Set of flexible, generic wrapper components (Box, Container, Grid, Stack)
    • can be quickly customized using the sx prop
      • styles can be defined directly within the components themselves
      • simplifies bulky and redundant definitions of styled-components
      • ensures consistency in one-off styles too
  • CSS tricks - https://css-tricks.com/snippets/css/a-guide-to-flexbox/

BaseUI

  • Library of "unstyled" React components (standalone) and low-level hooks
  • Set of foundational "headless" components
    • can be built with using any styling solution
    • no need to override any default style engine or theme
  • "skeletal" version of Material UI

MaterialUI

MaterialUI Exercise

  1. Here choose the environment
  2. Next decide about the template (or not)
  3. Finally try to use this builder

JoyUI

React with Typescript

React Native

  • Creates native apps for Android, iOS
  • Written in JavaScript — rendered with native code
  • Seamless Cross-Platform
    • React components wrap existing native code
    • interact with native APIs via React’s declarative UI paradigm and JavaScript
  • Setup and Using
    • Expo Go - quick and easy, but restricted SDK libs
    • Native CLI - longer to prepare, but gives access to many external libs
  • Related docs, examples

React Native Con't

npx create-expo-app nativeExample
cd nativeExample
npx expo start

Later on

  • connect on the same Wifi with phone
  • install Expo Go app on phone
  • scan the QR

Full stack with React

Server side rendering and SEO

  • Next.js
    • Server-rendered by default
    • Automatic code splitting for faster page loads
    • Simple client-side routing (page based)
    • Webpack-based dev environment which supports Hot Module Replacement (HMR)
    • Able to implement with Express or any other Node.js HTTP server
    • Customizable with our own Babel and Webpack configurations
    • Out-of-the-box tools for setting HTML tags for SEO
    • https://nextjs.org/learn/
    • https://zeit.co/blog/next
  • react-seo npm package
  • react-examples
    • 14-Server-Rendering
    • quickfix: remove internal from package.json npm run ssr-... scripts

Why not angular?

Debugging

Upgrading, Refactoring

React's Lego

Testing your React web application

JestGeneric.jpg

Jest Intro

  • No config
    • Supports - Babel, TypeScript, Node, React, Angular, Vue, etc
  • "Snapshotting"
  • Isolated
  • Good API

Overview of Jest

  • Zippy, secure
    • tests have unique global state
    • reliably runs tests in parallel
    • runs previously failed tests first
    • re-organizes runs based on how long test files take

Overview Con't

  • Code coverage (--coverage)
    • No setup needed, collects from entire projects (incl untested files)
  • Simple mocking
    • custom resolver for imports, mocks any object outside of scope
    • rich Mock Functions API with readable syntax

Overview

  • Whacking exceptions
    • rich context for test-fail: toStrictEqual, toHaveProperty, toMatchSnapshoot, toThrowError, etc
  • Well-documented
    • maintained by Christopher Pojer from Facebook and contributors within the community
  • Requires little configuration
  • Extendable

Benefits of testing

Why and how

  • Interfaces depend on browsers, user interactions, and many other variables
    • implementing an effective testing strategy is difficult
  • Consistent results are often affected by false negatives due to different factors (i.e. network)
  • Frequent updates in UI: improve the experience, maximize conversions, new features
  • Tests are hard to write and maintain ==> developers are less prone to cover their applications
  • Tests make developers more confident ==> speed and quality
  • Well tested code and well written tests ==> it works and is ready to ship
    • Easier to refactor the code ==> functionalities do not change during the rewrite
  • Running tests using Node.js and the console ==> faster, more predictable

Benefits of testing Con't

  • Is new feature affecting other parts of the application
    • Tests avoid regressions ==> tell if the new code breaks the old tests
  • Greater confidence in writing new features ==> faster releases
  • Code base more solid ==> new bugs can be reproduced, fixed, and covered by tests
  • Testing components is easy - well organised and assigned responsibilities and boundaries
    • Well written components (pure, composable, reusable) ==> can be tested as simple functions
  • Main goals to test
    • given different sets of props, components output is always correct
    • covering all the various states that a component can have
      • clicking a button ==> tests to check all the related event handlers

Benefits of testing Con't - tdd, design

  • Tests can verify the component's behavior on edge cases
    • all the props are null, or there is an error
  • With React we can mount a tree of components and test the integration between them
  • Techniques - test-driven development (TDD)
    • Applying TDD ==> writing the tests first and then writing the code to pass the tests
    • Force to think more about the design before implementing ==> usually leads to higher quality

Setting up the Testing Environment

  • Examples
    • Folder "testing"
    • Tests from "react-training"
  • Enzyme - helps to test Components' output
    • manipulate, traverse, and in some ways simulate (like with jQuery)
  • Cypress - e2e framework

Installing and Configuring Jest

  • Initial setup
## Installing
npm install --save-dev jest

## Config
jest --init

## The rest depends on other tools: babel, webpack, typescript, etc
  • More here:
    • jestjs.io/docs/getting-started#additional-configuration

Running Watch Mode to Test File Changes

live-server --cors #manual tests with real browser
jest -o  #runs only with uncommited changes (git, mercurial)
jest --watch  #runs jest -o by default
jest --watchAll  #runs all tests

### Interactively - Watch Usage: Press w to show more.

## Watch Usage
# › Press f to run only failed tests.
# › Press o to only run tests related to changed files.
# › Press q to quit watch mode.
# › Press p to filter by a filename regex pattern.
# › Press t to filter by a test name regex pattern.
# › Press Enter to trigger a test run.

More here: jestjs.io/docs/cli

Running Browser Tests through Node

npm test  #via 'create-react-app' it uses by default the extension called 'react-scripts'

Testing a Sample JavaScript Application

TDD - Test Driven Development

  • Red-Green-Refactor cycle
  • Triangulation
    • The more specific tests get, the more general production code needs to get
    • Always implement the simplest thing that will possibly work
    • Hard-coding when it's possible and to get to the real implementation - add more tests
  • AAA rule: Arrange(Assemble), Act, Assert
  • Design principles
    • DRY - Don't Repeat Yourself
      • Drying up tests when refactoring
    • YAGNI - You Ain't Gonna Need It
      • Hold off adding anything to project until we're really sure that it's necessary (adding a user story, a customer asks for it, etc)
      • For example create-react-app application template - favicon.ico, a sample logo, CSS files, etc

TDD Con't

TddJestReact.png

Good vs Great

Good test Great test
Arrange: Sets up test dependencies Short
Act: Executes production code under test Descriptive
Assert: Checks expectations are met Independent of other tests
lol Has no side-effects

Examples

  • Example 1 - "1oneDetailOnly"
  • Example 2 - "2viewsListAndDetail"

Design upfront

JewelsListViewDesign.png

Exercises

  1. ("ex1") Display in the UI the rest of fields from model
    • Wrap data into HTML table
    • Add a heading to Appointment to make it clear which appointment time is being viewed
    • Prepare fake data generator (DRY)
  2. Prepare new Find The Jewel project (name it ftj)
    • Use create-react-app ( cd ; cd Documents/reactjsMaterials ; npx create-react-app ftj )
    • Make oneJewel component and test it
    • Make listJewels component and test it
      • Clicked jewel button should be different then the rest of jewel buttons (add css class dynamically)

Testing a React App

  • Simple form and user input
  • Testing React Components
  • About Stateful Components

Set up, execute function, assert results

Example

  • "4simpleForm"

Exercises

  1. Add dropdown - choosing stylist (before time slot), filtering based on required service
    • reflect stylists availability and the choice (update the table)
  2. Make form for adding new jewel and test it
    • Refactor the app into JewelGame class component with state

Testing the Business Logic

JestJoke.jpeg

Test Doubles

  • Used to verify interactions with collaborating objects
  • Spies and stubs
  • Extracting helper code into modules (readability - shorter and clearer)

Examples

  • "6testDoubles"

Exercises

  1. Extend 6testDoubles
    • Add a test to CustomerForm - the error state is cleared when the form is submitted again
    • Update the AppointmentForm tests to use jest.fn(), jest.spyOn(), and all of the helpers
    • Extend AppointmentForm so that it submits an appointment using a POST request to /appointments (endpoint returns a 201 Created status without any body)
    • Update the tests in AppointmentsDayView to use the helpers
  2. Add engine component in ftj app
    • Test it with doubles
  3. In ftj app refactor 'adding new jewel' into another component

Testing the User Interface

  • Loader and render, shallow rendering
  • Forms
  • Filtering and Searching Data
  • Testing Router
  • Testing Redux
  • Testing GraphQL

Loader and render, shallow rendering

  • Stubbing out components
    • Loader components correctly instantiate rendering components.
  • Shallow rendering
  • Stubbing and spying on child components - avoids running their side effects (fetch requests, etc)
    • Easily assert that we set their props correctly
  • Examples - "8forms"

Forms

  • Client-side validation
  • Handling server errors
  • Indicating submit
  • Examples - "8forms"
  • Exercises
  1. Within "8forms"
    • Clear any validation errors when corrected
    • Use the 'onChange' handler instead of 'onBlur' (faster interaction with user)
    • Disables the Submit button after submitting
    • Write tests all functions within the formValidation module
    • Simplify 'handleSubmit' - extract doSave func (pulls out the main body of the if statement)
  2. With ftj
    • Validate adding new jewel, not allow when it exists already

Filtering and Searching Data

  • More complex user interactions (between UI and an API)
  • Order of adding features
  • Tests help admit when we're doing the wrong thing
  • Examples - "10searchingPaging"
  • Exercises
  1. With "10searchingPaging"
    • Disable the 'Previous' button if the user is on the first page
    • Disable the 'Next' button if the current listing has fewer than 10 records on display
    • The /customers endpoint supports a limit parameter - specifies the number of records that are returned
      • Provide a mechanism for the user to change the number of records returned on each page
  2. For ftj
    • Allow to search for jewels

Testing Router

  • General rules for testing React Router
  • Building a root component
  • Using the location query string to store component state
  • Examples - "12router"
  • Exercises
  1. For ftj
    • Make router for showing list of jewels and a single jewel

Testing Redux

  • Testing a Redux saga
  • Asynchronous requests with saga
  • Switching out component state for Redux state
  • Shifting workflow to Redux
  • Examples - "13redux"
  • Exercises
  1. For todo_ts
    • Add button "RemoveAll"

Testing GraphQL

  • Testing the Relay environment
  • Building the GraphQL reducer
  • Examples - "14graphql"
  • Exercises
  1. With "14graphql"
    • Reshape the remaining fetch calls to use their GraphQL counterparts

Running Snapshot Tests

JestSnapshot.png

Snapshot Tests

  • Useful especially when testing presentational components
    • They do not manage state
    • Typically used to display data passed down from parent components as props
    • or to display hardcoded data directly in the component itself
  • Provided by Jest
    • We simply want to make sure the HTML output of a component does not change unexpectedly
  • Case scenario - developer does change the component's HTML structure
    • He adds another paragraph element with static text
    • Snapshot test will fail and provide a visual of the changes
  • Example - "rtlExamples", 2
  • Exercise - in ftj make a snapshot test
    • Separate game description into new presentational component

Troubleshooting

  • jest-dom methods
    • allow to write more descriptive test code
    • provide better context-specific error messages
  • Using RTL (React Testing Library)
    • Approach
      • render React elements into the Document Object Model (DOM)
      • select resulting DOM elements
      • make assertions on the expected resulting behavior
    • Examples - "rtlExamples"

RTL examples

  • 1 - test-utils VS enzyme VS rtl
  • 2 - presentational, debug
  • 3 - user events, interacting with APIs, again TDD
  • 4 - integration testing, 3rd party plugins
  • 5 - updating dependencies, enzyme2rtl, test-utils2rtl, best practices
  • 6 - tools and plugins, more best practices
  • 7 - e2e, cypress, cucumber

Photos Sources

  • kurierhistoryczny.pl/uploads/articles/237/stanczykmaly_rf89RS.jpg
  • cdn.shopify.com/s/files/1/0882/5118/products/Batman-The-Killing-Joke-Deluxe-Graphic-Novel-1008696_1024x1024.jpeg?v=1461690370
  • wpblog.semaphoreci.com/wp-content/uploads/2019/01/Snapshot_Testing_React_Components_with_Jest_-_Semaphore_CI-1024x575.png