I'm trying to write a React component which contains a panel of buttons, and each of the buttons needs to be able to be independently switched in and out depending on the state of the app. To do this I've created a useTransition
, wrapped in a custom hook, which is supposed to trigger the transition when it recieves a new SVG functional component.
The issue I'm having is that while the buttons are currently transitioning in correctly, I can't seem to get the useTransition
to swap them out when it receives a new component. It will remove one when the component is replaced with an empty array, which is intended, and a useEffect
inside the hook triggers when a new component is passed in, but for some reason that change is not picked up by the useTransition
.
I'm guessing it has something to do with object equality between the components but I don't know how to work around it without forcing the change with window.requestAnimationFrame
to set the state twice. Here is a codesandbox with a minimal demo.
Parent Component:
import React, { useState } from "react";
import { a } from "react-spring";
import "./styles.css";
import PlaySVG from "./svgs/PlaySVG";
import SaveSVG from "./svgs/SaveSVG";
import SearchSVG from "./svgs/SearchSVG";
import TrashSVG from "./svgs/TrashSVG";
import { useVertTransition } from "./Hooks";
export default function ActionButtons() {
const svgs = {
play: <PlaySVG style={{ width: "100%", height: "auto" }} id="play" />,
search: <SearchSVG style={{ width: "80%", height: "auto" }} id="search" />,
save: <SaveSVG style={{ width: "80%", height: "auto" }} id="save" />,
trash: <TrashSVG style={{ width: "80%", height: "auto" }} id="trash" />
};
const [actions, setActions] = useState({
one: svgs.play,
two: svgs.search,
three: svgs.trash
});
const [slotOne] = useVertTransition(actions.one);
const [slotTwo] = useVertTransition(actions.two);
const [slotThree] = useVertTransition(actions.three);
function buttonHandler() {
setActions({
...actions,
one: [],
two: svgs.save
});
}
return (
<div className="container">
<div className="panel">
<div className="button-container">
{slotOne.map(({ item, props, key }) => (
<a.button key={key} style={props} onClick={buttonHandler}>
{item}
</a.button>
))}
</div>
<div className="button-container">
{slotTwo.map(({ item, props, key }) => (
<a.button key={key} style={props} onClick={buttonHandler}>
{item}
</a.button>
))}
</div>
<div className="button-container">
{slotThree.map(({ item, props, key }) => (
<a.button key={key} style={props} onClick={buttonHandler}>
{item}
</a.button>
))}
</div>
</div>
</div>
);
}
useVertTransition hook:
import React, { useEffect } from "react";
import { useTransition } from "react-spring";
export function useVertTransition(item) {
useEffect(() => {
console.log(item);
}, [item]);
const vertTransition = useTransition(item, null, {
from: { transform: "translateY(-20px) rotateX(-90deg)", opacity: 0 },
enter: { transform: "translateY(0px) rotateX(0deg)", opacity: 1 },
leave: { transform: "translateY(20px) rotateX(90deg)", opacity: 0 },
trail: 400,
order: ["leave", "enter"]
});
return [vertTransition];
}
SVG component example:
import React from "react";
function PlaySVG({ style }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" style={style} viewBox="0 0 24 24">
<path d="M7 6L7 18 17 12z" />
</svg>
);
}
export default PlaySVG;
You are right, that react-spring is not able to detect the difference between buttons. But it does not examine the items, it examine only the keys. So you have to add something for unique keys. So you may want to changed the input from svg to an object containing a name beside the svg.
const getSvg = name => ({ name, svg: svgs[name] });
const [actions, setActions] = useState({
one: getSvg("play"),
two: getSvg("search"),
three: getSvg("trash")
});
You can now add the key function:
const vertTransition = useTransition(item, svg => svg.name, {
And you should change the render:
{slotOne.map(({ item, props, key }) => (
<a.button key={key} style={props} onClick={buttonHandler}>
{item.svg}
</a.button>
))}
And now it works as intended. I think.
https://codesandbox.io/s/usetransition-demo-152zb?file=/src/ActionButtons.js:1253-1437
And I also learned a new trick, I never used the order property.