Category Archives: React

Getting Started Storybook (Latest update)

A while back I posted Getting started with Storybook. However, like with most things JavaScript, things have changed, and those instructions are now out of date and also, now, much simpler.

I have an existing React web app. so how do I add storybook ?

Note: Do not add storybook from NPM via yarn/npm, instead use the following instructions.

From you web app’s root folder run the following from the root of an existing project

npx storybook init

This will try to detect the framework being use. In the case of React this worked a treat. It will then add a stories folder to your src folder with a bunch of examples. It adds the .storybook folder with the information to tell storybook what files to look for and, ofcourse, add all the package.json dependencies etc.

Now all you need do is run

yarn storybook

So simple.

Here’s a very simple example of a .stories.tsx. I have a Header component which simpler writes a string out for a given date.

import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';

import { Header } from '../controls/Header';

export default {
  title: 'Header',
  component: Header,
  parameters: {
    layout: 'fullscreen',
  },
} as ComponentMeta<typeof Header>;

const Template: ComponentStory<typeof Header> = (args) => <Header {...args} />;

export const Display = Template.bind({});
Display.args = {
   endDateTime: new Date(Date.parse("2022-12-31T23:59:00.000Z"))
};

The Display export is shown as a view on a component within storybook and we also get the endDateTime editor for trying different inputs out.

React router dom direct URL or refresh results in 404

When using React, we’re writing a SPA and when using the React Router we actually need all pages/URL’s to go through App.tsx and the React Router. If everything is not set up correctly you’ll find when you navigate to a page off of the route and refresh the page, or just try to navigate via a direct URL you may end up with a 404 in production, even though everything worked in dev.

Note: Obviously we do not have an App.tsx when transpiled, but I’ll refer to that page as it makes things more obvious what’s going on.

To be more specific to my situation where I discovered these issues – I’ve created a subdomain (on a folder off of the root) for a React site I’m developing that will hosted from that subdomain.

As stated, all works great using serve or the start script. When deployed to production the root page works fine and links from that page via React Router also work fine but refreshing one of those links or trying to navigate directly to a link off of the root results in a 404. So what’s going on?

This issue is that the web server being used is not routing those relative URL’s via the App.tsx router page now and so the React Router is not actually doing anything, it only works when we route things via the App.tsx code.

To solve this, within your React public folder or the root of where you eventually deploy the React web site to, add a .htaccess file (if one is not already there and if your webserver supports this file). Add the following to that file

<IfModule mod_rewrite.c>

  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-l
  RewriteRule . /index.html [L]

</IfModule>

And that’s it, now the web server will route via our App.tsx (index.html).

See the following references which supplied all this information and thanks to these posts I was able to get my React direct links working

404: React Page Not Found
Simple Steps on how to Deploy or Host your ReactJS App in cPanel
Routing single page application on Apache with .htaccess

Resizing a component relative to it’s parent (position: fixed)

In the previous post I mentioned how we can fix a footer to the bottom of the viewport, but we have an issue where the width extends outside the width of the parent.

NOTE: This post discusses a partial solution to this issue. In that, if the browser window size changes this works, but if your viewport size changes, it doesn’t. So for example if you have a flyout Drawer component or the likes which takes up space on the page, then no window resize takes place, only a layout change takes place. If I resolve this I’ll update this post.

To solve this we’re going to create a ref on our parent div and monitor it’s size changes and then apply them to our footer.

So assuming we’re using material-ui’s createStyle, with the following CSS (or use plain CSS or your preferred CSS library)

footer: {
  position: "fixed",
  bottom: 0,
  marginBottom: "10px",
  textAlign: "left",
},

Here’s an abridged version of my code (using a React functional component)

const targetRef: any = useRef(null);
const [relativeWidth, setRelativeWidth] = useState(0);

useLayoutEffect(() => {
  function updateWidth() {
    if (targetRef != null && targetRef.current != null) {
      setRelativeWidth(targetRef.current.offsetWidth);
    }
  }

  window.addEventListener("resize", updateWidth);
  updateSize();
  return () => window.removeEventListener("resize", updateWidth);
}, []);

return (
  <div ref={targetRef}>
    {/* our other elements */}
    <div className={classes.footer}
      style={{ width: relativeWidth }}>
      {/* our footer elements */}
    </div>
  </div>
);

So what we’re doing is getting a ref to the parent div and storing the relativeWidth in the component’s state using useState (which we set up at the start of the function). Using useEffect, we create a listener to the window’s resize events and then update our state. Finally in the footer style we set the width explicitly using then relativeWidth that we stored.

Not only will this now display correctly relative to the parent div, but also resize relative to it as well.

These are early days for this code, but so far this looks to work a treat.

Storybook render decorator

