Josh Pollock, Founder & Lead Developer, CalderaWP Reading estimate: 10 minutes
TypeScript For WordPress Basics
WordPress 5.0 was the first WordPress release to incorporate the new React-powered Gutenberg UI framework. With Gutenberg as the standard for WordPress interface development, WordPress developers can now benefit from more from the awesome React and JavaScript ecosystems to improve the developer experience and user experience of creating and using our plugins, themes, and sites.
One of the great things about using build tools like Webpack for JavaScript development is you can choose the best JavaScript dialect (or with web assembly, or any language) for the job. One of the most popular JavaScript dialects is TypeScript - a strongly typed dialect of JavaScript.
Because TypeScript gets used a lot in React development, understanding it is important for WordPress developers in the Gutenberg-era. There is a reason TypeScript is taking off - it’s really great! In this article, I will show you why by explaining the basics of how you would use TypeScript to display WordPress posts and comparing TypeScript to PHP. I am going to assume you know the basics of React and are familiar with object-oriented PHP.
There is a demo of the code I am showing in this article, working with live data fetched from a Pantheon-hosted site via the WordPress REST API that you can play with as you go through this article.
What Are Types?
Languages ship with primitive types - integer, string, float, etc. In an object-oriented language like JavaScript, when you assign a primitive type to a variable, that variable is an object of the primitive type.
For example, in JavaScript, strings have the method splice. When we assign a string, to a variable, we can use it as a string, or modify the string using the string methods, such as splice:
const string = 'Hi Roy';
console.log(string);//logs a string
console.log(string.slice(4));//logs result of the slice method of string
In PHP we can’t work with strings or integers this way. In both PHP and JavaScript we have composable types - objects and arrays. PHP allows us to define types of objects using an interface.
Comparing PHP7 types and interfaces to TypeScript types and interfaces
Interfaces establish new “generic” types. Take a look at this class, with no interface:
<?php
interface PostContract {
/**
* Get post ID
*
* @return int
*/
public function getID() : string;
/**
*
* @param int $ID
*
* @return Post
*/
public function setID(int $ID): PostContract;
}
This class has the type “Post”, but that’s not a standard. Any other class that needed this class, would be strongly coupled to Post. We can reduce this coupling by defining an interface - a contract that setups of the rules of this generic type - and having Post implement it. Here is that interface:
The class Post needs to be refactored to implement the interface:
<?php
class Post implements PostContract{
/**
* Post ID
*
* @var int
*/
protected $ID;
/**
* Get post ID
*
* @return int
*/
public function getID() : string
{
return $this->ID;
}
/**
*
* @param int $ID
*
* @return Post
*/
public function setID(int $ID): PostContract
{
$this->ID = $ID;
return $this;
}
}
What is Typescript?
Typescript lets you add new types to JavaScript. It provides many features that we have in PHP7. For example, in PHP7, you can specify the scalar or object types for function arguments, and return types:
<?php
function addFloatToIntegerCastToString(float $a,int $b) : string
{
return (string) $a * $b;
}
In JavaScript, we can not add strong typing like this. Typescript -- or Flow -- adds these features. Here is the equivalent function, in Typescript:
function addFloatToIntegerCastToString(a: number, b: number): string {
return (a + b).toString();
}
Typescript lets us define our data structures in types and interfaces as well as typing the arguments and return values of functions.
Static Analysis
Typescript makes your code more verbose. By investing more time in defining how the code MUST be used, it becomes harder to use it in ways it shouldn’t be used, which should result in fewer bugs and faster development time over the life of the project, long-term. With a properly configured IDE, Typescript also improves the quality of code-autocomplete, which is a major productivity boost.
When Typescript compiles to JavaScript, the code is analyzed to see if it follows all of the rules. If not, you get a compile-time error, which should reduce the number of run-time errors. This adds confidence to your code.
"Static typing and linting tools like Flow and ESLint can get you a remarkable amount of confidence, and if you're not using these tools I highly suggest you give them a look. That said, even a strongly typed language should have tests. Typing and linting can't ensure your business logic is free of bugs." - Kent C. Dobbs
Quick Start Typescript With React For WordPress Developers
I think the best way to learn a new thing is to build something practical with it. Let’s spin up a quick React app with Typescript and play with some WordPress posts, that way it’s a familiar data structure that we are typing. Typescript has a great quick start that I recommend reading first.
You can create the app and edit it in your browser with Code Sandbox. Follow this link:
https://codesandbox.io/s/react-ts
Or, if you want to build the app locally, you can create the same thing with one command:
npx create-react-app learn-type-script --typescript
I don’t know how to setup Typescript from scratch and I don’t really care. There are starters for Next.js and Gatsby and other React app frameworks as well as other JavaScript frameworks like Vue and Angular.
Describing A WordPress Post With Typescript
Let’s start by creating an interface for a WordPress post - as returned by the WordPress REST API. To keep things simple, let’s start with the post id, which is a number, and the title, which is an object with one or more properties.
/**
* Interface descrinbing a WordPress post
*/
interface Post {
title: {
//This property is always present
rendered: string;
//This property is only present in some contexts
raw?: string;
},
id: number;
}
In this interface, we say that the property “id” MUST be present and it MUST be a number. The property title, we say it MUST be present and it MUST have the property render, which MUST be a string. Also, the object title MAY have a property “raw” - WordPress returns this if the user making the request can edit the post - and if that property is present, it MUST be a string. The “?” after the property name makes it optional, same as in PHP.
Properties of an interface can be defined with an interface. This is helpful as post title, content, and excerpt all have the same “shape”. That’s what interfaces are for, grouping objects with the same shape into reusable types. So, instead of defining content and excerpt by cutting and pasting title, let’s make a new interface and then reuse that interface:
/**
* Interface for post title, content and excerpt
*/
interface ContentObject {
//This property is always present
rendered: string;
//This property is only present in some contexts
raw?: string;
}
/**
* Interface for describing post title
*/
interface PostTitle extends ContentObject {}
/**
* Interface for describing post content
*/
interface PostContent extends ContentObject {}
/**
* Interface for describing post content
*/
interface PostExcerpt extends ContentObject {}
/**
* Interface descrinbing a WordPress post
*/
interface Post {
title: PostTitle;
content: PostContent;
excerpt: PostExcerpt;
date: string;
id: number;
}
If you read through the code above, first an interface that could be used for post title, content, or excerpt is defined. Then three interfaces, one for each are defined -- by extending the basic interface with no modifications -- and then they are used to define the Post interface. I used this approach for two reasons. First, you saw that interfaces can be extended, just like PHP or JavaScript classes can, which is useful. The second and more practical reason is I may want to create a post title component that while it would work with the data structure of post content, that’s not what it’s designed for.
Take a look at this component:
const PostTitle = (props: { postTitle: PostTitle}) => (
<h2>{props.postTitle.rendered}</h2>
);
Without typescript, I could pass it any object with a property of “rendered” and it would work. Typescript added more specificity. With plain JavaScript, I could also pass it any object or a string, or a float. The error would not be found until the program was run. Typescript would find this error when compiling, instead of at run-time. Again, a properly configured IDE works with Typescript to warn you of mistakes.
Here is a component that can show a WordPress post that uses this interface. It also accepts a boolean argument to determine if it should show the post content or excerpt:
/**
* Display A Blog Post
*
* @param props
*/
const BlogPost = (props: { post: Post; showContent: boolean }) => {
const { post, showContent } = props;
function createMarkup(markup: string) {
return { __html: markup };
}
return (
<article id={`post-${post.id}`}>
<h1>{post.title.rendered}</h1>
<p>Published {moment(post.date).fromNow()}</p>
<div>
{showContent ? (
<div dangerouslySetInnerHTML={createMarkup(post.content.rendered)} />
) : (
<div dangerouslySetInnerHTML={createMarkup(post.excerpt.rendered)} />
)}
</div>
</article>
);
};
That hide/show boolean will come in handy later on in this post when I cover typing change handlers.
Looping WordPress Posts With React and TypeScript
With one component to show a post, we can show a loop of posts. This is a good opportunity to show how we can use types in function arguments, and loops. To illustrate this, I will walk through building a component to list posts.
Here is the component with its arguments:
/**
* Display a list of posts
*
* @param props
*/
const ListOfPosts = (props: {
posts: Array<Post>;
showContent: boolean;
toggleShowContent: (enable: boolean) => void;
}) => {
const { posts, showContent, toggleShowContent } = props;
return (
<div>
{/** **/}
</div>
);
};
This component accepts one argument - the object “props”. This object is type so that it must contain the property “posts”. This is a generic type defining that the primitive type array MUST contain objects that implement the interface Post. The property "showContent” is a boolean value, that will be used to determine if the post content or the post excerpt is shown. The property toggleShowContent must be a function. This is the function that will be called to change the value of showContent - in the parent component. We define that function’s arguments and return type inline as well. This function must receive exactly one argument, which must be a boolean and it must return void.
To take this one step further lets loop through the array of Post objects. It’s cool that we know this array has to have properly formed data. It allows us to pass these objects to the BlogPost component that we created in the last step without additional validation:
/**
* Display a list of posts
*
* @param props
*/
const ListOfPosts = (props: {
posts: Array<Post>;
showContent: boolean;
toggleShowContent: (enable: boolean) => void;
}) => {
const { posts, showContent, toggleShowContent } = props;
return (
<div>
<button />
<Fragment>
{posts.map((post: Post) => (
<BlogPost key={post.id} post={post} showContent={showContent} />
))}
</Fragment>
</div>
);
};
Notice that the function being called by posts.map() has its function argument typed so that it only accepts Post. The typescript interpreter knows that’s safe to use because posts must be an array of Post objects.
Working With Change Handlers
In React, one thing we do a lot is bind functions to change events. When a button is clicked, we want to do something - in this case, call the toggleShowContent function with the opposite of the current value of showContent.
A change handler or on click function is a function that takes an Event object - in React this is abstracted to the Synthetic Event, or other types that inherit from that interface. For our function, we will type the event as a React.FormEvent<HTMLInputElement>.
Typescript tells us how everything fits together. In this case, we are working with a function that receives the synthetic event and then calls the function toggleShowContent with a boolean. We have to do a little bit of work to make the pieces fit together:
const ListOfPosts = (props: {
posts: Array<Post>;
showContent: boolean;
toggleShowContent: (enable: boolean) => void;
}) => {
const { posts, showContent, toggleShowContent } = props;
return (
<div>
<button
onClick={(e:React.FormEvent<HTMLInputElement>) => {
e.preventDefault();
toggleShowContent(!showContent);
}}
>
Show Full Content
</button>
<Fragment>
{posts.map((post: Post) => (
<BlogPost key={post.id} post={post} showContent={showContent} />
))}
</Fragment>
</div>
);
};
Here is another component that uses a callback function - bound to an input change event this time - using the same type. In this case, the callback function expects to be passed a string, which is set from the event’s current value.
Should You Use Typescript For WordPress Development?
That’s a crash course in Typescript, enough to get a developer who knows PHP and JavaScript started with using Typescript to improve the stability of your React for WordPress code base. Typescript has a lot of benefits, though I must admit I don’t use it a ton. It adds overhead both mentally and to compilation and testing, at least when you first start using it. As you become more comfortable with it, as your codebase grows in complexity and as your team grows in size the strictness of TypeScript should generate time savings through clarity.
Being able to read code written in Typescript is really helpful, whether you use it or not. Personally, I’m using it on fairly low-level stuff only right now. I started using Flow - Facebook’s alternative to Typescript - fairly early on in my React journey. It slowed down both my refactoring time and it slowed down my computer a lot.
Anyway, I think it’s worth learning. And once you’ve learned it, you can decide if the benefits of Typescript are worth it for you. If you want a quick start, take a look at the code sandbox I created while working on this article. It adds live data fetched via the WordPress REST API:
Topics
Discover More
Safely Publish to Web from Google Docs with Pantheon Content Publisher
Roland Benedetti (Senior Director, Product) and Zack Rosen (Co-Founder)
Reading estimate: 7 minutes
Unifying Content and Code: Inside Pantheon’s Vision for Content Operations
Chris Yates
Reading estimate: 5 minutes
How Pantheon Protects Your Site from Software Supply Chain Risks in Open Source
Steve Persch
Reading estimate: 8 minutes