react native DeviceEventEmitter unsubscribe from event - javascript

I'm using DeviceEventEmitter to handle events of a favorite method, to which is subscribed in the constructor:
DeviceEventEmitter.addListener("FavoriteClick", async (e) =>
{
// do something
})
This event listener stays active whenever the components unmounts (permenantly). What do I have to call to unsub? I've tried storing the event as a variable and calling listener.removeCurrentListener() in the componentWillUnmount() like the (limited) documentation states, if I understand that correctly, but removeCurrentListener() is not a method.

DeviceEventEmitter is deprecated, you should use NativeEventEmitter instead.
Example :
import { NativeEventEmitter, NativeModules } from 'react-native';
const { CalendarManager } = NativeModules;
const calendarManagerEmitter = new NativeEventEmitter(CalendarManager);
const subscription = calendarManagerEmitter.addListener(
'EventReminder',
(reminder) => console.log(reminder.name)
);
...
// Don't forget to unsubscribe, typically in componentWillUnmount
subscription.remove();

Related

Getting a document and window is not defined error in NextJS [duplicate]

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).
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 />
</>
global?.window && window.innerHeight
It's important to use the operator ?., otherwise the build command might crash.
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;
`
}} />

Invalid hook call error when trying to set state

I have a scenario where I am forced to call a trigger method to show a modal from two different places, one using a hotkey combination and another by clicking on a toolbar button. In order to do so I have the following code, where I call the triggerCustomLinkModal to set the state but then I am hit with the Invalid Hook call error.
import { useState, useCallback, useEffect } from "react"
import { Dialog } from "#blueprintjs/core"
const useLocalState = () => {
const [isShown, setIsShown] = useState(false)
const setState = useCallback((state) => {
setIsShown(state)
})
const getState = useCallback(() => {
return isShown
})
return {
setState,
getState
}
}
export const CustomLinkModalUI = () => {
const { getState } = useLocalState()
return (
<>
<Dialog isOpen={getState()} />
</>
)
}
export const triggerCustomLinkModal = () => {
const { setState } = useLocalState()
setState()
}
Expanding from Chris answer in the comments ( You can't use hooks outside React components. -> so you can't call useLocalState() inside triggerCustomLinkModal since triggerCustomLinkModal is not a React component ):
You don't really need the useCallback hook or even the functions itself. Aaccording to react docs :
Note
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
This also means that using useCallback hook to set a state it doesn't really make sense (because useCallback role is just to return a memoized callback)
What you basically need is a state set up in the closest parrent component and pass the setIsShown as a prop as well as the isShown function.
Your current implementation, even if it weren't for the error, it wouldn't refer to the same state since on each useLocalState() you are initializing a fresh new state (so you are not pointing to the same state in CustomLinkModalUI and triggerCustomLinkModal)

pass custom event properties with fireEvent (testing-library and jest)

What I would like to do
I would like to pass some custom properties to an event during some tests (using react-testing-library and jest). I am using the fireEvent function. I understand from the docs that the properties in the second argument are added to the event. This is what I can't do at the moment.
Minimal reproducible example
import React from 'react'
import { render, fireEvent } from '#testing-library/react'
test('check event', () => {
const DOM = render(
<div
onClick={event => {
console.log(event.foo)
}}
>
Click Me
</div>
)
// here I am expecting foo to be a property on the event passed
// to the event handler. But that doesn't happen.
fireEvent.click(DOM.getByText('Click Me'), { foo: 'bar' })
})
The result is that undefined is logged.
Approaches I've tried / thoughts
I have tried various variations of this using different event types, using createEvent, using custom events, manually adding an event listener etc. and I can't seem to access any of the event properties I pass in with any of these variations.
I've looked under the cover a bit at what's going on in fireEvent here. It certainly looks like those additional properties should be added.
The fireEvent function allows initializing intrinsic properties of Event objects, but it doesn't add arbitrary properties. For example, calling
fireEvent.click(DOM.getByText('Click Me'), { button: 2 })
dispatches a MouseEvent with its button property set to 2.
Note that you may want to revisit how you're testing your component—passing custom properties to an event in a test runs counter to the guiding principle of the DOM Testing Library:
The more your tests resemble the way your software is used, the more confidence they can give you.
However, your workflow is technically possible by passing custom properties to the detail property of a CustomEvent. This approach could be feasible depending on your goals, and perhaps in conjunction with an onClick handler. For example, this logs bar:
import React, { useEffect, useRef } from 'react'
import { fireEvent, render } from '#testing-library/react'
test('custom event', () => {
const MyComponent = ({ customEventHandler, children }) => {
const ref = useRef(null)
useEffect(() => {
ref.current.addEventListener('my-event', customEventHandler)
return () => {
ref.current.removeEventListener('my-event', customEventHandler)
}
}, [customEventHandler])
return <div ref={ref}>{children}</div>
}
const customEventHandler = (event) => {
console.log(event.detail.foo)
}
const { getByText } = render(
<MyComponent customEventHandler={customEventHandler}>
Click Me
</MyComponent>
)
const elem = getByText('Click Me')
const event = createEvent(
'my-event',
elem,
{
detail: {
foo: 'bar',
},
},
{ EventType: 'CustomEvent' }
)
fireEvent(elem, event)
})

Add multiple observers to this.listener to React component class

I want to add multiple observer functions to a React component. I use Firebase authentication and want to trigger actions
when the user first signs in or out: onauthstatechanged
everytime the idToken changes: onidtokenchanged
For this component I followed this tutorial:
export class AuthContextProvider extends React.Component {
componentDidMount() {
if (!this.state.firebase) {
/** set listener whether user is signed in */
this.listener = this.props.firebase.auth().onAuthStateChanged(
authUser => {
if (authUser) {
this.setState({ authUser });
} else {
this.setState({ authUser: null });
}
},
);
/**
* ########
* Question: Can I just add another function to this.listener?
* Will both listeners trigger the appropriate times?
* ########
*/
this.listener = this.props.firebase.auth().onIdTokenChanged(
authUser => {
if (authUser) {
// do something
}
},
);
}
}
}
It seemed to work in my implementation. Both events are triggered. But it seems wrong to me since I am reassigning this.listener when using .onIdTokenChanged().
How can I add both onAuthStateChanged AND onIdTokenChanged to this.listener correctly?
My question is also about this.listener. What exactly does it do? Where can I find documentation about it? Typing '"this.listener" react' shows many results about window.addEventListener. Is this an equivalent and achieves the same goal?
I assume this in this.listener refers to my class which extends React.Component. The React documentation does not mention anything about this listener property. Please correct me here if this refers to something else.
#Ross Allen correctly pointed out in his comment:
this is the component instance, but listener is unrelated to React; it's an arbitrary instance variable name. You won't find anything in React documentation about it because it's not specific to React. The second time you assign this.listener = you are writing to the same variable and losing the reference to the onAuthStateChanged handler.
The solution is to define two different arbitrary variables that are assigned to the component class:
componentDidMount() {
if (!this.state.firebase) {
// rename to onAuthStateChangedlistener
this.onAuthStateChangedlistener = this.props.firebase.auth().onAuthStateChanged(
authUser => {
if (authUser) {
this.setState({ authUser });
} else {
this.setState({ authUser: null });
}
},
);
// rename to onIdTokenChangedlistener
this.onIdTokenChangedlistener = this.props.firebase.auth().onIdTokenChanged(
authUser => {
if (authUser) {
// do something
}
},
);
}
}
Now both listeners are active.

React Hook "useState" or React Hook "useEffect" is called in function which is neither a React function

TL;DR: My GOAL is to separate the API functions, and import them when I need them. And then call them under a componentDidMount scenario. Also, I've been told that async and await shall be used with, since: getCurrentPosition is an asynchronous function.
All the hints that you need to solve your problem are in the error code
Hooks or custom hooks are meant to be used within functional components
A custom hook is a hook that can be called like a function. It however must be prefixed with use to let react know that it is a custom hook
According to the above condition, your Weather component is a class component which either you need to convert to Functional component or avoid using geolocation as a custom hook
Secondly, since geoLocation is meant to be a custom hook you must call it useGetLocation
import React from 'react';
import { useGetLocation } from './getlocation';
const Weather = (props) => {
const geoLocation = useGetLocation();
useEffect(() => {
document.title = "Weather";
}, []);
return(
<>
<h1>Weather</h1>
<h2>{React.version}</h2>
</>
);
}
export default Weather;
import {useState, useEffect} from 'react';
export const useGetLocation = () => {
const [position, setPosition] = useState({});
const [error, setError] = useState(null);
const successHandler = ({coords}) => {
setPosition({
latitude: coords.latitude,
longitude: coords.longitude
});
};
const errorHandler = (error) => { setError(error.message); };
useEffect(() => {
if (!navigator.geolocation) {
setError("Geolocation might not be supported.");
return;
}
navigator.geolocation.getCurrentPosition(
successHandler,
errorHandler);
return () => {}
}, []);
return [position, error];
};
Working demo
Firstly, you have to call the hook inside of a React functional component, and not a class.
The docs:
Hooks ... let you use state and other React features without writing a class.
and
Only Call Hooks from React Functions
Secondly, change getGeolocation to useGeolocation
The docs:
A custom Hook is a JavaScript function whose name starts with ”use” ...
If a function doesn't start with "use", React won't treat it as a hook and won't allow you to call hooks inside it
You can't invoke a hook inside a class. Hooks can only be called inside stateless components. If you want to have access to that information you should either transform it in a HOC or transform the class of the componentDidMount in a function.
https://reactjs.org/docs/hooks-faq.html#what-can-i-do-with-hooks-that-i-couldnt-with-classes

Categories

Resources