Storybook have a bunch of really useful decorators.

This one is useful to see when changes, either data, state or properties cause re-rendering of our React UI. In some cases it may be that a change should not cause a subcomponent to render, with this decorator we can see what caused the render to occur (or at least help point us towards possible reasons).

We need to install the add-on, like this

yarn add -D storybook-addon-react-renders

and we just add code like this

import { withRenders } from "storybook-addon-react-renders";

storiesOf("Some Component Test", module)
  .addDecorator(withRenders)

Deploying React to GitHub Pages

  • Create a new repository on GitHub, so we have something to refer to mine’s called react.io so simply replace this with your repo name within these steps, also the steps will show my GitHub repository at putridparrot, again replace this with your GitHub username.
  • Clone the repository onto your local machine
  • In the parent folder of your cloned repository run
    yarn create react-app react.io --typescript
    

    Note: without –typescript if you want plain JavaScript code.

  • cd back into your repo folder and type
    yarn add gh-pages -D
    
  • Open your packages.json and add the following (below the “verson” is a good place)
    "homepage": "http://putridparrot.github.io/react.io",
    
  • Next up we need to add a predeploy and deploy script within the “scripts” section of the packages.json, so add these
    "deploy": "gh-pages -d build",
    "predeploy": "yarn run build"
    
  • Run the following from your shell/terminal
    yarn deploy
    

When we run yarn deploy the predeploy script is run and will create a gh-pages branch on your GitHub remote repo. Also it will default to this branch and when you go to the Settings page of your repository and scroll to the GithHub Pages section, you should see the source set to gh-pages branch.

As this point my React GitHub pages site should be visible at

https://putridparrot.github.io/react.io/

You may well want to push the source of your React application to GitHub as well, so simply commit and push to master or another branch, DO NOT overwrite the gh-pages branch, this is basically your deployed site.

To update your GitHub pages, simply run the following each time you’re ready to deploy to gh-pages

yarn deploy

The updated deployment may take up to ten minute to become available.

Splitter bars in React

I went through a load of splitter bar implementations for use within a React site, i.e. I simply wanted to partition my application into two halves (top and bottom), with a horizontal splitter bar.

The top half should be filled with a grid of data (I was using ag-grid), the bottom half filled with the query builder control. Also the button half should be capable of being collapse/hidden.

I was surprised at how hard it was to find anything that “just worked”. But this one did…

Run

yarn add m-react-splitters

Mine is “m-react-splitters”: “^1.2.0”. Now import the following

import Splitter from "m-react-splitters";
import "m-react-splitters/lib/splitters.css";

and here’s the basics for the code (obviously replacing the comment blocks with your components).

<Splitter
   position="horizontal"
   maximizedPrimaryPane={!showQueryBuilder}>
