Category Archives: React

React pure component

I’m using the Airbnb style guide with eslint and one warning mentioned that a very simple Component subclass with no properties or state but it just has a render method, should probably be written as a pure component.

Below is an example of a pure component in it’s most simplistic and minimal form.

import React, { ReactElement } from 'react';

const MyButton = (): ReactElement => (
  <div>My Button</div>
);

export default MyButton;

React also comes with a PureComponent base class, hence we could write

class MyButton extends React.PureComponent {
   render() {
      <div>My Button</div>
   }
}

This is slightly different to our function version of a pure component in that it also handles properties and state, see the documentation for more information.

In both cases, these changes from a standard React Component result in performance increases, so very much worth baring in mind if your components are able to be written in these ways.

Getting started with Redux in React

Lot’s of Getting started posts at the moment, and probably a lot more to come. This one’s on using Redux with React and TypeScript.

Let’s create a sample application…

  • yarn create react-app {insert app name} –typescript
  • yarn add redux
  • yarn add react-redux
  • yarn add @types/react-redux
  • We can now delete the App.* files and the *.svg
  • Add folder components to src and also reducers
  • In src/components add Counter.tsx (we’re going to recreate the redux example component for in a tsx). Here’s the code
    import React, { Component } from 'react';
    
    interface CounterProps {
        value: number;
        onIncrement: () => void;
        onDecrement: () => void;
    }
    
    export default class Counter extends Component<CounterProps, {}> {
        constructor(props: CounterProps) {
            super(props);
            this.incrementAsync = this.incrementAsync.bind(this);
            this.incrementIfOdd = this.incrementIfOdd.bind(this);
        }
    
        incrementIfOdd() {
            if (this.props.value % 2 !== 0) {
              this.props.onIncrement()
            }
          }
        
        incrementAsync() {
          setTimeout(this.props.onIncrement, 1000)
        }
    
        render() {
            const { value, onIncrement, onDecrement } = this.props
            return (
              <p>
                Clicked: {value} times
                {' '}
                <button onClick={onIncrement}>
                  +
                </button>
                {' '}
                <button onClick={onDecrement}>
                  -
                </button>
                {' '}
                <button onClick={this.incrementIfOdd}>
                  Increment if odd
                </button>
                {' '}
                <button onClick={this.incrementAsync}>
                  Increment async
                </button>
              </p>
            );
        }
    }
    
  • Now in src/reducers add the file counterReducer.ts and put the following code into it
    export default (state: number = 0, action: any) => {
        switch (action.type) {
          case 'INCREMENT':
            return state + 1
          case 'DECREMENT':
            return state - 1
          default:
            return state
        }
    }
    
  • Finally, replace the contents of the index.tsx with the following
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import * as serviceWorker from './serviceWorker';
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import counter from './reducers';
    import Counter from './components/Counter';
    
    const store = createStore(counter);
    
    const render = () => {
        ReactDOM.render(
            <Provider store={store}>
                <Counter
                    value={store.getState() as number}
                    onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
                    onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
                />,
            </Provider>, 
            document.getElementById('root'));
    }
    
    render();
    store.subscribe(render);
    
    serviceWorker.unregister();
    
  • If everything is correct you should be able to execute yarn start and see the web page with +, – buttons etc. and clicking + or – will increment and decrement the displayed counter.

That’s a lot to take in, so what have we actually done?

Redux supplied a storage container for our application state. In the index.tsx file we create a store passing our reducer (src/reducers/counterReducer.ts) Reducers are called in response to the store.dispatch calls. In this case within index.tsx we dispatch the actions with the type set to strings INCREMENT and DECREMENT. The reducer receives these messages then makes state changes (although not actually changing the state directly but returning a new state).

As the Redux documentation on reducers states, “Remember that actions only describe what happened, but don’t describe how the application’s state changes”.

The next piece of code to look at is

store.subscribe(render);

We’ve wrapped the render code for the application in the render function. Subscribe then allows us to listen to messages on the redux store and calls the render function to render changes. The following part of the Counter code within the index.tsx file then simply assigns the current store state to the Counter value property

value={store.getState() as number

There’s not lot to really say about the Counter component which is standard React code for displaying buttons etc. and exposing properties as used in index.tsx.

More…

This is a simplistic example. Redux documentation states that it’s not advisable to write the store.subscribe code but instead use the connect function provided by React Redux for this functionality.

In this instance we create a src/containers folder and add the file CounterContainer.ts, here’s the code

import {  connect } from 'react-redux'
import Counter from './../components/Counter';

const mapStateToProps = (state: any) => {
    return {
        value: state as number
    }
}

const mapDispatchToProps = (dispatch: any) => {
    return {
        onIncrement: () => dispatch({ type: 'INCREMENT' }),
        onDecrement: () => dispatch({ type: 'DECREMENT' }),
    }
}

export const CounterLink = connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter);

