reactQueryで取得したデータをタグでフィルタリングする

react Queryを少し学んでみてます。
非同期関数を渡すだけで値をシンプルに取得できて楽ですが、
その値をさらに画面の中でフィルタリングするコードを書いてみました。


## やろうとしたこと 
1.React Queryでtopicsのデータを取得。
topicsは配列で、下記の構造。
```
title:string
body:string
tags:string[]//「アクション」「RPG」などのタグを複数持つことができる
```

2.取得後、サイトに表示
3.画面上のソートをボタンクリック時にソートする

という実装をReact Queryで行いました。
コードは以下のものです。


```tsx

import { useQuery } from 'react-query';

const Topics = () => {
    //まずはuseQueryでデータを取得。
    //下記の記述でtopicsにセットされる
    const { isLoading, error, data:topics = [] } = useQuery(
        'topics',
        () => GetTopics()
    );
    
    const [filtedTopics,setTopics] = useState();
    
    //初回セットし、監視
    useEffect(() => {
    	setTopics(topics); 
    }, [topics]);
    
    //フィルタリングする関数
    //各トピックに複数のタグがあるので、トピックごとにtagsをforeachする
    const handleTagFilter = (orgs:string) =>{
    	if(orgs === 'すべて'){//すべてを選んだ場合はデフォルトのtopicsをセット
    		setTopics(topics);
        }else{
    		let temp = [];//フィルタリングしたトピックを詰め込む配列
               topics.forEach( _topic => {
                    _topic.tags.forEach(each_tags =>{
                        if(Object.values(each_tags).includes(orgs)){
                            temp.push(_topic)
                        }
                    })
                })
                setTopics(temp);
    		}
    	}
    
    return (
    	<>
            <ul>
                {filtedTopics.map((topic)=>(
            		<li key={topic.id}>
            			{topic.id}/{topic.title}
            				{topic.tags && 
            					<div className="tags_wrap">
            						{topic.tags.map((tag) => (
            							<span>{tag.name}</span>
            						))}
            					</div>
                			}
            		</li>
            	))}  
            </ul>  
    	</>
    )
}

```




## 少しハマった点

この状態だと、useeffectの中でtopicsを監視しているので、

1.初回、クエリでtopicsを取得→
2.useeffectの中でsetTopicsし、監視対象が変更→
3.なのでもう一度レンダーされ、1から再度開始

という無限ループが開始されます。
画面上、topicsのコンテンツは表示されますが、consoleに注意喚起的エラーが表示されます。
```
Warning: Maximum update depth exceeded. 
This can happen when a component calls setState inside useEffect, 
but useEffect either doesn't have a dependency array, 
or one of the dependencies changes on every render.
```

対策として、useEffectの中で第二引数を空にし、初回だけ値を設定する方法にしても良いですが、
下記のようにuseStateで初期値を設定してあげるとuseEffect自体不要になるので良いと思います。

```diff_tsx

//ここで、初期値を「topics」にしておくことで、
//初回取得時、フィルタリング後ともにfiltedTopicsを使うことができる
- const [filtedTopics,setTopics] = useState([]);
+ const [filtedTopics,setTopics] = useState([topics]);


- useEffect(() => {
-   setTopics(topics); 
-}, [topics]);
+ // useEffect(() => {
+    // 	setTopics(topics); 
+// }, [topics]);

```

参考：

>🤔 非同期処理の無限ループがたまに起こるのはなぜ？

https://overreacted.io/ja/a-complete-guide-to-useeffect/

(useeffect難しいのでいつかきちんと読み込みたい。。)



## まとめ
初期のtopicsを取得後、フィルタリングされたtopicsは別変数にするのか？
(レンダリングのjsxのところで別変数があればそれを出す)みたいなことを最初は考えていたのですが、
useStateだけでうまく行えたので良かったです。
その過程でよくある無限ループにもハマって、良い勉強になりました！


## 追記（@honey32 様よりいただいたコメントを元に改修）
宣言型、というのかこういう書き方がなかなか浮かばないのですごく参考になりました！
ありがとうございました！

```ts

import { useQuery } from 'react-query';
import { useMemo} from "react";

const Topics = () => {
    const { isLoading, error, data:topics = [] } = useQuery(
        'topics',
        () => GetTopics()
    );
    
//const [filtedTopics,setTopics] = useState(); //不要
    
//不要
//初回セットし、監視
//useEffect(() => {
//	setTopics(topics); 
//}, [topics]);
    
//不要
// const handleTagFilter = (orgs:string) =>{
// 	if(orgs === 'すべて'){//すべてを選んだ場合はデフォルトのtopicsをセット
// 		setTopics(topics);
// 	}else{
// 		let temp = [];//フィルタリングしたトピックを詰め込む配列
// 		   topics.forEach( _topic => {
// 				_topic.tags.forEach(each_tags =>{
// 					if(Object.values(each_tags).includes(orgs)){
// 						temp.push(_topic)
// 					}
// 				})
// 			})
// 			setTopics(temp);
// 		}
// 	}

    //useMemoを利用し、変化がある時のみ描写
	const filtedTopics = useMemo(() => {
		return topics.filter((topic) => {

            //すべてを選んだ時用
			if (selectedTag === 'すべて') return true;
            //初回レンダリング用
			if (selectedTag === undefined) return true;
            //フィルタリング用。記載不足でしたがtagsの各種値はオブジェクトだったので、調整
			return topic.tags.some(_tag => _tag.name === selectedTag)
		});
	},[selectedTag,topics])
    
    return (
    	<>
            <ul>
                {filtedTopics.map((topic)=>(
            		<li key={topic.id}>
            			{topic.id}/{topic.title}
            				{topic.tags && 
            					<div className="tags_wrap">
            						{topic.tags.map((tag) => (
            							<span>{tag.name}</span>
            						))}
            					</div>
                			}
            		</li>
            	))}  
            </ul>  
    	</>
    )
}



```