Using Storybook for React with Ionic Framework is a great way to increase your efficiency and so much more if you want to build real component based solutions in your application.
You can quickly implement UI components along with validating the actions/events that the components respond to with out constantly rebuilding the entire application and repeating a set of action to get to the proper component on the proper page.
Here I will take the sample list application template and create a story around the message item component
- move all events to parent component
- add ability to have event for deleting the item
- add ability to have event for favoriting the item
- create a 'decorator' to handle the default Ionic App wrapper
Setup
Working with sample Ionic App with a list of messages.
First we install storybook; this will take a minute, be patient.
npx -p @storybook/cli sb init
Then open a new terminal window and start storybook, make sure you are in the root of the project directory.
npm run storybook
All the Ionic Stuff
Storybook has the concept of decorators which can be used to wrap the stories. So I created an IonWrap
decorator that has all of the code to setup a page and content in an Ionic App.
// .storybook/preview.js
import React, { useState } from 'react';
import { addDecorator } from "@storybook/react";
import {
IonApp, IonContent, IonPage, IonList, IonButton
} from "@ionic/react";
/* Core CSS required for Ionic components to work properly */
import "@ionic/react/css/core.css";
/* Basic CSS for apps built with Ionic */
import "@ionic/react/css/normalize.css";
import "@ionic/react/css/structure.css";
import "@ionic/react/css/typography.css";
/* Optional CSS utils that can be commented out */
import "@ionic/react/css/padding.css";
import "@ionic/react/css/float-elements.css";
import "@ionic/react/css/text-alignment.css";
import "@ionic/react/css/text-transformation.css";
import "@ionic/react/css/flex-utils.css";
import "@ionic/react/css/display.css";
const IonWrapper = ({ children }) => {
return (
<IonApp>
<IonPage>
<IonContent>{children}</IonContent>
</IonPage>
</IonApp>
);
};
addDecorator((storyFn) => <IonWrapper>{storyFn()}</IonWrapper>);
This allows me to keep the stories light and focus on just the content of the component I am working on
Setting Up Basic Story For MessageListItem
We need the basic imports for storybook and then we need to import the component that we are working with. We need to import IonList
to ensure the IonItem
in the MessageListItem
is rendered properly.
Create the file, 2-MessageListItem.stories
and start to add the following code:
// 2-MessageListItem.stories
import React from "react";
import { action } from "@storybook/addon-actions";
import MessageListItem from "../components/MessageListItem";
import { IonList } from "@ionic/react";
Set the default export for the story to using the MessageListItem
component and create the first story
export default {
title: "MessageListItem",
component: MessageListItem,
};
export const BasicMessage = () => {
let message = {
fromName: "Andrea Cornerston",
subject: "Last minute ask",
summary: "Basic Message Summary",
date: "Yesterday",
id: 5,
};
return (
<IonList>
<MessageListItem
key={message.id}
message={message}
/>
</IonList>
);
};
I have mocked the data message
to that we have some content to render in the ListItem, you should have the component rendering in the storybook web application.
Working On Component Actions
Storybook Addon Actions can be used to display data received by event handlers in Storybook.See documentation
Let's set the component up properly so there is no business logic handled in the component, just responding to actions and passing the actions up to the parent.
First the Click Event On The Item
Modify the properties that are passed into the component to also include the click event which is called when the item is clicked. We want the function to return the message object that was clicked.
// src/components/MessageListItem.tsx
interface MessageListItemProps {
message: Message;
handleClick: any;
}
We will change the IonItem
to handle the event
// src/components/MessageListItem.tsx
<IonItem onClick={()=>handleClick(message)} detail={false}>
... OTHER CODE ...
</IonItem>
Now back in our story, we can use the action
add-on to handle the response from the click event to know it is working properly
// 2-MessageListItem.stories
<IonList>
<MessageListItem
key={m.id}
message={m}
handleClick={action("item-clicked")}
handleFavorite={action("option-handleFavorite")}
handleDelete={action("option-handleDelete")}
/>
</IonList>
HANDLE CLICK ACTION
Handle Item Option Events
One way to handle multiple actions on a List Item is using the IonOptions
that are displayed when you swipe the item. In this example we will support deleting the item or adding it to you favorites. Once again we want to keep this component simple and have the parent respond to the event.
Lets add the additional properties to the component
// src/components/MessageListItem.tsx
interface MessageListItemProps {
message: Message;
handleClick: any;
handleFavorite: any;
handleDelete: any;
}
We will change the IonItem
to handle the events and once again pass back the associated object
// src/components/MessageListItem.tsx
<IonItemSliding>
<IonItem
onClick={()=>handleClick(message)}
detail={false}>
... OTHER CODE ...
</IonItem>
<IonItemOptions side="end">
<IonItemOption
onClick={() => handleFavorite(message)}>
Favorite
</IonItemOption>
<IonItemOption
onClick={() => handleDelete(message)} color="danger">
Delete
</IonItemOption>
</IonItemOptions>
</IonItemSliding>
Now when we click the options we get the corresponding events and properties displayed in the actions area in storybook.
Top comments (3)
added video to post - youtube.com/watch?v=vHJK3OwDZTA
Hi @aaronksaunders any chance you can post the whole thing in github?
I don't know if I'm missing anything but Ionic classes are not rendering for me.
Thanks
Just trying to et this working myself and came across that hte md/ios classes weren't bein inserted in the the ionic components as needed.
To fix this, a little piexe of code I found in the v5 to v6 upgrade doco helped.
Put this in the
preview.js
file and it seems to work!