<div style={{width: "100%, height: "100%" }}>
<!-- grid -->
</div>
<div style={{width: "100%, height: "100%", overflow: "auto" }}>
<!-- query builder -->
</div>

Redux and storybook

We’ve implemented our React UI and set-up storybook to test it but we’re using redux as our store, so how do we use this in storybook?

Storybook allow us to create a decorator which is really just a wrapper around our story, so for example we add a decorator using .addDecorator like this

storiesOf("SomeComponent", module)
  .addDecorator(withProvider)
  .add("default", () => 
    <SomeComponent />
 );

Within the .addDecorator we can add more React code or HTML, maybe to position our test component centrally in the screen or in this case we can use the same code to wrap a Provider.

As you can see from the above code we’ve got a withProvider value which looks like this

const withProvider = (story) => <Provider store={store}>{story()}</Provider>

Hence the code takes a story (the story we’re testing) and we simply wrap it within a Provider element having previously created the redux store using the standard createStore function, i.e.

const store = createStore(reducer);

Now we can test our UI/UX code within storybook using the redux store for it’s state.

React context (developing a ThemeProvider)

I’ve been looking into theming a React project and whilst I ultimately intended to use the theming capabilities within material-ui I was interested in how I might write our own.

We’re going to be using React context.

Let’s start by creating a theme.ts file which will include our “base” theme style

export const theme = {
  button: {
    backgroundColor: "white",
    borderColor: "red",
    color: "blue",
    borderWidth: "2px",
    borderStyle: "solid",
    padding: "10px",
    fontSize: "24px",
  }
};

In the above code we created a button property which obviously defines our (rather horrible) default button style. Now we need some way to apply this theme (and any new version of our theme) to our application.

Hence we create a ThemeProvider (ThemeProvider.tsx in my case), which looks like this

import React from "react";
import { theme } from "../theme";

export const ThemeContext = React.createContext(theme);

export function ThemeProvider(props: any) {
  return (
    <ThemeContext.Provider value={props.theme}>
      {props.children}
    </ThemeContext.Provider>
  );
}

In this case, we create a React context, and assign a default value to it (in the shape of our base theme). The ThemeProvider function is then used to wrap our application supplying the theme to all components that wish to use it. So here’s an example of the ThemeProvider in use (in the App.tsx)

import React from 'react';
import { ThemeProvider } from './components/ThemeProvider';
import { Button } from './components/Button';
import { myTheme } from './myTheme';

const App: React.FC = () => {
  return (
    <ThemeProvider theme={myTheme}>
      <div className="App">
        <Button>Themed Button</Button>
      </div>
    </ThemeProvider>
  );
}

export default App;

You’ll notice we’ve also taken this opportunity to supply our overridden theme (with a slightly nicer button style) which is shown below (myTheme.ts)

export const myTheme = {
  button: {
    backgroundColor: "green",
    color: "white",
    borderWidth: "2px",
    borderStyle: "solid",
    padding: "10px",
    fontSize: "24px",
  }
};

As you can see from our App.tsx code, we’re using a Button component, so let’s look at that code now (Button.tsx)

import React from "react";
import { ThemeContext } from "./ThemeProvider";

export const Button = (props: any) => (
  <ThemeContext.Consumer>
    {theme => (
      <button style={theme.button} {...props}>
        {props.children}
      </button>
    )}
  </ThemeContext.Consumer>
);

In this code we consume the React context we created via the ThemeProvider file and apply the style to the button, also passing through any props to the button.

Now if you were to run this application the button will be displayed with our new theme (a nice green background button). If you change the App.tsx theme property to the default “base” theme, then we our default style.

Storybook and state (also uses @sambego/storybook-state)

There’s several ways to setup your components for testing within Storybook to allow us to change our component’s properties – using Knobs or creating wrapper components to encompass state changes through to hooking up to a redux store.

Let’s look at an example of wrapping our component within a story.

So I have an AppBar within a selection component. I want to see the story maintain the state changes when the user changes the selection. Now, no actual state is stored within the component itself as it’s passed up the component hierarchy to a parent component and/or redux store. This means, without setting up a parent component or redux store the selection changes but the actual value on the component remains the same.

One way to achieve this is to create a wrapper parent component, i.e. a minimal wrapper component around our component under test, for example

class WrapperComponent extends React.Component<any, any> {
  constructor(props) {
    super(props);

    this.state = { ...props }
  }

  render() {
    const props = { 
      ...this.props,
      onSelectionChanged: (selected, _) => this.setState({selected: selected}),
      selected: this.state.selected
    }

    return <ComponentUnderTest {...props} />
  }
}

So as you can see, the ComponentUnderTest is wrapped in a WrapperComponent and hooked up to props along with state changes, now we simply have the story return the WrapperComponent, i.e.

const props = {
  selected: 'None'
}

return <WrapperComponent {...props}/>

Easy enough and could be rewritten to make it more generic, but there’s an alternative and that’s to use something like @sambego/storybook-state

yarn add -D @sambego/storybook-state

In which case we can write our story like this instead

import { Store, State } from "@sambego/storybook-state";

const storeState = new Store({
  selectedPublicQuery: "None"
});

storiesOf("ComponentUnderTest", module)
  .add("default", () => {
    return (
      <State store={storeState}>
        {state => 
          <ComponentUnderTest selected={state.selected} 
              onSelectionChanged={(selected, _) => storeState.set({selected: selected})}/>
          }
        </State>
      );
  });

Storing extra data within your HTML elements using data-* attributes

I came across a simple problem, I have an array of objects in React with a name (something to display to the user) and an expression tree (which makes up a query associated with the name).

What I wanted to do is that when a name is selected in a material-ui Select component, that it then passes the name and the expression data to a handler.

Ofcourse there are several ways to achieve this but this is a really simply solution. We create an array of MenuItem‘s for the Select and we store the expression tree along with the MenuItem

If you take a look at Using data attributes you’ll see that HTML5 has a way of adding attributes to an element using the data-* syntax.

The name of the attribute to be added simply starts with data-, for example data-expression and now when the Select onChange event is handled, we can get at, not only the value selected but also the data attribute value.

Here’s an example of us setting up a MenuItem

return [{name: "None", expression: undefined}]
  .concat(publicQueries).map(e => {
    return <MenuItem key={e.name} 
      value={e.name} 
      data-expression={e.expression}>{e.name}</MenuItem>
});

Notice that we simply declare the data-expression and assign some data to it. Nothing special here.

Now within the onChange handler of the Select component we might have a handler like this

handleOnChange(e: any, child?: any) {
   const selecteNamed = e.target.value;
   const selectedExpression = child.props['data-expression'];
   // do something useful with these
}