Reputation: 174
I have a list of restaurants. I want to click on the name of a restaurant to go to its page. I use "react-router-dom": "^6.4.4"
In the file RestaurantItem.jsx I did the following:
const navigate = useNavigate()
...
<div onClick={() => navigate('/restaurant/' + restaurant.id)} className={cl.name}>
{restaurant.name}
</div>
And I wrote this routing:
<BrowserRouter>
<Routes>
<Route path="/" element={<Header/>}>
<Route index element={<Main/>}/>
<Route path="/restaurant/:id" element={<Restaurant/>}/>
<Route
path="*"
element={<Navigate to="/" replace />}
/>
</Route>
</Routes>
</BrowserRouter>);
Now when you click on the name of the restaurant, the address in the address bar of the browser changes as it should. But I see a white screen in front of me. If I refresh the page, I see the restaurant page. There is no such problem when switching back to the main one.
Here is what I see in the console:
While the React logs are extremely uninformative to me, all I understand is that some homepage components get null in their props and therefore React can't render the homepage. But at the same time, I already want to go to another page and I don’t need the main one now.
Here are the components that are mentioned in the logs.
usePressingButton:
export const usePressingButton = (buttonRef) => {
const [isPressing, setIsPressing] = useState(false)
useEffect(() => {
buttonRef.current.addEventListener('mousedown', (event) => setIsPressing(true))
buttonRef.current.addEventListener('mouseup', (event) => setIsPressing(false))
return () => {
buttonRef.current.removeEventListener('mousedown', (event) => setIsPressing(true))
buttonRef.current.removeEventListener('mouseup', (event) => setIsPressing(false))
}
}, [])
return isPressing
};
SelectCuisineLine:
const SelectCuisineLine = ({cuisines, setCuisines, selectСuisine}) => {
const lineRef = useRef()
const innerLineVisorRef = useRef()
const rightArrow = useRef()
const leftArrow = useRef()
const leftRedSignal = useRef()
const rightRedSignal = useRef()
const [arrowsPressed, setArrowsPressed] = useState({left: false, right: false})
const [linePosition, setLinePosition] = useState(0)
const [limitPositions, setLimitPositions] = useState({left: true, right: false})
const [lineLength, setLineLength] = useState(0)
const [step, setStep] = useState(0)
const [currentCuisine, setCurrentCuisine] = useState('ALL')
useEffect(() => {
setLineLength(cuisines.length * 130)
setStep(Math.floor(getLineVisorWidth() / 130) * 130)
}, [])
useEffect(() => {
rightArrow.current.addEventListener('mousedown', (event) => setArrowsPressed({
...arrowsPressed,
right: true
}));
rightArrow.current.addEventListener('mouseup', (event) => setArrowsPressed({
...arrowsPressed,
right: false
}));
leftArrow.current.addEventListener('mousedown', (event) => setArrowsPressed({
...arrowsPressed,
left: true
}));
leftArrow.current.addEventListener('mouseup', (event) => setArrowsPressed({
...arrowsPressed,
left: false
}));
return () => {
rightArrow.current.removeEventListener('mousedown', (event) => setArrowsPressed({
...arrowsPressed,
right: true
}));
rightArrow.current.removeEventListener('mouseup', (event) => setArrowsPressed({
...arrowsPressed,
right: false
}));
leftArrow.current.removeEventListener('mousedown', (event) => setArrowsPressed({
...arrowsPressed,
left: true
}));
leftArrow.current.removeEventListener('mouseup', (event) => setArrowsPressed({
...arrowsPressed,
left: false
}));
}
}, [])
useEffect(() => {
if (arrowsPressed.right && limitPositions.right) {
lineRef.current.setAttribute('style', 'left:' + (linePosition - 10) + 'px')
rightRedSignal.current.setAttribute('style', 'box-shadow: 0 0 8px 2px red;')
} else if (!arrowsPressed.right) {
rightRedSignal.current.setAttribute('style', 'box-shadow: none;')
}
if (arrowsPressed.left && limitPositions.left) {
lineRef.current.setAttribute('style', 'left:' + (linePosition + 10) + 'px')
leftRedSignal.current.setAttribute('style', 'box-shadow: 0 0 8px 2px red;')
} else if (!arrowsPressed.left) {
leftRedSignal.current.setAttribute('style', 'box-shadow: none;')
}
}, [arrowsPressed])
function getLineVisorWidth() {
return parseInt(window.getComputedStyle(innerLineVisorRef.current).width.replace('px', ''), 10)
}
function leftMotion() {
setLimitPositions({...limitPositions, right: false})
if (linePosition + step > 0) {
lineRef.current.setAttribute('style', 'left: 0')
setLinePosition(0)
setLimitPositions({...limitPositions, left: true})
return
}
lineRef.current.setAttribute('style', 'left:' + (linePosition + step) + 'px')
setLinePosition(linePosition + step)
if (limitPositions.left && arrowsPressed.left) {
lineRef.current.setAttribute('style', 'left:' + (linePosition + 10) + 'px')
}
}
function rightMotion() {
setLimitPositions({...limitPositions, left: false})
lineRef.current.setAttribute('style', 'left:' + (linePosition - step) + 'px')
setLinePosition(linePosition - step)
const lineVisorWidth = getLineVisorWidth()
if (lineVisorWidth > (lineLength - (Math.abs(linePosition))) - lineVisorWidth) {
const limitRightPosition = lineLength - lineVisorWidth
lineRef.current.setAttribute('style', 'left:' + (-limitRightPosition) + 'px')
setLinePosition(-limitRightPosition)
setLimitPositions({...limitPositions, right: true})
}
}
return (
<div className="externalLineVisor">
<div style={{margin: "3px", float: "left"}}>
<Arrow
direction='left'
onClick={leftMotion}
ref={leftArrow}
/>
</div>
<div className="innerLineVisor" ref={innerLineVisorRef}>
<div className="cuisineLine" ref={lineRef}>
{cuisines.map(cuisine =>
<SelectCuisineButton
key={cuisine.id}
selectСuisine={selectСuisine}
currentCuisine={currentCuisine}
setCurrentCuisine={setCurrentCuisine}
>
{cuisine}
</SelectCuisineButton>
)}
</div>
<div className={limitPositions.left ? "leftBlur off" : "leftBlur"}/>
<div className={limitPositions.right ? "rightBlur off" : "rightBlur"}/>
<div className="leftRedSignal" ref={leftRedSignal}/>
<div className="rightRedSignal" ref={rightRedSignal}/>
</div>
<div style={{margin: "3px", float: "right"}}>
<Arrow
direction='right'
onClick={rightMotion}
ref={rightArrow}
/>
</div>
</div>
);
};
export default SelectCuisineLine;
SpetialOffer:
const SpecialOffer = ({trySpecialOffer}) => {
const leftArrowRef = useRef()
const rightArrowRef = useRef()
const isLeftArrowPressing = usePressingButton(leftArrowRef)
const isRightArrowPressing = usePressingButton(rightArrowRef)
const areaOfVisibilityRef = useRef()
const [specialOffers, setSpecialOffers] = useState([])
const [currentOfferIndex, setCurrentOfferIndex] = useState(0)
const [areaOfVisibilityWidth, setAreaOfVisiblyWidth] = useState(0)
const [lineLength, setLineLength] = useState(0)
const [linePosition, setLinePosition] = useState(0)
const [fetchSpecialOffers, isLoading, error] = useFetching(async () => {
const response = await SpecialOfferAPI.getAll()
setSpecialOffers(response.data)
})
useEffect(() => {
fetchSpecialOffers()
setAreaOfVisiblyWidth(areaOfVisibilityRef.current.offsetWidth)
}, [])
useEffect(() => {
setLineLength(areaOfVisibilityWidth * specialOffers.length)
}, [specialOffers])
useEffect(() => {
if (isLeftArrowPressing && linePosition === 0) {
setLinePosition(20)
}
if (!isLeftArrowPressing && linePosition === 20) {
setLinePosition(0)
}
if (isRightArrowPressing && linePosition === areaOfVisibilityWidth - lineLength) {
setLinePosition(areaOfVisibilityWidth - lineLength - 20)
}
if (!isRightArrowPressing && linePosition === (areaOfVisibilityWidth - lineLength - 20)) {
setLinePosition(areaOfVisibilityWidth - lineLength)
}
}, [isLeftArrowPressing, isRightArrowPressing])
function rightMotion() {
if (linePosition > areaOfVisibilityWidth - lineLength) {
setLinePosition(linePosition - areaOfVisibilityWidth)
setCurrentOfferIndex(currentOfferIndex + 1)
}
}
function leftMotion() {
if (linePosition < 0) {
setLinePosition(linePosition + areaOfVisibilityWidth)
setCurrentOfferIndex(currentOfferIndex - 1)
}
}
function tryCurrentOffer() {
trySpecialOffer(specialOffers[currentOfferIndex].restaurant)
}
return (
<div className="specialOffer">
<div className="somethingNewText">ЧТО-ТО НОВОЕ</div>
<div className="specialOfferCenter">
<div className="arrowArea">
<Arrow
direction='left'
onClick={leftMotion}
ref={leftArrowRef}
/>
</div>
<div className="areaOfVisibility" ref={areaOfVisibilityRef}>
{areaOfVisibilityWidth > 0 &&
<SpecialOfferLine
specialOffers={specialOffers}
itemWidth={areaOfVisibilityWidth - 1}
lineLength={lineLength}
linePosition={linePosition}
/>}
</div>
<div className="arrowArea right">
<Arrow
direction='right'
onClick={rightMotion}
ref={rightArrowRef}
/>
</div>
</div>
<div className="wantToTry" onClick={tryCurrentOffer}>ГДЕ ПОПРОБОВАТЬ?</div>
</div>
);
};
export default SpecialOffer;
Arrow:
const Arrow = forwardRef((props, ref) => (
<div className="arrowContainer" onClick={props.onClick} ref={ref}>
<div className={props.direction === 'left' ? "left1" : "left1 right1"}/>
<div className={props.direction === 'left' ? "left2" : "left2 right2"}/>
</div>
));
export default Arrow;
What am I doing wrong?
Upvotes: 1
Views: 346
Reputation: 202608
Short of a running code demo the closest thing I see to an issue is referencing the ref values when the component is unmounting in the useEffect
hooks' cleanup functions. The event listeners should also be passed a stable handler reference. In other words, the function that is removed needs to be the same function that was added.
Try creating a listener callback for the events and saving a copy of the ref in the hook's callback scope and reference this in the cleanup functions.
Example:
export const usePressingButton = (buttonRef) => {
const [isPressing, setIsPressing] = useState(false);
useEffect(() => {
const ref = buttonRef.current;
const setTrue = (event) => setIsPressing(true);
const setFalse = (event) => setIsPressing(false);
ref.addEventListener('mousedown', setTrue);
ref.addEventListener('mouseup', setFalse);
return () => {
ref.removeEventListener('mousedown', setTrue);
ref.removeEventListener('mouseup', setFalse);
}
}, []);
return isPressing;
};
Do the same thing for the other useEffect
hook and handlers in the SelectCuisineLine
component.
Upvotes: 2