Files
Micah Godbolt 7cea32428e Rewrite of Day 1 to use modern React (#294)
* update to hooks

* more class to function

* cleanup

* finish ts final

* update html lesson

* add lessons page

* clean up

* move getters into context

* adding type

* fix bug

* step 5 cleanup

* init final pass

* text tweak

* fix ternaries

* readme cleanup

* fixed root readme
2022-01-13 09:22:50 -08:00
..

Step 1.4 - Introduction to React (Demo)

In this demo we'll be creating a simple counter that will display a count and increment on click.

Let's start this demo in CodePen.

React Hello World

ReactDOM.render(<p>Hello World</p>, document.getElementById('app'));

Calling ReactDOM.render() is how our code gets on the page. The function takes two parameters: the content to place on the page, and the element in which you want it placed.

The first parameter to render() looks a lot like HTML, but actually, it's JSX. There are a few key differences between JSX and HTML:

  • Since class is a reserved word in JavaScript, you will need to use className on your HTML tags: <div className="foo">
  • We can use custom HTML tags corresponding to the React components we create: <div><MyControl>hi</MyControl></div>
  • Controls can be self-closing: <MyControl text='hi' />
  • You can use JavaScript inside of JSX!

Writing a React component

A React component is a function that returns a portion of your application. This can include HTML markup, CSS styles, and JavaScript driven functionality.

const App = (props) => {
  return <p>Hello World</p>;
};

Moving our "Hello World" markup into our App's render function, we can now update the ReactDOM.render() call to look like this:

ReactDOM.render(<App />, document.getElementById('app'));

Props

A component can take in additional props using the same syntax as HTML attributes like id or href.

<App text="Hello World" />

The text prop can be accessed inside your component via props.text in a component.

const App = (props) => {
  return <p>{props.text}</p>;
};

props allow your component to be more reusable, since you can create multiple instances of the same component with different props.

ReactDOM.render(
  <div>
    <App text="Hello World" />
    <App text="How are you doing?" />
  </div>,
  document.getElementById('app')
);

Note that a render function can only return a single element, so our two App components need to be wrapped in a div.

const App = (props) => {
  return <p>{props.text ? props.text : 'oops!'}</p>;
};

Destructuring props

Writing props.text over and over in a function can be quite tedious. Since this is all JavaScript, you could create a new variable for this text using variable assignment.

const App = (props) => {
  const text = props.text;
  return <p>{text ? text : 'you missed something'}</p>;
};

This works fine for a single prop, but as your component starts to become more complex:

<App
  open={false}
  count={5}
  text="Hello World"
  items={['cat', 'dog', 'bird']}
  config={{
    start: 1,
    end: 10,
    autoStart: true,
  }}
/>

Note that all non-string values are passed through as JavaScript by wrapping them in {}.

Your code starts to look like this:

const open = props.open;
const text = props.text;
const count = props.count;
const items = props.items;

A common approach to simplify this process is to use a syntax called destructuring.

Destructuring allows you to pull individual pieces of information out of an object in a single statement.

const { open, text, count, items } = props;

So while this might be overkill right now, it makes it easier to add props down the road.

Cleanup

Before we move on, we'll modify our ReactDOM.render call to just include our App. This render call typically includes just a single component with no props.

Next we'll be creating a Counter component. We'll add that to our App now, and then start to write the control.

const App = (props) => {
  return <Counter text="chickens" />;
};

ReactDOM.render(<App />, document.getElementById('app'));

Note the capitalization of Counter. HTML might not be case-sensitive, but JSX is! A common practice is to use the capitalized names of HTML elements to name corresponding React components: Button, Select, Label, Form, etc.

Writing a stateful Counter component

The power of React, past being a good templating language, is that it provides us a way to maintain and modify state over the componet's lifecycle.

Adding state

State is added to a component by using the useState hook. Hooks are special React methods that can only be called within a React component, and provide ways to maintain state and perform other lifecycle methods.

const Counter = (props) => {
  const [clicks, setClicks] = React.useState(0);
};
  • The component takes in someprops.
  • clicks is a stateful value that will be updated each time setClicks is called with a new value

Rendering our Counter

For our Counter component, the goal is to be able to track how many times the counter's button is clicked. We'll use the following markup.

const { text } = props;
return (
  <div>
    {text}: {clicks}
    <button>Click</button>
  </div>
);

Writing our button click handler

Our next step is to wire up the button to increment the clicks in our component state.

This function will increment the clicks value by 1.

const handleClick = () => {
  setClicks(clicks + 1);
};

Now that we have a function to increment our count, all that's left is to connect it to our button.

<button onClick={handleClick}>Click</button>

Also note that each Counter maintains its own state! You can modify the state inside of one counter without affecting the others.

Try it all out!

Add a couple Counters to our App, each with different text. Notice how they can easy take in different props and maintain their own state.

Moving this into our codebase

To scale our application, we'll need to break up the file into smaller, reusable pieces. In this part of the demo we'll look at the final folder and how the JavaScript module system allows us to break up our components into a collection of files exporting their functionality.

Module exports and imports

Open up step1-04/final/components/Counter.tsx and look at the Counter component.

export const Counter = (props) => {
  // ...
};

This file exports the Counter component as a named export. This means when we import it we do the following:

import { Counter } from './components/Counter';

Note the {} wrapped around the import value. This is actually an example of destructuring.

Default exports

We typically use named exports, but it's also possible export a default value like this:

export default const Counter = (props) =>{
  // ...
}

When we import the component we can call it whatever we want:

import SomeCounterComponent from './components/Counter';

Writing a Button component

Buttons are among the most commonly written components. Custom buttons help abstract common styling, add icons or other decorations, and increase functionality (menu buttons etc). Let's take a quick look at a custom button component to see how it comes together.

import React from 'react';
import './Button.css';

export const Button = (props) => {
  return (
    <button className="Button" onClick={props.onClick}>
      {props.children}
    </button>
  );
};