React Hook Invalid Usage - javascript

I've been having an issue which returns the classic Hook issue:
×
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
Now it appears iv'e narrowed it done and it's my div, which is a div with animated css. Please see the code below for the styled component:
const BackDrop = styled(motion.div)`
position: absolute;
width: 160%;
height: 550px;
border-radius: 50%;
transform: rotate(60deg);
top: -290px;
left: -70px;
background: #8360c3; /* fallback for old browsers */
background: -webkit-linear-gradient(
to right,
#2ebf91,
#8360c3
); /* Chrome 10-25, Safari 5.1-6 */
`;
And it's inside of a function which I call to the App.js:
export function AccountBox(props) {
const { initialActive } = props;
const [isExpanded, setExpanded] = useState(false);
const [active, setActive] = useState(
initialActive ? initialActive : "signin"
);
const playExpandingEffect = () => {
setTimeout(() => {
setExpanded(false);
}, expandingTransition.duration * 1000 - 1500);
};
const switchActive = (active) => {
setTimeout(() => setActive(active), 400);
};
const switchToSignup = () => {
playExpandingEffect();
switchActive("signup");
};
const switchToSignin = () => {
playExpandingEffect();
switchActive("signin");
};
const contextValue = {
switchToSignup,
switchToSignin,
playExpandingEffect,
};
return (
<AccountContext.Provider value={contextValue}>
<BoxContainer>
<TopContainer>
<BackDrop
variants={backdropVariants}
transition={expandingTransition}
initial={false}
animate={isExpanded ? "expanded" : "collapsed"}
/>
{active === "signin" && (
<>
<HeaderContainer>
<HeaderText>Welcome</HeaderText>
<HeaderText>Back</HeaderText>
</HeaderContainer>
<SmallText>Please sign-in to continue!</SmallText>
</>
)}
{active === "signup" && (
<>
<HeaderContainer>
<HeaderText>Create </HeaderText>
<HeaderText>Account</HeaderText>
</HeaderContainer>
<SmallText>Please sign-up to continue!</SmallText>
</>
)}
</TopContainer>
<InnerContainer>
{active === "signin" && <LoginForm />}
{active === "signup" && <SignupForm />}
</InnerContainer>
</BoxContainer>
</AccountContext.Provider>
);
}
Now is the issue that i'm calling the Hook incorrectly? I can see some documentation online however I can't figure out why this is an issue as it's called in the function.
Does anybody knows!

Related

How to rid error of null properties in react hook?

