How can I detect browser back button in react class component - javascript

I am trying to create a function when user hit browser back button it will run function deleteHeldResort that I created.
Here is my code for deleteHeldResort:
deleteHeldResorts(ReservedInventoryID: number | null = null, refreshHeldResorts: boolean = true) {
return new Promise((resolve, reject) => {
this.setState({heldResortsShowLoader: true});
this.reservationService.deleteHeldResorts(ReservedInventoryID)
.then(() => {
refreshHeldResorts && this.getHeldResorts();
resolve();
})
.catch((error: any) => {
this.catchHeldResortsError(error);
reject(error);
});
this.setState({
heldResortsShowLoader: false
});
});
}
Updated code base:
handleNavigateBack = useCallback(
(event) => {
// call your function here with whatever argument your code provides
this.reservationService.deleteHeldResorts(this.props.resId);
}
, []);
useEffect(() => {
document.addEventListener('popstate', this.handleNavigateBack );
return () => {
document.removeEventListener('popstate', this.handleNavigateBack)
}
}, [this.handleNavigateBack]);

Updated answer: class component based
Since you're using class components, I'm adding this update.
Inside the class component having deleteHeldResorts function, you can listen to popstate event:
Remark how bind keyword helps us keep the right this.
import React from "react";
import { render } from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.handleNavigateBack = this.handleNavigateBack.bind(this);
}
componentDidMount() {
document.addEventListener("popstate", this.handleNavigateBack);
}
componentWillUnmount() {
document.removeEventListener("popstate", this.handleNavigateBack);
}
handleNavigateBack(event) {
console.log("inside callback", event);
// change arguments as you want
this.deleteHeldResorts(null, false);
}
deleteHeldResorts(ReservedInventoryID = null, refreshHeldResorts = true) {
// your function goes down there content
console.log("inside deleteHeldResorts");
}
render() {
return <h2>popstate browser listener</h2>;
}
}
render(<App />, document.getElementById("root"));
Initial answer: function component based
Inside the component having deleteHeldResorts function, you can listen to popstate event:
// your-component.js
function YourComponent() {
function deleteHeldResorts(ReservedInventoryID: number | null = null,
refreshHeldResorts: boolean = true) {
// your function content
}
const handleNavigateBack = useCallback(
(event) => {
// call your function here with whatever argument your code provides
deleteHeldResorts(reservedInventoryID,refreshHeldResorts);
}
// depending on your logic, add deps to this array dependency
}, [])
useEffect(() => {
document.addEventListener('popstate', handleNavigateBack );
return () => {
document.removeEventListener('popstate', handleNavigateBack)
}
}, [handleNavigateBack])
return ( <>something dope</>);
}

Related

Notify other component by raising a manual event

