Categories
Storybook for React

Getting Started with Storybook for React

Storybook lets us prototype components easily with various parameters.

In this article, we’ll look at how to get started with Storybook.

Getting Started

We can get started by first creating a React project with Create React App.

To create it, we run:

npx create-react-app storybook-project

Then we can add Storybook to it by running:

npx sb init

Then we can run it by running:

npm run storybook

to run Storybook.

Now we can create a story.

A story has the component which may take some arguments to let us adjust it.

We can provide default values for these arguments.

First, we create a component that takes some props.

In the src/stories folder, we create a Button.js to create a button that takes some props:

import React from 'react';
import PropTypes from 'prop-types';
import './button.css';

export const Button = ({ primary, backgroundColor, size, label, ...props }) => {
  const mode = primary ? 'button-primary' : 'button-secondary';
  return (
    <button
      type="button"
      className={['button', `button-${size}`, mode].join(' ')}
      style={backgroundColor && { backgroundColor }}
      {...props}
    >
      {label}
    </button>
  );
};

Button.propTypes = {
  primary: PropTypes.bool,
  backgroundColor: PropTypes.string,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  label: PropTypes.string.isRequired,
  onClick: PropTypes.func,
};

Button.defaultProps = {
  backgroundColor: null,
  primary: false,
  size: 'medium',
  onClick: undefined,
};

Then we add a button.css to style it:

.button {
  font-weight: 700;
  border: 0;
  border-radius: 3em;
  cursor: pointer;
  display: inline-block;
  line-height: 1;
}
.button-primary {
  color: white;
  background-color: #1ea7fd;
}
.button-secondary {
  color: #333;
  background-color: transparent;
}
.button-small {
  font-size: 12px;
  padding: 10px;
}
.button-medium {
  font-size: 14px;
  padding: 11px;
}
.button-large {
  font-size: 16px;
  padding: 12px;
}

Our button takes a bunch of props to let us modify the button with different styles.

primary is a boolean prop.

backgroundColor is a string prop.

size lets us change the size with an enum type.

label lets us show a label.

onClick is a function to let us handle clicks.

To make it display in Storybook, we add the Button.stories.js file and add:

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

We export an object with the things we want to display.

title has the title we want to display in Storybook.

component is the component we want to preview.

argTypes lets us set the control types for various props so we can change and preview them in Storybook.

Below that is the args. The args lets us pass in props to our component so that we can change it.

We provide some default values with the Template.bind method and setting the args property to the values we want.

Conclusion

We can create components and preview them with Storybook.

They can be changed if they accept props and bind to them with args.

Categories
React

React-Intl — Message Formatting

Many apps have to be made usable by different users from various parts of the world.

To make this easier, we can use the react-intl to do the internationalization for us.

In this article, we’ll look at how to get started with formatting messages with the react-intl library.

Message Syntax

We can format messages with placeholders and quantities.

For instance, we can write:

import React from "react";
import { IntlProvider, FormattedMessage } from "react-intl";
const messages = {
  en: {
    greeting: `Hello, {name}, you have {itemCount, plural,
      =0 {no items}
      one {# item}
      other {# items}
    }.`
  }
};
export default function App() {
  const [name] = React.useState("james");
  const [itemCount] = React.useState(20);
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedMessage id="greeting" values={{ name, itemCount }} />
      </p>
    </IntlProvider>
  );
}

to create a translation with the placeholders.

We have placeholder for name and itemCount .

The plural rules are defined by:

{itemCount, plural,
  =0 {no items}
  one {# item}
  other {# items}
}

Then we pass in the name and itemCount and they’ll be interpolated.

The id also has to be set.

Default Message

We can set the default message in the FormattedMessage component.

For instance, we can write:

import React from "react";
import { IntlProvider, FormattedMessage } from "react-intl";
const messages = {
  en: {
    greeting: `Hello, {name}.`
  }
};
export default function App() {
  const [name] = React.useState("james");
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedMessage
          id="greeting"
          description="a greeting"
          defaultMessage="Hello, {name}!"
          values={{
            name
          }}
        />
      </p>
    </IntlProvider>
  );
}

We have the defaultMessage prop which is overridden by the message that we defined.

If we remove the greeting message, then it’ll be used:

import React from "react";
import { IntlProvider, FormattedMessage } from "react-intl";
const messages = {
  en: {}
};
export default function App() {
  const [name] = React.useState("james");
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedMessage
          id="greeting"
          description="a greeting"
          defaultMessage="Hello, {name}!"
          values={{
            name
          }}
        />
      </p>
    </IntlProvider>
  );
}