export default CounterLink;

Here we write code to map state to properties and dispatch to properties (if we don’t handle either of these we simply supply a null in place of the function in the connect function.

As you can probably see, in mapStateToProps, the state is supplied and we simply apply it to the value property (which is on our Counter). In mapDispatchToProps we link the onIncrement and onDecrement to the dispatch functions.

Finally in index.tsx replace the Counter component with our newly created and exported CounterLink, i.e. here the new index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import counterReducer from './reducers/counterReducer';
import CounterLink from './containers/CounterContainer';

const store = createStore(counterReducer);

ReactDOM.render(
    <Provider store={store}>
        <CounterLink />
    </Provider>, 
    document.getElementById('root')
);

serviceWorker.unregister();

Getting started with Storybook

Storybook allows us to prototype and test UI components in isolation from your application. We’re going to “get started” using Storybook with React, but it also supports Vue and Angular.

Let’s create the project and add required libraries.

Note, that the npx command should not be run from VSCode (or other Atom based editor by the sounds of it), I got an error EPERM: operation not permitted, unlink.

In the steps below we also install enzyme for test rendering. We’ll also add material-ui, just for the fun of it.

  • npx -p @storybook/cli sb init
  • yarn create react-app storybooksample –typescript
  • yarn add @types/storybook__react -D
  • yarn add @types/enzyme -D
  • yarn add enzyme -D
  • yarn add enzyme-adapter-react-16 -D
  • yarn add @types/enzyme-adapter-react-16 -D
  • yarn add @material-ui/core

The storybook “getting started” web page suggests we now run the following commands to check everything was installed correctly, so let’s do this, run

  • yarn test
  • yarn storybook

Let’s now remove the storybook generated code as we’ll replace this with our own code. So in the folder src/stories, delete index.js and add a file named setupTests.ts to the src folder, here’s the code for this file

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

Before we go too much further let’s create a sample component that will use to demonstrate testing via storybook. In the src folder add a folder named components/LogButton and in this create a file named LogButton.tsx – the name’s unimportant ofcourse, this is just a sample component which will be a new Button component, but hopefully will help demonstrate how we can use storybook.

In LogButton.tsx place the following code

import React from 'react';
import Button from '@material-ui/core/Button';

interface LogButtonProps {
    onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
}

export class LogButton extends React.Component<LogButtonProps, {}>{    
   render() {
      return (
         <div>
            <Button variant='contained' onClick={this.props.onClick}>Log</Button>
         </div>
      );
   }
}

Let’s now create a standard unit test for this component, this is not required for storyboard, but it’s good practise. In the same src/components/LogButton folder add the file LogButton.test.tsx and here’s a very simple test to got into this file

import React from 'react';
import { mount } from "enzyme";
import { LogButton } from "./LogButton";

test('Loading component should not throw error', () => {
    const logButton = mount(<LogButton />);
    expect(logButton.exists()).toBe(true);
});

This will just check that the component, when loaded, loads successfully. We can now run yarn test to verify this code works.

Now let’s get storybook up and running with our component.

In the .storybook folder, replace the config.js code with the following

import { configure } from '@storybook/react';

const req = require.context('../src', true, /.stories.(ts|tsx|js)$/)
const loadStories = () => req.keys().forEach(filename => req(filename));
configure(loadStories, module)

This will automatically load stories based upon the filename/extensions .stories.

Alongside LogButton.tsx and LogButton.test.tsx, add LogButton.stories.tsx. This will supply the code required to integrate into storybook and also allow us to write code to allow us to test the UI component via storybook.

import React from 'react';
import { storiesOf } from '@storybook/react';
import { LogButton } from './LogButton';
import { action } from '@storybook/addon-actions';

storiesOf('LogButton', module)
    .add("Log", () => <LogButton onClick={action('clicked')} />);

Now run yarn storybook and the storybook server and UI should display along with our LogButton now being the only component available. Selecting the “Log” story, when the LogButton is clicked the action will display the text “clicked”.

So what we’ve done is create a very simply component based upon a Button which we also declared some properties – well in this case we’ve created a single property, an onClick event. The story created for the button then hooks into the button’s click event and when used via storybook allows us to test the control in isolation via storybook’s UI. This is not a unit test or automated test so much as an interactive UI test where, as developers, we can verify our functionality. Let’s say the Button’s text changed when clicked, now we could start interacting with the button via storybook and confirm everything works as expected.

There’s a lot more to storyboard than the above, but this is a good starting point.

Creating React components in TypeScript

By default we’ll usually create a React component (in a .tsx file) using a React.Component base class, for example

export default class GridSampleComponent extends Component<{}, {}> {
   render() {
      return (
         <div></div>
      );
   }
}

But as per my post here we can also create components through functions, which ofcourse makes sense when you see a .jsx file which tend to default to using functions.

Hence we can create the following

const MyButton = (props: any) => {
   return 
      <Button variant="contained" 
         color="secondary">{props.children}</Button>;
};

In the code above we’ve basically created a new component from the function which takes properties which are implicitly passed to the function via React, i.e. the code for using the above might look like this

<MyButton>Hello</MyButton>

The child elements are passed through to our function. In this case it’s just the string “Hello”.

React & Material UI “Invalid hook call. Hooks can only be called inside of the body of a function component”

When looking through some examples of writing Material UI code within React and using TypeScript (within a .tsx file to be precise) you might come across an error at runtime such as “Invalid hook call. Hooks can only be called inside of the body of a function component”.

Here’s an example of the code which causes this error

import React, { Component }  from "react";
import AddIcon from "@material-ui/icons/Add";
import { Fab } from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles";

const useStyles = makeStyles(theme => ({
   fab: {
      margin: theme.spacing(1),
   },
}));

const classes = useStyles();

export default class SampleComponent extends Component<{}, {}> {
   public render() {
      return (
         <div>
            <Fab color="primary" aria-label="Add" className={classes.fab}><AddIcon /></Fab>
         </div>
       );
   }
}

What we need to do is wrap the useStyles code in a function and replace the code which uses it, so for example

const SampleFab = () => {
   const classes = useStyles();
   return <Fab color="primary" aria-label="Add" className={classes.fab}><AddIcon /></Fab>;
}

export default class SampleComponent extends Component<{}, {}> {
   public render() {
      return (
         <div>
            <SampleFab />
         </div>
      );
   }
}

This also shows how we can create reusable components from just a simple function.

Unit testing and React

If you’ve created a React application using yarn, you’ll actually have the script for yarn test as well as App.test.tsx for running unit tests against React code (i.e. capable of running against tsx/jsx files etc.).

Jest is used for unit testing by default, but if you need to install Jest yourself simple run

yarn add --dev jest

The test script (within package.json) will execute react-scripts test but if we want to add our own script you could add

"jest": "jest --watch"

–watch will allow jest to monitor file changes and rerun tests automatically.

Create a folder off of your src folder (it can be at any depth) named __tests__ and within this we’ll add a simple .js file (or .ts) named files, for example number.test.js which we’ll create as a simple demonstration of writing tests and running the test runner. We should use either .test. or .spec. within the filename.

Within my number.test.js file I have the following

export function getNumber() {
    return 1234;
}

test('Test 1', () => {
    expect(getNumber()).toEqual(123);
});

test('Test 2', () => {
    expect(getNumber()).toEqual(1234);
});

Note: we can use “it” instead of “test” in the above

Obviously we’d normally not have the actually function we want to test within the test file, this is solely for simplicity of writing this post.

As you can see, each test comes with a string which is the name/description of the test, followed by a function which is executed by the test runner. Obviously in the above code “Test 1” will fail and “Test 2” will pass, so let’s run the test runner and see.

Like other test frameworks, we have functions to assert/expect certain values within our tests.

If you’ve added the script now run yarn jest or use yarn test. The Jest test runner will run and remain running, watching for file/test changes. Select the “a” option to run all tests after executing the jest script.

From the test runner (jest) you might wish to filter file name by regex, simply running tests against the __test__ folder.

React Router

There are many way to route web calls to our web pages etc. React also comes with it’s own capability, which we can load by running

yarn add react-router-dom
yarn add @types/react-router-dom

If you’ve created your React application using yarn create react-app –typescript then go to the index.tsx and edit it to look like this

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { BrowserRouter, Route } from 'react-router-dom'
import * as serviceWorker from './serviceWorker';
import About from './components/About';
import Home from './components/Home';

ReactDOM.render((    
    <BrowserRouter>
      <Route exact path="/home" component={Home} />
      <Route exact path="/about" component={About} />
  </BrowserRouter>), 
    document.getElementById('root'));

serviceWorker.unregister();

Now, let’s create a couple of React components which will act as our routes. Create a folder named components off of the root folder and add two files, about.tsx and home.tsx to the folder. The code for each is shown below (and the files should go in the folder src/components)

// about.tsx

import React, { Component } from 'react';

export default class About extends Component {
    render() {
        return (
            <div>About</div>
        );
    }
}

and

// home.tsx

import React, { Component } from 'react';

export default class Home extends Component {
    render() {
        return (
            <div>Home</div>
        );

    }
}

We’re not using the App.* files, so they can be deleted.

Obviously we can also add a root route, i.e.

<Route exact path="/" component={Home} />

Connecting event handlers to React components using TypeScript

In the previous post we created React components which we can pass attributes/properties to. As TypeScript aims to make JavaScript type safe we need to ensure the correct types are expected and passed, so for example here’s some code from the TicTacToe tutorial on the React site, but in this example we’re passing an event handler property from the Board component to the Square component, where it’s wired up to the button

interface SquareProperties {value: number, onclick: (ev: React.MouseEvent<HTMLButtonElement>) => void};

class Square extends Component<SquareProperties, {}> {
  render() {
    return (
      <button className="square" onClick={this.props.onclick}>
        {this.props.value}
      </button>
    );
  }
}

class Board extends Component {
  renderSquare(i: number) {
    return <Square value={i} onclick={() => alert('click')}/>;
  }

JSX, TSX in React, what are they?

React allows us to write JavaScript code in the standard .js files along with TypeScript in the .ts files, but if you’ve created either a TypeScript or JavaScript React app using yarn’s create command, i.e.

yarn create react-app my-app

you’ll notice .jsx or .tsx files. These are similar to Razor, ASP/ASP.NET, JSP and the likes in that they allow us to embed XML (or strictly speaking in our usage XHTML) into source code.

Let’s look at a React component (I’ll use TypeScript in this example but JavaScript jsx components are much the same), in our App.tsx we might want to include our own tag/element, for example

<HelloMessage name="PutridParrot" />

Yes, we’re always obsessed with the Hello World example and this one’s no different.

The above doesn’t really give any context, so here’s the whole of the App.tdx file

import React, { Component } from 'react';
import './App.css';
import HelloMessage from "./components/HelloMessage";

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
            <HelloMessage name="Mark" />
        </header>
      </div>
    );
  }
}

