I have a code similar to this one:
function Component1(...) {
...
function checkIfCtrlKey(event) {
return event.ctrlKey;
}
return (
{ checkIfCtrlKey() && (<Component2 .../>) }
);
}
The sense behind this is that the Component2 is just rendered if the Ctrl-key is being pressed. When running this code, I get following error message: TypeError: Cannot read property 'ctrlKey' of undefined
What is my mistake? Is there a solution or other possibility to implement my need?
You need to put an event listener on the window object and within that hander you can set some state to switch between a true and false
Something like this.
function Component1() {
const [ctrlActive, setCtrlActive] = useState(false)
useEffect(() => {
window.addEventListener('keydown', (e => {
if("Do a check here to see if it's CTRL key") {
setCtrlActive(!ctrlActive)
}
}), [])
})
return ctrlActive ? <Component2 /> : null
}
You can use Vanilla JS for that like this:
import React, { useEffect, useState } from "react";
export default function App() {
const [ctrlPressed, setCtrlPressed] = useState(false);
const handleKey = (e) => setCtrlPressed(e.ctrlKey);
useEffect(() => {
window.addEventListener("keydown", handleKey);
window.addEventListener("keyup", handleKey);
return function cleanup() {
window.removeEventListener("keydown", handleKey);
window.removeEventListener("keyup", handleKey);
};
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{ctrlPressed ? <h2>You're pressing CTRL Key</h2> : null}
</div>
);
}
Working example over here
Your making mistake here,
{ checkIfCtrlKey() && (<Component2 .../>) }
refer
function checkIfCtrlKey(event) {
return event.ctrlKey;
}
How u suppose that checkIfCtrlKey will be passed with event arg when your calling like this checkIfCtrlKey() ??
You might wanted to attach it to window,
function Component1() {
const [ctrlKeyPressed, setCKP] = useState(false)
const handleKey = ev => {
setCKP(ev.ctrlKey)
}
useEffect(() => {
window.addEventListener('keydown', handleKey);
window.addEventListener('keyup', handleKey);
return () => {
window.removeEventListener('keydown', handleKey);
window.removeEventListener('keyup', handleKey);
}
}, [])
return (
<>
...
{ctrlKeyPressed && <Component2 />}
...
</>
)
}
Shows Component2 as long as ctrlKey is pressed
Related
I recently learned about props, as I am fairly new to React, and am trying to implement them in places to reduce the amount of repeated code in each page of a web app I am building.
Originally, I had the following useEffect and useState placed above each return to simply control if something is available on the page or not.
const [waiting, setWaiting] = useState(false);
useEffect(() => {
setWaiting(true)
}
return (
<>
{ waiting ? ( *displays a "Coming Soon" page* ) : ( *displays the webpage* )
</>
)
Which ran well and I could simply change the boolean value on the useEffect if I wanted to remove the waiting screen and display the page content.
But this resulted in me repeating that useEffect for every page that had content, which is enough for me to realize I don't have to repeat this code.
So I set up the following component:
import { useState, useEffect } from "react";
const NoPhotos = (props) => {
//Photos Aren't Ready Yet
const [waiting, setWaiting] = useState(false);
useEffect(() => {
if (waiting) {
setWaiting(true);
} else {
setWaiting(false);
}
}, [props.wait, waiting]);
};
export default NoPhotos;
And I replace the "waiting" from the original ternary operator, with the NoPhotos component, as follows:
return (
<>
{ <NoPhotos wait={true} ? ( *displays a "Coming Soon" page* ) : ( *displays the webpage* )
</>
)
But I noticed that regardless of what boolean I put in the "wait={}", it doesn't change and always shows "true".
Is there a way to make it so the boolean value that is passed in "wait=" controls the state?
A hook like this can be helpful:
function useWait(setWaiting) {
useEffect(() => {
setTimeout(() => {
setWaiting(false);
}, 1000);
}, [setWaiting]);
}
Usage:
const [waiting, setWaiting] = useState(true);
useWait(setWaiting);
return (
<>
<p>{waiting ? "Coming Soon" : "the webpage"}</p>
</>
);
But I would rather use something like this:
function useWait() {
const [waiting, setWaiting] = useState(true);
useEffect(() => {
setTimeout(() => {
setWaiting(false);
}, 1000);
}, []);
return waiting;
}
or this:
function Wait({ loader, content }) {
const [waiting, setWaiting] = useState(true);
useEffect(() => {
setTimeout(() => {
setWaiting(false);
}, 1000);
}, []);
return <>{waiting ? loader : content}</>;
}
Usage:
const waiting = useWait(true);
return (
<>
<p>{waiting ? "Coming Soon" : "the webpage"}</p>
<p>
<Wait loader="Coming soon" content="the webpage" />
</p>
</>
);
here is the code :
const [isAcces, setIsAcces] = useState(false);
useEffect(() => {
accesList
.filter((acces) => acces.idAcces === 2)
.map((acces, index) => setIsAcces(true));
}, []);
return <div>Hello World</div>;
I want to render 'Hello World' if 'isAcces' is equal true.
If not, I use <Navigate to="/" /> to redirect.
I know 'useEffect' is render AFTER the return, but I can't find a solution to solve my issue (and of course, 'isAcces' is always false before the redirection).
I found on Google that I can use the 'Promises', but I don't know how it's work.
Can somebody help me ?
Thank you !
It looks like you should be using useMemo for isAccess. That will evaluate before the return, and it depends on accesList. And since you're just looking for a boolean based on whether a matching item is in the list, use some:
const isAcces = useMemo(
() => accesList.some((acces) => acces.idAcces === 2),
[accesList]
);
if (!isAccess) {
return <Navigate to="/" />; // ??
}
return <div>Hello World</div>;
If I understand correctly you are looping an array and if there are items there that equal 2, you set your isAcces true, correct?
If yes you don't need a map() after the filter
You can simplify your code a bit.
function YourComponent({ accesList }) {
const [isAcces, setIsAcces] = useState(false);
useEffect(() => {
const newArr = accesList.filter((acces) => acces.idAcces === 2);
if (newArr.length > 0) {
setIsAcces(true);
}
}, []);
useEffect(() => {
if (isAcces) {
// import history from react-router-dom or any method to navigate to another route
history.push("/");
}
}, [isAcces]);
return <div>hello world!</div>
}
export default function App() {
const accesList = [{ idAcces: 1 }, { idAcces: 2 }];
const [isAcces, setIsAcces] = useState(false);
useEffect(() => {
let showHelloWorld = accesList.some((acces) => acces.idAcces === 2);
if (showHelloWorld) {
setIsAcces(true);
} else {
//history.push("/");
}
}, []);
return isAcces && <div>Hello World</div>;
}
I am passing functions to my child component. And I am using React.memo to restrict compoenent from re-rendering. But My component rerenders when parent re-renders. I tried to check why this is happening by using useEffect on all the props and I get to this point that my functions are causing compoenent to re-renders.
// my functions
const scrollToView = (index) => {
if (scrollRef && scrollRef.current && scrollRef.current[index]) {
scrollRef.current[index].scrollIntoView({ behavior: 'smooth' });
}
};
const scrollToReportView = (reportIndex) => {
if (scrollToReportRef && scrollToReportRef.current &&
scrollToReportRef.current[reportIndex]) {
scrollToReportRef.current[reportIndex].scrollIntoView({
behavior: 'smooth' });
}
}
.......
function LeftNav({
scrollToView, //function
scrollToReportView, //function
reports, //object
}) {
useEffect(() => {
console.log('scrollToView')
}, [scrollToView])
useEffect(() => {
console.log('scrollToReportView')
}, [scrollToReportView])
useEffect(() => {
console.log('reports')
}, [reports])
return (
<div>{'My Child Component'}</div>
);
}
export default memo(LeftNav);
And this is how my left nav is being called
<LeftNav
scrollToView={(index) => scrollToView(index)}
scrollToReportView={(repIndex)=> scrollToReportView(repIndex)}
reports={reports}
/>
With
<LeftNav
scrollToView={(index) => scrollToView(index)}
scrollToReportView={(repIndex)=> scrollToReportView(repIndex)}
reports={reports}
/>
you're creating new anonymous functions every time you render the LeftNav component, so memoization does absolutely nothing.
Just
<LeftNav
scrollToView={scrollToView}
scrollToReportView={scrollToReportView}
reports={reports}
/>
instead (assuming those functions are stable by identity (e.g. are declared outside the component or are properly React.useCallbacked or React.useMemoed).
In other words, if your component is currently
function Component() {
// ...
const scrollToView = (index) => {
if (scrollRef && scrollRef.current && scrollRef.current[index]) {
scrollRef.current[index].scrollIntoView({ behavior: "smooth" });
}
};
const scrollToReportView = (reportIndex) => {
if (scrollToReportRef && scrollToReportRef.current && scrollToReportRef.current[reportIndex]) {
scrollToReportRef.current[reportIndex].scrollIntoView({
behavior: "smooth",
});
}
};
return (
<LeftNav
scrollToView={(index) => scrollToView(index)}
scrollToReportView={(repIndex) => scrollToReportView(repIndex)}
reports={reports}
/>,
);
}
it needs to be something like
function Component() {
// ...
const scrollToView = React.useCallback((index) => {
if (scrollRef?.current?.[index]) {
scrollRef.current[index].scrollIntoView({ behavior: "smooth" });
}
}, []);
const scrollToReportView = React.useCallback((reportIndex) => {
if (scrollToReportRef?.current?.[reportIndex]) {
scrollToReportRef.current[reportIndex].scrollIntoView({
behavior: "smooth",
});
}
}, []);
return (<LeftNav scrollToView={scrollToView} scrollToReportView={scrollToReportView} reports={reports} />);
}
so the scrollToView and scrollToReportView functions have stable identities.
Im just wondering how i can stop the function from returning before the state has been updated - Here is my code so i can explain more clearly.
const mockData = {
current: {
temperature: 'loading'
}
}
export default function Weather({ city }) {
const [data, setData] = useState(mockData)
const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
useEffect(() => {
axios.get(url).then(({ data }) => {
setData(data, () => { })
})
}, [])
return (
<div>
<h1>Weather</h1>
<p>temperature: {data.current.temperature}</p>
</div>
)
}
Right now i am using mockData because if i dont i will get an error because the .current.temperature properties do not exist (because set state hasnt been updated yet).
How can i stop the error and stop the div being returned before the set state has been updated or atleast stop the error and return an empy div or something.
What you can do is add a conditional within your return.
Try this:
return (
<div>
<h1>Weather</h1>
<p>Temperature: {(data && data.current) ? data.current.temperature : ""}</p>
</div>
)
You can also use optional chaining to achieve the same result.
return (
<div>
<h1>Weather</h1>
<p>Temperature: {data?.current?.temperature || ""}</p>
</div>
)
You need to check that data exists before you reference data.current, and you need to check that data.current exists before you reference data.current.temperature. If you access a property of undefined, your code will crash.
You need to have some additional state, such as isLoading
If you want a hack, you can do this:
export default function Weather({ city }) {
const [data, setData] = useState(mockData)
const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
useEffect(() => {
axios.get(url).then(({ data }) => {
setData(data, () => { })
})
}, [])
function renderTemperature() {
if (!data) {
return null;
} else if (data && !data.current) {
return null;
} else if (data.current && data.current.temperature) {
return <p>temperature: {data.current.temperature}</p>
}
}
return (
<div>
<h1>Weather</h1>
{ renderTemperature() }
</div>
)
}
Much better solution:
export default function Weather({ city }) {
const [data, setData] = useState(null)
const [isLoaded, setIsLoaded] = useState(false);
const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
useEffect(() => {
axios.get(url).then(({ data }) => {
setData(data, () => { })
setIsLoaded(true);
})
}, [])
function renderTemperature() {
}
return (
<div>
<h1>Weather</h1>
{ isLoaded ? <p>temperature: {data.current.temperature}</p> : null}
</div>
)
}
I have this component, and I am using useRef and useEffect to handle a click outside a popup so I can close it.
I have added the two dependencies the useEffect needs but I get this error:
The 'handleClickOutside' function makes the dependencies of useEffect Hook (at line 117) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'handleClickOutside' definition into its own useCallback()
As you can see here, I am adding both dependencies, but it still throws this error/warning:
useEffect(() => {
if (isOverlayOpen) {
document.addEventListener("mousedown", handleClickOutside);
} else {
document.removeEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isOverlayOpen, handleClickOutside]);
Any ideas how to fix this?
Here i the codesandbox: https://codesandbox.io/s/laughing-newton-1gcme?fontsize=14&hidenavigation=1&theme=dark The problem is in src/components/typeahead line 100
And here the component code:
function ResultsOverlay({
isOpen,
items,
selectItem,
highlightedOption,
setIsOverlayOpen,
isOverlayOpen
}) {
const node = useRef();
const handleClickOutside = e => {
if (node.current.contains(e.target)) {
return;
}
setIsOverlayOpen(false);
};
useEffect(() => {
if (isOverlayOpen) {
document.addEventListener("mousedown", handleClickOutside);
} else {
document.removeEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isOverlayOpen]);
function matchedOptionsClass(index) {
if (highlightedOption === index) {
return "ph4 list f3 sesame-red list-item pointer";
}
return "ph4 list sesame-blue list-item pointer";
}
if (isOpen) {
return (
<div className="absolute" ref={node}>
<ul className="w5 mt0 pa0 h5 overflow-scroll shadow-5 dib">
{items &&
items.map((item, index) => (
<li
onClick={() => selectItem(item)}
className={matchedOptionsClass(index)}
>
{item}
</li>
))}
</ul>
</div>
);
} else {
return null;
}
}
Two problems:
First, do what the linst is telling you and move your function definition inside your effect
useEffect(() => {
const handleClickOutside = e => {
if (node.current.contains(e.target)) {
return;
}
setIsOverlayOpen(false);
};
if (isOverlayOpen) {
document.addEventListener("mousedown", handleClickOutside);
} else {
document.removeEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isOverlayOpen]);
Second, setIsOverlayOpen is a callback provided via props, so it doesn't have a stable signature and triggers the effect on each render.
Assuming that setIsOverlayOpen is a setter from useState and doesn't need to change it's signature you can workaround this by wrapping your handler in an aditional dependency check layer by using useCallback
const stableHandler = useCallback(setIsOverlayOpen, [])
useEffect(() => {
const handleClickOutside = e => {
if (node.current.contains(e.target)) {
return;
}
stableHandler(false);
};
if (isOverlayOpen) {
document.addEventListener("mousedown", handleClickOutside);
} else {
document.removeEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isOverlayOpen, stableHandler]);