Slide 1

Slide 1 text

Vipul A M TYPES & HOOKS WITH

Slide 2

Slide 2 text

@vipulnsward Saeloun Inc

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

@ReactPune https://www.meetup.com/ReactJS-and-Friends/ 3.2k+

Slide 6

Slide 6 text

https://www.meetup.com/ReactJS-and-Friends/

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

WITH TYPES & HOOKS

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

- Separation of Concerns - No direct interaction between components -Events broadcasted on a global level

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Snappy and Lightweight

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Unmaintained: Last commit 20 Jun 2017

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

- Big Churn -Large codebases are confusing - Changes at a lot of places - Harder to make change

Slide 19

Slide 19 text

FlightJS

Slide 20

Slide 20 text

Introducing React

Slide 21

Slide 21 text

@goshakkk Gosha Arinich

Slide 22

Slide 22 text

Product Edit

Slide 23

Slide 23 text

Purchase Flow- Before

Slide 24

Slide 24 text

After

Slide 25

Slide 25 text

React Bits!

Slide 26

Slide 26 text

- No Big rewrites - Start small -Breaking down existing files

Slide 27

Slide 27 text

Co-location

Slide 28

Slide 28 text

Components + calls from Flight

Slide 29

Slide 29 text

"use strict"; import React from "react"; import { useOnChange } from "$app/components/useOnChange"; import { CustomFieldRow } from "./row"; const ADD_FIELD = "add-field"; const REMOVE_FIELD = "remove-field"; const CHANGE_NAME = "change-name"; const CHANGE_IS_REQUIRED = "change-is-required"; const RESET = "reset"; const customFieldsReducer = (state, action) => { switch (action.type) { case ADD_FIELD: { return [...state, { name: "", required: false }]; } case REMOVE_FIELD: { return state.filter((_, idx) => idx !== action.index); } case CHANGE_NAME: { return state.map((fld, idx) => { if (idx === action.index) { return { ...fld, name: action.newName }; } return fld; }); } case CHANGE_IS_REQUIRED: { return state.map((fld, idx) => { if (idx === action.index) { return { ...fld, required: action.newValue }; } return fld; }); } case RESET: { return action.customFields; } default: return state; } }; const CustomFields = ({ customFields, onStateChange }) => { const [customFieldsState, dispatch] = React.useReducer( customFieldsReducer, customFields, customFields => customFields.length === 0 ? [{ name: "", required: false }] : customFields ); const isInitial = React.useRef(true); React.useEffect(() => { onStateChange( customFieldsState.filter(field => field.name.length > 0), isInitial.current ); isInitial.current = false; }, [customFieldsState]); useOnChange(() => { isInitial.current = true; dispatch({ type: RESET, customFields }); }, [customFields]); return ( {customFieldsState.map((field, idx) => ( dispatch({ type: CHANGE_NAME, index: idx, newName }) } onChangeIsRequired={newValue => dispatch({ type: CHANGE_IS_REQUIRED, index: idx, newValue }) } onRemove={() => dispatch({ type: REMOVE_FIELD, index: idx })} /> ))} dispatch({ type: ADD_FIELD })} > {I18n.t("js.add_custom_field_button_label")} ); }; export { CustomFields };

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

const CustomFields = ({ customFields, onStateChange }) => { const [customFieldsState, dispatch] = React.useReducer( customFieldsReducer, customFields, customFields => customFields.length === 0 ? [{ name: "", required: false }] : customFields ); const isInitial = React.useRef(true); React.useEffect(() => { onStateChange( customFieldsState.filter(field => field.name.length > 0), isInitial.current ); isInitial.current = false; }, [customFieldsState]); useOnChange(() => { isInitial.current = true; dispatch({ type: RESET, customFields }); }, [customFields]); };

Slide 32

Slide 32 text

{customFieldsState.map((field, idx) => ( dispatch({ type: CHANGE_NAME, index: idx, newName }) } onChangeIsRequired={newValue => dispatch({ type: CHANGE_IS_REQUIRED, index: idx, newValue }) } onRemove={() => dispatch({ type: REMOVE_FIELD, index: idx })} /> ))} dispatch({ type: ADD_FIELD })} > {I18n.t("js.add_custom_field_button_label")}

Slide 33

Slide 33 text

const ADD_FIELD = "add-field"; const REMOVE_FIELD = "remove-field"; const CHANGE_NAME = "change-name"; const CHANGE_IS_REQUIRED = "change-is-required"; const RESET = "reset"; const customFieldsReducer = (state, action) => { switch (action.type) { case ADD_FIELD: { .... } case REMOVE_FIELD: { .... } case CHANGE_NAME: { ... } case CHANGE_IS_REQUIRED: { .... } case RESET: { ... } default: return state; } };