export default App;

Now the HelloMessage element is a React component. In this example I’ve created a components folder off of the src folder and created the file named HelloMessage.tsx, let’s look at the code for this component and then discuss how it works, here’s the code

import React, { Component } from 'react';

export interface HelloMessageProps {name: string;}

class HelloMessage extends Component<HelloMessageProps, {}> {
    render() {
      return (
        <div>
          Hello World {this.props.name}
        </div>
      );
    }
  }

export default HelloMessage;

In this example we’ve imported React, hence need not type React.Component, see Import Declarations for more information on this syntax.

Next up, we’ve created and exported an interface. This acts as the shape of the data that the Component expects to see. As TypeScript supports duck typing, we’re basically saying we expect a thing that looks like HelloMessageProps (i.e. has a name of type string) to be passed into the component. This is our attribute list from the example in App.tsx.

The HelloMessage class extends/inherits from Component and we tell it what data shape we expect to be passed into it, but we do not want to pass any state hence we have a second generic parameter of {}. As we’re writing this as a .tsx we’ve embedded the XHTML along with the use of the property name passed as an attribute in the App.tsx. The render method simply returns the embedded XHTML and that’s it.

We can write the same thing without using the embedded syntax, like this

import React, { Component } from 'react';

export interface HelloMessageProps {name: string;}

