Graduate Program KB

React

History

  • jQuery
    • Application lives in DOM
    • Update state -> traverse DOM to find node to update, update it
    • shared mutable state was a bad idea
      • Spaghetti of mutations which were hard to predict and keep track of
  • Backbone.js
    • MVC
    • State lived inside models
    • When a model changed all views using it would re-render
  • Angular.js
    • Two way data binding
      • Has to constantly scan app looking for changes in order to re-render
      • Implicit state changes hard to follow and debug
    • Data filters
    • Routing
    • Controllers
    • Dependency injection
    • Templates
  • React
    • View is a function of the application's state
    • v = f(s)
    • Separation of concerns
      • Historically HTML is separate from CSS which is separate from JS
      • React: they're just different technologies, not concerns
      • JSX combines HTML and JS
    • Tradeoffs
      • Large bundle sizes
      • Whenever state is needed in multiple components have to
        • Raise it
        • Use contexts
        • Use library like Redux
    • Was treated more as a UI primitive than an application platform
    • Next.js
      • Built on top of React

Npm

  • CDNs and script tags
    <script src="..."></script>
    
    • Doesn't scale well
    • Server has to be available
    • Order of tags matters
    • Versioning
  • Registry of modules
  • Cli tool
  • npm init
    • Create project
  • npm install
    • Add dependency
  • npm install --save-dev
    • Add development dependency (tool, not included in build)
  • package.json
    • dependencies
    • devDependencies
    • scripts
  • node_modules contains dependencies
  • package.json settings for publishing
    • name
    • version
    • main
  • Versioning
    • major.minor.patch
    • major = breaking
    • minor = non-breaking feature
    • patch = everything else
    • ^version
      • newest with major version
    • ~version
      • newest with major and minor version

Webpack

  • Module bundler
  • Looks at all modules, creates dependency graph, put them into bundles that index.html can reference
  • Installing
    npm install webpack webpack-cli --save-dev
    
  • webpack.config.js
    • Exports an object with settings
    • module.exports = {};
    • Needs
      1. Entry point
      2. Transformations to make
      3. Location to put bundles
  • Entry
    • Used to start creating dependency graph
      module.exports = {
          entry: "./app/index.js",
      };
      
  • Transformations with loaders
    • Out of the box webpack only knows how to process js, json
    • Want to import css, svg, etc...
      module.exports = {
          ...
          module {
              rules: []
          }
      }
      
    • Rule: type of file to run on, loader to use
      npm install svg-inline-loader --save-dev
      
      {test: /\.svg$/, use: "svg-inline-loader" }
      
      npm install css-loader --save-dev
      
      {test: /\.css$/, use: "css-loader"}
      
    • want to import css and have webpack put it into a style tag
      npm install style-loader --save-dev
      
      {test: /\.css$/, use: ["style-loader", "css-loader"]}
      
    • Note: style-loader before css-loader
      • Run in reverse order
    • Newer javascript for older browsers
      npm install babel-loader --save-dev
      
      {test: /\.(js)$/, use: "babel-loader"}
      
    • Transformations list
  • Output
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "index_bundle.js",
    }
    
    1. Grabs entry point
    2. Examines import and require statements to create dependency graph
    3. Starts creating bundle, if a path has a loader it will be run
    4. Takes final bundle and outputs it to dist/index_bundle.js
  • Plugins
    • Execute tasks after bundle has been created
    • Can be on the bundle or just to the codebase
    • HtmlWebpackPlugin
      • Will generate index.html, put in same dir as bundle and automatically include a script tag to reference
      • npm install html-webpack-plugin --save-dev
      • plugins: [new HtmlWebpackPlugin()]
    • EnvironmentPlugin
      • Want to set process.env.NODE_ENV to production before you deploy your code
      new webpack.EnvironmentPlugin({
          NODE_ENV: "production",
      })
      
    • Plugins list
  • Mode
    • Development, production
    • mode: "production"
    • Can remove EnvironmentPlugin, Webpack will automatically set that env
  • Running
    • package.json
      "scripts": {"build": "webpack"}
      
    • npm run build
  • Production vs deployment
    • Remove hardcoded mode from js
    • package.js
      "scripts": {"build": "NODE_ENV='production' webpack"}
      
    • webpack.config.js
      mode: process.env.NODE_ENV === 'production' ? 'production' : 'development'}
      
  • Webpack DevServer
    • Keeps track of files in memory, serve via local server, reload browser
      npm install webpack-dev-server --save-dev
      
      "scripts":{"start": "webpack-dev-server"}
      

