The Power of React Hooks - Create an app using only this feature in React
Tags: react, javascript, webdev, node
Published: 2019-09-16 14:51:19

Find me on [medium](https://medium.com/@jsmanifest)

React [hooks](https://reactjs.org/docs/hooks-intro.html) are a new addition to the react library and has since been widely adopted by react developers by storm. These hooks allow you to write state logic and use other react features without having to write a [class component](https://reactjs.org/docs/components-and-props.html#function-and-class-components). You can make your own apps with just using react hooks alone and this proves that the concept of hooks is a prosperous turnaround for the react team.

In this article we will be building an app which I'll call **Slotify**, with just react hooks.

_Slotify_ will provide a user interface to the user, presenting them a [textarea](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea) that can take any blog post and insert quotes in them where newlines (`\n`) and word count will play a role in the quantity applied. A slotified post will have a minimum of one quote and a maximum of three quotes.

A quote and the author of the quote is allowed to be inserted wherever a _slot_ is available. The user will be able to interact with the slot and type/paste in a quote/author of their choice. When they are done, they can click the save button and a refurbished blog post will be recreated that includes their quotes. This is the final version that the user is intended to use as their next blog post.

These are the hook apis we will be using: (Basically all of them)

- [React.useState](https://reactjs.org/docs/hooks-reference.html#usestate)
- [React.useEffect](https://reactjs.org/docs/hooks-reference.html#useeffect)
- [React.useRef](https://reactjs.org/docs/hooks-reference.html#usecontext)
- [React.useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer)
- [React.useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback)
- [React.useMemo](https://reactjs.org/docs/hooks-reference.html#usememo)
- [React.useImperativeHandle](https://reactjs.org/docs/hooks-reference.html#useimperativehandle)
- [React.useLayoutEffect](https://reactjs.org/docs/hooks-reference.html#uselayouteffect)
- `useSlotify` (custom)

This is what we'll be building: (Converts a blog post into a blog post with styled quotes, and returns back an HTML source code of the post that includes the styles)

![slotify build your app with just react hooks](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/build-your-app-with-just-react-hooks/slotify2.gif)

Without further ado, let's get started!

In this tutorial we are going to quickly generate a react project with create-react-app.

(If you want to get a copy of the repository from github, click [here](https://github.com/jsmanifest/build-with-hooks)).

Go ahead and create a project using the command below. For this tutorial i’ll call our project _build-with-hooks_.

```bash
npx create-react-app build-with-hooks
```

Now go into the directory once it's done:

```bash
cd build-with-hooks
```

Inside the main entry `src/index.js` we're going to clean it up a bit so we can focus on the `App` component:

> src/index.js

```jsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './index.css'
import * as serviceWorker from './serviceWorker'
ReactDOM.render(<App />, document.getElementById('root'))
serviceWorker.unregister()
```

Now go to `src/App.js` and lets start with rendering nothing:

```jsx
import React from 'react'

function App() {
  return null
}

export default App
```

The core functionality of our app is to allow a user to insert/write a blog post into some type of field so that quotes can be inserted.

To make sure that we stay positive and optimistic that we can do this, let's just tackle down the core functionality first so that we know we're in good shape.

That means we're going to first make a button so that the user has the ability to start by clicking it. Then, we're also going to create the `textarea` element so that the user can insert content into.

**src/Button.js**

```jsx
import React from 'react'

function Button({ children, ...props }) {
  return (
    <button type="button" {...props}>
      {children}
    </button>
  )
}

export default Button
```

Inside index.css I applied some styling so that every `button` will have the same styles:

**src/index.css**

```css
button {
  border: 2px solid #eee;
  border-radius: 4px;
  padding: 8px 15px;
  background: none;
  color: #555;
  cursor: pointer;
  outline: none;
}

button:hover {
  border: 2px solid rgb(224, 224, 224);
}
button:active {
  border: 2px solid #aaa;
}
```

Lets proceed to create the textarea component. We'll call it `PasteBin`:

**src/PasteBin.js**

```jsx
import React from 'react'

function PasteBin(props) {
  return (
    <textarea
      style={{
        width: '100%',
        margin: '12px 0',
        outline: 'none',
        padding: 12,
        border: '2px solid #eee',
        color: '#666',
        borderRadius: 4,
      }}
      rows={25}
      {...props}
    />
  )
}

export default PasteBin
```

Now we're using inline styles because we want the styles to be included when the final content is generated. If we use pure CSS, only class name strings will be generated so the components would turn out styleless.

We're going to create a react [context](https://reactjs.org/docs/context.html) to wrap this whole thing from the top so that we force all child components to have the ability to stay in sync with the rest of the components by using [React.useContext](https://reactjs.org/docs/hooks-reference.html#usecontext)

Create a `Context.js` file:

**src/Context.js**

```jsx
import React from 'react'

const Context = React.createContext()

export default Context
```

Now we are going to create `Provider.js` which will import `Context.js` and will hold all the logic in managing state:

**src/Provider.js**

```jsx
import React from 'react'
import Slot from './Slot'
import { attachSlots, split } from './utils'
import Context from './Context'

const initialState = {
  slotifiedContent: [],
}

function reducer(state, action) {
  switch (action.type) {
    case 'set-slotified-content':
      return { ...state, slotifiedContent: action.content }
    default:
      return state
  }
}

function useSlotify() {
  const [state, dispatch] = React.useReducer(reducer, initialState)
  const textareaRef = React.useRef()

  function slotify() {
    let slotifiedContent, content
    if (textareaRef && textareaRef.current) {
      content = textareaRef.current.value
    }
    const slot = <Slot />
    if (content) {
      slotifiedContent = attachSlots(split(content), slot)
    }
    dispatch({ type: 'set-slotified-content', content: slotifiedContent })
  }

  return {
    ...state,
    slotify,
    textareaRef,
  }
}

function Provider({ children }) {
  return <Context.Provider value={useSlotify()}>{children}</Context.Provider>
}

export default Provider
```

I would like to take a moment to explain what this last code snippet is doing as it is very important. We would have used [React.useState](https://reactjs.org/docs/hooks-reference.html#usestate) to manage our state, but when you think about what our app is going to do, you might realize that it isn't just a single state because there are situations from both sides that need to be taken into consideration:

1. When does the user want to slotify their blog post?
2. When should we show them the final, refurbished content?
3. How many slots should we insert into the blog post?
4. When should we show or hide the slots?

Knowing this, we ought to use a [React.useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer) to design our state to encapsulate state update logic into a single location, and so our first action is declared by adding the first switch case accessible by dispatching an action with type `'set-slotified-content'`.

The way we are going to insert slots into the blog post is grabbing a string and converting it to an array delimiting it by newlines `'\n'` which is why the initial state declares `slotifiedContent` as an array, because that's where we will putting our working data in.

We also see a `textareaRef` declared as we need to use it to grab a reference to our `PasteBin` component we created earlier. We could have made the textarea completely [controlled](https://reactjs.org/docs/forms.html#controlled-components), but the easiest and most performant way to communicate with that is to just grab a reference to the root `textarea` element because all we need to do is grab its value instead of setting states. This will be grabbed from using the `ref` prop on `textarea` later.

Our `slotify` function is invoked when the user presses the _Start Quotifying_ button to slotify their blog post. The intention is to pop up a modal and show them the slots that they can enter their quote/authors into. We use the reference to the `PasteBin` component to grab the current value of the textarea and migrate the content to the modal.

We then use two utility functions, `attachSlots` and `split` to slotify the blog post and use that to set `state.slotifiedContent` so that our UI can pick it up and do their job.

We put `attachSlots` and `split` into a `utils.js` file as follows:

**src/utils.js**

```js
export function attachSlots(content, slot) {
  if (!Array.isArray(content)) {
    throw new Error('content is not an array')
  }
  let result = []
  // Post is too short. Only provide a quote at the top
  if (content.length <= 50) {
    result = [slot, ...content]
  }
  // Post is a little larger but 3 quotes is excessive. Insert a max of 2 quotes
  else if (content.length > 50 && content.length < 100) {
    result = [slot, ...content, slot]
  }
  // Post should be large enough to look beautiful with 3 quotes inserted (top/mid/bottom)
  else if (content.length > 100) {
    const midpoint = Math.floor(content.length / 2)
    result = [
      slot,
      ...content.slice(0, midpoint),
      slot,
      ...content.slice(midpoint),
      slot,
    ]
  }
  return result
}

// Returns the content back as an array using a delimiter
export function split(content, delimiter = '\n') {
  return content.split(delimiter)
}
```

To apply the `textareaRef` to the `PasteBin`, we have to use `React.useContext` to get the [React.useRef](https://reactjs.org/docs/hooks-reference.html#useref) hook we declared earlier in `useSlotify`:

**src/PasteBin.js**

```jsx
import React from 'react'
import Context from './Context'

function PasteBin(props) {
  const { textareaRef } = React.useContext(Context)
  return (
    <textarea
      ref={textareaRef}
      style={{
        width: '100%',
        margin: '12px 0',
        outline: 'none',
        padding: 12,
        border: '2px solid #eee',
        color: '#666',
        borderRadius: 4,
      }}
      rows={25}
      {...props}
    />
  )
}

export default PasteBin
```

The last thing we are missing is creating the `<Slot />` component because we used it inside our context. This slot component is the component that takes in a quote and author from the user. This won't be visible to the user right away because we are going to put it inside the modal component which will open only when the user clicks the _Start Quotifying_ button.

This slot component will be a little tough, but i'll explain what's happening afterwards:

```jsx
import React from 'react'
import PropTypes from 'prop-types'
import cx from 'classnames'
import Context from './Context'
import styles from './styles.module.css'

function SlotDrafting({ quote, author, onChange }) {
  const inputStyle = {
    border: 0,
    borderRadius: 4,
    background: 'none',
    fontSize: '1.2rem',
    color: '#fff',
    padding: '6px 15px',
    width: '100%',
    height: '100%',
    outline: 'none',
    marginRight: 4,
  }

  return (
    <div
      style={{
        display: 'flex',
        justifyContent: 'space-around',
        alignItems: 'center',
      }}
    >
      <input
        name="quote"
        type="text"
        placeholder="Insert a quote"
        style={{ flexGrow: 1, flexBasis: '70%' }}
        onChange={onChange}
        value={quote}
        className={styles.slotQuoteInput}
        style={{ ...inputStyle, flexGrow: 1, flexBasis: '60%' }}
      />
      <input
        name="author"
        type="text"
        placeholder="Author"
        style={{ flexBasis: '30%' }}
        onChange={onChange}
        value={author}
        className={styles.slotQuoteInput}
        style={{ ...inputStyle, flexBasis: '40%' }}
      />
    </div>
  )
}

function SlotStatic({ quote, author }) {
  return (
    <div style={{ padding: '12px 0' }}>
      <h2 style={{ fontWeight: 700, color: '#2bc7c7' }}>{quote}</h2>
      <p
        style={{
          marginLeft: 50,
          fontStyle: 'italic',
          color: 'rgb(51, 52, 54)',
          opacity: 0.7,
          textAlign: 'right',
        }}
      >
        - {author}
      </p>
    </div>
  )
}

function Slot({ input = 'textfield' }) {
  const [quote, setQuote] = React.useState('')
  const [author, setAuthor] = React.useState('')
  const { drafting } = React.useContext(Context)

  function onChange(e) {
    if (e.target.name === 'quote') {
      setQuote(e.target.value)
    } else {
      setAuthor(e.target.value)
    }
  }

  let draftComponent, staticComponent

  if (drafting) {
    switch (input) {
      case 'textfield':
        draftComponent = (
          <SlotDrafting onChange={onChange} quote={quote} author={author} />
        )
        break
      default:
        break
    }
  } else {
    switch (input) {
      case 'textfield':
        staticComponent = <SlotStatic quote={quote} author={author} />
        break
      default:
        break
    }
  }

  return (
    <div
      style={{
        color: '#fff',
        borderRadius: 4,
        margin: '12px 0',
        outline: 'none',
        transition: 'all 0.2s ease-out',
        width: '100%',
        background: drafting
          ? 'rgba(175, 56, 90, 0.2)'
          : 'rgba(16, 46, 54, 0.02)',
        boxShadow: drafting
          ? undefined
          : '0 3px 15px 15px rgba(51, 51, 51, 0.03)',
        height: drafting ? 70 : '100%',
        minHeight: drafting ? 'auto' : 70,
        maxHeight: drafting ? 'auto' : 100,
        padding: drafting ? 8 : 0,
      }}
    >
      <div
        className={styles.slotInnerRoot}
        style={{
          transition: 'all 0.2s ease-out',
          cursor: 'pointer',
          width: '100%',
          height: '100%',
          padding: '0 6px',
          borderRadius: 4,
          display: 'flex',
          alignItems: 'center',
          textTransform: 'uppercase',
          justifyContent: drafting ? 'center' : 'space-around',
          background: drafting
            ? 'rgba(100, 100, 100, 0.35)'
            : 'rgba(100, 100, 100, 0.05)',
        }}
      >
        {drafting ? draftComponent : staticComponent}
      </div>
    </div>
  )
}

Slot.defaultProps = {
  slot: true,
}

Slot.propTypes = {
  input: PropTypes.oneOf(['textfield']),
}

export default Slot
```

The most important part in this file is `state.drafting`. We didn't declare this in the context yet, but its purpose is to give us a way to know when to show the user the slots as well as when to show them the final output. When `state.drafting` is true (which is going to be the default value), we will show them the slots which are the blocks that they can insert their quote and quote's author to. When they click on the _Save_ button, `state.drafting` will switch to `false` and we will use that to determine that they want to look at their final output.

We declared an `input` parameter with a default value of `'textfield'` because in the future we might want to use other input types to let users insert quotes to besides typing (example: file inputs where we can let them upload images as quotes, etc). For this tutorial we're only going to support `'textfield'`.

So when `state.drafting` is `true`, `<SlotDrafting />` is used by `Slot`, and when it's `false`, `<SlotStatic />` is used. It's better to separate this distinction into separate components so we don't bloat components with a bunch of `if/else` conditionals.

Also, although we declared some inline styles for the quote/author input fields, we still applied `className={styles.slotQuoteInput}` so that we can style the placeholder since we won't be able to do that with inline styles. (This is okay for the final refurbished content because inputs won't even be generated)

Here is the css for that:

**src/styles.module.css**

```css
.slotQuoteInput::placeholder {
  color: #fff;
  font-size: 0.9rem;
}
```

Let's go back and declare the `drafting` state to the context:

**src/Provider.js**

```jsx
import React from 'react'
import Slot from './Slot'
import { attachSlots, split } from './utils'
import Context from './Context'

const initialState = {
  slotifiedContent: [],
  drafting: true,
}

function reducer(state, action) {
  switch (action.type) {
    case 'set-slotified-content':
      return { ...state, slotifiedContent: action.content }
    case 'set-drafting':
      return { ...state, drafting: action.drafting }
    default:
      return state
  }
}

function useSlotify() {
  const [state, dispatch] = React.useReducer(reducer, initialState)
  const textareaRef = React.useRef()

  function onSave() {
    if (state.drafting) {
      setDrafting(false)
    }
  }

  function setDrafting(drafting) {
    if (drafting === undefined) return
    dispatch({ type: 'set-drafting', drafting })
  }

  function slotify() {
    let slotifiedContent, content
    if (textareaRef && textareaRef.current) {
      content = textareaRef.current.value
    }
    const slot = <Slot />
    if (content && typeof content === 'string') {
      slotifiedContent = attachSlots(split(content), slot)
    }
    dispatch({ type: 'set-slotified-content', content: slotifiedContent })
  }

  return {
    ...state,
    slotify,
    onSave,
    setDrafting,
    textareaRef,
  }
}

function Provider({ children }) {
  return <Context.Provider value={useSlotify()}>{children}</Context.Provider>
}

export default Provider
```

Now finally lets put this into the `App.js` component so we can see what this all looks like so far:

(Note: in this example I used a modal component from _semantic-ui-react_ which is **not** required for the modal. You can use any modal or create a plain modal of your own using the **react portal** api):

**src/App.js**

```jsx
import React from 'react'
import { Modal } from 'semantic-ui-react'
import Button from './Button'
import Context from './Context'
import Provider from './Provider'
import PasteBin from './PasteBin'
import styles from './styles.module.css'

// Purposely call each fn without args since we don't need them
const callFns = (...fns) => () => fns.forEach((fn) => fn && fn())

const App = () => {
  const {
    modalOpened,
    slotifiedContent = [],
    slotify,
    onSave,
    openModal,
    closeModal,
  } = React.useContext(Context)

  return (
    <div
      style={{
        padding: 12,
        boxSizing: 'border-box',
      }}
    >
      <Modal
        open={modalOpened}
        trigger={
          <Button type="button" onClick={callFns(slotify, openModal)}>
            Start Quotifying
          </Button>
        }
      >
        <Modal.Content
          style={{
            background: '#fff',
            padding: 12,
            color: '#333',
            width: '100%',
          }}
        >
          <div>
            <Modal.Description>
              {slotifiedContent.map((content) => (
                <div style={{ whiteSpace: 'pre-line' }}>{content}</div>
              ))}
            </Modal.Description>
          </div>
          <Modal.Actions>
            <Button type="button" onClick={onSave}>
              SAVE
            </Button>
          </Modal.Actions>
        </Modal.Content>
      </Modal>
      <PasteBin onSubmit={slotify} />
    </div>
  )
}

export default () => (
  <Provider>
    <App />
  </Provider>
)
```

Before we start up our server we need to declare the `modal` states (open/close):

**src/Provider.js**

```jsx
import React from 'react'
import Slot from './Slot'
import { attachSlots, split } from './utils'
import Context from './Context'

const initialState = {
  slotifiedContent: [],
  drafting: true,
  modalOpened: false,
}

function reducer(state, action) {
  switch (action.type) {
    case 'set-slotified-content':
      return { ...state, slotifiedContent: action.content }
    case 'set-drafting':
      return { ...state, drafting: action.drafting }
    case 'open-modal':
      return { ...state, modalOpened: true }
    case 'close-modal':
      return { ...state, modalOpened: false }
    default:
      return state
  }
}

function useSlotify() {
  const [state, dispatch] = React.useReducer(reducer, initialState)
  const textareaRef = React.useRef()

  function onSave() {
    if (state.drafting) {
      setDrafting(false)
    }
  }

  function openModal() {
    dispatch({ type: 'open-modal' })
  }

  function closeModal() {
    dispatch({ type: 'close-modal' })
  }

  function setDrafting(drafting) {
    if (typeof drafting !== 'boolean') return
    dispatch({ type: 'set-drafting', drafting })
  }

  function slotify() {
    let slotifiedContent, content
    if (textareaRef && textareaRef.current) {
      content = textareaRef.current.value
    }
    const slot = <Slot />
    if (content && typeof content === 'string') {
      slotifiedContent = attachSlots(split(content), slot)
    }
    if (!state.drafting) {
      setDrafting(true)
    }
    dispatch({ type: 'set-slotified-content', content: slotifiedContent })
  }

  return {
    ...state,
    slotify,
    onSave,
    setDrafting,
    textareaRef,
    openModal,
    closeModal,
  }
}

function Provider({ children }) {
  return <Context.Provider value={useSlotify()}>{children}</Context.Provider>
}

export default Provider
```

And here's what we should have so far:

![build your app with just react hooks 2019](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/build-your-app-with-just-react-hooks/slotify1.gif)

(Note: The _SAVE_ button is closing the modal in the image, but that was a minor error. It should not close the modal)

Now we're going to change `PasteBin` a little to declare a new api using [React.useImperativeHandle](https://reactjs.org/docs/hooks-reference.html#useimperativehandle) for the textarea so that we can use it in `useSlotify` and we don't bloat the hook with a bunch of functions but instead provide back an encapsulated api:

**src/PasteBin.js**

```jsx
import React from 'react'
import Context from './Context'

function PasteBin(props) {
  const { textareaRef, textareaUtils } = React.useContext(Context)

  React.useImperativeHandle(textareaUtils, () => ({
    copy: () => {
      textareaRef.current.select()
      document.execCommand('copy')
      textareaRef.current.blur()
    },
    getText: () => {
      return textareaRef.current.value
    },
  }))

  return (
    <textarea
      ref={textareaRef}
      style={{
        width: '100%',
        margin: '12px 0',
        outline: 'none',
        padding: 12,
        border: '2px solid #eee',
        color: '#666',
        borderRadius: 4,
      }}
      rows={25}
      {...props}
    />
  )
}

export default PasteBin
```

`textareaUtils` will also be a `React.useRef` which will be placed right next to `textareaRef` in the `useSlotify` hook:

```jsx
const [state, dispatch] = React.useReducer(reducer, initialState)
const textareaRef = React.useRef()
const textareaUtils = React.useRef()
```

We will use this new api in the `slotify` function:

**src/Provider.js**

```jsx
function slotify() {
  let slotifiedContent, content
  if (textareaRef && textareaRef.current) {
    textareaUtils.current.copy()
    textareaUtils.current.blur()
    content = textareaUtils.current.getText()
  }
  const slot = <Slot />
  if (content && typeof content === 'string') {
    slotifiedContent = attachSlots(split(content), slot)
  }
  if (!state.drafting) {
    setDrafting(true)
  }
  dispatch({ type: 'set-slotified-content', content: slotifiedContent })
}
```

Now the next thing we are going to do is that when the user is looking at the slots and we detect that they haven't inserted an author yet, we flash that element to bring more of their attention.

For this, we are going to use [React.useLayoutEffect](https://reactjs.org/docs/hooks-reference.html#uselayouteffect) inside the `SlotDrafting` component because _SlotDrafting_ contains the author input:

**src/Slot.js**

```jsx
function SlotDrafting({ quote, author, onChange }) {
  const authorRef = React.createRef()

  React.useLayoutEffect(() => {
    const elem = authorRef.current
    if (!author) {
      elem.classList.add(styles.slotQuoteInputAttention)
    } else if (author) {
      elem.classList.remove(styles.slotQuoteInputAttention)
    }
  }, [author, authorRef])

  const inputStyle = {
    border: 0,
    borderRadius: 4,
    background: 'none',
    fontSize: '1.2rem',
    color: '#fff',
    padding: '6px 15px',
    width: '100%',
    height: '100%',
    outline: 'none',
    marginRight: 4,
  }

  return (
    <div
      style={{
        display: 'flex',
        justifyContent: 'space-around',
        alignItems: 'center',
      }}
    >
      <input
        name="quote"
        type="text"
        placeholder="Insert a quote"
        onChange={onChange}
        value={quote}
        className={styles.slotQuoteInput}
        style={{ ...inputStyle, flexGrow: 1, flexBasis: '60%' }}
      />
      <input
        ref={authorRef}
        name="author"
        type="text"
        placeholder="Author"
        onChange={onChange}
        value={author}
        className={styles.slotQuoteInput}
        style={{ ...inputStyle, flexBasis: '40%' }}
      />
    </div>
  )
}
```

We probably didn't need the use of `useLayoutEffect` here, but it's just for demonstration. It's known to be a good option for style updates. since the hook is invoked after the dom is mounted and has had its mutations updated. The reason it's good for styling reasons is because it's invoked _before_ the next browser repaint whereas the `useEffect` hook is invoked afterwards--which can cause a [sluggy flashy effect in the UI](https://blog.logrocket.com/useeffect-vs-uselayouteffect/).

styles:

**src/styles.module.css**

```css
.slotQuoteInputAttention {
  transition: all 1s ease-out;
  animation: emptyAuthor 3s infinite;
  border: 1px solid #91ffde;
}

.slotQuoteInputAttention::placeholder {
  color: #91ffde;
}

.slotQuoteInputAttention:hover,
.slotQuoteInputAttention:focus,
.slotQuoteInputAttention:active {
  transform: scale(1.1);
}

@keyframes emptyAuthor {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
```

On the bottom of the modal we put a `SAVE` button which will invoke `onSave` from `useSlotify`. When the user clicks this, the slots will convert to finalized slots (when `drafting === false`). We will also render a button nearby that will copy the source code in HTML to their clipboard so that they can paste the content on their blog post.

So far, here is what we have:

Everything will _stay the same_, except now we work with CSS class names. For the new css class names they are suffixed with `Static` to indicate that they are used when `drafting === false`. Here is a slight change to the `Slot` component to accomodate the CSS changes:

**src/Slot.js**

```jsx
function Slot({ input = 'textfield' }) {
  const [quote, setQuote] = React.useState('')
  const [author, setAuthor] = React.useState('')
  const { drafting } = React.useContext(Context)

  function onChange(e) {
    if (e.target.name === 'quote') {
      setQuote(e.target.value)
    } else {
      setAuthor(e.target.value)
    }
  }

  let draftComponent, staticComponent

  if (drafting) {
    switch (input) {
      case 'textfield':
        draftComponent = (
          <SlotDrafting onChange={onChange} quote={quote} author={author} />
        )
        break
      default:
        break
    }
  } else {
    switch (input) {
      case 'textfield':
        staticComponent = <SlotStatic quote={quote} author={author} />
        break
      default:
        break
    }
  }

  return (
    <div
      style={{
        color: '#fff',
        borderRadius: 4,
        margin: '12px 0',
        outline: 'none',
        transition: 'all 0.2s ease-out',
        width: '100%',
        background: drafting
          ? 'rgba(175, 56, 90, 0.2)'
          : 'rgba(16, 46, 54, 0.02)',
        boxShadow: drafting
          ? undefined
          : '0 3px 15px 15px rgba(51, 51, 51, 0.03)',
        height: drafting ? 70 : '100%',
        minHeight: drafting ? 'auto' : 70,
        maxHeight: drafting ? 'auto' : 100,
        padding: drafting ? 8 : 0,
      }}
      className={cx({
        [styles.slotRoot]: drafting,
        [styles.slotRootStatic]: !drafting,
      })}
    >
      <div
        className={styles.slotInnerRoot}
        style={{
          transition: 'all 0.2s ease-out',
          cursor: 'pointer',
          width: '100%',
          height: '100%',
          padding: '0 6px',
          borderRadius: 4,
          display: 'flex',
          alignItems: 'center',
          textTransform: 'uppercase',
          justifyContent: drafting ? 'center' : 'space-around',
          background: drafting
            ? 'rgba(100, 100, 100, 0.35)'
            : 'rgba(100, 100, 100, 0.05)',
        }}
      >
        {drafting ? draftComponent : staticComponent}
      </div>
    </div>
  )
}
```

And here are the newly added CSS styles:

```css
.slotRoot:hover {
  background: rgba(245, 49, 104, 0.3) !important;
}

.slotRootStatic:hover {
  background: rgba(100, 100, 100, 0.07) !important;
}

.slotInnerRoot:hover {
  filter: brightness(80%);
}
```

Here is what our app looks like now:

![slotify2](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/build-your-app-with-just-react-hooks/slotify2.gif)

The last thing we need to do is add a _Close_ button to close the modal, and a _Copy_ button to copy the source code of their finalized blog post.

Adding the _Close_ button is easy. Just add this button next to the _Save_ button. The _Copy_ button will be placed next to the _Close_ button. These buttons will be given some `onClick` handlers:

**src/App.js**

```jsx
<Modal.Actions>
  <Button type="button" onClick={onSave}>
    SAVE
  </Button>
  &nbsp;
  <Button type="button" onClick={closeModal}>
    CLOSE
  </Button>
  &nbsp;
  <Button type="button" onClick={onCopyFinalDraft}>
    COPY
  </Button>
</Modal.Actions>
```

We _should_ be done when we implement the `onCopyFinalContent` function, but we're not yet. We're missing one last step. When we copy the finalized content, _which_ part of the UI are we copying? We can't be copying the entire modal because we don't want the _SAVE_, _CLOSE_ and _COPY_ buttons in our blog posts or it would look awfully awkward. We have to make another `React.useRef` and use that to attach to a specific element that _only includes the content we want_.

This is why we \*_used inline styles and not entirely CSS classes_ because we want the styles to be included in the refurbished version.

Declare `modalRef` in `useSlotify`:

```jsx
const textareaRef = React.useRef()
const textareaUtils = React.useRef()
const modalRef = React.useRef()
```

Attach it to the element that will _only contain the content_:

**src/App.js**

```jsx
const App = () => {
  const {
    modalOpened,
    slotifiedContent = [],
    slotify,
    onSave,
    openModal,
    closeModal,
    modalRef,
    onCopyFinalContent,
  } = React.useContext(Context)

  const ModalContent = React.useCallback(
    ({ innerRef, ...props }) => <div ref={innerRef} {...props} />,
    [],
  )

  return (
    <div
      style={{
        padding: 12,
        boxSizing: 'border-box',
      }}
    >
      <Modal
        open={modalOpened}
        trigger={
          <Button type="button" onClick={callFns(slotify, openModal)}>
            Start Quotifying
          </Button>
        }
        style={{
          background: '#fff',
          padding: 12,
          color: '#333',
          width: '100%',
        }}
      >
        <Modal.Content>
          <Modal.Description as={ModalContent} innerRef={modalRef}>
            {slotifiedContent.map((content) => (
              <div style={{ whiteSpace: 'pre-line' }}>{content}</div>
            ))}
          </Modal.Description>
          <Modal.Actions>
            <Button type="button" onClick={onSave}>
              SAVE
            </Button>
            &nbsp;
            <Button type="button" onClick={closeModal}>
              CLOSE
            </Button>
            &nbsp;
            <Button type="button" onClick={onCopyFinalContent}>
              COPY
            </Button>
          </Modal.Actions>
        </Modal.Content>
      </Modal>
      <PasteBin onSubmit={slotify} />
    </div>
  )
}
```

Note: We wrapped `ModalContent` with a `React.useCallback` because we want the reference to _stay the same_. If we don't, then the component will be re-rendered and all of the quotes/author values will be reset since the `onSave` function updates the state. When the state updates, `ModalContent` will re-create itself, making a new fresh empty state which is what we *don't* want.

And finally, `onCopyFinalDraft` will be placed inside the `useSlotify` hook that will use the `modalRef` ref:

**src/Provider.js**

```jsx
function onCopyFinalContent() {
  const html = modalRef.current.innerHTML
  const inputEl = document.createElement('textarea')
  document.body.appendChild(inputEl)
  inputEl.value = html
  inputEl.select()
  document.execCommand('copy')
  document.body.removeChild(inputEl)
}
```

And we are done!

Here is our app now:

![build your web app with just react hooks 2019](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/build-your-app-with-just-react-hooks/slotify2.gif)

## Conclusion

And that concludes the end of this post! I hope you found it useful and look out for more in the future!

Find me on [medium](https://medium.com/@jsmanifest)