Getting to Know… React

TL;DC, jump to the Notes, Tips & Tricks Section

Why

I admit, I totally missed the boat on the whole MVC library/framework movement (I’m just going to call them all frameworks from here on). I come from a mindset that says “your site should work just fine without JS” and, at that time, all of those frameworks certainly flew in the face of that mindset; without JS, you usually get nothing but a blank page…

Well, my opinion was clearly not shared by the masses, and now some of the frameworks do offer SSR… So now I find myself a bit behind the times, but nevertheless ready to dig in.

What

But before I could dig in, I had to make a monstrous decision: Which one??? Angular was certainly the first powerhouse and still is, it seems, with enterprises, but it certainly does not control the job market anymore. React is easily the biggest player right now, with jobs and jobs galore. Backbone and Ember are both still very viable options, though neither is very strong in the job market. And recently there have been some very interesting new kids on the block that have a slightly different take on things, like Vue, Svelte, Qwik, and, well, the list goes on and on and on

But, figuring most people are probably learning a new tech to help find a new job or improve their standings at their current one, I went with the current king of the job boards, and that is definitively React…

So, let’s get to know React!

Getting Started

As with all new things, the first stab at learning is a massive Google search… And boy are there a lot of tutorials! Articles, videos, articles with videos, documentation…

Of course, the first place we should all start is the documentation. But man can that stuff be dry… And when I was starting my search, React’s documentation was all words and code samples, no videos, no interaction… And I definitely learn best by tinkering!

So I went looking for articles & videos. There are naturally a lot of “Learn React in 10 minutes!” types of articles/videos out there, and while they do at least expose you to some bits and pieces, you are never going to actually learn something in 10 minutes… Or 15, or 20…

So then I went looking for courses. Again, there are plenty! And I tried plenty!! But with each one, I found myself walking away… Either the code was really old (tech changes fast!), I had trouble understanding or following the presenter, or they just bounced around too much…

But finally, I found my way to Scrimba and found a course description that sounded perfect. But it was not free, which is fine with me, I definitely do not mind paying for someone’s time, but I was now quite gun-shy, and was afraid of paying for a 12-hour, 126-lesson course, only to find the presenter drove me nuts!

So I reached out to Scrimba support and asked if they had a sample video of the presenter. To my surprise, they did not… But they did refer me to a free intro course (you do have to create an account and login), on the same subject, by the same instructor! :-)

I immediately took that intro course and loved it and the presenter so much that I subscribed and took the full course! They are both awesome, and , for me, is an excellent instructor: He speaks slowly and calmly; he presents subjects methodically, some times deviating to a seemingly unrelated subject or example, only to bring it back around and incorporate that subject or example in what he was previously teaching; he breaks up the lessons with lots of “hands on the keyboard” time; and the Scrimba interface itself is fantastic, allowing you to sort of interact with what appears to be the video, editing and testing code right in the browser, then returning seamlessly to the lesson…

Between Tania’s initial article, a host of other articles and videos, and these two courses, I have collected the following, which will likely be updated as I continue to learn more… Have fun!