Slide 34

Slide 34 text

this.renderCustomFieldsComponent = function(customFields) { const customFieldsRoot = document.getElementById("custom_fields_root"); // There isn't much that can go wrong here, but in case something does, // let's log the error and show a message to the user in place of the component. // Error boundary is like a "try-catch" for components. ReactDOM.render( {I18n.t("js.error_placeholder")}}> { this.attr.customFieldsState = newState; if (!isInitial) { this.updateCustomFieldsInputs(); } this.trigger("uiUpdateCurrentProductPageTabHeight"); }} /> , customFieldsRoot ); };

Slide 35

Slide 35 text

Breaking down commits:

Slide 36

Slide 36 text

Introduction in 2019: Skipping Classes & using Hooks

Slide 37

Slide 37 text

Moving side effects and event handlers and listeners to hooks

Slide 38

Slide 38 text

// Behaves like `useEffect`, except isn't not called on the first render. export function useOnChange(cb, deps) { const isFirstRender = React.useRef(true); React.useEffect(() => { if (isFirstRender.current === false) { cb(); } else { isFirstRender.current = true; } }, deps); }

Slide 39

Slide 39 text

Error Boundary and others for free

Slide 40

Slide 40 text

class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI. return { hasError: true }; } componentDidCatch(error, errorInfo) { console.error(error); } render() { if (this.state.hasError) { return this.props.placeholder; } return this.props.children; } } export { ErrorBoundary };

Slide 41

Slide 41 text

Side-Effect> Moving to typescript with effectively defined data structures

Slide 42

Slide 42 text

const ADD_FIELD = "add-field"; const REMOVE_FIELD = "remove-field"; const CHANGE_NAME = "change-name"; const CHANGE_IS_REQUIRED = "change-is-required"; const RESET = "reset"; const customFieldsReducer = (state, action) => { switch (action.type) { case ADD_FIELD: { .... } case REMOVE_FIELD: { .... } case CHANGE_NAME: { ... } case CHANGE_IS_REQUIRED: { .... } case RESET: { ... } default: return state; } };

Slide 43

Slide 43 text

type CustomField = { name: string; required: boolean }; type State = Array; type Action = | { type: "add-field" } | { type: "remove-field"; index: number } | { type: "change-name"; index: number; newName: string } | { type: "change-is-required"; index: number; newValue: boolean } | { type: "reset"; customFields: Array }; const customFieldsReducer = (state: State, action: Action): State => { switch (action.type) { case "add-field": { .. } case "remove-field": { ... } case "change-name": { ... } case "change-is-required": { ... } case "reset": { ... } default: return state; } };

Slide 44

Slide 44 text

Documentation => Types

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

type FilesAction = | { type: "start-file-upload"; fileEntry: FileEntry } | { type: "update-file-upload-progress"; fileId: string; progress: UploadProgress; } | { type: "finish-file-upload"; fileId: string } | { type: "add-existing-file"; fileEntry: FileEntry } | { type: "upsert-dropbox-files"; fileEntries: Array } | { type: "remove-file"; fileId: string } | { type: "rename-file"; fileId: string; newName: string } | { type: "cancel-file-upload"; fileId: string } | { type: "upload-subtitle-files"; fileId: string; subtitleFiles: Array;} | { type: "start-subtitle-upload"; fileId: string; subtitleEntry: SubtitleFile; } | { type: "update-subtitle-upload-progress"; fileId: string; subtitleUrl: string; progress: UploadProgress; } | { type: "finish-subtitle-upload"; fileId: string; subtitleUrl: string } | { type: "cancel-subtitle-upload"; fileId: string; subtitleUrl: string } | { type: "remove-subtitle"; fileId: string; subtitleUrl: string } ...

Slide 47

Slide 47 text

Consideration: Build Size

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

Consideration: Speed

Slide 51

Slide 51 text

Consideration: Team onboarding

Slide 52

Slide 52 text

Consideration: Churn

Slide 53

Slide 53 text

Consideration: Error Reductions

Slide 54

Slide 54 text

Products created over time (red = distinct creators)

Slide 55

Slide 55 text

THANK YOU https://speakerdeck.com/vipulnsward/reactjs-with-types-and-hooks

Slide 56

Slide 56 text

@vipulnsward @HiSaeloun