React js - div onClick link to page - javascript

I'm trying to simulate a link in react js clicking on a div.
This is my code:
function handleClick(myLink){
window.location.href=myLink;
}
and here where I call it:
<Col className="aslink" onClick={handleClick('/path/to/myUrl')}>
<div>...</div>
</Col>
But it goes directly to the URL without clicking, so it starts an infinite loop.
How can I solve it?
Many thanks in advance!

This is because you are calling the function in this part <Col className="aslink" onClick={handleClick('/path/to/myUrl')}> instead of providing reference to it to be used on users click action. What you can do is define it like this:
const handleClick = (myLink) => () => {
window.location.href=myLink;
}
then it will work as you want it.

handclick('your path')
is already running the code. Try
onClick = {() => handlick('your path')}
This will stop it from automatically running

First off, I would recommend using React Router's Link over history.location.href, as it uses the routers history api, using this declarative, accessible navigation instead of history means that you are safe to any changes to the history api in the future.
You can import it like this:
import { Link } from 'react-router-dom'
Secondly, you were calling the handleClick function instead of executing the function.

If you use react-router*(which is most possible - if not - then you should research value of this)* then you can get access to browser-history via react router provider
pass router api to your component
if you use modern react version - use hook useHistory -
const Comp = () => {
const history = useHistory()
const handleRedirect = useCallback((path) => {
return () => {
history.push(path);
}
}, [])
return <div onClick={handleRedirect('path-to-page')}>Navigate</div>
}
export default Comp;
or 2. extract history object from taken props in your component
you can wrap you component by HOC - withRouter.
const Comp = ({history}) => {
const handleRedirect = useCallback((path) => {
return () => {
history.push(path);
}
}, [])
return <div onClick={handleRedirect('path-to-page')}>Navigate</div>
}
export default withRouter(Comp)

Related

why this setState is not firing?