We can also format the translated text with a function:

import React from "react";
import { IntlProvider, FormattedMessage } from "react-intl";
const messages = {
  en: {}
};
export default function App() {
  const [name] = React.useState("james");
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedMessage
          id="greeting"
          description="a greeting"
          defaultMessage="Hello, {name}!"
          values={{
            name
          }}
        >
          {txt => <h1>{txt}</h1>}
        </FormattedMessage>
      </p>
    </IntlProvider>
  );
}

We have a function instead of nothing as the child of FormattedMessage so we’ll see an h1 heading instead of the default style.

Rich Text Formatting

We can divide the text into chunks and format it.

For instance, we can write:

import React from "react";
import { IntlProvider, FormattedMessage } from "react-intl";
const messages = {
  en: {}
};
export default function App() {
  const [name] = React.useState("james");
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedMessage
          id="greeting"
          description="greeting"
          defaultMessage="Hello, <b>{name}</b>"
          values={{
            b: (...chunks) => <b>{chunks}</b>,
            name
          }}
        />
      </p>
    </IntlProvider>
  );
}

to format the bold text by returning our own component.

b is the tag name and it should match the message.

name has the value of the name placeholder.

It can be any XML tag.

We can have more complex formatting:

import React from "react";
import { IntlProvider, FormattedMessage } from "react-intl";
const messages = {
  en: {}
};
export default function App() {
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedMessage
          id="greeting"
          defaultMessage="Go <a>visit our website</a> and <cta>read it</cta>"
          values={{
            a: (...chunks) => (
              <a
                class="external_link"
                target="_blank"
                rel="noopener noreferrer"
                href="https://www.example.com/"
              >
                {chunks}
              </a>
            ),
            cta: (...chunks) => <b>{chunks}</b>
          }}
        />
      </p>
    </IntlProvider>
  );
}

We replace the a and cta tags with our own components.

FormattedDisplayName

We can format the display name with the FormattedDisplayName component.

For example, we can write:

import React from "react";
import { IntlProvider, FormattedDisplayName } from "react-intl";
const messages = {
  en: {}
};
export default function App() {
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedDisplayName type="currency" value="USD" />
      </p>
    </IntlProvider>
  );
}

We can pass in the type and value props to return the full name of the value .

Conclusion

We can display translated and formatted text with the FormattedMessage component.

FormattedDisplayName lets us display the full name of languages and currencies.

Categories
React

Formatting Dates, Numbers, and Lists with React-Intl

Many apps have to be made usable by different users from various parts of the world.

To make this easier, we can use the react-intl to do the internationalization for us.

In this article, we’ll look at how to get started with formatting dates with the react-intl library.

FormattedRelativeTime

We can use the FormattedRelativeTime component to format relative time.

For example, we can write:

import React from "react";
import { IntlProvider, FormattedRelativeTime } from "react-intl";

const messages = {
  en: {
    greeting: "Hello {name}! How's it going?"
  },
  es: {
    greeting: "¡Hola {name}! ¿Cómo te va?"
  },
  fr: {
    greeting: "Bonjour {name}! Comment ça va?"
  },
  de: {
    greeting: "Hallo {name}! Wie geht's?"
  }
};

export default function App() {
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedRelativeTime
          value={1000}
          numeric="auto"
          updateIntervalInSeconds={10}
        />
      </p>
    </IntlProvider>
  );
}

We use the FormattedRelativeTime component with a few props.

value has the relative time value in seconds.

numeric meabns whether we display the item numerically.

updateIntervalInSeconds is the time interval in seconds in which we update the formatted string.

Number Formatting Components

We can format numbers with a few components.

They include the FormattedNumber , FormattedNumberParts and FormattedPlural components.

FormattedNumber

The FormattedNumber complete renders the formatted number into a fragment.

We can customize this as we wish.

For instance, we can use it by writing:

import React from "react";
import { IntlProvider, FormattedNumber } from "react-intl";

const messages = {
  en: {
    greeting: "Hello {name}! How's it going?"
  },
  es: {
    greeting: "¡Hola {name}! ¿Cómo te va?"
  },
  fr: {
    greeting: "Bonjour {name}! Comment ça va?"
  },
  de: {
    greeting: "Hallo {name}! Wie geht's?"
  }
};

export default function App() {
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedNumber
          value={1024}
          style="unit"
          unit="kilobyte"
          unitDisplay="narrow"
        />
      </p>
    </IntlProvider>
  );
}

We use the FormattedNumber component with the value to format.

