コンポーネントをマウスでスライド可能にしようとしています。しかし、マウスを離すと、実行中にDOM要素の1つの参照がnullになり、その理由がわかりません。
問題を説明するためにサンドボックスを作成しました。(これは完全な実装ではありません。問題を実験するために必要なものだけです)。
要素をドラッグしてコンソールを確認するだけです。ref が最初は正しく設定されていることがわかりますが、マウスを放すと、ref が消えます。
これがいつ、なぜ起こっているのかわかりません。
完全な App.js ファイルは次のとおりです。
import React from "react";
import styled from "styled-components";
const Container = styled.div`
width: 100%;
overflow: hidden;
`;
const Slides = styled.div`
position: relative;
cursor: pointer;
user-select: none;
white-space: nowrap;
`;
const Slide = styled.div`
width: ${props => props.size}%;
cursor: pointer;
height: 100%;
display: inline-block;
`;
const Carrousel = ({
children,
columnCount = 1,
onSlideChanged = () => {},
active = 0
}) => {
const [dragging, setDragging] = React.useState(false);
const initialMouseX = React.useRef(0); // Starting drag x coordinates
const initialLeft = React.useRef(0); // Left value when the drag started
const currentLeft = React.useRef(-active * (100 / children.length)); // Current left value
const sliderRef = React.createRef();
const slidesRef = React.createRef();
// This will directly manipulate the DOM for better performances
const setLeft = React.useCallback(
newLeft => {
currentLeft.current = newLeft;
if (!slidesRef.current) return;
slidesRef.current.style.left = currentLeft.current + "%";
},
[currentLeft, slidesRef]
);
// Starts the drag
const startDrag = ({ screenX }) => {
setDragging(true);
initialMouseX.current = screenX;
initialLeft.current = currentLeft.current;
};
// We need the references to be updated before being able to listen to move and mouseup events
React.useLayoutEffect(() => {
if (!dragging) return;
console.log("at last render, slidesRef was:", slidesRef.current);
const move = event => {
if (dragging && sliderRef.current) {
const width = sliderRef.current.getBoundingClientRect().width;
const newLeft =
initialLeft.current +
((event.screenX - initialMouseX.current) * 100) / width;
setLeft(newLeft);
event.preventDefault();
}
};
const stopDrag = () => {
setDragging(false);
console.log("when calling stopDrag, slidesRef was:", slidesRef.current);
};
document.addEventListener("mouseup", stopDrag);
document.addEventListener("mousemove", move);
return () => {
document.removeEventListener("mousemove", move);
document.removeEventListener("mouseup", stopDrag);
};
}, [initialLeft, dragging, sliderRef, setLeft, slidesRef]);
return (
<Container ref={sliderRef}>
<Slides ref={slidesRef} onMouseDown={startDrag}>
{children.map((child, index) => (
<Slide key={index} size={100 / columnCount}>
{child}
</Slide>
))}
</Slides>
</Container>
);
};
const App = () => (
<Carrousel columnCount={2}>
{new Array(4).fill("").map((_, i) => (
<div key={i}>
<span>Test{i + 1}</span>
</div>
))}
</Carrousel>
);
export default App;
問題はこの部分にあります:
React.useLayoutEffect(() => {
if (!dragging) return;
console.log("at last render, slidesRef was:", slidesRef.current);
const move = event => {
if (dragging && sliderRef.current) {
const width = sliderRef.current.getBoundingClientRect().width;
const newLeft =
initialLeft.current +
((event.screenX - initialMouseX.current) * 100) / width;
setLeft(newLeft);
event.preventDefault();
}
};
const stopDrag = () => {
setDragging(false);
console.log("when calling stopDrag, slidesRef was:", slidesRef.current);
};
document.addEventListener("mouseup", stopDrag);
document.addEventListener("mousemove", move);
return () => {
document.removeEventListener("mousemove", move);
document.removeEventListener("mouseup", stopDrag);
};
}, [initialLeft, dragging, sliderRef, setLeft, slidesRef]);
ドラッグすると: を取得at last render, slidesRef was: <div ...>
し、リリースすると:when calling stopDrag, slidesRef was: null