JSX tips and gotchas

  • Variables
    <h1>Hello, {name}</h1>
    
  • Rendering nothing
    • Return null
      render() {
          if(isLoading() === true) return null;
          return (...}
      }
      
  • if/else
    if(firstLogin === true) return <h1>Welcome</h1>
    else return <h1> Welcome back </h1>
    
  • Ternary operator
    return firstLogin ? <h1>Welcome</h1> : <h1>Welcome back</h1>
    
    {showWarning() == true ? <Warning/> : null}
    
  • Logical &&
    {showWarning() && <Warning />}
    
  • React fragments
    • Ajacent JSX elements must be wrapped in an enclosing tag
      <React.Fragment>
          <h1> Hello </h1>
          <p> some text </p>
      </React.Fragment>
      
    • Instead of creating an extra div <> is shorthand for <React.Fragment>
  • Capitalization
    • Components are capitalised to distinguish from tags

Elements vs Components

  • Element
    • Describes what you see on the screen
    • Object representation of a DOM node
      const element = React.createElement("div", {id: "login-btn"}, "Login");
      
    • Arguments: tag string, attributes, children
    • Returns an object like
      {
          type: 'div',
          props: {
              children: 'Login',
              id: 'login-btn'
          }
      }
      
    • Renders to a dom node like
      <div id='login-btn'>Login</div>
      
  • Component
    • Function or class which optionally accepts input and returns a React element
      function Button({onLogin}) {
          return React.createElement("div", {id: "login-btn", onClick: onLogin}, "Login");
      }
      
    • Button component accepts and onLogin input and returns a React element
      • onLogin is a prop
      • pass along as 2nd arg to createElement
    • If react sees a class or function as 1st arg it will check to see what it renders give the props
      const element = React.createEement(User, {name: "Tyler"}, null);
      
    • Creates a full representation of the dom like above
    • Called reconciliation
    • Triggered on every state change

Props

  • Are to components what args are to functions
  • Passing data
    class Hello extends React.Component {
        render() {
            return (
                <h1> Hello, {this.props.first} {this.props.last} </h1>
            );
        }
    }
    
  • Each prop is added as a key on this.props
  • If no props are passed it will be an empty object
  • Not limited to what you can pass to props
    • e.g. can pass components
  • Prop without value defaults to true

Lists

    <ul id="tweets">
        {tweets.map((tweet) => (
            <li>{tweet.text}</li>
        ))}
    </ul>
  • Caveat
    • When using map you need to make sure there's a unique key prop on each item
    <ul id="tweets">
        {tweets.map((tweet) => (
            <li key={tweet.id}>{tweet.text}</li>
        ))}
    </ul>
    
  • NOTE: Do not use indices as keys on lists which can be modified

Managing state in React

  • Adding state
    • Constructor method (for object)
      class Hello extends React.Component {
          constructor(props) {
              super(props);
              this.state = {
                  name: "Tyler",
              };
          }
          render() {
              return <h1>Hello, {this.state.name}</h1>;
          }
      }
      
    • NOTE: make sure you call super(props)
    • Can now access state with this.state anywhere in the class
  • Updating state
    • WRONG: this.state.name = "Mikenzi";
    • View is a function of state
    • Need to re-render
      this.setState({name: newName})
      
      • Object as 1st arg that is merged with current state
        class Hello extends React.Component {
            constructor(props) {
                super(props);
        
                this.state = {
                name: "Tyler",
                };
        
                this.updateName = this.updateName.bind(this);
            }
            updateName() {
                this.setState({
                name: "Mikenzi",
                });
            }
            render() {
                return (
                <React.Fragment>
                    <h1>Hello, {this.state.name}</h1>
                    <button onClick={this.updateName}>Change Name</button>
                </React.Fragment>
                );
            }
        }
        
    • NOTE: this.updateName.bind
      • Passing as a prop to onClick with no context
    • Set state with function
      addFriend(newFriend) {
          this.setState((state) => {
          return {
              friends: state.friends.concat(newFriend)
          };
          })
      }
      
      • Function will be called with the current state and returns result to merge
    • If updating based on previous state use function, otherwise use object
      • Need to use function to guarantee the current state is up to date (updates are asynchronous)

Function Components

  • Components with just a render method can be written as a function
    function HelloWorld(props) {
        return <div>Hello {props.name}</div>;
    }
    

Prop types

  • prop-types package
  • When you create a component that accepts props, add a static propTypes property
    • Object
      • Keys: props the component accepts
      • Values: data types for those props
  • If types don't match get a warning in console
    import PropTypes from "prop-types"
    export default function Hello({name}) { ... }
    Hello.propTypes = {name: PropTypes.string.isRequired };
    
  • Some types
    • PropTypes.arrayOf(PropTypes.string)
    • PropTypes.element
      • React element
    • PropTypes.exact({...})
      • Object with specific shape
      • Particular values for keys
    • PropTypes.func
      • Must be a function
    • PropTypes.instanceOf
    • PropTypes.objectOf({...})
      • Object with k/v = name/type
      • Like propTypes
    • PropTypes.oneOf([...])
      • One of a particular value
    • PropTypes.oneOfType([...])
    • PropTypes.shape
      • Like exact, but allows extra keys on the object