I'm trying to custom hook to get few data. I want check where is one element in comparison to second element. I want to center it among themselves. It works but I have repeated errors in console.
Error:
Uncaught TypeError: Cannot read properties of null (reading
'offsetLeft') at getPosition (usePosition.js:18:1)
import { useEffect, useRef, useState } from "react";
const usePosition = () => {
const elRef = useRef();
const elSecondRef = useRef();
const [fromLeftEl, setFromLeftEl] = useState();
const [fromTopEl, setFromTopEl] = useState();
const [widthEl, setWidthEl] = useState();
const [heightEl, setHeightEl] = useState();
const [widthElSecond, setWidthElSecond] = useState();
const [finalX, setFinalX] = useState();
const [finalY, setFinalY] = useState();
// This function calculates the position underneath the element and centering it with respect to the other element
const getPosition = () => {
const fromLeftEl = elRef.current.offsetLeft;
setFromLeftEl(fromLeftEl);
const fromTopEl = elRef.current.offsetTop;
setFromTopEl(fromTopEl);
const widthEl = elRef.current.offsetWidth;
setWidthEl(widthEl);
const heightEl = elRef.current.offsetHeight;
setHeightEl(heightEl);
const widthElSecond = elSecondRef.current.offsetWidth;
setWidthElSecond(widthElSecond);
const middleEl = widthEl / 2;
const middleElSecond = widthElSecond / 2;
const finalX = fromLeftEl + middleEl - middleElSecond;
setFinalX(finalX);
const finalY = fromTopEl + heightEl;
setFinalY(finalY);
};
// Get the position of the first element
useEffect(() => {
getPosition();
}, []);
useEffect(() => {
window.addEventListener("resize", getPosition);
window.addEventListener("click", getPosition);
window.addEventListener("scroll", getPosition);
return () => window.removeEventListener("scroll", getPosition);
}, []);
return {
elRef,
elSecondRef,
fromLeftEl,
fromTopEl,
widthEl,
heightEl,
finalY,
finalX,
widthElSecond,
};
};
export default usePosition;
component that uses usePosition:
import classNames from "classnames";
import { useState } from "react";
import useSticky from "./useSticky";
import usePosition from "./usePosition";
import "../style/sass/Nav.sass";
import { NavLink } from "react-router-dom";
const Nav = (props) => {
const [isActive, setActive] = useState(false);
const { sticky, stickyRef } = useSticky();
const { elRef, elSecondRef, finalY, finalX } = usePosition();
const handleToggle = () => {
setActive(!isActive);
};
return (
<div
ref={stickyRef}
className={classNames("nav", { sticky })}
style={{
display: props.display,
height: sticky ? `${stickyRef.current?.clientHeight}px` : "10vh",
width: props.width,
gridTemplateColumns: props.navGridCol,
gridTemplateRows: props.navGridRow,
height: props.height,
position: props.position,
left: props.left,
animation: props.animationMenu,
}}
>
<div
className="nav__logo"
style={{
gridArea: props.gArea1,
height: props.heightHomeBtn,
width: props.btnWidth,
animation: props.animationHome,
display: props.homeDisplay,
}}
>
<NavLink className="link__logo" to="/">
<i
class="icon-home"
style={{
width: props.btnWidth,
fontSize: props.fweight,
}}
></i>
</NavLink>
</div>
<NavLink
className="link__about"
to="/about"
exact="true"
style={{ gridArea: props.gArea2 }}
>
<button
className="nav__aboutBtn"
style={{
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationAbout,
display: props.aboutDisplay,
}}
>
<p>o firmie</p>
</button>
</NavLink>
<button
onClick={handleToggle}
className={!isActive ? "offerDD__btn" : "offerDD__btn_active"}
style={{
gridArea: props.gArea3,
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationOffer,
display: props.offerDisplay,
}}
ref={elRef}
>
<i class="icon-down-open"></i>
<p>oferta</p>
</button>
<div
className={
!isActive ? "offerDD__dropdown_disabled" : "offerDD__dropdown"
}
ref={elSecondRef}
style={{ top: `${finalY}px`, left: `${finalX}px` }}
>
<NavLink className="link__air" to="/air-conditioning">
<div
className="air__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>klimatyzacje</p>
</div>
</NavLink>
<NavLink className="link__vent" to="/ventilation">
<div
className="vent__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>wentylacje</p>
</div>
</NavLink>
<NavLink className="link__heat" to="/heat-pump">
<div
className="heat__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>pompy ciepła</p>
</div>
</NavLink>
<NavLink className="link__recu" to="/recuperation">
<div
className="recu__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>rekuperacja</p>
</div>
</NavLink>
<NavLink className="link__fire-protection" to="/fire-protection">
<div
className="fire__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>ppoż</p>
</div>
</NavLink>
<NavLink className="link__shop" to="/shop">
<div
className="shop__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>sklep</p>
</div>
</NavLink>
</div>
<NavLink
className="link__blog"
to="/blog"
style={{ gridArea: props.gArea4 }}
>
<button
className="nav__blogBtn"
style={{
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationBlog,
display: props.blogDisplay,
}}
>
<p>blog</p>
</button>
</NavLink>
<NavLink
className="link__contact"
to="/contact"
style={{ gridArea: props.gArea5 }}
>
<button
className="nav__contactBtn"
style={{
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationContact,
display: props.contactDisplay,
}}
>
<p>kontakt</p>
</button>
</NavLink>
<NavLink
className="link__login"
to="/login"
style={{ gridArea: props.gArea6 }}
>
<button
className="nav__loginBtn"
style={{
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationLogin,
display: props.loginDisplay,
}}
>
<p>zaloguj</p>
</button>
</NavLink>
</div>
);
};
export default Nav;
I want know how to code this issue correctly without error in console and working hook.
I know that this error is due to the fact that it is impossible to read something that is not there and therefore it is null but how to write this code to make it correct?
I don't see any overt issue with the way the elRef is used in the consuming component. elRef is attached to a DOM element, e.g. button, and there is no conditional rendering so button is rendered to the DOM during the initial render.
const Nav = (props) => {
...
const { elRef, elSecondRef, finalY, finalX } = usePosition();
...
return (
<div
...
>
...
<button
onClick={handleToggle}
className={!isActive ? "offerDD__btn" : "offerDD__btn_active"}
style={{
gridArea: props.gArea3,
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationOffer,
display: props.offerDisplay,
}}
ref={elRef}
>
<i class="icon-down-open"></i>
<p>oferta</p>
</button>
...
</div>
);
};
I do however see potential issue in the usePosition hook. The hook doesn't clean up all the effects it creates. In the case of accessing into potentially null or undefined references it is advised to use a null-check/guard-clause or the Optional Chaining operator prior to accessing properties. In this case an if block to check if the refs have a truthy current value is sufficient.
const usePosition = () => {
const elRef = useRef();
const elSecondRef = useRef();
const [fromLeftEl, setFromLeftEl] = useState();
const [fromTopEl, setFromTopEl] = useState();
const [widthEl, setWidthEl] = useState();
const [heightEl, setHeightEl] = useState();
const [widthElSecond, setWidthElSecond] = useState();
const [finalX, setFinalX] = useState();
const [finalY, setFinalY] = useState();
// This function calculates the position underneath the element and centering it with respect to the other element
const getPosition = () => {
// Only access ref properties if there is a current value
if (elRef.current && elSecondRef.current) {
const fromLeftEl = elRef.current.offsetLeft;
setFromLeftEl(fromLeftEl);
const fromTopEl = elRef.current.offsetTop;
setFromTopEl(fromTopEl);
const widthEl = elRef.current.offsetWidth;
setWidthEl(widthEl);
const heightEl = elRef.current.offsetHeight;
setHeightEl(heightEl);
const widthElSecond = elSecondRef.current.offsetWidth;
setWidthElSecond(widthElSecond);
const middleEl = widthEl / 2;
const middleElSecond = widthElSecond / 2;
const finalX = fromLeftEl + middleEl - middleElSecond;
setFinalX(finalX);
const finalY = fromTopEl + heightEl;
setFinalY(finalY);
}
};
useEffect(() => {
window.addEventListener("resize", getPosition, { passive: true });
window.addEventListener("click", getPosition);
window.addEventListener("scroll", getPosition, { passive: true });
// Call once on initial component render
getPosition();
// Return cleanup function to remove all listener callbacks!
return () => {
window.removeEventListener("resize", getPosition, { passive: true });
window.removeEventListener("click", getPosition);
window.removeEventListener("scroll", getPosition, { passive: true });
};
}, []);
return {
elRef,
elSecondRef,
fromLeftEl,
fromTopEl,
widthEl,
heightEl,
finalY,
finalX,
widthElSecond,
};
};
export default usePosition;

