当サイトは、アフィリエイト広告を利用しています

【React × TypeScript】タグ絞り込み機能を実装する

作成日:2022月04月09日
更新日:2024年01月28日

everyメソッドを使ってReact(typescript)のタグ絞り込み機能を
サンプル実装してみる

everyメソッドについて

everyは配列の各要素に対して、関数を実行し、 boolean値を返却する。

  • 配列の各要素に対する関数の結果が全てtrueの場合→true
  • 配列の各要素に対する関数の結果が一つでもfalseの場合→false
everySample.ts
// 配列を定義
const array:Array<number> = Array.of(1,2,3,4,5)
// everyの関数の結果が全てtrueの場合にtrueが返却される
const result1:boolean = array.every(val=>val>0)
console.log(result1)
// everyの関数の結果が一つでもfalseの場合にfalseが返却される
const result2:boolean = array.every(val=>val>2)
console.log(result2)
// 実行結果
// true
// false

everyを使って絞り込み部分を書いてみる

サンプルとしてタグの配列を持つ記事オブジェクトリストから
選択したタグを全て含む記事を取得する実装をしてみる

every応用sample
// 選択済タグ
const selectedTag = Array.of("javascript", "react");
// 記事一覧
const articleList = Array.of(
{ title: "記事1", tags: ["java"], cotent: "aaaaaaaaaa" },
{
title: "記事2",
tags: ["react", "javascript"],
cotent: "bbb"
},
{ title: "記事3", tags: ["emotion"], cotent: "cccc" },
{ title: "記事4", tags: ["javascript"], cotent: "ddddddddddddd" },
{ title: "記事5", tags: ["css"], cotent: "eeeeeeeeeee" },
{ title: "記事6", tags: ["gatsby"], cotent: "f" }
);
// 選択済タグを全て含む記事を抽出
const filterdList = articleList.filter((article) => {
// 選択済タグをeveryでループ
// everyに渡した関数が全てtrueの場合にtrueが返される
return selectedTag.every((tag) => {
// findで記事のタグをループして探す
// 一致する場合はその値が返却される
const findResult = article.tags.find((articleTag) => articleTag === tag);
// 選択済タグが記事のタグになかった場合はfalseを返す
if (findResult) {
return true;
} else {
return false;
}
});
});
console.log(JSON.stringify(filterdList, null, 3));
// 実行結果
// 0: {
// title: "記事2"
// tags: {
// 0: "react"
// 1: "javascript"
// }
// cotent: "bbb"
// }

filter,every,findを使用して実現している

動作確認

TypeScript every

Reactでタグ絞り込み機能を実装してみる

上記のタグで記事を絞り込む機能を
reactで実装してみる

App.tsx
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { useEffect, useState } from "react";
import { tagsGrid, articleGrid } from "./emotionCss";
type article = {
title: string;
tags: string[];
cotent: string;
};
export default function App() {
// タグ初期値
const array: Array<string> = Array.of(
"java",
"javascript",
"gatsby",
"react",
"css",
"emotion"
);
// 記事初期値
const list: Array<article> = Array.of(
{ title: "記事1", tags: ["java"], cotent: "aaaa" },
{ title: "記事2", tags: ["react", "javascript"], cotent: "bbbbbb" },
{ title: "記事3", tags: ["emotion"], cotent: "cccc" },
{ title: "記事4", tags: ["javascript"], cotent: "ddd" },
{ title: "記事5", tags: ["css"], cotent: "ee" },
{ title: "記事6", tags: ["gatsby"], cotent: "f" }
);
// 選択中タグのstate
const [selectedTag, selectTag] = useState<Array<string>>([]);
// タグ検索結果のstate
// 初期値は全記事
const [searchResult, doSearch] = useState<Array<article>>(list);
//選択されたタグで記事を抽出する
// useStateと同じイベントで書くと同じイベント内ではstateが更新されないためうまく行かない
// そのため抽出処理はuseEffectで書く
useEffect(() => {
// 検索結果記事を抽出する
doSearch((prevList) => {
// タグに一致する記事をfilterで抽出
let newList = prevList.filter((obj) => {
// everyを使って選択中のタグを全て持っている
// 記事を抽出
// ※everyは実行する関数の結果が全てtrueの場合のみtrueが返される
let result = selectedTag.every((tag) => {
// 選択中タグが記事のタグ内にあるかfindで探す
// findの結果はあればその値が返却される
const findResult = obj.tags.find((kijiTag) => kijiTag === tag);
// find結果が存在する場合はtrueで返却
// ここが全てtrueの時にresultがtrueになる
if (findResult) {
return true;
} else {
return false;
}
});
// 選択中タグを全て含む記事をtrueとして返却
return result;
});
return newList;
});
console.log("start");
// クリーンアップ処理
return () => {
// 検索結果を初期状態に戻す
doSearch(() => list);
console.log("clean");
};
// タグが選択された時に実行する
}, [selectedTag]);
// タグ選択処理
const push = (e: any) => {
e.preventDefault();
// タグが既に選択済かチェックする
const check = selectedTag.find((tag) => {
return tag === e.target.id;
});
if (!check) {
//未選択の場合は追加
selectTag((selected) => [...selected, e.target.id]);
} else {
// 選択済の場合は削除
// filterで選択したタグ以外のlistを作り直す
selectTag((selected) => selected.filter((tag) => tag !== e.target.id));
}
};
// タグ選択時のスタイル変更
const grigItem = (selectTag: string[], select: string) => {
// 選択したタグが既に選択済かチェック
const count = selectTag.filter((selected) => select === selected);
// 選択してない場合はスタイル追加
if (count.length !== 0) {
return [
css`
background-color: red;
`
];
}
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>タグ</h2>
<div css={[tagsGrid]}>
{array.map((val, i) => (
<button
key={i}
id={val}
css={[grigItem(selectedTag, val)]}
onClick={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) =>
push(e)
}
>
{val}
</button>
))}
</div>
<h2>記事</h2>
<div css={[articleGrid]}>
{searchResult.map((kiji, i) => (
<div key={i}>
<h2>{kiji.title}</h2>
<div
css={[
css`
height: 50px;
`
]}
>
{kiji.tags.map((tag) => (
<div>{tag}</div>
))}
</div>
<h3>{kiji.cotent}</h3>
</div>
))}
</div>
</div>
);
}
  • タグの選択処理はuseStateのsetに設定した関数を使って行う(selectTag)
  • 選択したタグにより記事の絞り込みはuseEffectで行う。
  • 上記二つを同じイベント内ではできないためuseStateとuseEffectにわける。
    ※useStateやuseEffectの値は同一のレンダー内では同じ値を保持し続けるため

スタイリングはEmotionでする

emotonCss.tsx
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
export const tagsGrid = () => [
css`
display: grid;
grid-template-columns: repeat(3, minmax(100px, 10%));
grid-auto-rows: 50px;
justify-content: center;
align-items: center;
gap: 10px;
border: 1px solid black;
margin: auto;
`
];
export const articleGrid = () => [
css`
display: grid;
grid-template-columns: repeat(4, minmax(100px, 10%));
justify-content: center;
align-items: start;
gap: 20px;
border: 1px solid black;
margin: auto;
`
];

動作確認

React + TypeScript tagfilter

まとめ

大分複雑になってしまったが実装することができた。
要点としてはReactでは同一レンダー内ではuseStateは更新されないので
タグの選択はuseStateのset関数で実行し、絞り込みはuseEffectで実行すること

新着記事

タグ別一覧
top