Creating a Medium-like Blog Post Builder in React

Lee McGowan
Dec 3, 2019 · 13 min read

I love writing, mostly because I love talking, but after 25 years of machine-gun mouth, my family are pretty tired of listening to it. So I write, because other people don’t know how annoying I am yet.

Medium is a great platform for doing just that, with an intuitive post builder. Without it, I’d probably be fooling around with markdown, writing HTML & CSS for every new post, or simply releasing stuff as long walls of text. Who’s got time for that? Not this guy.

So I decided to learn how to create my own post builder in React, just in case there’s a future where sheep rise up and take over Medium, and refuse to let any non-sheep write on the platform. Baaa’d news.

The process was relatively straightforward, and I’m going to detail it here. Read on if you want to find out more.

Pre-requisites

  • Intermediate knowledge of React & React 16 features.
  • The ability to download packages from npm.
  • A computer.
  • Too much time on your hands. Like me.

Setup

I used create-react-app to get started, which I installed with npm i -g create-react-app. Then I ran create-react-app post-builder to generate the project, and loaded it in the browser with npm run start.

After that, I deleted the following files:

  • src/App.css
  • src/App.test.js
  • src/logo.svg
  • src/serviceWorker.js

And the service worker stuff from index.js.

Then, I edited my App.js file until it looked like this:

I used styled-components for this project, which is a great library for writing css-in-js. If you’re retracing what I did here, you might want to use it too. Download it by running npm i styled-components. But you can also use regular css, or sass, or the blood of your enemies. It don’t matter.

The PostBuilder Component

The <PostBuilder /> component was to be the main component. It would have a single state field, items. This would be an array of objects, each representing a single item on the page, like a textarea, an image or a code block.

The component would then map over the items array, and render a single <Item /> component for each.

I created <PostBuilder /> in src/components/PostBuilder/index.jsx, and after adding all the stuff above, it looked like the following image. So something would appear on the page later, I stuck an initial item in state too.

<Item /> wasn’t created yet, so <PostBuilder /> was throwing an error. I decided to do that next.

P.S. uuid is a nifty npm package which generates a unique id when you call it.

The Item Component

Then came the <Item /> component, responsible for rendering the default post item (a textarea) or some custom one (like an image or a code block). I created that in src/components/Item/index.jsx, and it looked like this:

The component used some conditional logic to render the default element (a textarea) whenever type was falsy. If it was truthy, it would render some other element. I would define those later.

With these things done, I imported & added the <PostBuilder /> component to <App /> (and added some padding to the page). With the initial element in the items array, I was left with a page like this:

Wow. Ship it.

Styling the Item Component

It was then I decided to dance with the devil and do some css, which was an absurd decision because you couldn’t even type into the thing yet. But hey ho. A textarea deserves to feel good in its own skin.

If you’ve never worked with styled-components before, it’s pretty easy. I created a style.js file in the same folder as the <Item /> component. The code looked like this:

this is probably all wrong

The call to styled.div returns a React component like any other. All children of that component then have those styles applied to them. I imported it into <Item /> and replaced the top-level div with it.

Done. My text box was looking sexy, and already on Tinder. (Btw, by sexy, I mean completely invisible. Which is quite poignant, if you think about it, because beauty is in the eye of the beholder, and it is therefore important not to set arbitrary standards. Thanks.)

Focusing the Text Box

The last thing I wanted to do, at that point, was make the textarea in <Item /> focus on creation, i.e. the caret would appear in the box without manually clicking on it. That would also give me a chance to try out the useRef & useEffect hooks. After doing that, <Item /> looked like this:

The new stuff was on lines 5, 7-9 & 15.

  • 5 created the initial ref. Refs provide a handle on specific DOM elements so that you can perform actions on them.
  • 7–9 told the browser to immediately focus the textarea.
  • 15 attached the textarea to that ref, giving access to the element in the DOM.

Adding and Updating Items

At that point, the textarea wasn’t actually working. You couldn’t type into it, because there was no way to update the value prop being passed to it. I also had no way to add new items to the page. So, I needed two functions: one to add new items to the items state field, and one to update the content of a specific item. I put them both in <PostBuilder />. Here’s what they looked like:

Lines 1–3

This is the addItem function, which is responsible for sticking a new item on the page. It receives the type of the new item and the content and adds them both to state.

The call to setItems uses the callback method of updating the state, so that it is always working with the most up-to-date version. Then it uses array spread syntax to add a new item to items, and returns the new array, which then becomes the new state.

Lines 5–12

This is the updateItem function, again using the callback method of updating the state. It receives the id of the item to update and the new content.

On line 7, it gets the correct item by using the provided id to retrieve its index.

On line 8, it uses array spread syntax to create a copy of the old state in an immutable way.

On line 9, it updates the content of the specific entry by using the index.

Then, on line 10, it returns the new array, which then becomes the state.

Letting Items Update Themselves

With the updateItem function available, I passed it to the <Item /> component so that it could be used.

And then in the <Item /> component, I wired it up.

The new stuff here is the addition of updateItem to the props being passed in, and setting the onChange prop of the textarea element.

After that, I tried typing in the textarea and it worked! Fabulous.

Creating a new Textarea

When writing a post on Medium, hitting the enter key will take you to a new text section. I wanted to do that too, because Medium shouldn’t get to have all the fun.

The easiest way was to create a new item when the onKeyPress event was fired from the textarea. I would grab it, check that the key was Enter, and then create a new item if it was.

I first added a handleKeyPress function in <PostBuilder />, and passed it down to <Item />.

I passed null as the type here because it is a falsy value, and therefore will result in a textarea being rendered, because of the conditional statement in <Item />.

To finish that off, I added handleKeyPress to <Item />’s props list and passed it to the textarea element through the onKeyPress event.

