Skip to content
This repository has been archived by the owner on Jun 5, 2023. It is now read-only.

Commit

Permalink
feat(Components): New BackButton component
Browse files Browse the repository at this point in the history
  • Loading branch information
diondiondion committed Feb 20, 2020
1 parent 5dfb0ec commit 57a84b5
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 0 deletions.
34 changes: 34 additions & 0 deletions src/BackButton/BackButtonProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, {createContext, memo, useEffect, useRef} from 'react';
import PropTypes from 'prop-types';

export const BackButtonContext = createContext(false);

function Provider({children, pathname}) {
const hasHistoryRef = useRef(false);

useEffect(() => {
// We're using a ref so that we don't trigger a rerender,
// as the new value should only apply on the next render,
// i.e. the next time `pathname` has changed
hasHistoryRef.current = true;
}, [pathname]);

return (
<BackButtonContext.Provider value={hasHistoryRef.current}>
{children}
</BackButtonContext.Provider>
);
}

// Memoise the component to make sure the component is only ever
// re-rendered when the `pathname` prop changes
const BackButtonProvider = memo(
Provider,
(prevProps, nextProps) => prevProps.pathname === nextProps.pathname
);

Provider.propTypes = {
pathname: PropTypes.string.isRequired,
};

export default BackButtonProvider;
63 changes: 63 additions & 0 deletions src/BackButton/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
name: BackButton
menu: Components
---

import {Playground, Props} from 'docz';
import InlineList from '../InlineList';
import BackButton, {BackButtonProvider} from './';
import Button from '../Button';
import TextLink from '../TextLink';

# BackButton

A component for handling backlinks for navigation in apps.

## The problem

Hard-coding back links in an app where pages may have different entry points can quickly lead to a bad user experience. Users expect a link or button labelled "Back" to take them back to the page they came from – just like their browser's back button.

So, one might think, why not just trigger the browser's back button using `history.back`? We need back buttons to have a sensible fallback for when there's no browser history yet, for example after a link has been opened in a new window, or when a page was reached via a link in an email.

This is where this component comes in. Wrap your app with the `BackButtonProvider` component and pass it the current pathname. In react-router, you can get it from the `useLocation` hook:

```js
import {useLocation} from 'react-router';
import {BackButtonProvider} from 'base5-ui/BackButton';

function YourApp({children}) {
const {pathname} = useLocation();

return (
<BackButtonProvider pathname={pathname}>{children}</BackButtonProvider>
);
}
```

`BackButtonProvider` tracks whether the user has navigated in the app. It then provides this information to the `BackButton` component via context. `BackButton` then either renders a `<button>` element, or the fallback link specified by you.

For visual customisation, you can pass the component to render using the `baseComponent` prop. Please make sure that this component has support for changing the underlying rendered element using the `as` prop (this is the case for components built using styled-components by default).

## Examples

<Playground>
<BackButtonProvider pathname="/current/url">
<BackButton
baseComponent={Button}
as="a"
href="/fallback-link"
icon="arrow"
>
Back button
</BackButton>
<br />
<br />
<BackButton baseComponent={TextLink} as="a" href="/fallback-link">
Backlink
</BackButton>
</BackButtonProvider>
</Playground>

## Props

<Props of={BackButton} />
51 changes: 51 additions & 0 deletions src/BackButton/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
import useBackButton from './useBackButton';
import BackButtonProvider from './BackButtonProvider';

import ButtonCore from '../ButtonCore';

function BackButton({
baseComponent: Component,
as,
onClick,
onGoBack,
...otherProps
}) {
const hasHistory = useBackButton();

function goBack(e) {
e.preventDefault;
onGoBack ? onGoBack() : history.back();
if (onClick) onClick(e);
}

if (hasHistory) {
return <Component as="button" onClick={goBack} {...otherProps} />;
}
return <Component as={as} onClick={onClick} {...otherProps} />;
}

BackButton.defaultProps = {
baseComponent: ButtonCore,
};

BackButton.propTypes = {
/**
* The base component to use, i.e. Button or TextLink.
* This component **must** support the `as` prop for
* changing the underlying rendered element
*/
baseComponent: PropTypes.elementType,
/**
* By default, `window.history.back` is triggered when
* a history is available. Use this prop to provide
* another function, e.g. react-router's `history.goBack`
*/
onGoBack: PropTypes.func,
};

export {BackButtonProvider};

// @component
export default BackButton;
8 changes: 8 additions & 0 deletions src/BackButton/useBackButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {useContext} from 'react';
import {BackButtonContext} from './BackButtonProvider';

function useBackButton() {
return useContext(BackButtonContext);
}

export default useBackButton;

0 comments on commit 57a84b5

Please sign in to comment.