なぽろぐ

気ままに感じたことを記事にまとめます。

react-transition-groupでリストを表示するときはkeyにindexを設定するのはやめよう

リストで表示したものにトランジションを適用したい

こんなやつをやりたかった

Image from Gyazo

なんかこうなる

Image from Gyazo

🤔

コードを比較

styles.tsについては一番下に書いておきます.

変な挙動をするコード

import React, { Fragment, useState, useCallback, useMemo } from "react";
import { Container, AnimationBox, FormContainer, Input, Submit } from "./styled";

export const ListTransition = () => {
  const [state, setState] = useState<string[]>(["hoge", "huga"]);
  const [value, setValue] = useState<string>("");
  const onAdd = useCallback(() => {
    if (value.length <= 0) return;

    setState(ls => [...ls, value]);
    setValue("");
  }, [value]);

  const onRemove = useCallback((s: string) => setState(l => l.filter(el => el !== s)), []);
  const disabled = useMemo(() => value.length <= 0, [value]);
  return (
    <Fragment>
      <Container>
        {state.map((s, idx) => (
          <AnimationBox key={idx} unmountOnExit timeout={800} onClick={() => onRemove(s)}>
            {s}
          </AnimationBox>
        ))}
      </Container>
      <FormContainer>
        <Input value={value} onChange={e => setValue(e.target.value)} />
        <Submit onClick={onAdd} disabled={disabled}>
          Add Item
        </Submit>
      </FormContainer>
    </Fragment>
  );
};

想定している動きをするコード

import React, { Fragment, useState, useCallback, useMemo } from "react";
import { Container, AnimationBox, FormContainer, Input, Submit } from "./styled";

export const ListTransition = () => {
  const [state, setState] = useState<string[]>(["hoge", "huga"]);
  const [value, setValue] = useState<string>("");
  const onAdd = useCallback(() => {
    if (value.length <= 0) return;

    setState(ls => [...ls, value]);
    setValue("");
  }, [value]);

  const onRemove = useCallback((s: string) => setState(l => l.filter(el => el !== s)), []);
  const disabled = useMemo(() => value.length <= 0, [value]);
  return (
    <Fragment>
      <Container>
        {state.map(s => (
          <AnimationBox key={s} unmountOnExit timeout={800} onClick={() => onRemove(s)}>
            {s}
          </AnimationBox>
        ))}
      </Container>
      <FormContainer>
        <Input value={value} onChange={e => setValue(e.target.value)} />
        <Submit onClick={onAdd} disabled={disabled}>
          Add Item
        </Submit>
      </FormContainer>
    </Fragment>
  );
};

原因

Image from Gyazo

keyをindexにしていたからでした.keyが変わるので最後のコンポーネントが消えたと認識されてしまってトランジションが最後のコンポーネントにしか効かなかったというオチでした.

styles.ts

import styled from "styled-components";
import { TransitionGroup } from "react-transition-group";
import transition from "styled-transition-group";

export const Container = styled(TransitionGroup)`
  position: absolute;
  top: 0px;

  display: flex;
  width: 300px;
  overflow-x: scroll;
  margin: 12px;
`;

export const AnimationBox = transition.p`
  padding: 8px 16px;
  font-size: 13px;
  border: solid 1px #c1c1c1;
  margin: 0 8px;
  border-radius: 3px;
  background-color: #c1c1c1;
  color: white;
  cursor: pointer;

  &:enter {
    opacity: 0;
  }
  &:enter-active {
    opacity: 1;
    transition: all 0.8s ease-out;
  }
  &:exit {
    opacity: 1;
  }
  &:exit-active {
    opacity: 0;
    transition: all 0.8s ease-out;
  }
`;

export const FormContainer = styled.div`
  display: flex;
  margin-top: 56px;
`;
export const Input = styled.input``;
export const Submit = styled.button``;