Learning Gutenberg: React 101

Publikováno: 23.5.2018

Although Gutenberg is put together with React, the code we’re writing to make custom blocks isn’t. It certainly resembles a React component though, so I think it’s useful to have a little play to get familiar with this sort of approach. There’s been a lot of reading in this series so far, so let’s roll-up our sleeves and make something cool.

Article Series:

  1. Series Introduction
  2. What is Gutenberg, Anyway?
  3. A Primer with create-guten-block
  4. Modern JavaScript Syntax
  5. React 101 (This

The post Learning Gutenberg: React 101 appeared first on CSS-Tricks.

Celý článek

Although Gutenberg is put together with React, the code we’re writing to make custom blocks isn’t. It certainly resembles a React component though, so I think it’s useful to have a little play to get familiar with this sort of approach. There’s been a lot of reading in this series so far, so let’s roll-up our sleeves and make something cool.

Article Series:

  1. Series Introduction
  2. What is Gutenberg, Anyway?
  3. A Primer with create-guten-block
  4. Modern JavaScript Syntax
  5. React 101 (This Post)
  6. Setting up a Custom webpack (Coming Soon!)
  7. A Custom "Card" Block (Coming Soon!)

Let’s make an “About Me” component

We’re going to make a single React component that updates the background color of a page and the intro text based on data you input into a couple of fields. “I thought this was supposed to be cool,” I hear you all mutter. I’ll admit, I may have oversold it, but we’re going to learn some core concepts of state-driven JavaScript which will come in handy when we dig into our Gutenberg block.

For reference, this is what we’re going to end up with:

Getting started

The first thing we’re going to do is fire up CodePen. CodePen can be used for free, so go head over there and create a new Pen.

Next, we’re going to pull in some JavaScript dependencies. There are three editor screens—find the JS screen and click the settings cog. This will open up a Pen Settings modal where you’ll find the section titled Add External Scripts/Pens. Right at the bottom, theres a Quick-add select menu. Go ahead and open that up.

A screenshot of the CodePen interface with the JavaScript settings open. The settings are in a split pane where the settings are on the left in a white box and advanced settings are on the right in a dark gray box.

From the menu, select React. Once that’s selected, open the menu and select ReactDOM. You’ll see that this has pre-filled some text boxes.

Lastly, we need to enable our ES6 code, so at the menu titled JavaScript Preprocessor, select Babel.

Now, go ahead and click the big Save & Close button.

What we’ve done there is pull the main React JS library and ReactDOM library. These will enable us to dive in and write our code, which is our next step.

Setup our CSS

Let’s make it look cool. First up though, let’s setup our CSS editor. The first thing we’re going to do is set it up to compile Sass for us. Just like we did with the JS editor, click on the settings cog which will bring up the Pen Settings modal again—this time with the CSS settings.

At the top, there’s a CSS Preprocessor menu. Go ahead and select SCSS from there.

When that’s done, go down to the Add External Stylesheets/Pens and paste the following three links into separate text-boxes:

https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.css
https://fonts.googleapis.com/css?family=Work+Sans:300
https://rawgit.com/hankchizljaw/boilerform/master/dist/css/boilerform.min.css

Those three in order give us a reset, a fancy font and some helpful form styles.

Now that they’re all set, go ahead and click the "Save & Close" button again.

Adding a bit of style

We’re all setup so this step should be easy. Paste the following Sass into the CSS editor:

:root {
  --text-color: #f3f3f3;
}

* {
  box-sizing: border-box;
}

html {
  height: 100%;
  font-size: 16px;
}

body {
  height: 100%;
  position: relative;
  font-size: 1rem;
  line-height: 1.4;
  font-family: "Work Sans", sans-serif;
  font-weight: 300;
  background: #f3f3f3;
  color: #232323;
}

.about {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  color: var(--text-color);
  transition: all 2000ms ease-in-out;
  
  &__inner {
    display: flex;
    flex-direction: column;
    height: 100%;
    margin: 0 auto;
    padding: 1.2rem;
  }
  
  &__content {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    flex: 1 1 auto;
    font-size: 3rem;
    line-height: 1.2;
    
    > * {
      max-width: 30ch;
    }
  }
  
  &__form {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 2rem 0;
    width: 100%;
    max-width: 60rem;
    margin: 0 auto;

    @media(min-width: 32rem) {
      flex-direction: row;
      justify-content: space-between;
      padding: 2rem;
    }
    
    > * {
      width: 15rem;
    }
    
    > * + * {
      margin: 1rem 0 0 0;
      
      @media(min-width: 32rem) {
        margin: 0;
      }
    }
    
    label {
      display: block;
    }
  }
}

// Boilerform overrides 
.c-select-field {
  &,
  &__menu {
    width: 100%;
  }
}

.c-input-field {
  width: 100%;
}

.c-label {
  color: var(--text-color);
}

That’s a big ol’ chunk of CSS, and it’ll look like nothing has really happened, but it’s all good—we’re not going to have to worry about CSS for the rest of this section.

Digging into React

The first thing we’re going to do is give React something to latch on to. Paste this into the HTML editor of your Pen:

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

That’s it for HTML—you can go ahead and maximize your JS editor so we’ve got complete focus.

Let’s start our component code, by creating a new instance of a React component by writing the following JavaScript:

class AboutMe extends React.Component {
}

What that code is doing is creating a new AboutMe component and extending React’s Component class, which gives us a load of code and tooling for free.

Right, so we’ve got a class, and now we need to construct it! Add the following code, inside the brackets:

constructor(props) {
  super(props);
  
  let self = this;
};

We’ve got a few things going on here, so I’ll explain each:

constructor is the method that’s called when you write new AboutMe(), or if you write <AboutMe /> in your JSX. This constructs the object. The props parameter is something you’ll see a lot in React. This is the collection of properties that are passed into the component. For example: if you wrote <AboutMe name="Andy" />, you’d be able to access it in your constructor with props.name.

super is how we tell the class that we’ve extended to construct with its own constructor. You’ll see we’re also passing the props up to it in case any parent components need to access them.

Finally let self = this is a way of controlling the scope of this. Remember, because we’re using let, self will only be available in the constructor function.

Quick note for those readers not-so-confident in JavaScript: I found a deeper look at scope in JavaScript to result in a lot of “aha” moments in my learning. I highly recommend Kyle Simpson’s You Don’t Know JS book series (available for free on GitHub!). Volumes of note: this and Object Prototypes and Scope & Closures. Good stuff, I promise.

Now we’ve covered the constructor, let’s add some more code to it. After the let self = this; line, paste the following code:

self.availableColors = [
  {
    "name": "Red",
    "value": "#ca3814"
  },
  {
    "name": "Blue",
    "value": "#0086cc"
  },
  {
    "name": "Green",
    "value": "#3aa22b"
  }
];

What we’ve got there is an array of objects that define our options for picking your favorite color. Go ahead and add your own if it’s not already there!

Your class definition and constructor should now look like this:

class AboutMe extends React.Component {
  
  constructor(props) {
    super(props);
    
    let self = this;
    
    // Set a list of available colors that render in the select menu
    self.availableColors = [
      {
        "name": "Red",
        "value": "#ca3814"
      },
      {
        "name": "Blue",
        "value": "#0086cc"
      },
      {
        "name": "Green",
        "value": "#3aa22b"
      }
    ];
  };
}

Pretty straightforward so far, right? Let’s move on and set some initial values to our reactive state. Add the following after the closing of self.availableColors:

// Set our initial reactive state values
self.state = {
  name: 'Foo',
  color: self.availableColors[0].value
};

This initial setting of state enables our component to render both a name and a color on load, which prevents it from looking broken.

Next, we’ll add our render function. This is a pure function, which does nothing but render the component based on the initial state or any state changes during the component’s lifecycle. You may have guessed already, but this is where the main body of our JSX lives.

Wait up! What’s a pure function? Welcome to functional programming, a hot topic in the React world. Pure functions are functions where, for input X, the output will always be Y. In an "impure" function, input X might result in different outputs, depending other parts of the program. Here’s a CodePen comparing pure and impure functions. Check out this article out, too, for more details.

Now, because there’s quite a lot of markup in this single component, we’re going to copy the whole lot into our function. Add the following under your constructor:

render() {
  let self = this;
  
  return (
    <main className="about" style={ { background: self.state.color } }>
      <section className="about__inner">
        <article className="about__content">
          { self.state.name ? <p>Hello there. My name is { self.state.name }, and my favourite color is { self.getActiveColorName() }</p> : null }
        </article>
        <form className="[ about__form ] [ boilerform ]">
          <div>
            <label className="c-label" htmlFor="name_field">Your name</label>
            <input className="c-input-field" type="text" id="name_field" value={ self.state.name } onChange={ self.updateName.bind(self) } />
          </div>
          <div>
            <label className="c-label" htmlFor="color_field">Your favourite color</label>
            <div className="c-select-field">
              <select className="c-select-field__menu" value={ self.state.color } onChange={ self.updateColor.bind(self) } id="color_field">
                { self.availableColors.map((color, index) => {
                  return (
                    <option key={ index } value={ color.value }>{ color.name }</option>
                  );
                })}
              </select>
              <span className="c-select-field__decor" aria-hidden="true" role="presentation">▾</span>
            </div>
          </div>
        </form>
      </section>
    </main>
  );
};

You may be thinking something like: “Holy cow, there’s a lot going on here.” Let’s dissect it, so don’t worry about copying code for a bit—I’ll let you know where we’re going to do that again. Let’s just focus on some key bits for now.

In JSX, you need to return a single element, which can have child elements. Because all of our code is wrapped in a <main> tag, we’re all good there. On that <main> tag, you’ll see we have an expression in an attribute, like we covered in Part 2. This expression sets the background color as the current active color that’s set in our state. This will update like magic when a user changes their color choice without us having to write another line of code for it in this render function. Pretty cool, huh?

Inside the <article class="about__content"> element, you’ll notice this:

{ self.state.name ? <p>Hello there. My name is { self.state.name }, and my favourite color is { self.getActiveColorName() }</p> : null }

This ternary operator checks to see if there’s a name set and renders either a sentence containing the name or null. Returning null in JSX is how you tell it to render nothing to the client. Also related to this snippet: we were able to run this ternary operator within our JSX because we created an expression by opening some brackets. It’s a really useful way of sprinkling small, simple bits of display logic within your render function.

Next up, let’s look at an event binding:

<input className="c-input-field" type="text" id="name_field" value={ self.state.name } onChange={ self.updateName.bind(self) } />

If you don’t bind an event to your input field, it’ll be read only. Don’t panic about forgetting though. React helpfully warns you in your console.

Remember, self is equal to this, so what we’re doing is attaching the updateName function to the input’s onChange event, but we’re also binding self, so that when we’re within the updateName function, this will equal AboutMe, which is our component.

The last thing we’re going to look at in the render function is loops. Here’s the snippet that renders the color menu:

<select className="c-select-field__menu" value={ self.state.color } onChange={ self.updateColor.bind(self) } id="color_field">
  { self.availableColors.map((color, index) => {
    return (
      <option key={ index } value={ color.value }>{ color.name }</option>
    );
  }) }
</select>

The value and change setup is the same as the above <input /> element, so we’ll ignore them and dive straight in to the loop. What we’ve done is open up an expression where we run a pretty standard Array Map function, but, importantly, it returns JSX in each iteration, which allows each option to render with the rest of the JSX.

Wiring it all up

Now that we’ve got our core aspects of the component running, we need to wire it up. You’ll notice that your CodePen isn’t doing anything at the moment. That’s because of two things:

  • We haven’t attached the component to the DOM yet
  • We haven’t written any methods to make it interactive

Let’s start with the former and add our change event handlers. Add the following underneath your constructor function:

updateName(evt) {
  let self = this;
  
  self.setState({
    name: evt.target.value
  })
};

updateColor(evt) {
  let self = this;
  
  self.setState({
    color: evt.target.value
  })
};

These two functions handle the onChange events of the <select> and <input> and set their values in state using React’s setState function. Now that the values are in state, anything that is subscribed to them will update automatically. This means that the ternary statement that renders your name and the background color will change in realtime as you type/select. Awesome. right?

Now, it would be recommended to make your code more DRY by combining these two as one change event handler that updates the relevant state. For this series though, let’s keep things simple and more understandable. 😀

Next up, let’s add the last method to our component. Add the following under your recently added update methods:

// Return active color name from available colors, based on state value
getActiveColorName() {
  let self = this;
  
  return self.availableColors.filter(color => color.value === self.state.color)[0].name;
};

This function uses one of my favorite JavaScript array methods: filter. With ES6, we can cherry pick array items based on their object value in one line, which is powerful stuff. With that power, we can pick the currently active availableColors item’s human-readable name and return it back.

JavaScript array methods are very cool and commonly spotted in the React ecosystem. Sarah Drasner made a seriously amazing “Array Explorer”—check it out here!

Attaching the component to the DOM

The last thing we’re going to do is attach our component to the DOM, using ReactDOM. What we’re doing is saying, “Hey browser, fetch me the <div id="root"> element and render this React component in it.” ReactDOM is doing all the magic that makes that possible.

ReactDOM is a really smart package that takes changes in your dynamic React components, calculates what needs to be changed in the DOM and applies those changes in the most efficient possible way. With ReactDOM’s renderToString() method, you can also render your React components to a static string, which can then be inserted to your page with your server-side code. What’s smart about this is that references are added so that if your front-end picks up some server-rendered React, it’ll work out which components are needed and make the whole chunk of static markup dynamic automatically. Pretty damn smart, huh?

Anyway, back to our Pen. Add this right at the bottom of your JS editor:

// Attach our component to the <div id="root"> element
ReactDOM.render(<AboutMe />, document.getElementById('root'));

Now, you’ll notice that your preview window has suddenly come alive! Congratulations — you just wrote a React component 🎉

See the Pen About Me React Component by Andy Bell (@hankchizlja) on CodePen.

Wrapping up

In this part, you’ve learned about reactive, component JavaScript by writing a React component. This is relevant to your learning because custom Gutenberg blocks follow a very similar setup to a React component. Now that you’ve got a better understanding of how a React component works, you should be able to understand how a custom Gutenberg block works too.

It took me a bit to wrap my mind around the fact that, in terms of Gutenberg, React is only relevant to building blocks within the admin. In Gutenberg, React functions as a means of preparing the markup to be saved to the database in the post_content column. Using React on the front-end of a WordPress site to build something like this would be separate from what we will be doing in this series.

Next up in this series, we’re going to edit our WordPress theme so that we can build our custom Gutenberg block.


Article Series:

  1. Series Introduction
  2. What is Gutenberg, Anyway?
  3. A Primer with create-guten-block
  4. Modern JavaScript Syntax
  5. React 101 (This Post)
  6. Setting up a Custom webpack (Coming Soon!)
  7. A Custom "Card" Block (Coming Soon!)

The post Learning Gutenberg: React 101 appeared first on CSS-Tricks.

Nahoru
Tento web používá k poskytování služeb a analýze návštěvnosti soubory cookie. Používáním tohoto webu s tímto souhlasíte. Další informace