A have two files, with two functional components A and B, in the first component A, i have a specialFunction that gets called with onClick, what i want to do is raise an event in specialFunction when it's called, and then in component B add a Listener for the event in specialFunction.
Component A:
function specialFunction(){
//raise the event and send some data
}
Component B:
//contains a listener that does some work when specialFunction is called, example:
(data) => {console.log("am called:",data)};
1. Create notifier class using observer pattern
class ChangeNotifier {
subscribers = [];
subscribe(callback) {
this.subscribers.push(callback);
}
unsubscribe(callback) {
const index = this.subscribers.indexOf(callback);
if (index > -1) {
this.subscribers.splice(index, 1);
}
}
notifyAll(data) {
this.subscribers.forEach(callback => callback(data));
}
}
2. ComponentA receives notifier as a prop and used to notify all subscribers
const ComponentA = ({ notifier }) => {
const triggerNotifier = () => {
notifier.notifyAll('Some data that will subscribers receive');
}
return <div>{/** Some content */}</div>
}
3. ComponentB receives notifier and subscribes to it to receive data sent by from ComponentB
const ComponentB = ({ notifier }) => {
useEffect(() => {
const callbackFn = data => {/** Do whatever you want with received data */ }
notifier.subscribe(callbackFn);
return () => notifier.unsubscribe(callbackFn);
}, [])
}
4. App holds both component. Create instance of notifier there and pass as a props
const App = () => {
const dataNotifier = new ChangeNotifier();
return <div>
<ComponentA notifier={dataNotifier} />
<ComponentB notifier={dataNotifier} />
</div>
}
If you have components on different levels deeply nested and it is hard to pass notifier as a prop, please read about React Context which is very helpful when you want to avoid property drilling
React Context
Here's implementation with context
class ChangeNotifier {
subscribers = [];
subscribe(callback) {
this.subscribers.push(callback);
return this.unsubscribe.bind(this, callback);
}
unsubscribe(callback) {
const index = this.subscribers.indexOf(callback);
if (index > -1) {
this.subscribers.splice(index, 1);
}
}
notifyAll(data) {
this.subscribers.forEach(callback => callback(data));
}
}
const NotifierContext = React.createContext();
const ComponentA = () => {
const { notifier } = useContext(NotifierContext);
const triggerNotifier = () => {
notifier.notifyAll('Some data that will subscribers receive');
}
return <div><button onClick={triggerNotifier}>Notify</button></div>
}
const ComponentB = () => {
const { notifier } = useContext(NotifierContext);
useEffect(() => {
const callbackFn = data => { console.log(data) }
notifier.subscribe(callbackFn);
return () => notifier.unsubscribe(callbackFn);
}, [notifier])
}
Now all components wrapped in NotifierContext.Provider (no matter how deep they are nested inside other components) will be able to use useContext hook to receive context value passed as value prop to NotifierContext.Provider
const App = () => {
const dataNotifier = useMemo(() => new ChangeNotifier(), []);
return <NotifierContext.Provider value={{ notifier: dataNotifier }}>
<ComponentA />
<ComponentB />
</NotifierContext.Provider>
}
export default App;
Last but not least, I guess you can avoid context or properties drilling and just create instance of ChangeNotifier in some utility file and export it to use globally...
Andrius posted a really good answer, but my problem was that the two components, one of them is used as an API, and the other had a parent component, am a beginner so maybe there is a way to use them but i just didn't know how.
The solution that i used, (maybe not the best) but did the job was to dispatch a custom event in a Promise from the specialFunction:
function specialFunction(){
new Promise((resolve) => {
console.log("am the promise");
document.dispatchEvent(event);
resolve();
});
And add a Listener in the other component using a useEffect hook:
useEffect(() => {
let handlePreview = null;
new Promise((resolve) => {
document.addEventListener(
"previewImg",
(handlePreview = (event) => {
event.stopImmediatePropagation();
//Stuff...
})
);
return () =>
window.removeEventListener("previewImg", handlePreview, false);
});
}, []);
Thank you for your help.

Trouble dealing with setState async function equivalent with hooks

import React from "react";
import { UserContext } from "./../contexts";
import {
removeStoredAuthData,
storedAuthIsValid,
storeNewAuthData,
} from "./../utils/auth";
import { getUserInfos } from "./../api/userAuthentication";
class UserProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
user: "",
};
}
render() {
return (
<UserContext.Provider
value={{
user: this.state.user,
clearUserProfile: () => {
const user = "";
removeStoredAuthData();
this.setState({ user });
},
saveUserProfile: (response) => {
const user = response.data;
storeNewAuthData(response);
this.setState({ user });
},
populateUserProfile: (displayLoader, hideLoader) => {
const storedToken = localStorage.getItem("appsante-token");
const storedId = localStorage.getItem("appsante-id");
if (storedAuthIsValid()) {
displayLoader(() => {
getUserInfos(storedId)
.then((response) => {
const user = { ...response.data, token: storedToken };
this.setState({ user }, hideLoader());
})
.catch((error) => console.log(error));
});
}
},
}}
>
{this.props.children}
</UserContext.Provider>
);
}
}
export default UserProvider;
Hi everyone !
I trying to convert a React class component into a function component, with hooks.
But I can't find a way to deal properly with that line :
this.setState({ user }, hideLoader());
Unlike setState in class components, useState doesn't take a callback as second parameter, and I can't find how to achieve it with useEffect.
Could anyone help me ? Thanks !
Because the loader's presence can't be determined from the value in / change in user alone, you'll need another state variable, maybe one that contains the callback - perhaps call it hideLoader. After getUserInfos resolves, call setHideLoader with the callback, so that a useEffect hook with that function as a dependency can see the change and call the callback:
const [hideLoader, setHideLoader] = useState();
useEffect(() => {
if (hideLoader) {
hideLoader(); // or, if this is a HOF: hideLoader()()
setHideLoader(); // callback done; remove callback from state
}
}, [hideLoader]);
// ...
populateUserProfile: (displayLoader, hideLoaderParam) => {
// ...
getUserInfos(storedId)
.then((response) => {
setUser({ ...response.data, token: storedToken }); // hook version
setHideLoader(hideLoaderParam);
})
and the rest of your code can be mostly the same - only call setHideLoader up above, inside getUserInfos.
I think you should do this :-
import React, { useState } from 'react';
const [user, setUser] = useState("");
populateUserProfile: async (displayLoader, hideLoader) => {
const storedToken = localStorage.getItem("appsante-token");
const storedId = localStorage.getItem("appsante-id");
if (storedAuthIsValid()) {
displayLoader();
let response = await getUserInfos(storedId)
const user = { ...response.data, token: storedToken };
setUser(user);
hideLoader();
};
}

React-Navigation: Call function whenever page is navigated to

I am developing a React-Native app using React-Navigation, and I am using a stack navigator.
How can I call a function whenever a page is navigated to, including on goBack() events? If I place the method in my constructor, it is only triggered on its initial creation, and not when it is attained through goBack().
use Navigation Events
I believe you can use did focus and will blur
<NavigationEvents
onWillFocus={payload => console.log('will focus', payload)}
onDidFocus={payload => console.log('did focus', payload)}
onWillBlur={payload => console.log('will blur', payload)}
onDidBlur={payload => console.log('did blur', payload)}
/>
https://reactnavigation.org/docs/en/navigation-events.html
EDIT 2022
import { useIsFocused } from '#react-navigation/native';
const isFocused = useIsFocused();
React.useEffect(()=>{
if(isFocused){
// callback
}
},[isFocused])
As you noted the component is never unmounted when changing pages, so you can't rely on the constructor or even componentDidMount. There is a lot of discussion about this topic in this issue.
You could e.g. listen to the didFocus and willBlur events and only render your page when it is focused.
Example
class MyPage extends React.Component {
state = {
isFocused: false
};
componentDidMount() {
this.subs = [
this.props.navigation.addListener("didFocus", () => {
this.setState({ isFocused: true })
}),
this.props.navigation.addListener("willBlur", () => {
this.setState({ isFocused: false })
})
];
}
componentWillUnmount() {
this.subs.forEach(sub => sub.remove());
}
render() {
const { isFocused } = this.state;
if (!isFocused) {
return null;
}
return <MyComponent />;
}
}

debounced function not being called in a react component

I have the react component below.
The raiseCriteriaChange method is called but the this.props.onCriteriaChange(this.state.criteria) line is never reached.
Any ideas why the code never reaches this.props.onCriteriaChange?
import * as React from 'react';
import { debounce } from 'lodash';
interface CustomerSearchCriteriaProps {
onCriteriaChange: (criteria: string) => void;
}
interface CustomerSearchCriteriaState {
criteria: string;
}
class CustomerSearchCriteria extends React.Component<CustomerSearchCriteriaProps, CustomerSearchCriteriaState> {
constructor() {
super();
this.state = {
criteria: ''
};
}
// problem method:
raiseCriteriaChange = () => {
// the call reaches here fine ...
debounce(
() => {
// ... but the call never reaches here ... why is this?
this.props.onCriteriaChange(this.state.criteria);
},
300);
}
handleCriteriaChange = (e: React.FormEvent<HTMLInputElement>) => {
this.setState({ criteria: e.currentTarget.value }, () => {
this.raiseCriteriaChange();
});
}
render() {
return (
<div className="input-group">
<input
type="text"
value={this.state.criteria}
onChange={this.handleCriteriaChange}
className="form-control"
/>
</div>
);
}
}
export default CustomerSearchCriteria;
_.debounce simply returns a function that must be called. Try changing your code like:
raiseCriteriaChange = debounce(() => {
this.props.onCriteriaChange(this.state.criteria);
}, 300);
See _.debounce, which says it
(Function): Returns the new debounced function.
so you need to call it like
var debounced = debounce(
() => {
// ... but the call never reaches here ... why is this?
this.props.onCriteriaChange(this.state.criteria);
},
300
);
debounced();

React routing without resetting DOM elements

I am working on a POC app that includes a third-party map. Using react-router, each time the user navigates to the map this one is created from scratch (loader appearing during few seconds, zoom/rotation/modal displayed/etc reseted).
We could think about storing zoom/rotation/etc in component' state and re-apply them (which the API does not permit anyway) but we especially cannot afford the map initialization time on each map display, the user experience is just awful.
Is there a way to tell react to keep a copy of the DOM tree for this component ? To not reset it ?
I tried returning false from the 'shouldComponentUpdate' function without any success.
I could also develop my own router, where component's domContainerNodes are simply set to display:none, but it totally feels like breaking React spirit.
Please, any clue is welcomed :-)
import React, { Component } from 'react';
import { browserHistory } from 'react-router'
import './Map.css';
class Map extends Component {
constructor(props) {
super(props);
console.log('Map props:');
console.log(props);
/**
* #see ../../README.md#using-global-variables
*/
this.ThirdPartyMapAPI = window.ThirdPartyMapAPI;
this.containerId = 'map-container';
this.isMapLoaded = false;
this.isMapReady = false;
this.areEventsBinded = false;
this.queuedAction = [];
this.state = {
poiId: props.routeParams.poiId,
poiType: props.routeParams.poiType,
};
}
bindEventHandlers = () => {
this.ThirdPartyMapAPI.Map.on('ready', () => {
this.isMapReady = true;
console.log('Map has successfully been loaded');
if (this.queuedAction.length > 0) {
this.queuedAction.pop()();
}
});
this.ThirdPartyMapAPI.Map.POI.on('tap', (data) => {
console.log('POI selected', data);
});
let commonErrorHandler = (error) => {
console.error('ThirdPartyMapAPI encountered an error: ', error);
};
this.ThirdPartyMapAPI.on('error', commonErrorHandler);
this.ThirdPartyMapAPI.Map.on('error', commonErrorHandler);
this.ThirdPartyMapAPI.Map.Route.on('error', commonErrorHandler);
this.areEventsBinded = true;
};
loadDataset = () => {
if (!this.areEventsBinded) {
this.bindEventHandlers();
}
if (!this.isMapLoaded && this.ThirdPartyMapAPI.load('MeL8ooso') === true) {
this.ThirdPartyMapAPI.Map.create(document.getElementById(this.containerId), { showMapTitle: false });
this.isMapLoaded = true;
}
this.applyOptions();
};
/**
* Apply options from querystring (e.g poiId to show)
*/
applyOptions = () => {
if (typeof this.state !== 'undefined') {
let action;
// Show a POI
if (typeof this.state.poiId !== 'undefined') {
let poiId = this.state.poiId;
action = () => {
this.ThirdPartyMapAPI.Map.POI.show(poiId);
/* Used to go back to POI list and test a new map display
window.setTimeout(() => {
browserHistory.push(`/list`);
}, 4000);*/
};
}
// Perform the action, or queue it if mobigeo is not ready yet
if (typeof action === 'function') {
if (this.isMapReady) {
action();
} else {
this.queuedAction.push(action);
}
}
}
};
componentDidMount() {
this.loadDataset();
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
shouldComponentUpdate() {
console.log('shouldComponentUpdate');
return false;
}
render() {
return <div id={this.containerId}></div>
}
}
export default Map;

Categories