After that, I could create textareas all day. And I did. Yeehaw.

Images

Lastly, I decided to work on a single custom component: images. At that point, the content property on each of the items was only text, because textareas only need text. But theoretically, it could be anything. So, for images, I decided it would look something like this:

And using that knowledge, I created the <Image /> component at src/components/Image/index.jsx.

But I couldn’t render it yet. That was the next step.

Component Mapping

The <Item /> component could only render textareas or a <p> tag at that point. In order to render custom components, I had to think of some way to map them to their specific type. A mapping table seemed fine. In <Item />, outside of the component body, I wrote:

And then I replaced the <p> tag in <Item /> with what you see highlighted below:

So, if type was not falsy, the condition would not return a textarea, and instead would retrieve the correct component from the mapping table. Of course, if you passed it a type that didn’t exist, the whole thing would blow up. But what’s life without a little danger?

The Toolbar

I know I said “lastly” above, but it was a dirty lie. I still had to create some way for the user to choose which new item to add to the post. Well, that, or styling, but since styling is about as enjoyable as chronic flatulence, I decided to create <Toolbar />.

I exported the componentMappings object from <Item />, so that I could use it to dynamically generate the toolbar, i.e. each key in componentMappings would spawn its own button.

Then, in src/components/Toolbar/index.jsx, I created the <Toolbar /> component itself.

So, <Toolbar /> loops over each key in componentMappings and creates a button for it. Using the addItem function which will be passed from <PostBuilder />, it attaches an onClick event and adds a new item, passing the key which will be the type of the component, and an empty object for content. I added <Toolbar /> to the bottom of <PostBuilder /> and passed addItem to it.

At this point, I became aware of some problems.

Firstly, trying to create an image would break the app. This was because the ref being used in <Item /> only existed when the textarea was rendered. Because I was rendering as custom component instead of a textarea, the ref wasn’t getting defined, and therefore trying to call focus on it was throwing an error.

It was an easy fix, though. In <Item />, I changed the useEffect hook to this:

Problem solved.

But there was a second issue. How would I get the src & alt attributes for the image? I didn’t know these values on creation. The user had no way to provide them when clicking the image button on the toolbar.

This one was a little trickier. In the end I decided to create new components for providing each of the properties that custom items would need. I made an <EditImage /> component, and rendered that in <Image /> if the src and alt attributes were missing or falsy.

That was all pretty straightforward. <EditImage /> provided two simple inputs and a button for setting the src and alt attributes. It would use an updateItem function, not yet provided, pass those values back up to the items array in <PostBuilder />. <Image /> would render <EditImage /> if src or alt were falsy.

I wasn’t sure if this was the best way to approach the problem, but it did have a nice side effect. The edit component could be used more than once. If a user wanted to change the alt and src attributes later, the addition of this kind of component would make that much easier. So I decided to stick with it.

<EditImage /> needed access to the updateItem function, so I added it to the componentMappings object in <Item />.

And updated the componentMappings function call to pass the parameter:

And then added it to the props list and <EditImage /> jsx in <Image />:

And finally, updated the updateImageProperties function in <EditImage />:

With all that complete, I was left with this:

And when I clicked the img button:

And finally, when I added a src and alt:

Hello, Google? Yes. Hire me.

Styling

The last thing to do was styling. This section doesn’t actually add anything to the functionality of the application, so you could skip it entirely if you’ve been following along. But, if you’re weird and troubled and enjoy css, then read on, soldier.

Page Margin & Heading

In Medium, the body of your post is centered on the screen with a margin to either side. I added this by creating a style.js file at the root of the src/ folder which exported a new styled component called AppWrapper. It had the following styles:

Then I used it in <App />, and added a header:

The page then looked like this:

Styling the Toolbar

In the same folder as the <Toolbar /> component, I created a style.js file with the following code:bb

Then I added it to <Toolbar />:

Which left me with this:

Gorj.

Styling <Image /> and <EditImage />

Because <EditImage /> is a direct child of <Image />, I would style them both at once. In a style.js file in the same folder as <Image />, I wrote this monstrosity:

Then I added the wrapper to the <Image /> component.

And added the .create-img class to the div in <EditImage />:

And finally, my page looked like this:

So. Cute.

Extra Steps

If you followed along with this, and you enjoyed it, then perhaps you would like some ideas for what to do next. Perhaps you wouldn’t, in which case feel free to email me and let me know. You could:

  • Add more custom components, including a <Video /> component
  • Make the <Image /> component less awkward (any src and alt will be accepted, even bad ones, so you could validation, or a button that reloads the edit component)
  • Add a button to remove items

Reflections

For the most part, creating the Post Builder was quite straightforward, but there are some things I might rethink in future.

  • The mapping tables feel a little weak. There might be a better way to do these.
  • The <EditImage /> component was a last minute solution to a problem I hadn’t foreseen. I’m certain there are more suitable alternatives.

I learned a lot doing this. It may not have been the most complex of tasks, but it allowed me to use many different features of React, and required a lot of thinking about how to make components generic enough (me think? very hard). I also liked the way the top-level items data structure influenced the entire view, and therefore most of <PostBuilder />’s children were relatively dumb. I like dumb things, cos they make me look better.

Thanks for reading.

JavaScript in Plain English

Learn the web's most important programming language.

Lee McGowan

Written by

I write about programming, and I write about writing. I also might write about philosophy, if I ever get ‘round to engaging my tiny brain.

JavaScript in Plain English

Learn the web's most important programming language.

More From Medium

More from JavaScript in Plain English

More from JavaScript in Plain English

More from JavaScript in Plain English

5 Secret features of JSON.stringify()

More from JavaScript in Plain English

More from JavaScript in Plain English

7 really good reasons not to use TypeScript

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade