Can't use document in gatsby build, must convert to hooks - javascript

I have a script that works fine in the gatsby dev server but when trying to run gatsby build I get an error stating
"document" is not available during server side rendering.
The error fires on this code snippet
const useActiveElement = () => {
const [active, setActive] = useState(document.activeElement)
I assume that this error is also present any place I use document in this file, maybe outside of the useEffect places I use document. However, I have not been able to properly convert the code to using react hooks in a way that would allow Gatsby to build. Any suggestions on what I should do?
The full file (minus imports)
const useActiveElement = () => {
const [active, setActive] = useState(document.activeElement)
const handleKeyup = (e) => {
if (e.keyCode === 9) setActive(document.activeElement)
}
useEffect(() => {
document.addEventListener("keyup", handleKeyup)
return () => {
document.removeEventListener("keyup", handleKeyup)
}
}, [])
return active
}
const Layout = ({
children,
crumbLabel,
subStatus,
parentPageLabel,
parentPageLink,
}) => {
const focusedElement = useActiveElement()
const data = useStaticQuery(graphql`
query SiteTitleQuery {
sanitySiteSettings {
title
}
}
`)
useEffect(() => {
const prevTabbedElements = document.getElementsByClassName("tabbed")
for (let i = 0; i < prevTabbedElements.length; i++) {
prevTabbedElements[i].classList.remove("tabbed")
}
focusedElement.value && focusedElement.value.classList.add("tabbed")
focusedElement.classList.add("tabbed")
}, [focusedElement])
return (
<>
<Header siteTitle={data.sanitySiteSettings.title}/>
<nav>
{subStatus ? (
<Parentcrumbs
crumbLabel={crumbLabel}
parentPageLabel={parentPageLabel}
parentPageLink={parentPageLink}
/>
) : (
<Breadcrumbs crumbLabel={crumbLabel}/>
)}
</nav>
<main>{children}</main>
<Footer/>
</>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout

At the initial render point, your document is not defined yet so:
const useActiveElement = () => {
const [active, setActive] = useState('')
const handleKeyup = (e) => {
if (e.keyCode === 9) setActive(document.activeElement)
}
useEffect(() => {
setActive(document.activeElement);
document.addEventListener("keyup", handleKeyup)
return () => {
document.removeEventListener("keyup", handleKeyup)
}
}, [])
return active
}
Init the useState as empty and fill in in your componentDidMount (useEffect with empty deps).

Related

why the changing state of my component do not transmit when i export this component?

I have a hook that rules my requests. It has state "loading" that becoming true while loading
export const useHttp = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const request = useCallback(async (url, method = 'GET', body = null, headers = {'Content-Type': 'application/json' }) => {
setLoading(true);
try {
const res = await fetch(url, {method, body, headers});
if (!res.ok) {
throw new Error(`Could not fetch ${url}, status: ${res.status}`);
}
const data = await res.json();
setLoading(false);
return data;
} catch(e) {
setLoading(false);
setError(e.message);
throw e;
}
}, [])
const clearError = useCallback(() => {
setError(null);
}, [])
return {
loading,
error,
request,
clearError
}
}
Also i have a service that makes requests:
import { useHttp } from "../hooks/http.hook";
const useNasaService = () => {
const { loading, error, request, clearError } = useHttp();
const _apiBase = 'https://api.nasa.gov/';
const _apiKey = 'api_key=DEMO_KEY';
const getMissionManifest = async (rover) => {
const res = await request(`${_apiBase}mars-photos/api/v1/manifests/${rover}/?${_apiKey}`);
return _transformManifestData(res.photo_manifest);
}
const getImagesData = async (rover, sol, page = 1) => {
const res = await request(`${_apiBase}mars-photos/api/v1/rovers/${rover}/photos?sol=${sol}&page=${page}&${_apiKey}`);
return res.photos.map(_transformImagesData);
}
const _transformImagesData = (data) => {
return {
id: data.id,
sol: data.sol,
earthDate: data.earth_date,
path: data.img_src,
camera: data.camera.full_name,
rover: data.rover.name
}
}
const _transformManifestData = (data) => {
return {
landingDate: data.landing_date,
launchDate: data.launch_date,
maxDate: data.max_date,
maxSol: data.max_sol,
name: data.name,
photos: data.photos,
status: data.status,
totalPhotos: data.total_photos
}
}
return {
loading,
error,
clearError,
getImagesData,
getMissionManifest
}
}
export default useNasaService;
Finally i have a component that needs state "loading" for disabling the inputs.
The question is why "loading" is never getting true in this component:
import useNasaService from '../../services/useNasaService';
const RoverFilter = (props) => {
const { loading } = useNasaService();
console.log(loading); /* always false */
const onRadioChange = (e) => {
props.onRoverSelected(e.target.value);
props.onRoverClicked(e.target.value);
}
return (
<div className="roverFilter" >
<h2 className="roverFilter__title">Select rover</h2>
<div className="roverFilter__inputs">
<label htmlFor="curiosity">Curiosity</label>
<input disabled={loading} type="radio" name="rover-choise" id="curiosity" value="curiosity" onChange={onRadioChange}/>
<label htmlFor="opportunity">Opportunity</label>
<input disabled={loading} type="radio" name="rover-choise" id="opportunity" value="opportunity" onChange={onRadioChange}/>
<label htmlFor="spirit">Spirit</label>
<input disabled={loading} type="radio" name="rover-choise" id="spirit" value="spirit" onChange={onRadioChange}/>
<label htmlFor="perseverance">Perseverance</label>
<input disabled={loading} type="radio" name="rover-choise" id="perseverance" value="perseverance" onChange={onRadioChange}/>
</div>
</div>
)
}
export default RoverFilter;
By the way, in my app there are another components, where "loading" becoming true without any problems. I cant see the difference.
for example, here loading works good:
import { useEffect, useState } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import useNasaService from '../../services/useNasaService';
import ImageGallerySkeleton from '../imageGallerySkeleton/ImageGallerySkeleton';
import Spinner from '../spinner/Spinner';
import ErrorMessage from '../errorMessage/ErrorMessage';
import SliderModal from '../sliderModal/SliderModal';
const ImageGallery = (props) => {
const {loading, getImagesData, clearError, error} = useNasaService();
const [imagesData, setImagesData] = useState([]);
const [nextPage, setNextPage] = useState(1);
const [firstLoading, setFirstLoading] = useState(true);
const [imagesDataLoaded, setImagesDataLoaded] = useState(false);
const [itemIndex, setItemIndex] = useState(0);
const [sliderOpen, setSliderOpen] = useState(false);
const transitionDuration = 1000;
const onImagesDataLoaded = (newData) => {
setImagesData(data => [...data, ...newData]);
setNextPage(page => page + 1);
setFirstLoading(false);
setImagesDataLoaded(true);
}
const onRequestImages = (rover, sol, page) => {
clearError();
if (!rover || !sol) return;
getImagesData(rover, sol, page)
.then(onImagesDataLoaded);
}
const onSliderClosed = () => {
setSliderOpen(false);
}
useEffect(() => {
onRequestImages(props.selectedRover, props.selectedSol, nextPage);
// eslint-disable-next-line
}, [props.selectedRover, props.selectedSol])
if (sliderOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "visible";
}
function renderItemList(arr) {
const itemList = arr.map((item, i) => {
return (
<CSSTransition
key={item.id}
in={imagesDataLoaded}
timeout={transitionDuration}
classNames='imageGallery__card'>
<li className="imageGallery__card"
onClick={() => {
setSliderOpen(true);
setItemIndex(i);
}}>
<img src={item.path} alt="img from mars"/>
<div className="imageGallery__descr">
<ul>
<li>Rover: {item.rover}</li>
<li>Earth_date: {item.earthDate}</li>
<li>Sol: {item.sol}</li>
<li>{item.camera}</li>
</ul>
</div>
</li>
</CSSTransition>
)
})
return (
<ul className="imageGallery__list">
<TransitionGroup component={null}>
{itemList}
</TransitionGroup>
</ul>
)
}
const spinner = loading && firstLoading ? <Spinner/> : null;
const skeleton = imagesData.length === 0 && firstLoading && !loading && !error ? <ImageGallerySkeleton/> : null;
const items = renderItemList(imagesData);
const errorMessage = error ? <ErrorMessage/> : null;
const counter = imagesData.length === 0 || error ? null :
<h2 className="imageGallery__title">
Showed {loading ? "..." : imagesData.length} photos of {props.totalPhotosInSol}
</h2>
const button = props.totalPhotosInSol === imagesData.length ? null :
<button
onClick={() => onRequestImages(props.selectedRover, props.selectedSol, nextPage)}
disabled={loading}
className="imageGallery__btn">{loading ? "Loading..." : "Load next page" }
</button>
const slider = <SliderModal
open={sliderOpen}
items={imagesData}
slideIndex={itemIndex}
onSliderClosed={onSliderClosed} />
const wrapStyles = firstLoading && loading ? {"padding": "50px"} : null;
return (
<section className="imageGallery" style={wrapStyles}>
{counter}
{spinner}
{skeleton}
{imagesData.length === 0 && !firstLoading ?
<h2 className="imageGallery__title">There is no photo for this sol</h2> :
items
}
{button}
{errorMessage}
{slider}
</section>
)
}
export default ImageGallery;
When you call useState in two different components, those states are independant from eachother. This is still true if you move the useState calls inside a custom hook. If two components call useNasaService (which calls useHttp, which calls useState), then the two components are creating their own states and own functions. If component A starts loading data, that will have no effect on component B.
So ImageGallery is working because it makes a call to getImagesData. This sets the loading state of ImageGallery to true. No other components are affected by this though. When the loading finishes, ImageGallery will set state to have the new data, but again, no other components can use this. RoverFilter on the other hand never calls getImagesData, so its loading state stays false, and it never gets any data.
In react, the typical way to share data is to lift state up. You have a component higher up in the tree, which is responsible for loading the data and setting state. That component then passes the data and functions down to any children that need it. You can either pass the data down using props, or if you need to pass the data a long distance you can consider using context instead.
There's also a wide variety of 3rd party libraries which can be used to manage global state. For example, Redux, Jotai, Zustand, MobX. These can make it simpler to share data between components in far-flung parts of the component tree.

State not updating until I add or remove a console log

const BankSearch = ({ banks, searchCategory, setFilteredBanks }) => {
const [searchString, setSearchString] = useState();
const searchBanks = (search) => {
const filteredBanks = [];
banks.forEach((bank) => {
if (bank[searchCategory].toLowerCase().includes(search.toLowerCase())) {
console.log(bank[searchCategory].toLowerCase());
filteredBanks.push(bank);
}
});
setFilteredBanks(filteredBanks);
};
const debounceSearch = useCallback(_debounce(searchBanks, 500), []);
useEffect(() => {
if (searchString?.length) {
debounceSearch(searchString);
} else setFilteredBanks([]);
}, [searchString, searchCategory]);
const handleSearch = (e) => {
setSearchString(e.target.value);
};
return (
<div className='flex'>
<Input placeholder='Bank Search' onChange={handleSearch} />
</div>
);
};
export default BankSearch;
filteredBanks state is not updating
banks is a grandparent state which has a lot of objects, similar to that is filteredBanks whose set method is being called here which is setFilteredBanks
if I add a console log and save or remove it the state updates
Adding or removing the console statement and saving the file, renders the function again, the internal function's state is updated returned with the (setState) callback.
(#vnm)
Adding filteredBanks to your dependency array won't do much because it is part of the lexical scope of the function searchBanks
I'm not entirely sure of the total context of this BankSearch or what it should be. What I do see is that there are some antipatterns and missing dependencies.
Try this:
export default function BankSearch({ banks, searchCategory, setFilteredBanks }) {
const [searchString, setSearchString] = useState();
const searchBanks = useCallback(
search => {
const filteredBanks = [];
banks.forEach(bank => {
if (bank[searchCategory].toLowerCase().includes(search.toLowerCase())) {
filteredBanks.push(bank);
}
});
setFilteredBanks(filteredBanks);
},
[banks, searchCategory, setFilteredBanks]
);
const debounceSearch = useCallback(() => _debounce(searchBanks, 500), [searchBanks]);
useEffect(() => {
if (searchString?.length) {
debounceSearch(searchString);
} else setFilteredBanks([]);
}, [searchString, searchCategory, setFilteredBanks, debounceSearch]);
const handleSearch = e => {
setSearchString(e.target.value);
};
return (
<div className="flex">
<Input placeholder="Bank Search" onChange={handleSearch} />
</div>
)}
It feels like the component should be a faily simple search and filter and it seems overly complicated for what it needs to do.
Again, I don't know the full context, however, I'd look into the compont architecture/structuring of the app and state.

React Router useLocation() location is not followed to the current page

I'm using react-router-dom: "^6.2.2" in my project for a long time, but I don't know before that this version is not included useBlocker() and usePrompt(). So I'm found this solution and followed them. Then implemented into React Hook createContext() and useContext(). The dialog is displayed when changing route or refresh the page as expected.
But it has an error that useLocation() get the previous location despite the fact that I'm at the current page.
The NavigationBlocker code.
import React, { useState, useEffect, useContext, useCallback, createContext } from "react"
import { useLocation, useNavigate, UNSAFE_NavigationContext } from "react-router-dom"
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "#mui/material"
const navigationBlockerContext = createContext()
function NavigationBlocker(navigationBlockerHandler,canShowDialogPrompt) {
const navigator = useContext(UNSAFE_NavigationContext).navigator
useEffect(()=>{
console.log("useEffect() in NavigationBlocker")
if (!canShowDialogPrompt) return
// For me, this is the dark part of the code
// maybe because I didn't work with React Router 5,
// and it emulates that
const unblock = navigator.block((tx)=>{
const autoUnblockingTx = {
...tx,
retry() {
unblock()
tx.retry()
}
}
navigationBlockerHandler(autoUnblockingTx)
})
return unblock
})
}
function NavigationBlockerController(canShowDialogPrompt) {
// It's look like this function is being re-rendered before routes done that cause the useLocation() get the previous route page.
const navigate = useNavigate();
const currentLocation = useLocation();
const [showDialogPrompt, setShowDialogPrompt] = useState(false);
const [wantToNavigateTo, setWantToNavigateTo] = useState(null);
const [isNavigationConfirmed, setIsNavigationConfirmed] = useState(false);
const handleNavigationBlocking = useCallback(
(locationToNavigateTo) => {
// currentLocation.pathname is the previous route but locationToNavigateTo.location.pathname is the current route
if (!isNavigationConfirmed && locationToNavigateTo.location.pathname !== currentLocation.pathname) // {
setShowDialogPrompt(true);
setWantToNavigateTo(locationToNavigateTo);
return false;
}
return true;
},
[isNavigationConfirmed]
);
const cancelNavigation = useCallback(() => {
setIsNavigationConfirmed(false);
setShowDialogPrompt(false);
}, []);
const confirmNavigation = useCallback(() => {
setIsNavigationConfirmed(true);
setShowDialogPrompt(false);
}, []);
useEffect(() => {
if (isNavigationConfirmed && wantToNavigateTo) {
navigate(wantToNavigateTo.location.pathname);
setIsNavigationConfirmed(false)
setWantToNavigateTo(null)
}
}, [isNavigationConfirmed, wantToNavigateTo]);
NavigationBlocker(handleNavigationBlocking, canShowDialogPrompt);
return [showDialogPrompt, confirmNavigation, cancelNavigation];
}
function LeavingPageDialog({showDialog,setShowDialog,cancelNavigation,confirmNavigation}) {
const preventDialogClose = (event,reason) => {
if (reason) {
return
}
}
const handleConfirmNavigation = () => {
setShowDialog(false)
confirmNavigation()
}
const handleCancelNavigation = () => {
setShowDialog(true)
cancelNavigation()
}
return (
<Dialog fullWidth open={showDialog} onClose={preventDialogClose}>
<DialogTitle>ต้องการบันทึกการเปลี่ยนแปลงหรือไม่</DialogTitle>
<DialogContent>
<DialogContentText>
ดูเหมือนว่ามีการแก้ไขข้อมูลเกิดขึ้น
ถ้าออกจากหน้านี้โดยที่ไม่มีการบันทึกข้อมูล
การเปลี่ยนแปลงทั้งหมดจะสูญหาย
</DialogContentText>
</DialogContent>
<DialogActions>
<Button variant="outlined" color="error" onClick={handleConfirmNavigation}>
ละทิ้งการเปลี่ยนแปลง
</Button>
<Button variant="contained" onClick={handleCancelNavigation}>
กลับไปบันทึกข้อมูล
</Button>
</DialogActions>
</Dialog>
)
}
export function NavigationBlockerProvider({children}) {
const [showDialogLeavingPage,setShowDialogLeavingPage] = useState(false)
const [showDialogPrompt,confirmNavigation,cancelNavigation] = NavigationBlockerController(showDialogLeavingPage)
return (
<navigationBlockerContext.Provider value={{showDialog:setShowDialogLeavingPage}}>
<LeavingPageDialog showDialog={showDialogPrompt} setShowDialog={setShowDialogLeavingPage} cancelNavigation={cancelNavigation} confirmNavigation={confirmNavigation}/>
{children}
</navigationBlockerContext.Provider>
)
}
export const useNavigationBlocker = () => {
return useContext(navigationBlockerContext)
}
Expected comparison.
"/user_profile" === "/user_profile"
Error in comparison code.
"/user_profile" === "/home"
// locationToNavigateTo and currentLocation variable
The NavigationBlocker consumer code usage example.
function UserProfile() {
const prompt = useNavigatorBlocker()
const enablePrompt = () => {
prompt.showDialog(true)
}
const disablePrompt = () => {
prompt.showDialog(false)
}
}
The dialog image if it works correctly and if I click discard change, then route to the page that I clicked before. (Not pop-up when clicked anything except changing route.)
There is a bug that the dialog is poped-up when clicked at the menu bar button. When I clicked discard change the page is not changed.
Thank you, any help is appreciated.
From what I can see your useNavigationBlockerController hook handleNavigationBlocking memoized callback is missing a dependency on the location.pathname value. In other words, it is closing over and referencing a stale value.
Add the missing dependencies:
const navigationBlockerContext = createContext();
...
function useNavigationBlockerHandler(
navigationBlockerHandler,
canShowDialogPrompt
) {
const navigator = useContext(UNSAFE_NavigationContext).navigator;
useEffect(() => {
if (!canShowDialogPrompt) return;
// For me, this is the dark part of the code
// maybe because I didn't work with React Router 5,
// and it emulates that
const unblock = navigator.block((tx) => {
const autoUnblockingTx = {
...tx,
retry() {
unblock();
tx.retry();
}
};
navigationBlockerHandler(autoUnblockingTx);
});
return unblock;
});
}
...
function useNavigationBlockerController(canShowDialogPrompt) {
// It's look like this function is being re-rendered before routes done that cause the useLocation() get the previous route page.
const navigate = useNavigate();
const currentLocation = useLocation();
const [showDialogPrompt, setShowDialogPrompt] = useState(false);
const [wantToNavigateTo, setWantToNavigateTo] = useState(null);
const [isNavigationConfirmed, setIsNavigationConfirmed] = useState(false);
const handleNavigationBlocking = useCallback(
(locationToNavigateTo) => {
// currentLocation.pathname is the previous route but locationToNavigateTo.location.pathname is the current route
if (
!isNavigationConfirmed &&
locationToNavigateTo.location.pathname !== currentLocation.pathname
) {
setShowDialogPrompt(true);
setWantToNavigateTo(locationToNavigateTo);
return false;
}
return true;
},
[isNavigationConfirmed, currentLocation.pathname] // <-- add current pathname
);
const cancelNavigation = useCallback(() => {
setIsNavigationConfirmed(false);
setShowDialogPrompt(false);
}, []);
const confirmNavigation = useCallback(() => {
setIsNavigationConfirmed(true);
setShowDialogPrompt(false);
}, []);
useEffect(() => {
if (isNavigationConfirmed && wantToNavigateTo) {
navigate(wantToNavigateTo.location.pathname);
setIsNavigationConfirmed(false);
setWantToNavigateTo(null);
}
}, [isNavigationConfirmed, navigate, wantToNavigateTo]); // <-- add navigate
useNavigationBlockerHandler(handleNavigationBlocking, canShowDialogPrompt);
return [showDialogPrompt, confirmNavigation, cancelNavigation];
}
...
export function NavigationBlockerProvider({ children }) {
const [showDialogLeavingPage, setShowDialogLeavingPage] = useState(false);
const [
showDialogPrompt,
confirmNavigation,
cancelNavigation
] = useNavigationBlockerController(showDialogLeavingPage);
return (
<navigationBlockerContext.Provider
value={{ showDialog: setShowDialogLeavingPage }}
>
<LeavingPageDialog
showDialog={showDialogPrompt}
setShowDialog={setShowDialogLeavingPage}
cancelNavigation={cancelNavigation}
confirmNavigation={confirmNavigation}
/>
{children}
</navigationBlockerContext.Provider>
);
}
...
export const useNavigationBlocker = () => {
return useContext(navigationBlockerContext);
};

Generator function inside useCallback is returning same values in react, How to solve this?

I am creating to-do app in react and for the id of task i am using generator function. But This generator function is giving value 0 everytime and not incrementing the value.I think the reason for issue is useCallback() hook but i am not sure what can be the solution.How to solve the issue?Here i am providing the code :
import DateAndDay, { date } from "../DateAndDay/DateAndDay";
import TaskList, { TaskProps } from "../TaskList/TaskList";
import "./ToDo.css";
import Input from "../Input/Input";
import { ChangeEvent, useCallback, useEffect, useState } from "react";
function ToDo() {
const [inputShow, setInputShow] = useState(false);
const [valid, setValid] = useState(false);
const [enteredTask, setEnteredTask] = useState("");
const [touched, setTouched] = useState(false);
const [tasks, setTasks] = useState<TaskProps[]>(() => {
let list = localStorage.getItem("tasks");
let newdate = String(date);
const setdate = localStorage.getItem("setdate");
if (newdate !== setdate) {
localStorage.removeItem("tasks");
}
if (list) {
return JSON.parse(list);
} else {
return [];
}
});
const activeHandler = (id: number) => {
const index = tasks.findIndex((task) => task.id === id);
const updatedTasks = [...tasks];
updatedTasks[index].complete = !updatedTasks[index].complete;
setTasks(updatedTasks);
};
const clickHandler = () => {
setInputShow((prev) => !prev);
};
const input = inputShow && (
<Input
checkValidity={checkValidity}
enteredTask={enteredTask}
valid={valid}
touched={touched}
/>
);
const btn = !inputShow && (
<button className="add-btn" onClick={clickHandler}>
+
</button>
);
function checkValidity(e: ChangeEvent<HTMLInputElement>) {
setEnteredTask(e.target.value);
}
function* idGenerator() {
let i = 0;
while (true) {
yield i++;
}
}
let id = idGenerator();
const submitHandler = useCallback(
(event: KeyboardEvent) => {
event.preventDefault();
setTouched(true);
if (enteredTask === "") {
setValid(false);
} else {
setValid(true);
const newtitle = enteredTask;
const newComplete = false;
const obj = {
id: Number(id.next().value),
title: newtitle,
complete: newComplete,
};
setTasks([...tasks, obj]);
localStorage.setItem("setdate", date.toString());
setEnteredTask("");
}
},
[enteredTask, tasks, id]
);
useEffect(() => {
const handleKey = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setInputShow(false);
}
if (event.key === "Enter") {
submitHandler(event);
}
};
document.addEventListener("keydown", handleKey);
return () => {
document.removeEventListener("keydown", handleKey);
};
}, [submitHandler]);
useEffect(() => {
localStorage.setItem("tasks", JSON.stringify(tasks));
}, [tasks]);
return (
<div className="to-do">
<DateAndDay />
<TaskList tasks={tasks} activeHandler={activeHandler} />
{input}
{btn}
</div>
);
}
export default ToDo;
useCallBack()'s is used to memorize the result of function sent to it. This result will never change until any variable/function of dependency array changes it's value. So, please check if the dependencies passed are correct or if they are changing in your code or not ( or provide all the code of this file). One of my guess is to add the Valid state as dependency to the array
It's because you are calling the idGenerator outside of the useCallback, so it is only generated if the Component is re-rendered, in your case... only once.
Transfer it inside useCallback and call it everytime the event is triggered:
// wrap this on a useCallback so it gets memoized
const idGenerator = useCallback(() => {
let i = 0;
while (true) {
yield i++;
}
}, []);
const submitHandler = useCallback(
(event: KeyboardEvent) => {
event.preventDefault();
let id = idGenerator();
// ... rest of logic
},
[enteredTask, tasks, idGenerator]
);
If you're using the generated id outside the event handler, store the id inside a state like so:
const idGenerator = useCallback(() => {
let i = 0;
while (true) {
yield i++;
}
}, []);
const [id, setId] = useState(idGenerator());
const submitHandler = useCallback(
(event: KeyboardEvent) => {
event.preventDefault();
let newId = idGenerator();
setId(newId)
// ... rest of logic
},
[enteredTask, tasks, id, idGenerator]
);