How to show a loading white overlay page when route changes?

I have to show a loading white overlay page when route changes in Next.js. I used n-progess, it's working. But I have to show a white overlay page instead of the n-progress. My _app.js is written in class based views. So I don't know where I can put those code. I tried lot of ways, but none is working.
_app.jsx
function Loading() {
const router = useRouter();
const [loading, setLoading] = useState(false);
useEffect(() => {
const handleStart = (url) => url !== router.asPath && setLoading(true);
const handleComplete = (url) =>
url === router.asPath &&setLoading(false)
router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);
return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleComplete);
router.events.off('routeChangeError', handleComplete);
};
});
return (
loading && (
<div
className="spinner-wrapper"
style={{
height: '100%',
width: '100%',
backgroundColor: 'blue',
}}>
<div
className="spinner"
style={{ width: '50px', height: '50px' }}></div>
</div>
)
);
}
class MyApp extends App {
constructor(props) {
super(props);
this.persistor = persistStore(props.store);
}
componentDidMount() {
setTimeout(function () {
document.getElementById('__next').classList.add('loaded');
}, 100);
this.setState({ open: true });
}
render() {
const { Component, pageProps, store } = this.props;
const getLayout =
Component.getLayout ||
((page) => <DefaultLayout children={page} />);
return getLayout(
<Provider store={store}>
<PersistGate
loading={<Component {...pageProps} />}
persistor={this.persistor}>
<Loading />
<Component {...pageProps} />
</PersistGate>
</Provider>
);
}
}
export default withRedux(createStore)(withReduxSaga(MyApp))
What changes needed in this case? How can do those things?

Framer motion modal - Animate presence does not work

