2 chap05
Jason Zhu edited this page 2022-01-12 22:15:55 +11:00

Chapter 5. React with JSX

Real React developer does not use React.createElement function to construct React element. JSX is the default way for developer.

5.1 React Elements as JSX

In JSX:

  • An element's type is specified with a tag.
  • Tag's attribute represents the properties
    • Properties have 2 types: string or JS expression
  • Element's children are added between opening and closing tags
  • JS expression in JSX must sorrounded with curly braces {} (e.g. array, object)

React Element vs JSX

  • <IngredientList ...> as tag is element's type
  • list:[...] has all children.

5.1.2 JSX Tips

JSX share similarity with HTML syntax, with few tips

Nested component

JSX allows you to add components as children of other componenets

<IngredientsList>
  <Ingredient />
  <Ingredient />
  <Ingredient />
</IngredientsList>

className

Since class is reserved word in JS, className is used by React to define class attribute

<h1 className="fancy">Baked Salmon</h1>

JavaScript expression

JavaScript expression in JSX are wrapped in curly braces {}, so they can be evaluated and returned.

<h1>{title}</h1>
<input type="checkbox" defaultChecked={false} />

Evaluation

JS between {} will be evaluated, which include e.g. concatenation, addition, function invokation, etc.

<h1>{"Hello" + title}</h1>
<h1>{title.toLowerCase().replace}</h1>

5.1.3 Mapping Arrays with JSX

Since JSX is JS, JSX can be directly incorporated in JS functions.

e.g. map an array to JSX elements

<ul>
    {props.ingredients.map((ingredient, i) => ( // Here we specify what to be returned by using `()`
        <li key="{i}">{ingredient}</li>
    ))}
</ul>

5.2 Babel

All JSX must be converted into createElement calls to be interpreted by a browser. Babel is one of them