Component lifecycle

  • Components
    • Manage their own state
    • Receive data via prop
    • Describe their UI
  • But end up having more responsibilities
    • Ajax requests
    • Setting and removing listeners
    • Reacting to new props
  • Lifecycle
    1. Added to DOM (mounting)
    2. Updates its state or receives new data via props (updating)
    3. Gets removed from DOM (unmounting)
  • Mounting (in order they occur)
    • Set the components initial state
      • Constructor method
    • Render a DOM node
      • Render method
      • Pure function, looks at state,props and returns a description of the UI
    • Make an Ajax request
      • componentDidMount
      • Invoked once when first mounted
    • Set up listeners (e.g. websockets)
      • Do in componentDidMount
  • Updating
    • Re-render UI with updated props, state
      • Render also invoked when state changes via setState or when it receives new updated props
    • Re-fetch data
      • componentDidUpdate method
      • Invoked when state changes or receives new props
      • But not on initial render
      • Passed previous props and previous state
      • Can compare to decide if need to do anything
    • Re-set a listener
      • componentDidUpdate
  • Unmounting
    • Cleanup
    • Remove listeners
    • componentWillUnmount
  • Other methods
    • getDerivedStateFromProps, shouldComponentUpdate, getSnapshotBeforeUpdate

Controlled vs Uncontrolled Components

  • React: state lives inside components, not dom
  • Traditional forms: state lives in DOM (input fields)
  • Controlled component: the 'react way', state lives inside components state, value of input is what's in component
  • Uncontrolled: no components state, lives inside DOM
  • Controlled
    • handleChange on input, to update value in react
    • value on input set to state
      class Form extends React.Component {
      constructor(props) {
          super(props);
      
          this.state = {
          email: "",
          };
      
          this.handleChange = this.handleChange.bind(this);
          this.handleSubmit = this.handleSubmit.bind(this);
      }
      handleChange(e) {
          this.setState({
          email: e.target.value,
          });
      }
      handleSubmit() {
          alert("The email is " + this.state.email);
      }
      render() {
          return (
          <div>
              <pre>The email is {this.state.email}</pre>
              <br />
              <input
              type="text"
              placeholder="Email"
              value={this.state.email}
              onChange={this.handleChange}
              />
              <button onClick={this.handleSubmit}>Submit</button>
          </div>
          );
      }
      } 
      
  • Uncontrolled
    • createRef and set as prop
    • Access ref with .current.value
    • React can't re-render when ref changes
      constructor(props) {
          super(props);
          this.input = React.createRef("");
      }
      render() {return (<input ref={this.input}>);}
      handleSumbit(){alert("Value is " + this.input.current.value);}
      

React children

  • Can pass data to a html tag within opening closing tags as well as props
  • Child tags in react: props.children

Default props

  • Static property defaultProps
    • Object: keys are props being passed, values default values
  • ES6
    • Default function parameters
    • Use with destructuring to set default props for function components

Loading component

  • Change text every x ms, to change number of dots
  • Loading, Loading., Loading.., Loading...
  • On comopnentDidMount
    • Register handler
    • window.setInterval with closure which sets the state
  • On componentWillUnmount
    • Need to deregister window handler
  • Better to not show loading indicator right away
    • Will flash indicator if content loads fast
    • Delayed component which decides whether to show or hide - toggles on window.setTimeout

Higher order components

  • Function that takes a component and returns a new component
  • New component can render original
  • New component needs to pass through props to original
  • Inversion of control
    • Handing execution of our component over to a 3rd party library
    • Naming collisions
    function withHover(Component) {
        return class WithHover extends React.Component {
            constructor(props) {
                super(props);
                this.state = {
                    hovering: false
                };
                this.mouseOver = this.mouseOver.bind(this);
                this.mouseOut = this.mouseOut.bind(this);
            }
            mouseOver() {
                this.setState({ hovering: true }):
            }
            mouseOut() {
                this.setState({ hovering: false });
            }
            render() {
                return (
                    <div
                        onMouseOver={this.mouseOver}
                        onMouseOut={this.mouseOut}
                    >
                    <Component hovering={this.state.hovering} { ... props } />
                    </div>
                );
            }
        };
    }
    