I am using modal with framer motion, the initial animation for the modal works fine, but on the exit it does not work at all. The modal disappears immediately for some reason.
This is where I am opening the modal:
const Agenda = () => {
const [modalOpen, setModalOpen] = useState(false);
const close = () => setModalOpen(false);
const open = () => setModalOpen(true);
return (
<>
<AnimatePresence
initial={false}
exitBeforeEnter={true}
>
{modalOpen && (
<Modal modalOpen={modalOpen} handleClose={close}>
<AgendaContent />
</Modal>
)}
</AnimatePresence>
</>
);
};
export default Agenda;
And here is the modal itself:
const newspaper = {
hidden: {
transform: "scale(0) rotate(720deg)",
opacity: 0,
transition: {
delay: 0.3,
},
},
visible: {
transform: " scale(1) rotate(0deg)",
opacity: 1,
transition: {
duration: 0.5,
},
},
exit: {
transform: "scale(0) rotate(-720deg)",
opacity: 0,
transition: {
duration: 0.3,
},
},
};
const Modal = ({ handleClose, children }) => {
return (
<Backdrop onClick={handleClose}>
<motion.div
drag
onClick={(e) => e.stopPropagation()}
className="modal"
variants={newspaper}
initial="hidden"
animate="visible"
exit="exit"
>
<img
className="close-icon"
src={CloseIcon}
alt="close"
onClick={handleClose}
/>
{children}
</motion.div>
</Backdrop>
);
};
export default Modal;
I followed this tutorial. I am not sure what I am doing wrong here, I am using the AnimatePresence there, any idea what could be wrong?
From the AnimatePresence docs:
Note: Direct children must each have a unique key prop so
AnimatePresence can track their presence in the tree.
It's frustratingly easy to forget even if you know about the requirement.
Something like this should work:
<AnimatePresence
initial={false}
exitBeforeEnter={true}
>
{modalOpen && (
<Modal key="modal" modalOpen={modalOpen} handleClose={close}>
<AgendaContent />
</Modal>
)}
</AnimatePresence>

Why is my ref always null even though I'm setting it to a component