JS is an interpreted language: the browser interprets the code as text (they don't need to compile JS). However different browser support different JS standard. Babel can transpile to browser understandable code.

Easiest way of work with Babel: include a link to the Babel CDN directly in HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>React Examples</title>
  </head>
  <body>
    <div id="root"></div>

    <!-- React Library & React DOM -->
    <script src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

    <script type="text/babel">
      // JSX code here. Or link to separate JavaScript file that contains JSX.
    </script>
  </body>
</html>

5.3 Recipes as JSX

const data = [
  {
    name: "Baked Salmon",
    ingredients: [
      { name: "Salmon", amount: 1, measurement: "l lb" },
      { name: "Pine Nuts", amount: 1, measurement: "cup" },
      { name: "Butter Lettuce", amount: 2, measurement: "cups" },
      { name: "Yellow Squash", amount: 1, measurement: "med" },
      { name: "Olive Oil", amount: 0.5, measurement: "cup" },
      { name: "Garlic", amount: 3, measurement: "cloves" }
    ],
    steps: [
      "Preheat the oven to 350 degrees.",
      "Spread the olive oil around a glass baking dish.",
      "Add the yellow squash and place in the oven for 30 mins.",
      "Add the salmon, garlic, and pine nuts to the dish.",
      "Bake for 15 minutes.",
      "Remove from oven. Add the lettuce and serve."
    ]
  },
  {
    name: "Fish Tacos",
    ingredients: [
      { name: "Whitefish", amount: 1, measurement: "l lb" },
      { name: "Cheese", amount: 1, measurement: "cup" },
      { name: "Iceberg Lettuce", amount: 2, measurement: "cups" },
      { name: "Tomatoes", amount: 2, measurement: "large" },
      { name: "Tortillas", amount: 3, measurement: "med" }
    ],
    steps: [
      "Cook the fish on the grill until cooked through.",
      "Place the fish on the 3 tortillas.",
      "Top them with lettuce, tomatoes, and cheese."
    ]
  }
];

// The data, an array of Recipe objects
const data = [ ... ];

// A function component for an individual Recipe
function Recipe (props) {
  ...
}

// A function component for the Menu of Recipes
function Menu (props) {
  ...
}

// A call to ReactDOM.render to render our Menu into the current DOM
ReactDOM.render(
  <Menu recipes={data} title="Delicious Recipes" />,
  document.getElementById("root")
);
  • props.recipes of Menu component will take the data array

Improve Menu componenet by takes in props argument.

function Menu(props) {
  return (
    <article>
      <header>
        <h1>{props.title}</h1>
      </header>
      <div className="recipes">
        {props.recipes.map((recipe, i) => (
          <Recipe 
            key={i} 
            name={recipe.name} 
            ingredients={recipe.ingredients} 
            steps={recipe.steps}
          />
        ))}
      </div>
    </article>
  );
}

Further improvement by desctructing the props object

function Menu({ title, recipes }) {
  return (
    <article>
      <header>
        <h1>{title}</h1>
      </header>
      <div className="recipes">
        {recipes.map((recipe, i) => (
          <Recipe key={i} {...recipe} />
        ))}
      </div>
    </article>
  );
}

Complete code for app is:

const data = [
  {
    name: "Baked Salmon",
    ingredients: [
      { name: "Salmon", amount: 1, measurement: "l lb" },
      { name: "Pine Nuts", amount: 1, measurement: "cup" },
      { name: "Butter Lettuce", amount: 2, measurement: "cups" },
      { name: "Yellow Squash", amount: 1, measurement: "med" },
      { name: "Olive Oil", amount: 0.5, measurement: "cup" },
      { name: "Garlic", amount: 3, measurement: "cloves" }
    ],
    steps: [
      "Preheat the oven to 350 degrees.",
      "Spread the olive oil around a glass baking dish.",
      "Add the yellow squash and place in the oven for 30 mins.",
      "Add the salmon, garlic, and pine nuts to the dish.",
      "Bake for 15 minutes.",
      "Remove from oven. Add the lettuce and serve."
    ]
  },
  {
    name: "Fish Tacos",
    ingredients: [
      { name: "Whitefish", amount: 1, measurement: "l lb" },
      { name: "Cheese", amount: 1, measurement: "cup" },
      { name: "Iceberg Lettuce", amount: 2, measurement: "cups" },
      { name: "Tomatoes", amount: 2, measurement: "large" },
      { name: "Tortillas", amount: 3, measurement: "med" }
    ],
    steps: [
      "Cook the fish on the grill until hot.",
      "Place the fish on the 3 tortillas.",
      "Top them with lettuce, tomatoes, and cheese."
    ]
  }
];

function Recipe({ name, ingredients, steps }) {
  return (
    <section id={name.toLowerCase().replace(/ /g, "-")}>
      <h1>{name}</h1>
      <ul className="ingredients">
        {ingredients.map((ingredient, i) => (
          <li key={i}>{ingredient.name}</li>
        ))}
      </ul>
      <section className="instructions">
        <h2>Cooking Instructions</h2>
        {steps.map((step, i) => (
          <p key={i}>{step}</p>
        ))}
      </section>
    </section>
  );
}

function Menu({ title, recipes }) {
  return (
    <article>
      <header>
        <h1>{title}</h1>
      </header>
      <div className="recipes">
        {recipes.map((recipe, i) => (
          <Recipe key={i} {...recipe} />
        ))}
      </div>
    </article>
  );
}

ReactDOM.render(
  <Menu recipes={data} title="Delicious Recipes" />,
  document.getElementById("root")
);
  • Here we can see the Menu and its child elements. The data array contains two objects for recipes, and we have two Recipe elements. Each Recipe element has properties for the recipe name, ingredients, and steps. The ingredients and steps are passed down to their own components as data.

Effect of rendered HTML

Figure 5-2. Delicious Recipes output

React virtual DOM of can be viewed by using React Developer Tools

Figure 5-3. Resulting virtual DOM in React Developer Tools

5.4 React Fragments

  • React won't render multiple adjacent elements as a single component, so we used to have to wrap these in an enclosing tag like a div (e.g. shown above in Recipe component).
  • Error Adjacent JSX elements must be wrapped in an enclosing tag will be generate from console
  • Hence, a log of unnecessary tags are created (wrapper)
  • Solution is using React.Fragment tag (a relative new feature)

e.g. Functional component return multiple adjacent/sibling components

function Cat({ name }) {
  return (
    <h1>The cat's name is {name}</h1>
    <p>He's good.</p>
  );
}

How to use <React.Fragment>: Wrapping adjacent tags h1 and p with React.Fragment tag:

function Cat({ name }) {
  return
    <React.Fragment>
      <h1>The cat's name is {name}</h1>
      <p>He's good.</p>
    </React.Fragment>
}

Fragment shorthand <> simplify the wrapping:

function Cat({ name }) {
  return (
    <>
      <h1>The cat's name is {name}</h1>
      <p>He's good.</p>
    </>
  );
}

After rendering, DOM is shown below (fragment is invisible)

<div id="root">
  <h1>The cat's name is Jungle</h1>
  <p>He's good</p>
</div>

5.5 Intro to webpack

Webpack is a module bundler that takes all different files (JS, LESS, CSS, JSX, ESNext, etc) to turn them into a single file.

Advantage of module bundler:

  • modularity
  • network performance

Aside from code compilation, webpack can handle:

  • Code splitting
  • Minification
  • Feature Flagging
  • Hot Module Replacement (HMR)

Benefit of incorporating webpack module bundler:

  • Modularity
  • Composition
  • Speed
  • Consistency

5.5.1 Creating the Project

5.5.2 Loading the Bundle

5.5.3 Source Mapping

  • Problem: Bunding the code into a single file makes debugging difficult in browser, as developer tool shows the source code which is not human friendly.
  • Solution: Source Map is a file that maps a bundle to the original source file.
  • How to use create/use source map: add devtool property to #source-map in webpack.config.js, which tell webpack we want to use source mapping. (as shown below)
    • dist folder will have bundle.js and bundle.js.map. .map file let you debug using original source file.
    • In Source tab of browser's developer tools, we can find webpack:// folder containing all files used to debug
// webpack.config.js with source mapping

module.exports = {
  ...
  devtool: "#source-map"
}

5.5.4 Create React App