やろうとしたこと
配列peopleに入ってる人たちをliで表示して、
削除ボタン押したら、フロントから消し去る
(ちなみに、サーバサイドはGraphQL。今回は特に関係ないので割愛)
peopleは、こんな感じで定義してあるよ。
const [people, setPeople] = useState<string[]>([]);
return (
<>
<ul>
{people.map(person => (
<li onClick={handleDelete}>{person}</li>
)
)}
</ul>
</>
);
かなり簡略化して書きました。まあ多少はね?
mapでpersonをリストで表示させる。
で、personをクリックするとhandleDeleteが呼ばれてその中でサーバサイドのデータが削除され
フロントからも消えるはず、はずだったw
peopleという配列はコンポーネントが描画されるときに
GraphQLのフックが起動して取得した配列を
setPeople(pickUpPeople)みたいな感じで突っ込んでいるのですが・・・
このフックは最初に描画した時、1度しか呼ばれない。
つまり、サーバサイドではデータは消えているけど
フロント側は何も変化がないということになる。
(もちろん、F5で更新すれば消えている)
失敗例
「せや、デリートボタン押したときに
配列にフィルターかけてsetPeopleしなおせばええんや!」
具体的には、
setPeople(pickedUpPeople.filter(person => person !== デリートしたpersonのid))
要するに、peopleにデリートしたidに合致しないやつだけピックアップしてセットする。
という意味。
結果
「テストデータが2つ・・・1つを削除・・・を消えた!!」
「よし、もう一つも削除・・・!?・・・さっき消したやつ戻ってきた・・・」
「連打してみよ・・・交互に消したはずのデータがひょこひょこ出てくるンゴ^q^」
なぜかというと、
フックで取ってきたpickedUpPeople配列からはデータが消えているわけではないので
クリックされたやつ以外が表示されてしまう・・・
つまり、別なのを削除するとそれは消えるけど、前のやつがリバイブする
成功例
delete押されたidをステートで管理して、そこに足していけばええんや!
なので、さっきのコードをこんな感じにする。
* ...deletedListは配列の中身をバラして並べる、という意味です
const[deletedList, setDeletedList] = useState<string[]>([])
setPeople(
pickedUpPeople.filter(
person =>
![デリートしたpersonのID, ...deletedList].includes(person.id),
),
);
setDeletedList([デリートしたpersonのID, ...deletedList]);
こうすれば、一回消したidがどんどんListに追加されていくので
フックで取得した全体と消し去ったListのXORを取ることで
消されていない奴らだけ表示できた!
もっとお利口な方法があるんだろうけど、
元のコードを崩さずにやろうとしたらこうなりました(小並感)
まとめ
・Reactでliを扱う時は、filterをうまく使う
・弾くべきデータをリストとして、別のステートに保持しておくと便利