So basically I have 2 pieces, the sidebar, then the opener. I'm trying to setup a ref that will connect the sidebar to the current opener. The opener is a functional component, and no matter what I do the current value is null. Am I missing something? I'm just trying to resize a component. My goal is to be able to resize the shown sidebar with the opener.
Here's part of the Render function.
render() {
const { selected, isSidebar, selectedType, search, active } = this.state;
const { pending, callback, resource } = this.props;
const pendingLengh = pending ? pending.length : 0;
const callbackLength = callback ? callback.length : 0;
const isResource = !resource || !Object.keys(resource).length;
return (
<div className="newPatientPage mainPage">
{this.renderMetadata()}
<SubTopBar
title="New Patient Processing"
noLeftSide={true}
subStatus={this.getStatus(pendingLengh, callbackLength)}
isBarcode={!isResource}
sideComponent={this.renderSideComponent()}
/>
{
active ?
<SnapshotSideBar
ref={this.sidebarRef}
patientResource={this.props.patientResource}
isShow={isSidebar}
settup={this.state.settup}
isScan={true}
handleCloseSidebar={this.handleCloseSidebar}
/> :
<NewPatientSideBar
ref={this.sidebarRef}
stepProps={this.state.stepProps}
selected={selected}
isShow={isSidebar}
handleCloseSidebar={this.handleCloseSidebar}
/>
}
<SidebarExtension sidebarToggle={this.toggleSidebar} sidebarReference={this.sidebarRef} sidebarState={isSidebar}/>
Here's the SidebarExtension component
const SidebarExtension = ({
sidebarToggle,
sidebarReference,
sidebarState,
...restProps
}) => {
const [xPos, setXPos] = useState(0);
const [width, setWidth] = useState();
const [openerPosition, setOpenerPosition] = useState(50);
const [isOpen, setIsOpen] = useState(false);
const toggleSidebar = () => {
sidebarToggle();
setIsOpen(!isOpen);
};
useEffect(() => {
setIsOpen(sidebarState);
}, [sidebarState])
if ((!isOpen && !sidebarState)) {
return (
<>
<div
className="resizeHandle"
style={{
right: "0Px",
}}
onClick={toggleSidebar}
>
<LeftCharvenSVG />
</div>
</>
);
}
return (
<>
<div
className="resizeHandle active"
onClick={toggleSidebar}
onMouseDown={startResize}
>
<LeftCharvenSVG />
</div>
</>
);
};
export default SidebarExtension;
Here's what the constructor looks like.
Main Constructor
From the docs https://reactjs.org/docs/forwarding-refs.html you need to wrap your functional component in React.forwardRef()
Example
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
In your case that would be:
const SidebarExtension = React.forwardRef(({
sidebarToggle,
sidebarReference,
sidebarState,
...restProps
}, ref) => {
const [xPos, setXPos] = useState(0);
const [width, setWidth] = useState();
const [openerPosition, setOpenerPosition] = useState(50);
const [isOpen, setIsOpen] = useState(false);
const toggleSidebar = () => {
sidebarToggle();
setIsOpen(!isOpen);
};
useEffect(() => {
setIsOpen(sidebarState);
}, [sidebarState])
if ((!isOpen && !sidebarState)) {
return (
<>
<div
className="resizeHandle"
style={{
right: "0Px",
}}
ref={ref}
onClick={toggleSidebar}
>
<LeftCharvenSVG />
</div>
</>
);
}
return (
<>
<div
className="resizeHandle active"
onClick={toggleSidebar}
onMouseDown={startResize}
>
<LeftCharvenSVG />
</div>
</>
);
});
export default SidebarExtension;

Using React-Hooks, how do I prevent a component created from Array.map from re-rendering if one of it's siblings changes state?

I've created a grid of components using array.map. Using console.log I'm able to see that each component is re-rendering whenever one of the components is changing state. When I have a grid of 50x50, this becomes noticeably slow.
import React, { useState } from 'react';
function Cell({ cell, cellState, updateBoard }) {
console.log('cell rendered')
const CellStyle = {
display: 'inline-block',
width: '10px',
height: '10px',
border: '1px green solid',
background: cellState ? 'green' : 'purple'
};
function handleClick(e) {
updateBoard(cell, !cellState)
}
return (
<span
style={CellStyle}
onClick={handleClick}
/>
)
}
function App() {
console.log('board rendered')
const initialState = new Array(10).fill().map(() => new Array(10).fill(false));
let [board, setBoard] = useState(initialState);
function updateBoard(cell, nextState) {
let tempBoard = [...board];
tempBoard[cell[0]][cell[1]] = nextState;
setBoard(tempBoard)
}
return (
<div style={{ display: 'inline-block' }}>
{board.map((v, i, a) => {
return (
<div
key={`Row${i}`}
style={{ height: '12px' }}
>
{v.map((w, j) =>
<Cell
key={`${i}-${j}`}
cell={[i, j]}
cellState={board[i][j]}
updateBoard={updateBoard}
/>
)}
</div>
)
}
)}
</div>
)
}
export default App;
When I click on one of the components, I want the parent state to update, and the clicked component to update and re-render. Since the rest of the components aren't changed, I don't want the other components to re-render. How do I accomplish this using React-Hooks?
There is few things that would greatly improve performance:
using memo()
const MemoizedCell = memo(Cell);
/*...*/
<MemoizedCell
/*...*/
/>
not passing new references to <Cell /> every time
You are passing cell={[i, j]} - it creates new Array every time you invoke it(!) that means that props of Cells has been changed - why would it not render again then?
Same with passing updateBoard={updateBoard} - you are creating new function every time <App /> renders. You need to memorize it and use old state in function.
const updateBoard = useCallback(
(cell, nextState) => {
setBoard(oldBoard => {
let tempBoard = [...oldBoard];
tempBoard[cell[0]][cell[1]] = nextState;
return tempBoard;
});
},
[setBoard]
);
you are creating initialState every render - move it above (outside) <App /> or create it inside useState as function (and use const instead of let here).
const [board, setBoard] = useState(() =>
new Array(10).fill().map(() => new Array(10).fill(false))
);
final solution:
import React, { useState, memo, useCallback } from "react";
import ReactDOM from "react-dom";
function Cell({ i, j, cellState, updateBoard }) {
console.log(`cell ${i}, ${j} rendered`);
const CellStyle = {
display: "inline-block",
width: "10px",
height: "10px",
border: "1px green solid",
background: cellState ? "green" : "purple"
};
function handleClick(e) {
updateBoard([i, j], !cellState);
}
return <span style={CellStyle} onClick={handleClick} />;
}
const MemoizedCell = memo(Cell);
function App() {
console.log("board rendered");
const [board, setBoard] = useState(() =>
new Array(10).fill().map(() => new Array(10).fill(false))
);
const updateBoard = useCallback(
(cell, nextState) => {
setBoard(oldBoard => {
let tempBoard = [...oldBoard];
tempBoard[cell[0]][cell[1]] = nextState;
return tempBoard;
});
},
[setBoard]
);
return (
<div style={{ display: "inline-block" }}>
{board.map((v, i, a) => {
return (
<div key={`Row${i}`} style={{ height: "12px" }}>
{v.map((w, j) => (
<MemoizedCell
key={`${i}-${j}`}
i={i}
j={j}
cellState={board[i][j]}
updateBoard={updateBoard}
/>
))}
</div>
);
})}
</div>
);
}
export default App;
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Categories

Resources