Notes, Tips & Tricks

  • Glossary
    Component
    Reusable function that returns HTML, could be a button, widget, module, etc.
    Controlled Components
    Form elements that are controlled by React, in order to assure a “single source of truth” regarding the “state” of the form and your app.
    Hooks
    Functions that add Lifecycle Methods to Functional Components, such as useState and useEffect.
    JSX
    React markup language, stands for JavaScript XML.
    Lifecycle
    Order in which methods are called.
    Lifecycle Methods
    Methods called at certain points during the lifecycle, such as componentDidMount.
    Lifting State
    Moving the state of a component to a parent component, to help coordinate state among similar components.
    Mount
    Refers to an element being inserted into the DOM.
    Props
    Properties, read-only data passed to a component as an array.
    State
    Temporary, read/write data store representing the “state” of your app; think “To Do list” or “Shopping Cart” items, where adding, editing, removing items changes the “state” of your app and its components.
    Unmount
    Refers to an element being removed from the DOM (or not included in a re-render).
  • Primer
    • Open source library, not framework
    • Developed by Facebook in 2011
    • Can be complete project, or added into existing pages/sites
    • Used to build a frontend, the “View” in MVC
    • Builds using “components”, or reusable HTML (elements, sections, widgets, modules)
    • Entire app is inserted into a Root component, often using id="root"
    • All JSX tags must be explicitly closed:
      <img ... />, not <img ...>
    • Switching to JS while within JSX requires the JS to be wrapped within {} and do not use "" around the value:
      <img src={image_url} />
      <p>Status: {active ? 'On' : 'Off'}</p>
    • Stores data as state and props
    • Version 16.8 introduced Hooks, so Functional Components can handle State and Lifecycle Methods
    • State cannot be altered by a child component; in order for a child to affect the parent’s state, the parent must pass its “change” function to the child component as a prop
    • Use this.setState() or “set state” function to update state
    • State change triggers a re-render of any component, and it’s children, that are affected by that state
  • Add React to an existing page/site, using ES6

    React doesn’t have to be the entire site or app, and it doesn’t have to be installed and compiled!

    It can be added to an existing page/site, even just a flat HTML page:
    https://reactjs.org/docs/add-react-to-a-website.html

    Add a React container to an existing HTML page, something like:

    <div id="like_button_container"></div>
    

    Along with the stand-alone React JS:

    <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
    

    The latest CDN links can be found here:
    https://reactjs.org/docs/cdn-links.html

    Be sure to replace “development.js” with “production.min.js” when deploying live.

    And be sure to add your custom React component script as well:

    <script src="my_like_button.js"></script>
    

    Then, in addition to the JS magic you have written, your custom React component script would contain something like:

    // create your component as a JS Class, extending `React.Component`
    class LikeButton extends React.Component {
        // receive any props and set the initial state
        constructor(props) {
            super(props)
            this.state = { liked: false }
        }
        // `render` the contents of your component
        render() {
            // if the `state` is true, return the "true" message
            if (this.state.liked) {
                return 'You liked this.'
            }
            // otherwise, create the actionable component
            return React.createElement(
                'button',
                { onClick: () => this.setState({ liked: true }) },
                'Like'
            )
        }
    }
    // cache the React component container in your DOM
    const container = document.querySelector('#like_button_container')
    // render the actual component into the component container
    ReactDOM.render(React.createElement(LikeButton), container);
    

    When the above code loads, inside of your component container (#like_button_container), React will either display the “You liked this.” message, or create a button element, with a click event listener that changes the component’s state to true, and with the text of “Like”.

  • Add React to an existing page/site, using JSX

    Or step it up a notch and get to know some JSX:
    Getting Started with React

    (There is a lot more about JSX below.)

    Add a React component container to an existing HTML page, something like:

    <div id="root"></div>
    

    Along with the stand-alone React and Babel JS:

    <script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
    <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/[email protected]/babel.js"></script>
    

    The latest CDN links can be found here:
    https://reactjs.org/docs/cdn-links.html

    Be sure to replace “development.js” with “production.min.js” when deploying live.

    And be sure to add your custom React component script as well:

    <script src="App.js"></script>
    

    Your custom React component script would contain something like:

    class App extends React.Component {
        render() {
            return <h1>Hello World!</h1>
        }
    }
    ReactDOM.render(<App/>, document.querySelector('#root');
    

    When the above code loads, inside of your component container (#root), React will create an h1 element with the text of “Hello World!”.

  • Create Complete React App

    Static HTML is cool, but it is not very scalable…

    Facebook created Created React App, a Node package to install a pre-configured dev environment with starter app code.

    This app allows you to very easily get a sample React app up and running, so you can see, touch and play with real, working code.

    1. In Terminal, navigate to the directory where you app should live, for example, a parent directory like /apps
    2. Install “Create React App”
      npx create-react-app react-app-demo

    Notes regarding the above installation:

    • Yes, the command is npx, not npm
    • react-app-demo is the directory into which the app will be installed
    • npx will first create the directory, then install the app into it
    • The directory into which you are installing the app must be empty or npx will not continue
    • Node will confirm you want to install, then download and install the sample app and all of its dependencies

    Once installation is complete, follow the on-screen instructions to navigate into the new directory and start a Node server:

    cd react-app-demo && npm start

    This should create a local server and open a default browser window for this app.

    If you look at the default structure of this demo app, you will see something similar to this (there will be other files, but this is the important part):

    /react-app-demo
        /node_modules
        /public
        /src
        .package.json
    

    Anything that NPM installs will save into the /node_modules folder.

    Your starter index.html files and some other starter files will be in the /public folder.

    And all of the components, custom JS, custom CSS, images, etc. that you add to your app will go into the /src folder.

    Feel free to click around, open files, dig through things. Get nosy, that’s how you’ll learn best!

    And remember, if you mess something up, you can always delete the demo app folder and re-install as you did above!

    Depending on your IDE and extensions, any changes you then make and save to the app files should auto-refresh in that browser tab.

    If you do not have an extension to auto-refresh, simply refresh the tab manually after saving changes.

    If you do something that breaks the app in the browser, you might have to manually refresh to get auto-refresh going again.

  • Add Dependencies to an Existing React Project

    “Create React App” installs all of its dependencies automatically.

    And any existing project you work on will likely have all of its dependencies installed too.

    But suppose you want to add something new at some point?

    1. Look-up the new package name in NPM:
      https://www.npmjs.com/, then use Search
    2. In Terminal, navigate to the app directory
    3. Tell npm to install that package, replacing new-package-name with the actual new package name found in NPM:
      npm install new-package-name --save-dev
    4. npm will automatically install the new package and any dependencies it may have
  • JSX

    JSX is React’s custom markup language. It stands for “JavaScript XML”.

    It looks a lot like HTML, but with a few caveats:

    • All tags need to be explicitly closed:
      <img ... /> instead of <img ...>
    • Attributes, properties and methods are camelCase:
      onClick instead of onclick
      backgroundColor instead of background-color
    • You need to use className instead of class:
      <h1 className="page-title">Hello World!</h1>
    • Do not use quotes around the JSX, as it is NOT a String:
      const header = <h1>Hello World!</h1>
    • Any JS, including variables, needs to be wrapped inside {}:
      <h1>Hello {name}</h1>
      <h1>Status: {active ? 'On' : 'Off'}</h1>
  • “Declarative” vs. “Imperative”

    Before digging any further into code, one of the first bits to grok is that React is “declarative”, as opposed to “imperative”.

    This means that we tell React “what we want done”, not “how to do it”.

    In a way, using JSX feels a bit like using jQuery…

    For example, in Vanilla JS, we would do something like this, telling JS every step it needs to take:

    // create an H1 element
    const elem = document.createElement('h1')
    // add a class attribute
    elem.className = 'site-heading'
    // add some text inside of the element
    elem.innerHTML = 'Hello World!'
    // append the element to #root
    document.querySelector('#root').append(elem)
    

    While with jQuery we could simply say:

    // grab #root and put this stuff in there
    $('#root').append('<h1 class="site-heading">Hello World!</h1>')
    

    We are not telling jQuery “how” to do all of that, just “do it”.

    Similarly, without JSX, we would have to tell React:

    ReactDOM.render(
        React.createElement(
            // create an H1 element
            'h1',
            // add a class attribute
            {class: 'site-heading'},
            // add some text inside of the element
            'Hello World!'
        ),
        // append the element to #root
        document.querySelector('#root')
    )
    

    While with JSX, we can tell React something like this:

    // put this stuff inside #root
    ReactDOM.render(
        <h1 className="site-heading">Hello World!</h1>,
        document.querySelector('#root')
    )
    

    And if you were to create a variable first, and log that to the console, you would see that it is not a String and is not an HTML node, but it is a JS Object:

    const elem = <h1 className="site-header">Hello World!</h1>
    console.log(elem)
    {
        type: "h1", 
        key: null, 
        ref: null, 
        props: {
            className: "site-header", 
            children: "Hello World!"
        }, 
        _owner: null, 
        _store: {}
    }
    

    This JS object tells React what we want done and it just does it.

  • render() 1 Line vs. Multiple Lines

    The render() function can only have one parent element returned to it.

    That one parent element can have unlimited child elements, but everything that you want to return must be wrapped in one single element.

    If render() is only returning 1 line, you can do it like this:

    class App extends React.Component {
        render() {
            return <h1>Hello World!</h1>
        }
    }
    

    But if render() is returning multiple lines, you need to wrap the JSX in parentheses:

    class App extends React.Component {
        render() {
            return (
                <header>
                    <h1>Hello World!</h1>
                </header>
            )
        }
    }
    

    However, if you do have multiple elements, but don’t really need a parent element, you can use an “empty” element as your wrapper:

    class App extends React.Component {
        render() {
            return (
                <>
                    <h1>Hello World!</h1>
                    <h2>It's a beautiful day!</h2>
                </>
            )
        }
    }
    

    React will see the <> and </> and ignore them when generating the actual output.

  • Various Ways to import React and Components

    Your initial JS file, usually index.js, will need something like this to get started:

    import React from 'react'
    import ReactDOM from 'react-dom'
    

    You can also import custom components with something like:

    import MyComponent from './MyComponent'
    

    The code above would import a component called “MyComponent” from a local file called “MyComponent.js”.

    Note that you do not need the “.js” in the from name; without a file extension, React will assume it is “.js”.

    React also allows you to import specific components via destructuring; Fruits.js might contain a component for every fruit known to mankind, but if you only need the Apple, Banana and Orange components, you can import only those:

    import {Apple, Banana, Orange} from './Fruits'
    

    The code samples above import React then use React.Component:

    import React from 'react'
    class App extends React.Component {
        ...
    }
    

    But you can also import Component itself, then use it directly:

    import React, {Component} from 'react'
    class App extends Component {
        ...
    }
    
  • Components

    Speaking so much about Components, a few quick notes, most elaborated on below:

    • Nearly everything in React is a component
    • Components can contain components
    • Convention puts each component into its own JS file, but you can have multiple components in a single JS file
    • Component names must be PascalCase:
      Table, TableHead, TableBody, TableRow, etc.
    • Convention names the related JS file the same way, so:
      Table.js, TableHead.js, etc.
    • Each Component JS file must export any Components that should be “importable” elsewhere
    • If your project has more than one or two Components, convention puts all Components in a separate directory like /components; you then import them something like:
      import TableHead from './components/TableHead'
    • Functional (Simple) Components are Function-driven, essentially just JS functions
    • Class Component is Class-driven, meaning JS Classes
    • Convention is moving away from Class Components, and toward Functional Components, for ease of writing, reading and debugging, as well as smaller code bases

    Functional Components are just JS functions:

    const FunctionalComponent = () => {
        return <div>Example</div>
    }
    

    Class Components use a JS Class:

    class ClassComponent extends Component {
        render() {
            return <div>Example</div>
        }
    }
    

    Exporting Components can be done either as the component is created:

    export default function FunctionalComponent() {
        return <div>Example</div>
    }
    

    Or at the end of the component file:

    function FunctionalComponent() {
        return <div>Example</div>
    }
    ...
    export default FunctionalComponent;
    

    If done at the end of the component file, you can export multiple components with a single line of code, but you cannot use the default keyword, and you need to wrap the multiple component names with { }:

    function FunctionalComponent1() {
        return <div>Example 1</div>
    }
    function FunctionalComponent2() {
        return <div>Example 2</div>
    }
    ...
    export {FunctionalComponent1, FunctionalComponent};
    

    Aside from the major difference of Functional Components being just a JS function and Class Components being a JS Class object:

    • Functional Components use return:
      const FunctionalComponent = () => {
          ...
          return <div>Example</div>
      }
      

      Class Components must render the return:

      class ClassComponent extends Component {
          ...
          render() {
              return <div>Example</div>
          }
      }
                      
    • Functional Components are stateless, but can add state using Hooks; Class Components are stateful by default, via their constructor() method
    • Functional Components cannot use lifecycle methods, but can add most lifecycle functionality using Hooks; Class Components can use lifecycle methods (i.e. componentDidMount)
    • Functional Components have no constructor; Class Components do

    Below are several code comparisons and conversions between Simple and Class Components...

    Basic Component construct:
    import React from 'react'
    const FunctionalComponent = () => {
        return <h1>Hello, world</h1>
    }
    

    vs.

    import React, { Component } from 'react'
    class ClassComponent extends Component {
        render() {
            return <h1>Hello, world</h1>
        }
    }
    

    Note that a Class Component must extend React.Component (or simply Component, if destructured, as above) and must use render().

    Using Props:
    Pass props into a component:
    <Component name="Aaron" />
    Then consume them:

    const FunctionalComponent = props => {
        return <h1>Hello, {props.name}</h1>
    }
    

    vs.

    class ClassComponent extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                name: props.name
            }
        }
        render() {
            return <h1>Hello, {this.state.name}</h1>
        }
    }
    

    Note that Class Components must import props via the super() function within their constructor(), and they are then available throughout the class via this..

    Using State:
    const FunctionalComponent = () => {
        const [ count, setCount ] = React.useState(0)
        return (
            <div>
                <p>count: {count}</p>
                <button onClick={() => setCount(count + 1)}>Click</button>
            </div>
        )
    }
    

    vs.

    class ClassComponent extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                count: 0
            }
        }
        render() {
            return (
                <div>
                    <p>count: {this.state.count} times</p>
                    <button onClick={() => this.setState({ count: this.state.count + 1 })}>
                        Click
                    </button>
                </div>
            )
        }
    }
    

    The Functional Component's React.useState Hook sets the initial value (0), and returns the current value and a function to change the state, destructured into count and setCount above.

    While the Class Component's constructor() sets its initial state (count: 0), inherits its props via super(props), and automatically has this.setState.

    Because the Functional Component's React.useState runs with each re-render, if it is doing anything that could cause a performance problem, you can use "lazy initialization", by wrapping the init "value" with a function that returns the end value, so that something like:

    const [state, setState] = React.userState(
        JSON.parse(localStorage.getItem('notes'))
    )
    

    becomes:

    const [state, setState] = React.userState(
        () => JSON.parse(localStorage.getItem('notes'))
    )
    
    Lifecycle Methods:
    const FunctionalComponent = () => {
        React.useEffect(() => {
            console.log("Hello")
        }, [])
        return <h1>Hello, World</h1>;
    }
    

    vs.

    class ClassComponent extends React.Component {
        componentDidMount() {
            console.log("Hello");
        }
        render() {
            return <h1>Hello, World</h1>;
        }
    }
    

    The Functional Component's React.useEffect Hook works as the Class Component's componentDidMount does, receiving a function to perform state changes, but also receives an array of states to react to; an empty array (as above) says "only run on the initial render".

    componentDidMount is automatically called when its component is mounted (added to the DOM).

  • Props

    Short for Properties, props are used to pass data down to Components.

    This data can be any valid JS data type: String, Number, Boolean, Function, Array, Object, etc.

    You can pass unlimited props to a component, but all will be received by the component wrapped in a single JS Object.

    Props cannot change! They are passed down and used as "read only" data.

    For example, you can pass props like this:

    <TableRow 
        id={id} 
        firstName={firstName} 
        lastName={lastName} 
        jobTitle={jobTitle} 
    />
    

    And they will be received inside a single JS Object, conventionally called props, and can be consumed from there:

    function TableRow(props) {
        const { id, firstName, lastName, jobTitle } = props
        ...
    }
    

    A deeper example, using the following data:

    const employees = [
        {
            id: 12345,
            firstName: 'Charlie',
            lastName: 'Brown',
            jobTitle: 'CEO',
        },
        {
            id: 23456,
            firstName: 'Linus',
            lastName: 'Van Pelt',
            jobTitle: 'CFO',
        },
        {
            id: 34567,
            firstName: 'Peppermint',
            lastName: 'Patty',
            jobTitle: 'COO',
        },
        {
            id: 45678,
            firstName: 'Snoopy',
            lastName: '',
            jobTitle: 'CTO',
        },
    ];
    

    The employees array could be passed from the TableBody Component to the TableRow Component like this:

    function TableBody() {
        return (
            <tbody>
                <TableRow data={employees}/>
            </tbody>
        )
    }
    

    Note that we are passing the entire data array as a single prop (data), and do not have to use the same name for the prop and the prop value.

    The TableRow Component receives the data, inside a single JS Object, and can consume it via any JS method, such as map below:

    function TableRow(props) {
        // loop thru props, 
        // build a `tr` for each employee
        const rows = props.data.map( employee => {
            return (
                <tr key={employee.id}>
                    <td>{employee.id}</td>
                    <td>{employee.firstName}</td>
                    <td>{employee.lastName}</td>
                    <td>{employee.jobTitle}</td>
                </tr>
            )
        })
        // return `rows` back to `TableBody`
        return (
            {rows}
        )
    }
    

    Note that you should always use a key on any iteration parent node to help React identify each "unique" iteration (it will complain in the console otherwise...).

    Note also that you can destructure props as they arrive in the component, then you no longer need the props. in the JSX return:

    <Component prop1="foo" prop2="bar" />
    
    function Component({ prop1, prop2 }){
        return(
            <p>{prop1} {prop2}</p>
        )
    }
    

    You can also rename props as they arrive:

    function Component({ prop1: newName, prop2: diffName }){
        return(
            <p>{newName} {diffName}</p>
        )
    }
    

    Remember that a prop can be any valid JS data type, including Objects, Arrays, Functions, and combos of all of the above, but only a String should be wrapped in quotes "...".

    If the prop value is not a String, then it must be wrapped in {...}, and should not have quotes.

    And if things start getting outta hand, it might be time to push each prop to a new line...

    <Component 
        prop1="foo" 
        isCool={true} 
        team={[
            {name: 'Charlie', title: 'CEO'},
            {name: 'Linus', title: 'CFO'},
            {name: 'Peppermint', title: 'COO'},
            {name: 'Snoopy', title: 'CTO'}
        ]} 
        onClick={myOnClickFunction}
    />
    

    And the above might be okay for a couple/few props, but if things get really outta hand, you can build all of your props into a JS Object, then pass the entire Object to the component:

    <Component allEmployees={data} />
    

    Then pull that data object apart within the component:

    function Component(props){
        const rows = props.allEmployees.map( (row, index) => {
            return (
                <tr key={index}>
                    <td>{row.name}</td>
                    <td>{row.title}</td>
                    ...
                </tr>
            )
        })
        return(
            <tbody>{rows}</tbody>
        )
    }
    

    Or, you can use the spread operator, so a single line creates all of the individual props for you:

    <Component {...data} />
    

    But then you need to go back to referencing the props via the prop. Object:

    function Component(props){
        return(
            <p>{props.prop1} {props.prop2}</p>
        )
    
  • State

    State can be a tricky one to conceptualize, because it is the "state" of your app.

    Meaning, if there is something that "will change", then that is the "state" of your app.

    For example, in a "To Do" list app, adding, removing, sorting, editing would all change the "state" of an app.

    When the State changes, React will refresh the UI to reflect the data change(s).

    State can be created and managed by a Component, but to be maintained between sessions or page refreshes, any values that should be stored permanently must be stored in some data storage, like a database or localStorage.

    State comes built-in with Class Components, but must be added to Functional Components, using the useState Hook.

    State must never be updated manually/directly, but rather through the "set state" functions.

    If we have a Functional Component, we first have to import the useState Hook, like either this:

    import React from 'react'
    export default function Component(){
        const [count, setCount] = React.useState(0);
        ...
    }
    

    or this:

    import React, {useState} from 'react'
    export default function Component(){
        const [count, setCount] = useState(0);
        ...
    }
    

    In either case above, note that useState can receive an optional starting value (0 in both examples above), and returns a two-item array that contains 1) the current state, and 2) a function to change the state; these usually get destructured into something like this:

    const [count, setCount] = useState(0);
    

    Convention starts the state change function name with "set" followed by the variable name, so in the above case, setCount, because this will be used to update the state of count.

    Then when we want to update the state, no matter where we need to do this, we can use the setCount function:

    setCount(1)
    

    If the "set state" function needs to use the current state, we need to use a callback function:

    setCount(prevCount => prevCount + 1)
    

    Note the prevCount variable. This is the state before the update begins, and is automatically provided by the "set state" function.

    Convention starts the previous state name "prev" + the variable name, so in the above case, prevCount, as this represents the previous state of count.

    It is very important never to try to update the previous state directly, with something like this, as this would directly affect the current state itself:

    /** DO NOT DO THIS **/
    setCount(prevCount => prevCount++)
    

    Instead, create a new value and pass that back, as above.

    Similarly, when the state is an array, we cannot do either of the below, as they would also alter the current state directly:

    /** DO NOT DO THIS **/
    setTasks(tasks.push(`Task ${tasks.length + 1}`))
    /** DO NOT DO THIS **/
    setTasks(prevTasks => prevTasks.push(`Task ${prevTasks.length + 1}`))
    

    In the case of an array, we need to create a new array, using the spread operator to replicate it, then append to it:

    setTasks(prevTasks => [...prevTasks, `Task ${prevTasks.length + 1}`])
    

    Objects are similar to arrays, where we can still use the spread operator to replicate the old state into a new object, but then we overwrite the old value with the new:

    setContact(prevContact => {
        return {...prevContact, phoneNumber: newPhoneNumber}
    })
    

    In the above case, phoneNumber already exists in the state, but by adding the update at the end, it overwrites the previous value with the new.

    Note that if we want to use the implicit return by putting the object on a single line, we need to wrap the object in ( ) or React will interpret the object's { } as the function brackets, so it would look like:

    setContact(prevContact => ({...prevContact, phoneNumber: newPhoneNumber}))
    

    If we have a Class Component, state is automatically included, and is used something like:

    class App extends Component {
        // receive any props and set the initial state
        constructor(props) {
            super(props);
            this.state = { count: 0 };
        }
        ...
    }
    

    To update state, use the built-in this.setState function:

    this.setState({
        state: (this.state + 1)
    })
    

    Remember that child components cannot change parent state by themselves; they would need to receive the "set state" function from the parent, something like:

    /*
        The App component receives `props`,
        defines `state`, and has a function 
        that updates `state`
    */
    class App extends Component {
        // receive `props` and set `state`
        constructor(props) {
            super(props)
            this.state = props.data
        }
        // method to remove item from array
        removeItem = ( index ) => {
            // get current state
            const { items } = this.state
            // update state by filtering out the `index` passed
            /* must use built-in `.setState` to alter `state` */
            this.setState({
                items: items.filter((item, i) => {
                    return i !== index
                })
            })
        }
        render() {
            // pass `items` and `removeItem` to Table Component
            return (
                <Table items={this.state.items} removeItem={this.removeItem} />
            )
        }
    }
    
    /*
        The Table component receives `props`,
        passes them to the `TableBody` component
    */
    const Table = props => {
        // receive the `props` from `Table`
        const { items, removeItem } = props
        // pass them to the `TableBody` component
        return (
            <table>
                <TableHeader />
                <TableBody items={items} removeItem={removeItem} />
            </table>
        )
    }
    
    /* 
        The TableBody component receives `props`,
        and consumes them
    */
    const TableBody = props => {
        // map data set, return `tr` for each,
        // include the "set state" function as an `onClick` event
        const rows = props.items.map( (row, index) => {
            return (
                <tr key={index}>
                    <td>{row.name}</td>
                    <td>{row.job}</td>
                    <td><button onClick={()=>props.removeItem(index)}>×</button></td>
                </tr>
            )
        })
        return(
            <tbody>{rows}</tbody>
        )
    }
    

    This is the only way for a child component to affect the state of a parent component: the parent must pass down a function, if necessary as above from child-to-child, which can then use the parent's state change function, which will trigger the state change, which will trigger the UI to re-render, reflecting the new state.

    Note that in the Table component example above, the props are destructured before being passed to the TableBody component, individually. This could have been done more succinctly, though not as intuitively, by using the Spread Operator:

    const Table = props => {
        return (
            <table>
                <TableHeader />
                <TableBody {...props} />
            </table>
        )
    }
    

    In the above case, the "spread" props would have arrived exactly the same in the TableBody component.

  • Lifting/Raising/Derived State

    Lifting or Raising State happens when you realize a sibling or parent component needs access to the state you created for some component.

    Derived State happens when you find yourself initializing the state in a component based on incoming props. This can create "two sets of truth": 1) in the parent, 2) in the child(ren).

    In all of these cases, it is probably better to set and maintain the state in a parent component, still keeping it as "local" as possible, then pass that state down as props to whatever components that need it.

  • Event Listeners

    Event listeners get added right to elements, and use PascalCase in JSX:

    <button onClick={myClickFunction}>Click me</button>
    <input onChange={myChangeFunction} value="Name" />
    

    When you need to tell a parent to change state, you often have to pass some identifier, like id here:

    function toggle(id) {
        setState(prev => {
            return prev.map(user => {
                return user.id === id ? {...user, active: !user.active} : user
            })
        })
    }
    

    But we cannot pass that identifier to the toggle function, as this would make the function fire on render:

    /** DO NOT DO THIS **/
    <button onClick={toggle(id)}>Click me</button>
    

    So we need to pass our function and parameter inside an anonymous function:

    <button onClick={() => {toggle(id)}}>Click me</button>
    

    You can read more about React Event Listeners here:
    https://reactjs.org/docs/events.html

  • Inline CSS

    While it is possible to add a style attribute on an element in React, it is not exactly how you do it in HTML.

    In HTML, you could do something like this:

    <div style="width: 100px">Hello World!</div>
    

    In React, you need one set of { } to tell JSX to "switch to JS", and another set to create a JS Object for the styles, and there are no "quotes" around the attribute value, so the example above would become:

    <div style={{width: 100px}}>Hello World!</div>
    

    Convention often creates the JS Object before, especially if there are a multiple style declarations, then adds it to the JSX like this:

    const styles = {width: 100px}
    
    <div style={styles}>Hello World!</div>
    

    Also note that, as this is JS, you need to use camelCase, and can use multiple rows for ease-of-reading:

    const styles = { 
        backgroundColor: "red", 
        fontColor: "white"
    }
    
    <div style={styles}>Hello World!</div>
    
  • Adding Assets (images, SVGs, videos, etc.)

    If you look at the default structure of a React app, you will see something similar to this:

    /my-react-app
        /node_modules
        /public
        /src
        .package.json
    

    All of your Components and custom CSS and JS should go into the /src folder.

    You can choose to also put your images, etc., in /src, or you can put them in /public.

    But how you reference those assets in your code will vary depending on which you choose...

    If you put your assets in /public, you can refer to them like you normally would in your HTML:

    function Header() {
        return (
            <header>
                <img src="/logo.png" ... />
            </header>
        )
    }
    

    If you put your assets in /src, however, you first have to import that asset, as a Component, then you can refer to it:

    import logo from './logo.png';
    function Header() {
        return (
            <header>
                <img src={logo} ... />
            </header>
        )
    }
    

    Note that {logo} above is the URL for the image, to be used as the src value, and does not use quotes. Think of logo as a variable, pointing to that image location.

    And don't forget that img elements must be explicitly closed in JSX!

  • Conditional Rendering

    Sometimes your UI will "toggle" some items depending on the current state.

    Of course you can use ternary statements:

    <h1>
        Hello {
            firstName !== '' ? 
            firstName : 
            'World' 
        }!
    </h1>
    

    This is okay for toggling small fragments.

    But for larger sections of code, or even entire elements, kind of like when you are using an if statement, you can use && and do something like this:

    { props.shouldShow && <h1>Hello</h1> }
    

    The above will only show the h1 if props.shouldShow is true.

  • Forms

    Forms in React are another complicated topic...

    Normal HTML forms do work just fine in React, but usually you want an Ajax submit.

    Which means form elements need to control their own state (as the user enters data) and React already controls its state via setState, so we need to combine the two so that React is the "single source of truth".

    Form elements controlled by React are called a "controlled components".

    Event handlers update the React state with every keystroke.

    This process works for input, select and textarea, although <input type="file" /> is considered an "uncontrolled component", as it is read-only.

    The basic idea is to setup state, create an event handler, and attach it to the form element:

    const [firstName, setFirstName] = React.useState('')
    
    function handleChange(event){
        setFirstName(event.target.value)
    }
    
    <input type="text" onChange={handleChange} />
    

    However, this can get pretty crazy, pretty quickly, with large forms, having to create state and an event handler for every form element...

    Instead, we can save all of the form data in a single state, as a JS Object, then handleChange can be expanded to handle all form fields:

    const [formData, setFormData] = React.useState({
        firstName: '',
        lastName: ''
    })
    
    function handleChange(event){
        setFormData(prevFormData => {
            return {
                ...prevFormData,
                [event.target.name]: event.target.value
            }
        })
    }
    
    <input name="firstName" onChange={handleChange} />
    <input name="lastName" onChange={handleChange} />
    

    Note that if the form element name attributes are the same as the state "key" name, then in the handleChange function we can use the spread operator to copy all existing object names & values, and use a dynamic variable name ([event.target.name]) to update just the data that is being updated by the user.

    To take things a little further, and ensure that React is the "single source of truth", we can create "Controlled Inputs" by adding a value attribute to each input, and letting React update it on render:

    <input 
        value={formData.firstName} 
        name="firstName" 
        onChange={handleChange} 
    />
    

    Yes, this might seem a bit circular (the user types into the input, React takes the input value and pushes it to the state, then the state updates the input value), but this means we have a "single source of truth", and React controls it.

    And, in a way, this is not that dissimilar to how an old-school PHP-driven form works: the user types into the input, when the form submits PHP takes the input value and pushes into the database, then PHP updates the input value before sending back to the browser.

    Not dissimilar, just... slower.

    Naturally, not all form elements are "so simple"...

    The select element is still fairly straight forward, as the name and onChange work similarly to native HTML and the above examples.

    A small difference from native HTML is that in React we add a value, like the input examples above, and React automatically "selects" the correct option based on that value, which is actually quite nice (none of that "if the selected value is the same as this value, mark this option as selected" stuff):

    <select 
        name="favColor"
        value={formData.favColor}
        onChange={handleChange}
    >
        <option value="">-- Choose --</option>
        <option value="Red">Red</option>
        <option value="Blue">Blue</option>
        ...
    </select>
    

    The textarea follows a similar pattern, with one more slight change...

    While in HTML, the textarea element is not self-closing, and its "value" is whatever is between the opening and closing tags:

    <textarea>The comment is here</textarea>
    

    In React, the textarea element is self-closing, and its "value" goes in a value attribute, just like all the examples above:

    <textarea value="The comment is here" />
    

    Next up is the checkbox elements, and since it really represents Boolean states (either "checked" or "not checked"), React does not use a String value attribute, but instead uses the checked attribute state, which must be either true or false:

    <input type="checkbox" checked={formData.optIn} onChange={handleChange} />
    

    Again, not bad, and actually kind of nice, but you might note that this breaks our handleChange function, which is looking for the event.target.value, so we need to update it a little...

    Convention typically destructures the event.target object to get the name and value parameters, and since we will also need a couple more parameters now, we can use a ternary to determine whether to use the value or checked attribute, based on the element's type:

    function handleChange(event){
        const {name, value, type, checkbox} = event.target
        setFormData(prevFormData => {
            return {
                ...prevFormData,
                [name]: (type === 'checkbox' ? checked : value)
            }
        })
    }
    

    Note that we are still using the spread operator to copy all previous form data, and, after destructuring, we are still using the dynamic variable [name] to determine which state property needs to be updated, but the ternary now determines which event.target parameter we use as the new state value: if the type is "checkbox", we will return either a true or false Boolean; otherwise we will return whatever String is in the value.

    Next, like regular HTML radio elements, multiple elements are used to indicate the value for a single state.

    All of these radio button have name="employment", so only one can ever be checked, but each has a unique value:

    <input 
        type="radio" 
        name="employment" 
        value="unemployed" 
        onChange={handleChange} 
    />
    <input 
        type="radio" 
        name="employment" 
        value="full-time"
        onChange={handleChange} 
    />
    <input 
        type="radio" 
        name="employment" 
        value="part-time"
        onChange={handleChange} 
    />
    

    However, since the value attribute is needed to designate the element's actual value, React sort of bastardizes the checkbox's checked attribute: if the state matches that radio's value, then its checked attribute is true:

    <input 
        type="radio" 
        name="employment" 
        value="part-time" 
        checked={formData.employment === 'part-time'} 
        onChange={handleChange} 
    />
    

    So if formData.employment is "part-time", then the above element's checked will be true, meaning, all of the other related elements will not be...

    And amazingly, since the above element's type is not checkbox, then this will also "just work" with our existing handleChange function, and it still store the form field's value...

    Finally, the submit button is also fairly straight-forward!

    The form gets an onSubmit attribute, which takes an event handler and function:

    <form onSubmit={handleSubmit}>
    

    But instead of using a classic input type="submit" to submit the form, we use a button element (which will also submit a form just fine in standard HTML):

    <button>Submit</button>
    

    And since we have been updating formData all along, the data is actually already ready to send, as a JS Object, to whatever data store we intend to use!

    function handleSubmit(event) {
        event.preventDefault()
        saveDataSomewhere(formData)
    }
    

    Note that we do still have to preventDefault if the form is being submitted via Ajax.

    And lastly, similarly to how React needs to use className and not class, note that the label element needs to use htmlFor instead of for...:

    <label htmlFor="employment">Employment</label>
    

    You can read more about React Forms here:
    https://reactjs.org/docs/forms.html

    You might also consider using pre-existing form handlers, some of which include validation, like:
    https://jaredpalmer.com/formik

  • Side Effects

    Considering that React's main tasks are:

    1. render the UI
    2. manage the state between renders
    3. keep the UI updated

    That leaves-out a lot of things that are often found in apps, like:

    • localStorage/database/API interactions
    • subscriptions to things like web workers
    • syncing multiple internal states
    • and the list goes one...

    This is where "Side Effects" come in; this is basically React's way of dealing with things that React doesn't do well, or at all, "out of the box".

    Also know as "the Effect Hook", and spotted in code as useEffect, Side Effects are to Functional Components what Lifecycle Methods are to Class Components (mostly): things like componentDidMount, componentDidUpdate and componentWillUnmount.

    Created by React, Side Effects allow us to interact with things that are "outside of React", syncing React state with outside systems: data fetching, setting up a subscriptions, and manually changing the DOM in React components, and more.

    useEffect tells React to do something after the render.

    The "Effect" is a function that you pass to useEffect:

    function Component() {
        const [count, setCount] = useState(0)
        useEffect(() => {
            // this will happen *after* the render, 
            // so state will always be "current"
            document.title = `You clicked ${count} times`
        })
        return (
            <button onClick={
                () => setCount(count + 1)
            }>
                Click me
            </button>
        )
    }
    

    So on initial load, the document.title will say "0", but with each click it will increment by 1.

    Because useEffect is inside the component, it is within the component scope, so it has access to all of the local variables and state, so we do not need to pass those around!

    useEffect runs after every render (initial and updates, unlike its related Class-based counterparts), allowing you to do things after the state has been updated, and a re-render has completed.

    There are two common kinds of Side Effects in React:

    1. those that do not require an< type of cleanup, like network requests, manual DOM updates, logging, etc.,
    2. and those that do, like things that might cause memory leaks, like event listeners, web socket subscriptions, etc.

    Cleaning up after a Side Effect can be handled by returning a "cleanup" function from useEffect:

    useEffect(() => {
        function handleStatusChange(status) {
            setIsOnline(status.isOnline)
        }
        ChatAPI.subscribe(id, handleStatusChange)
        // Specify how to clean up after this effect
        return function cleanup() {
            ChatAPI.unsubscribe (id, handleStatusChange)
        }
    })
    

    React will store the cleanup function and use it when the component unmounts.

    Note that the cleanup function doesn't have to be a "named" function, it can be anonymous, and is only named above for clarity.

    If the cleanup/re-setup after each render could cause performance issues, you can also pass previous state into useEffect (known as a "Dependencies Array", seen as [count] in the example below), which tells it to only process the effect if the state has changed.

    useEffect(() => {
        document.title = `You clicked ${count} times`;
    }, [count]);
    // Only re-run the effect if `count` changes
    

    One concern to be aware of is the possibility of creating an infinite "refresh" loop...

    Consider the following API call:

    // after the UI is rendered...
    useEffect(() => {
        // fetch something from an API
        fetch('https://swapi.dev/api/people/1')
            // convert to JSON
            .then(res => res.json())
            // and push into state
            .then(data => setData(data))
    })
    

    Now let's walk through what is happening here:

    1. the component renders
    2. after render, useEffect fetches data from the API
    3. once received, updates the state
    4. state update triggers a re-render
    5. go back to step 2...

    The solution is to pass a Dependencies Array (the [count] bit added above to avoid performance issues), but in this case, we want to pass an empty array:

    useEffect(() => {
        fetch('https://swapi.dev/api/people/1')
            .then(res => res.json())
            .then(data => setData(data))
    }, []);
    

    Note the empty Dependencies Array, telling React that there are no Dependencies to watch (and trigger a re-render) for, therefore this effect should run only once, on the initial render.

    You can read more about Side Effects here:

  • Deploy to Live Web Server

    In order to deploy a React app to a live server, so the rest of the world can see it, the process is slightly more difficult than the old "FTP to server, refresh page".

    Since I use Dreamhost as my web host, I found this article, but I do not see anything specific about Dreamhost in it, so hopefully it will work for you, too:
    Deploying a Simple React Application using FTP, FileZilla, & DreamHost

    My only notes from that article are:

    • I had to rewrite all relative URLs to absolute URLs because my test app was nested in several directories on my site.
    • The <script src="bundle.js"... did not get created and added to my build, not sure why, so I removed that from my app's index.html.

Summary

I am super happy that I have finally dug into this MVC stuff! I really enjoyed learning what I have already learned, and definitely plan to keep learning more! I don't know if I will ever need it, as most things that I build are web "sites", not really web "apps", meaning, mostly just content, not a lot of interaction.

And while I know I could use React for a content-driven site, it also seems like the wrong tool for the job...

But I know that if I ever do need to do something "appy", I could crank out a React component or two and have a super fly add-on to any existing site!

Resources

Below is a very short list of resources that I found useful while getting to know React. I highly recommend you visit them all as well!

Thanks a lot for following along, and please, if you think I missed anything or if you differ with my interpretation or understanding of something, let me know! My goal here is for everyone to read this and get to know this topic better! And you can help with that!

Happy reacting,
Atg

One Response to Getting to Know… React

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.