class HelloMessage extends Component<HelloMessageProps, {}> {
    render() {
        return React.createElement('div', null, 'Hello World ' + this.props.name);
      }
  }

export default HelloMessage;

Or better still we can use string interpolation by using the ` (back tick), i.e. replacing the Hello World string section with

`Hello World ${this.props.name}`

References

TypeScript specification
React without JSX

Creating a React application by hand

By default it’s probably better and certainly simpler to create a React application using yarn create but it’s always good to know how to create such an application from scratch yourself.

To start with, create a folder for our application and within it create a folder named src, within this create an empty file named index.tsx. Then add the following to this file

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

We don’t actually have React dependencies available to our application at this point so run the following, these will add React as well as TypeScript and TypeScript definitions of React (obviously if you don’t want to work in TypeScript, remove the –typescript switches and the last three yarn commands).

yarn add react --typescript
yarn add react-dom --typescript
yarn add react-scripts
yarn add typescript
yarn add @types/react
yarn add @types/react-dom

yarn will create the package.json file and download the required dependencies. They purpose of the dependencies are probably pretty obvious – the first includes React, the second React-dom and the third gives us the scripts we’re used to when running the code generated React applications, such as supplying the script for yarn start.

Within the package.json add the following, which will add the scripts that we’re used to having available

"scripts": {
   "start": "react-scripts start",
   "build": "react-scripts build",
   "test": "react-scripts test",
   "eject": "react-scripts eject"
}

We’re going to need to also add a folder named public which we’ll place an index.html file in which will act as our default page. Here’s a minimal version copied from a React generated application

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <meta name="theme-color" content="#000000" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>Game App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

At this point we should be able to execute

yarn start

and now we should see a React application running in the browser.

If you want to add some CSS to the application, we can simply create index.css in the src folder, create your styles then to use it in index.tsx add the following at the top of the file

import './index.css';

If you’re using VSCode, as I am, you may wish to click on the status bar where it display a version of TypeScript, when index.tsx is open, and set to the most upto date version listed.