I'm trying to build a notification React component for my application that I can call from anywhere. I have the below code where I'm trying to do some kinda hack for exporting the showNotif function
function FloatNotification() {
const [show, setShow] = useState(false);
const showNotif = () => {
console.log(`showNotif is called`);
setShow(true);
};
if (!FloatNotification.showNotification) {
FloatNotification.showNotif = showNotif;
}
return <>{show ? `Showing notification` : `not showing notification`}</>
}
export default FloatNotification;
On another file I'm trying to call showNotif like below
import FloatNotification from "./FloatNotification";
function MyComponent() {
const {showNotif} = FloatNotification;
return <><button onClick={() => showNotif()}>Click Me</button></>
}
but the setState isn't getting called unexpectedly. I'm getting the showNotif is called message in the console. so logically the setState should also get called.
I kinda understand it's happening because of how javascript handles reference data type. but I'm not sure what's actually happening behind the scene and how to get my goal
Suggest me if you have any other ideas to build this notification component (something I can call from anywhere in my component tree). Any kind of help will be kudos to me
[NOTE: I'm actually using NextJS and I've added this FloatNotification in the _app.js. So it's available in all the pages
useState is a special function called "hooks". React hooks are only available when the component is rendered in the VDOM tree.
Since you dosen't render FloatNotification as a element, calling setState is unexpected and may has no effects.
There are several ways to achieve what you want without hacking.
First is, lift up notification state to the parent component and inject only the dispatch that changes the state is through the context.
const NotificationContext = React.createContext(() => {});
function FloatNotification({ children }) {
const [show, setShow] = useState(false);
return (
<NotificationContext.Provider value={setShow}>
{children}
<>{show ? `Showing notification` : `not showing notification`}</>
</NotificationContext.Provider>
);
}
function MyComponent() {
const setShow = useContext(NotificaitonContext);
return (
<button onClick={() => setShow(true)}>
Show Notification
</button>
);
}
function App() {
return (
<FloatNotification>
<MyComponent />
</FloatNotification>
);
}
Or, you can exposing the handler by React.useImperativeHandle (commonly not recommended)

global React functions that utilize hooks

I have a function called scheduleShortcut that gets used in a couple places throughout an application. Initially, this function was local to the specific components but since it is being used multiple times, I want to refactor this function into a global function.
At first, I tried to do the following:
const history = useHistory();
const dispatch = useDispatch();
export const scheduleShortcut = (jobId: number) => {
dispatch(jobGanttFocus(jobId));
dispatch(focusJobScheduleFilter(jobId));
dispatch(toggleScheduleView('jobs'));
history.push('/schedule');
};
However, when I do this, I get an error that says I can't use useHistory or useDispatch unless they are inside a React component or a custom hook. Then, I tried to convert scheduleShortcut into a custom hook in the following way:
export const useScheduleShortcut = (jobId: number) => {
const history = useHistory();
const dispatch = useDispatch();
dispatch(jobGanttFocus(jobId));
dispatch(focusJobScheduleFilter(jobId));
dispatch(toggleScheduleView('jobs'));
history.push('/schedule');
};
This allowed me to utilize useDispatch and useHistory. However, when I try to call this function inside the specific components I need it in, I get a similar error. Basically, it says that I cannot use my custom hook (i.e. useScheduleShortcut) inside a callback.
<Roster
jobId={job.id}
assignWorkers={canAssignWorker ? handleAssignWorkers : undefined}
scheduleShortcut={() => useScheduleShortcut(jobId)}
/>
Is there a way I can get around these errors and use scheduleShortcut as a recyclable function? Or is this in fact not possible since I am using the hooks?
Hooks in fact must be called on top level, you are breaking that rule
You could expose(return) a function from hook that could be called as callback afterwards.
i.e.
export const useScheduleShortcut = () => {
const history = useHistory()
const dispatch = useDispatch()
const dispatchScheduleShortcut = (jobId) => {
dispatch(jobGanttFocus(jobId))
dispatch(focusJobScheduleFilter(jobId))
dispatch(toggleScheduleView('jobs'))
history.push('/schedule')
}
return {
dispatchScheduleShortcut
}
};
and then use it as
const { dispatchScheduleShortcut } = useScheduleShortcut()
return (
<Roster
jobId={job.id}
assignWorkers={canAssignWorker ? handleAssignWorkers : undefined}
scheduleShortcut={() => dispatchScheduleShortcut(jobId)}
/>
)

What is correct way to fix this 'Invalid Hook Call' error in react app?

Well, i have this error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
I tried alot of different options to fix this, but i failed.
Here is my code
export const DataInput = () => {
const Post = (testTitle, testText) => {
useFirestore().collection('schedule-data').doc('test').set({
testTitle: testTitle,
testText: testText
})
}
return(
<Button
variant="primary"
onClick={()=> Post(testTitle, testText)}>
POST data
</Button>
Deleted some of code that does not matter
Hooks can only be called while rendering a component, so they need to be in the body of your component.
export const DataInput = () => {
const firestore = useFirestore();
const Post = (testTitle, testText) => {
firestore.collection('schedule-data').doc('test').set({
testTitle: testTitle,
testText: testText
})
}
// etc
}
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, explanation available here)
According to you code samle I may suggest that testTitle, testText available in DataInput in some way, thus you may create onClick handler with useCallback. React will create callback for use as handler, and re-create only when testTitle, testText changed.
import {useCallback} from 'react';
export const DataInput = () => {
const makePost = useCallback(() => {
useFirestore().collection('schedule-data').doc('test').set({
testTitle: testTitle,
testText: testText
})
}, [testTitle, testText]);
return (
<Button
variant="primary"
onClick={makePost}
{/* Avoid inline callback declaration */}
>
POST data
</Button>
)
}

how can I test if useState hook has been called with jest and react testing library?

I have this component that I would like to test using Jest and React testing library :
export function GenericDivider({ stepsHeader = [], componentsFactory = [] }) {
const [currentPage, changePage] = useState(0);
const Component = componentsFactory[currentPage] || (() => <></>);
return (
<div className="container page__container">
...
<a
href="#"
key={name}
onClick={e => {
e.preventDefault();
if (index < componentsFactory.length) changePage(index);
}}
className={`nav-link ${currentPage === index && 'active'}`}
>
{name}
</a>
...
</div>
);
}
export default memo(GenericDivider);
I would like to test if changePage is called when I fire a click, is there a way i can do that using Jest and React testing library ?
I'm new to testing and I tried this but it doesn't seem to work
it('Should call changePage when button clicked', () => {
const changePage = jest.fn();
const { container } = render(
<GenericDivider
stepsHeader={['hamid', 'walid', 'khalid']}
componentsFactory={[() => <></>, () => <></>, () => <></>]}
/>,
);
const button = container.querySelector('a');
fireEvent.click(button);
expect(changePage).toHaveBeenCalled();
});
Short answer: don't.
Testing an internal state variable very much goes against the philosophy behind react-testing-library. That lib is focused on the user, and what the user can see. The user has no concept of a state variable. Instead of testing that, think of the changes the user would see, and test for that. How is the UI updated? What different markup or styling is displayed? Consider how to test from the user's perspective, and you'll find your answers.
Check out react-testing-library's Guiding Principles for more context. Then take a look through this page for guidelines on which queries make the most sense for your use case.

Window is not defined in Next.js React app

In my Next.js app I can't seem to access window:
Unhandled Rejection (ReferenceError): window is not defined
componentWillMount() {
console.log('window.innerHeight', window.innerHeight);
}
̶A̶n̶o̶t̶h̶e̶r̶ ̶s̶o̶l̶u̶t̶i̶o̶n̶ ̶i̶s̶ ̶b̶y̶ ̶u̶s̶i̶n̶g̶ ̶p̶r̶o̶c̶e̶s̶s̶.̶b̶r̶o̶w̶s̶e̶r ̶ ̶t̶o̶ ̶j̶u̶s̶t̶ ̶e̶x̶e̶c̶u̶t̶e̶ ̶ ̶y̶o̶u̶r̶ ̶c̶o̶m̶m̶a̶n̶d̶ ̶d̶u̶r̶i̶n̶g̶ ̶r̶e̶n̶d̶e̶r̶i̶n̶g̶ ̶o̶n̶ ̶t̶h̶e̶ ̶c̶l̶i̶e̶n̶t̶ ̶s̶i̶d̶e̶ ̶o̶n̶l̶y̶.
But process object has been deprecated in Webpack5 and also NextJS, because it is a NodeJS variable for backend side only.
So we have to use back window object from the browser.
if (typeof window !== "undefined") {
// Client-side-only code
}
Other solution is by using react hook to replace componentDidMount:
useEffect(() => {
// Client-side-only code
})
Move the code from componentWillMount() to componentDidMount():
componentDidMount() {
console.log('window.innerHeight', window.innerHeight);
}
In Next.js, componentDidMount() is executed only on the client where window and other browser specific APIs will be available. From the Next.js wiki:
Next.js is universal, which means it executes code first server-side,
then client-side. The window object is only present client-side, so if
you absolutely need to have access to it in some React component, you
should put that code in componentDidMount. This lifecycle method will
only be executed on the client. You may also want to check if there
isn't some alternative universal library which may suit your needs.
Along the same lines, componentWillMount() will be deprecated in v17 of React, so it effectively will be potentially unsafe to use in the very near future.
If you use React Hooks you can move the code into the Effect Hook:
import * as React from "react";
export const MyComp = () => {
React.useEffect(() => {
// window is accessible here.
console.log("window.innerHeight", window.innerHeight);
}, []);
return (<div></div>)
}
The code inside useEffect is only executed on the client (in the browser), thus it has access to window.
With No SSR
https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
The error occurs because window is not yet available, while component is still mounting. You can access window object after component is mounted.
You can create a very useful hook for getting dynamic window.innerHeight or window.innerWidth
const useDeviceSize = () => {
const [width, setWidth] = useState(0)
const [height, setHeight] = useState(0)
const handleWindowResize = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
}
useEffect(() => {
// component is mounted and window is available
handleWindowResize();
window.addEventListener('resize', handleWindowResize);
// unsubscribe from the event on component unmount
return () => window.removeEventListener('resize', handleWindowResize);
}, []);
return [width, height]
}
export default useDeviceSize
Use case:
const [width, height] = useDeviceSize();
componentWillMount() lifecycle hook works both on server as well as client side. In your case server would not know about window or document during page serving, the suggestion is to move the code to either
Solution 1:
componentDidMount()
Or, Solution 2
In case it is something that you only want to perform in then you could write something like:
componentWillMount() {
if (typeof window !== 'undefined') {
console.log('window.innerHeight', window.innerHeight);
}
}
In the constructor of your class Component you can add
if (typeof window === 'undefined') {
global.window = {}
}
Example:
import React, { Component } from 'react'
class MyClassName extends Component {
constructor(props){
super(props)
...
if (typeof window === 'undefined') {
global.window = {}
}
}
This will avoid the error (in my case, the error would occur after I would click reload of the page).
global?.window && window.innerHeight
It's important to use the operator ?., otherwise the build command might crash.
Best solution ever
import dynamic from 'next/dynamic';
const Chart = dynamic(()=> import('react-apexcharts'), {
ssr:false,
})
A bit late but you could also consider using Dynamic Imports from next turn off SSR for that component.
You can warp the import for your component inside a dynamic function and then, use the returned value as the actual component.
import dynamic from 'next/dynamic'
const BoardDynamic = dynamic(() => import('../components/Board.tsx'), {
ssr: false,
})
<>
<BoardDynamic />
</>
I have to access the hash from the URL so I come up with this
const hash = global.window && window.location.hash;
Here's an easy-to-use workaround that I did.
const runOnClient = (func: () => any) => {
if (typeof window !== "undefined") {
if (window.document.readyState == "loading") {
window.addEventListener("load", func);
} else {
func();
}
}
};
Usage:
runOnClient(() => {
// access window as you like
})
// or async
runOnClient(async () => {
// remember to catch errors that might be raised in promises, and use the `await` keyword wherever needed
})
This is better than just typeof window !== "undefined", because if you just check that the window is not undefined, it won't work if your page was redirected to, it just works once while loading. But this workaround works even if the page was redirected to, not just once while loading.
I was facing the same problem when i was developing a web application in next.js This fixed my problem, you have to refer to refer the window object in a life cycle method or a react Hook. For example lets say i want to create a store variable with redux and in this store i want to use a windows object i can do it as follows:
let store
useEffect(()=>{
store = createStore(rootReducers, window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__())
}, [])
....
So basically, when you are working with window's object always use a hook to play around or componentDidMount() life cycle method
I wrapped the general solution (if (typeof window === 'undefined') return;) in a custom hook, that I am very pleased with. It has a similiar interface to reacts useMemo hook which I really like.
import { useEffect, useMemo, useState } from "react";
const InitialState = Symbol("initial");
/**
*
* #param clientFactory Factory function similiar to `useMemo`. However, this function is only ever called on the client and will transform any returned promises into their resolved values.
* #param deps Factory function dependencies, just like in `useMemo`.
* #param serverFactory Factory function that may be called server side. Unlike the `clientFactory` function a resulting `Promise` will not be resolved, and will continue to be returned while the `clientFactory` is pending.
*/
export function useClientSideMemo<T = any, K = T>(
clientFactory: () => T | Promise<T>,
deps: Parameters<typeof useMemo>["1"],
serverFactory?: () => K
) {
const [memoized, setMemoized] = useState<T | typeof InitialState>(
InitialState
);
useEffect(() => {
(async () => {
setMemoized(await clientFactory());
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
return typeof window === "undefined" || memoized === InitialState
? serverFactory?.()
: memoized;
}
Usage Example:
I am using it to dynamically import libaries that are not compatible with SSR in next.js, since its own dynamic import is only compatible with components.
const renderer = useClientSideMemo(
async () =>
(await import("#/components/table/renderers/HighlightTextRenderer"))
.HighlightTextRendererAlias,
[],
() => "text"
);
As you can see I even implemented a fallback factory callback, so you may provide a result when initially rendering on the server aswell. In all other aspects this hook should behave similiar to reacts useMemo hook. Open to feedback.
For such cases, Next.js has Dynamic Import.
A module that includes a library that only works in the browser, it's suggested to use Dynamic Import. Refer
Date: 06/08/2021
Check if the window object exists or not and then follow the code along with it.
function getSelectedAddress() {
if (typeof window === 'undefined') return;
// Some other logic
}
For Next.js version 12.1.0, I find that we can use process.title to determine whether we are in browser or in node side. Hope it helps!
export default function Projects(props) {
console.log({ 'process?.title': process?.title });
return (
<div></div>
);
}
1. From the terminal, I receive { 'process?.title': 'node' }
2. From Chrome devtool, I revice { 'process?.title': 'browser' }
I had this same issue when refreshing the page (caused by an import that didn't work well with SSR).
What fixed it for me was going to pages where this was occurring and forcing the import to be dynamic:
import dynamic from 'next/dynamic';
const SomeComponent = dynamic(()=>{return import('../Components/SomeComponent')}, {ssr: false});
//import SomeComponent from '../Components/SomeComponent'
Commenting out the original import and importing the component dynamically forces the client-side rendering of the component.
The dynamic import is covered in Nextjs's documentation here:
https://nextjs.org/docs/advanced-features/dynamic-import
I got to this solution by watching the youtube video here:
https://www.youtube.com/watch?v=DA0ie1RPP6g
You can define a state var and use the window event handle to handle changes like so.
const [height, setHeight] = useState();
useEffect(() => {
if (!height) setHeight(window.innerHeight - 140);
window.addEventListener("resize", () => {
setHeight(window.innerHeight - 140);
});
}, []);
You can try the below code snippet for use-cases such as - to get current pathname (CurrentUrl Path)
import { useRouter } from "next/router";
const navigator = useRouter()
console.log(navigator.pathname);
For anyone who somehow cannot use hook (for example, function component):
Use setTimeout(() => yourFunctionWithWindow()); will allow it get the window instance. Guess it just need a little more time to load.
I want to leave this approach that I found interesting for future researchers. It's using a custom hook useEventListener that can be used in so many others needs.
Note that you will need to apply a little change in the originally posted one, like I suggest here.
So it will finish like this:
import { useRef, useEffect } from 'react'
export const useEventListener = (eventName, handler, element) => {
const savedHandler = useRef()
useEffect(() => {
savedHandler.current = handler
}, [handler])
useEffect(() => {
element = !element ? window : element
const isSupported = element && element.addEventListener
if (!isSupported) return
const eventListener = (event) => savedHandler.current(event)
element.addEventListener(eventName, eventListener)
return () => {
element.removeEventListener(eventName, eventListener)
}
}, [eventName, element])
}
If it is NextJS app and inside _document.js, use below:
<script dangerouslySetInnerHTML={{
__html: `
var innerHeight = window.innerHeight;
`
}} />

Categories