Give Modals, Drawers, and other Overlay Elements URLs with React Router

One of my pet peeves about webapps is that actions that happen in things like a modal or other popover are often not linkable. This makes sharing those actions with others hard: send a link to the original URL along with instructions about what to do in order to trigger the overlay.

A much better approach is to provide a URL that actually renders that overlay. Take a list of something and an edit screen for each of those somethings that happens via a drawer that slides in.

Both screens are built with Ant Design components.

In order to give the edit drawer a URL, we need to render it within a route. This can be done easily by just providing a component to the route.

Note: I’m going to leave out bits of code related to the table datasources and such, you can see those on github.

function DrawerFooter({ onClose }) {
    return (
        <div style={{textAlign: 'right'}}>
            <Button onClick={onClose} type="primary">
                Save
            </Button>
        </div>
    );
}

function EditDrawer({ person, history }) {
    // make sure we don't break anything if person is `null` (no matched URL)
    if (!person) {
        person = {
            email: '',
            name: '',
        };
    }

    const onClose = () => history.push('/');

    // form here doesn't actually work, form is not the point of the tutorial.
    return (
        <Drawer visible={true} footer={<DrawerFooter onClose={onClose} />} onClose={onClose} width={512}>
            <Form layout="vertical">
                <Form.Item label="Email">
                    <Input value={person.email} />
                </Form.Item>
                <Form.Item label="Name">
                    <Input value={person.name} />
                </Form.Item>
            </Form>
        </Drawer>
    );
}

export default function App() {
    return (
        <BrowserRouter>
            <div style={{padding: '2rem'}}>
                <Table dataSource={tableDataSource} columns={tableColumns} />
                <Route path="/edit/:id" component={EditDrawer} />
            </div>
        </BrowserRouter>
    );
}

This actually works pretty well. We get a nice slide in effect when the Edit link is clicked. When the user exits the drawer, however, it just disappears without a nice transition.

Edit Screen with No Transition

A better approach is to use children function to render the drawer. When there is no match, the match param will be null and the visibility on the drawer can be set to false which will trigger the slide out transition. This is much closer to the experience that happens when simple state is used to toggle visible on an overlay.

function EditDrawer({ visible, person, onClose }) {
    if (!person) {
        person = {
            email: '',
            name: '',
        };
    }

    return (
        <Drawer visible={visible} footer={<DrawerFooter onClose={onClose} />} onClose={onClose} width={512}>
            <Form layout="vertical">
                <Form.Item label="Email">
                    <Input value={person.email} />
                </Form.Item>
                <Form.Item label="Name">
                    <Input value={person.name} />
                </Form.Item>
            </Form>
        </Drawer>
    );
}

export default function App() {
    return (
        <BrowserRouter>
            <div style={{padding: '2rem'}}>
                <Table dataSource={tableDataSource} columns={tableColumns} />
                <Route path="/edit/:id">{({ match, history }) => (
                    <EditDrawer
                        visible={match !== null}
                        person={match ? people[match.params.id] : null}
                        onClose={() => history.push('/')}
                        />
                )}</Route>
            </div>
        </BrowserRouter>
    );
}

Edit Screen With Transition

This behavior is described in the React Router docs, but I didn’t really understand it until I started doing the above to render some nice transitions for overlay elements.