style is set to unit so that we format it with the unit.

unit is the unit we want to have with the number.

unitDisplay is the style of the unit we display.

We can only pass in units that are allowed by the Inyl.NumberFormat constructor.

narrow means that we display the abbreviation.

So we get:

1,024kB

as a result.

FormattedNumberParts

The FormattedNumberParts component lets us format each part of the number string individually.

For instance, we can write:

import React from "react";
import { IntlProvider, FormattedNumberParts } from "react-intl";

const messages = {
  en: {
    greeting: "Hello {name}! How's it going?"
  },
  es: {
    greeting: "¡Hola {name}! ¿Cómo te va?"
  },
  fr: {
    greeting: "Bonjour {name}! Comment ça va?"
  },
  de: {
    greeting: "Hallo {name}! Wie geht's?"
  }
};

export default function App() {
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedNumberParts value={1024}>
          {parts => (
            <>
              <b>{parts[0].value}</b>
              {parts[1].value}
              <em>{parts[2].value}</em>
            </>
          )}
        </FormattedNumberParts>
      </p>
    </IntlProvider>
  );
}

to format each part of the number.

In our example, the first parts entry is the thousands digit.

The 2nd part is the comma thousand separator.

The 3rd is the remaining digits.

FormattedPlural

We can format plural numbers with the FormattedPlural component.

To use it, we can write:

import React from "react";
import { IntlProvider, FormattedPlural } from "react-intl";

const messages = {
  en: {
    greeting: "Hello {name}! How's it going?"
  },
  es: {
    greeting: "¡Hola {name}! ¿Cómo te va?"
  },
  fr: {
    greeting: "Bonjour {name}! Comment ça va?"
  },
  de: {
    greeting: "Hallo {name}! Wie geht's?"
  }
};

export default function App() {
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedPlural value={10} one="cat" other="cats" />
      </p>
    </IntlProvider>
  );
}

We use the FormattedPlural compoennt to render the singular or plural according to the value that’s passed into the value prop.

List Formatting Components

We can format lists with react-intl.

FormattedList

We can use the FormmatedList component to format lists the way we want.

For instance, we can write:

import React from "react";
import { IntlProvider, FormattedList } from "react-intl";

const messages = {
  en: {
    greeting: "Hello {name}! How's it going?"
  },
  es: {
    greeting: "¡Hola {name}! ¿Cómo te va?"
  },
  fr: {
    greeting: "Bonjour {name}! Comment ça va?"
  },
  de: {
    greeting: "Hallo {name}! Wie geht's?"
  }
};

export default function App() {
  return (
    <IntlProvider locale="en" messages={messages.en}>
      <p>
        <FormattedList type="conjunction" value={["peter", "paul", "mary"]} />
      </p>
    </IntlProvider>
  );
}

to combine the array in the value prop into a string.

We specified that the type of conjunction so that we combine it with ‘and’ or their equivalent on other languages.

Since we set the locale to English, we get:

peter, paul, and mary

Conclusion

We can format relative time, lists, and numbers with react-intl.

Categories
Next.js

Next.js — Dynamic Imports and Static Pages

We can create server-side rendered React apps and static sites easily Next.js.

In this article, we’ll take a look at dynamic imports and render static pages with Next.js.

Dynamic Import

ES2020 supports dynamic imports natively.

We can use this within our Next.js to dynamically import our components.

It also works with server-side rendering.

This lets us split our code into manageable chunks.

For example, we can write:

components/name.js

function Name() {
  return (
    <span>world</span>
  )
}

export default Name

pages/hello.js

import dynamic from 'next/dynamic'

const NameComponent = dynamic(() => import('../components/name'))

function Hello() {
  return (
    <div>hello <NameComponent /></div>
  )
}

export default Hello

We imported the dynamic function and passed in a callback that calls import to import the component.

Then we can use the NameComponent in our code.

Now we should see:

hello world

displayed.

Dynamic Component With Named Exports

We can create dynamic components with named exports.

To do that, we can write:

components/name.js

export function Name({ }) {
  return (
    <span>world</span>
  )
}

pages/hello.js

import dynamic from 'next/dynamic'

const NameComponent = dynamic(() => import('../components/Name').then((mod) => mod.Name)
)

function Hello() {
  return (
    <div>hello <NameComponent /></div>
  )
}

export default Hello

In the callback, we called import with the then callback to return the Name component from the module.

With Custom Loading Component

We can also add a component to show the component when the component loads.

For example, we can write:

components/name.js

function Name() {
  return (
    <span>world</span>
  )
}
export default Name