Render props

  • May have wrapper components which handle some logic and render other components which they pass data
  • Can pass a function that returns UI as a prop and call from render
  • Can use this.props.children as a function
    • Function inside component tags
  • No inversion of control
    • We render the components in the render function passed in
    class Hover extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                hovering: false
            }
    
        this.mouseOver = this.mouseOver.bind(this)
        this.mouseOut = this.mouseOut.bind(this)
        }
        mouseOver() {
            this.setState({ hovering: true })
        }
        mouseOut() {
            this.setState({ hovering: false })
        }
        render() {
            return (
                <div
                    onMouseOver={this.mouseOver}
                    onMouseOut={this.mouseOut}
                >
                    {this.props.children(this.state.hovering)}
                </div>
            )
        }
    }
    
    <Hover> {(hovering) => {return <Info anyNameWeWant={hovering} />;}} </Hover>
    

React context

  • State shared between components at multiple levels of the tree
  • Recommended solution
    • Move state up to nearest common parent and pass it down via props
    • Can become unmanageable
  • Context
    1. Declare data that needs to be available throughout the component tree
      • Provider
    2. Need any component to be able to subscribe to it
      • Consumer
const MyContext = React.createContext()
<MyContext.Provider value=...> </MyContext.Provider>
  • Value prop with the data to be made available to children
<MyContext.Consumer> {(data) => {return (...); }} </MyContext.Consumer>
  • Changing context state
    • Can put a function on the value object which references this to change state
    • Performance
      • If we assign the value object every render() every component using the context will be redrawn since the component has changed
    • Wrap in a component and use the component's state as the value, and set state to change
      • Can put the change function on the state to be referenced by other components
      • Object isn't being recreated so fixes the problem
      class App extends React.Component {
          constructor(props) {
              super(props)
      
              this.state = {
              locale: 'en'
              }
          }
          render() {
              return (
              <LocaleContext.Provider
                  value={{
                  locale: this.state.locale,
                  toggleLocale: : () => {
                      this.setState(({ locale }) => ({
                      locale: locale === "en" ? "es" : "en"
                      }));
                  }
                  }}>
                  <Home />
              </LocaleContext.Provider>
              )
          }
      }
      
  • Default value
    • Provider gets value from the nearest Provider components of the context
    • If there isn't a parent it uses the first argument to createContext

React router

  • BrowserRouter component
    • Keeps track of application's browsing history
    • Makes it available with contexts
    • Probably put it at the root
  • Also MemoryRouter and StaticRouter
  • Route component
    • Map apps location to different components
      <Route path="/dashboard" element={<Dashboard/>} />
      
    • Renders null if the path doesn't match
    • Multiple routes might match
  • Routes component
    • Looks at every child Route and renders the best match
    • Should always wrap Route components with Routes
  • Link component
    <Link to="/">Home</Link>
    
    • Can pass to as an object
      to={{pathname: "/settings",
          search: "?sort=date", // query string
          state: {fromHome: true}, // data
      }}
      

Query strings

  • URLSearchParams API
    • Methods for handling query strings
      const sp = new URLSearchParams(queryString)
      
    • ex.: sp.has, sp.get, sp.getAll, sp.append(key, value), sp.toString, sp.set(key, value), sp.delete
  • useSearchParams
    import {useSearchParams} from 'react-router-dom'
    const Results = () => {
        const [searchParams, setSearchParams] = useSearchParams();
    }
    
    • Similar to URLSearchParams api
      • searchParams is an object for querying string
      • setSearchParams is an object for updating
    • useSearchParams is a hook which isn't supported in classes
    • Create a higher order component and consume in there
      export default function withSearchParams(Component) {
          function ComponentWithSearchParams(props) {
              const [searchParams] = useSearchParams();
              return <Component {... props} router={{ searchParams }} />;
          }
          return ComponentWithSearchParams;
      }
      
    • Wrap component with withSearchParams and access this.props.router

Class fields

  • Adds instance properties directly on class without using constructor
  • Adds static fields, so propTypes and defaultProps can go in the class definition
  • Arrow functions don't have their own this, so can use those for methods instead of binding
    • NOTE: function object will be created per instance rather than per class

Code splitting

  • Don't need to download entire app if user only needs a piece of it
  • Instead of using import normally used like a function that returns a Promise
  • Needs to be supported by bundler: Webpack guide
    if (editingPost === true) {
        import('./editpost')
            .then((module) => module.showEditor())
            .catch((e) => )
    }
    

Building react apps for production

  • Need to tell webpack to run in production mode
    • Minify code
    • Set process.env.NODE_ENV to production
  • package.json
    "scripts": {
        "dev": "webpack serve",
        "build": "NODE_ENV='production' webpack"
    }
    
  • webpack.config.js
    mode: process.env.NODE_ENV === 'production'
        ? 'production'
        : 'development'