Reactでページ内遷移のある横スクロールスライドメニューを実装する

#はじめに
スマホ向けのWebアプリケーションをReactで開発していて、横スクロール型のスライドメニュー(ナビゲーション)を作りたい！という時にご参考ください。 React×Railsで開発しています。

HTMLのアンカータグで#を使って特定のid属性に遷移させたい！それをReactでやりたい！みたいな時を想定。


#やりたいこと

1. アプリライクな横スクロール型のメニューを作りたい(スクロールバー非表示にして)
2. メニューの各要素をクリックしたらそれぞれの要素までぬるっとアニメーション付きでページ内遷移させたい
3. ヘッダーの高さを考慮してページ内遷移させたい
3. mapではき出した動的な要素にid属性を振って遷移先にしたい


#方針

Reactでページ内遷移するいい方法ないかなーと探していたところ、こちらの記事([Reactでページ内リンクを実装する](https://qiita.com/kume_krowl/items/607516e8878ac0653425))を発見。今回はこちらを参考に```react-router-hash-link```を使うことに。

Githubはこちら：[React Router Hash Link](https://github.com/rafgraph/react-router-hash-link)


前提として本実装では、```itemCategory```の```category_name```がカテゴリ名で、それをメニューバーにし、同一ページ内のそれぞれの```category_name```の部分に遷移させています。

#実装

###1. yarn add かnpmでインストール


```Terminal:Terminal
npm install --save react-router-hash-link
```

###2.```react-router-hash-link```と```MemoryRouter```(後述するエラーが出たため)をimport


```React:hoge.tsx
import {MemoryRouter} from 'react-router-dom';
import {HashLink} from 'react-router-hash-link';
```

###3.遷移元となる横スクロールのメニューを作成

###### ```smooth```をつけるだけでぬるっといい感じの遷移に
HashLinkの機能。とても便利。

##### ```href=#hoge```になるようにid設定
JSX記法で```to={'#' + itemCategory.category_name}```とした。これで#hogeとなり、idに遷移できる。



#####謎のエラーを```MemoryRouter```で解決

```HashLink```を使ったところ、```Uncaught Error: Invariant failed: You should not use <Link> outside a <Router>```のエラーが表示されたため、こちらの記事([解決方法: "Error: Invariant failed: You should not use <Link> outside a <Router>"](https://qiita.com/top4hac/items/7f45e9abed63fecc0462))を参考に、```MemoryRouter```で```HashLink```を囲う。

#####ヘッダーの高さ調整
```scroll={el => { el.scrollIntoView(true); window.scrollBy(0, -160) }}```でヘッダー部分の高さを考慮。この場合は160px分下げている。

```React:hoge.tsx
<div className={classes.scrollMenuList}>
  <div className={classes.scrollMenuListMask}>
    {props.shopItemCategories.map((itemCategory) => (
      <Typography component="h2" variant="h3" >
    <MemoryRouter>
      <HashLink 
        smooth to={'#' + itemCategory.category_name} 
        scroll={el => { el.scrollIntoView(true); window.scrollBy(0, -160) }}
        className={classes.scrollMenu}>{itemCategory.category_name}
      </HashLink>
    </MemoryRouter>
  </Typography>
  ))}
 </div>
</div>
```

###4.スクロールバーを非表示に

こちらの記事([Appleに学ぶ、横スクロールナビを組む時のCSSメモ](https://qiita.com/skwbr/items/412a2e48d813f2f52537))を参考に、Maskとなる```scrollMenuListMask```を用意し、親要素の```scrollMenuList```を```overflow:"hidden"```としラッパーする。各要素はアンダーバーがダサいので、```textDecoration:"none"```に。

```React:hoge.tsx
   scrollMenuList: {
      width: '900px',
      overflow: "hidden",
      height: 60,
    },
    scrollMenuListMask: {
      overflowX: "auto",
      whiteSpace: "nowrap",
      display: 'flex',
      height: 80, //Maskのheightを親要素より高くすることがミソ
    },
    scrollMenu: {
      margin: theme.spacing(1, 2, 1),
      fontSize: 36,
      color: "#8f8f8f",
      textDecoration: "none"
    },
```

###5.遷移先にid属性を振る
id属性を振るだけなので簡単。ここで振ったidを遷移元となるHashLinkで指定することでページ内遷移できる。


```React:hoge.tsx
{props.shopItemCategories.map((itemCategory) => (
 <div className={classes.root}>
   <Typography component="h2" variant="h3" id={itemCategory.category_name}>
    {itemCategory.category_name}
   </Typography>
 </div>
...省略
))}
```


#完成

完成したものが以下の通り。

```React:hoge.tsx
//Import
import {MemoryRouter} from 'react-router-dom';
import {HashLink} from 'react-router-hash-link';

//該当部分のみのCSS
const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    scrollMenuList: {
      width: '900px',
      overflow: "hidden",
      height: 60,
    },
    scrollMenuListMask: {
      overflowX: "auto",
      whiteSpace: "nowrap",
      display: 'flex',
      height: 80,
    },
    scrollMenu: {
      margin: theme.spacing(1, 2, 1),
      fontSize: 36,
      color: "#8f8f8f",
      textDecoration: "none"
    },
  })
)

//スクロールスライドメニューとなる部分
<div className={classes.scrollMenuList}>
  <div className={classes.scrollMenuListMask}>
    {props.shopItemCategories.map((itemCategory) => (
      <Typography component="h2" variant="h3" >
    <MemoryRouter>
      <HashLink 
        smooth to={'#' + itemCategory.category_name} 
        scroll={el => { el.scrollIntoView(true); window.scrollBy(0, -160) }}
        className={classes.scrollMenu}>{itemCategory.category_name}
      </HashLink>
    </MemoryRouter>
  </Typography>
  ))}
 </div>
</div>

//メニューからの遷移先になる要素たち
{props.shopItemCategories.map((itemCategory) => (
<>
 <Box>
   <div className={classes.root}>
  　　 <Typography component="h2" variant="h3" id={itemCategory.category_name}>
  　　 {itemCategory.category_name}
 　 </Typography>
　 </div>
 </Box>

...省略
</>
))}
```

今回も色々な記事を参考にさせていただきました。横スクロールのスライダーみたいなのは結構使いたい場面多いと思うので、少しでも参考になれば嬉しいです。


#参考

[横スクロールナビゲーションを実装する3つの方法](https://qiita.com/sottar/items/f6ee7426d05b47fbf7fb)
[Reactでページ内リンクを実装する](https://qiita.com/kume_krowl/items/607516e8878ac0653425)
[【React Router】画面遷移時に#を用いて特定の要素に移動させる方法](https://qiita.com/gcyagyu/items/3fef2034edbc5834021d)
[Reactで動的に属性の値を生成する方法](http://yugr.hatenablog.com/entry/2017/12/07/195515)