How to use useeffect hook in react?

i want to return a function that uses useEffect from the usehook and i am getting error "useeffect is called in a function which is neither a react function component or custom hook.
what i am trying to do?
i have addbutton component and when user clicks add button i want to call the function requestDialog.
below is my code within addbutton file
function AddButton () {
const count = useGetCount();
const requestDialog = useRequestDialog(); //using usehook here
const on_add_click = () => {
requestDialog(count); //calling requestDialog here
}
return (
<button onClick={on_add_click}>add</button>
);
}
interface ContextProps {
trigger: (count: number) => void;
}
const popupContext = React.createContext<ContextProps>({
trigger: (availableSiteShares: number) => {},
});
const usePopupContext = () => React.useContext(popupContext);
export const popupContextProvider = ({ children }: any) => {
const [show, setShow] = React.useState(false);
const limit = 0;
const dismiss = () => {
if (show) {
sessionStorage.setItem(somePopupId, 'dismissed');
setShow(false);
}
};
const isDismissed = (dialogId: string) =>
sessionStorage.getItem(dialogId) === 'dismissed';
const context = {
trigger: (count: number) => {
if (!isDismissed(somePopupId) && count <= limit) {
setShow(true);
} else if (count > limit) {
setShow(false);
}
},
};
return (
<popupContext.Provider value={context}>
{children}
{show && (
<Popup onHide={dismiss} />
)}
</popupContext.Provider>
);
};
export function useRequestDialog(enabled: boolean,count: number) {
return function requestDialog() { //here is the error
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}
}, [count, trigger]);
}
How to solve the error ""useEffect is called in a function which is neither a react function component or custom hook."
i am not knowing how to use useeffect and the same time use it in the addbutton component.
could someone help me with this. thanks
useEffect method is like, useEffect(() => {}, []), But your usage in requestDialog is wrong. Try changing with following.
function requestDialog() {
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}, [count, trigger]);
}

Categories

Resources