pages/hello.js

import dynamic from 'next/dynamic'

const NameComponent = dynamic(() => import('../components/Name'), { loading: () => <p>loading</p> })

function Hello() {
  return (
    <div>hello <NameComponent /></div>
  )
}

export default Hello

We have an object with the loading property in the code with a component as the value.

Disable Server-Side Rendering

Server-side rendering can be disabled by passing in an object into the dynamic function with the ssr property set to false .

For example, we can write:

components/name.js

function Name() {
  return (
    <span>world</span>
  )
}
export default Name

pages/hello.js

import dynamic from 'next/dynamic'

const NameComponent = dynamic(() => import('../components/Name'), { ssr: false }
)

function Hello() {
  return (
    <div>hello <NameComponent /></div>
  )
}

export default Hello

Static HTML Export

We can export our app to static HTML with the next export command.

It works by prerendering all the pages to HTML.

The pages can export a getStaticPaths property that returns all the paths that we want to prerender.

next export should be used when there are no dynamic data changes in our app.

To use it, we run:

next build && next export

next build builds the app and next export gets us the HTML pages.

We can also add both commands to our package.json file:

"scripts": {
  "build": "next build && next export"
}

and run npm run build to run it.

getInitialProps can’t be used with getStaticProps or getStaticPaths on any page.

Conclusion

We can dynamically import components with Next.js.

Also, we can build our Next.js app to static HTML files.

Categories
Next.js

Next.js — Middlewares and Preview Mode

We can create server-side rendered React apps and static sites easily Next.js.

In this article, we’ll take a look at route middlewares and preview mode with Next.js.

Connect/Express Middleware Support

We can use Connect compatible middleware.

For example, we can add the cors middleware by installing it and adding to our Next.js app.

To install it, we run:

npm i cors

or

yarn add cors

Then we can add it to our API route by writing:

import Cors from 'cors'

const cors = Cors({
  methods: ['GET', 'HEAD'],
})

function runMiddleware(req, res, fn) {
  return new Promise((resolve, reject) => {
    fn(req, res, (result) => {
      if (result instanceof Error) {
        return reject(result)
      }
      return resolve(result)
    })
  })
}

async function handler(req, res) {
  await runMiddleware(req, res, cors)
  res.json({ message: 'hello world' })
}

export default handler

We imported the Cors function.

Then we use it to create the cors middleware.

We can specify the request methods that can be used with the route.

The runMiddleware function runs the middleware by calling the fn middleware function and then resolve to the result if it’s successful.

Otherwise, the promise is rejected.

The handler is the route handler that runs the middleware and then returns the response.

Response Helpers

Next.js comes with various response helpers.

The res.status(code) method lets us set the status code of the response.

code is an HTTP status code.

res.json(json) method lets us send a JSON response.

json is a valid JSON object.

res.send(body) lets us send an HTTP response, where body is a string, object, or buffer.

res.redirect takes an optional status and path to let us redirects to a specific path or URL.

The default value of status is 307.

For example, we can call them by writing:

export default (req, res) => {
  res.status(200).json({ name: 'hello' })
}

They can be chained together.

Preview Mode

We can preview routes that are statically generated.

Next.js has a preview mode feature to solve the problem.

It’ll render the pages at request time instead of build time and fetch the draft content instead of the published content.

To use it, we call the res.setPreviewData method to do the preview.

For example, we can write:

export default (req, res) => {
  res.setPreviewData({})
  res.end('Preview mode')
}

to enable it.

Securely Accessing Data

We can access data by writing from the preview route and send to our page.

To do this, we create our preview API route by creating the pages/api/preview.js file:

export default async (req, res) => {
  const response = await fetch(`https://yesno.wtf/api`)
  const data = await response.json();
  res.setPreviewData(data)
  res.redirect('/post')
}

We get the data from an API and call setPreviewData so that we can get it from the getStaticProps function in our page JavaScript file.

Then we redirect to our file so that we can see the data on our page.

In pages/post.js , we write:

function Post({ data }) {
  return (
    <div>{data && data.answer}</div>
  )
}

export async function getStaticProps(context) {
  if (context.preview) {
    return {
      props: {
        data: context.previewData
      }
    }
  }
  return { props: { data: { answer: 'not preview' } } }
}

export default Post

We check is preview mode is on by checking the context.preview property.

If it’s true , then we get the preview data from context.previewData .

And then we display the data in our Post component.

Conclusion

We can add preview mode with Next.js so that we can view the output before it goes live.

Also, we can use Connect or Express middleware in our Next.js apps.