react native unsubscribe event listener - react-native

I have a react native component with two event listeners for linking and for dynamicLinks, how do I unsubscribe for both using hooks?
useEffect(() => {
// Update the document title using the browser API
if (Platform.OS === "ios") {
SecurityScreen.enabled(true);
}
// global.perra = "a";
usingAlternativeAPI();
Linking.addEventListener("url", deepLinkHandler);
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
// When the component is unmounted, remove the listener
return () => unsubscribe();
}, []);

Linking lib has a removeEventListener() function you can call with passing the url event type and the handler. This code should work.
useEffect(() => {
// useEffect code here
return function cleanup() {
unsubscribe();
Linking.removeEventListener("url", deepLinkHandler);
};
}, []);

Have you tried this before?
useEffect(() => {
// Update the document title using the browser API
if (Platform.OS === "ios") {
SecurityScreen.enabled(true);
}
// global.perra = "a";
usingAlternativeAPI();
const un = Linking.addEventListener("url", deepLinkHandler);
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
// When the component is unmounted, remove the listener
return () => {
unsubscribe();
un()
}
}, []);

At the moment the documentation points to do this way,
useEffect(() => {
const unsub = Linking.addEventListener("url", ({ url: _url }) => {
setUrl(_url);
});
return unsub.remove();
}, []);

Related

How to clean up React-Native useEffect with axios

Currently I have defined in a functional component a useEffect as below
useEffect(() => {
(async function () {
posts.current = await BlogConsumer.getBlogPosts();
setLoading(false);
})();
return () => {
BlogConsumer.call_controller.abort();
};
}, []);
where this BlogConsumer is defined as below
class BlogConsumer {
static posts = {};
static call_controller = new AbortController();
static async getBlogPosts() {
await axios
.get('https://nice.api', {
signal: this.call_controller.signal,
})
.then(response => {
// treatment for success
})
.catch(error => {
// treatment for erros
});
return this.posts;
}
}
export default BlogConsumer;
The overral ideia is that in the render of the component I'll be calling a static method from my consumer and will retrieve the necessary data. For the pourpuse of not having memory leaks, I have my callback function in my useEffect that will abort my call whenever I unmount the component, but this is not working. React's message of Warning: Can't perform a React state update on an unmounted component. still appears if I enter the component and leave the screen before the API call is finished. I don't know where I am wrong, so I'd like a little help.
Thanks in advance.
You could just cancel the request on unmount. Like this:
export const fetchData = async (signal) => {
try {
const res = await instance.get("/pages/home", {
signal,
});
return res.data;
} catch (error) {
return Promise.reject(error);
}
};
useEffect(() => {
const controller = new AbortController();
fetchData(controller.signal);
return () => {
controller.abort()
};
}, []);

Firestore keep loading old changes

I'm trying to create a firestore listener, which handles changes on my collection. After some research, I implement the feature as below.
useEffect(() => {
const firebaseApp = getFirebaseApp();
const db = firestore(firebaseApp);
const handleSnapshotChanges = ( snapshot: FirebaseFirestoreTypes.QuerySnapshot<FirebaseFirestoreTypes.DocumentData> ) => {
const changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === "added") {
console.log(change.doc);
console.log(change.type);
}
if (change.type === "modified") {
console.log("Doc modified");
}
if (change.type === "removed") {
console.log("Remove doc");
}
});
};
const query = db.collection("history");
const unsubscribe = query.onSnapshot(handleSnapshotChanges, (err) =>
console.log(err)
);
return () => {
unsubscribe();
};
}, []);
If I doing so, every time I enter the screen where I put the above useEffect, firestore keeps loading all documents in the collection and marks them as added. How can I implement this function properly.

How to use focus and blur listener in single useEffect react native

As you know in useEffect we return the unsubscribe at the end if we assign any listener to unsubscribe const as shown under
As we Using
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// code
})
return unsubscribe;
}, [navigation]);
As I want
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// code
})
const unsubscribe2 = navigation.addListener('blur', () => {
// code
})
// need to return both listeners
}, [navigation]);
You can cleanup like this
useEffect(() => {
navigation.addListener('focus', handler)
navigation.addListener('blur', handler)
return () => {
navigation.removeListener('focus', handler)
navigation.removeListener('blur', handler)
}
},[navigation])
The official example here https://reactjs.org/docs/hooks-effect.html#effects-with-cleanup
I didn't test this, but you might be able to do something like this:
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// code
});
const unsubscribe2 = navigation.addListener('blur', () => {
// code
});
return () => {
// executed when unmount
unsubscribe();
unsubscribe2();
}
}, [navigation]);

AppState removeEventListener not removing listener

Simple code:
useEffect(() => {
AppState.addEventListener("change", handleChangeEvent);
return (() => {
console.log("REMOVING EVENT LISTENER");
AppState.removeEventListener("change", handleChangeEvent);
});
}, []);
const handleChangeEvent = () => {
console.log("EVENT LISTENER FIRING");
};
Navigate to new screen:
const changeScreen = () => {
return props.navigation.navigate("MainView", {})
}
When arriving at new screen: The REMOVING EVENT LISTENER fires. But in this new screen, the event listener is still listening.
Why is it not being removed?
try using useFocussedEffect instead,
useFocusEffect(
React.useCallback(() => {
AppState.addEventListener("change", handleChangeEvent);
return () => {
AppState.removeEventListener("change", handleChangeEvent);
}
}, [])
);

How to test 'didFocus' from react navigation with jest

I have a component that looks like this
async componentDidMount() {
const { navigation } = this.props
this.subs = [
navigation.addListener('didFocus', () => this.onComponentFocus()),
]
}
onComponentFocus() {
const { dispatch } = this.props
dispatch(fetchDevices())
}
Now i want to write a test that chekcs fetchDevice got called once. The first idea was to mock Navigation like this
const navigation = {
navigate: jest.fn(),
}
But now how do I check this.subs and how do i check fetchDevices got fired?
If we suppose that fetchDevices comes from a library
Component.spec.js
import fetchDevices from 'device-fetcher';
jest.mock('device-fetcher');
// as your component accepts the dispatch function
// you can create it as mock function
const mockDispatch = jest.fn();
// since in your implementation you're calling navigation.addListener
const mockNavigation = {
navigate: jest.fn(),
// it should also have
addListener: jest.fn()
};
describe('Component', () => {
const wrapper = shallow(<Component navigation={mockNavigation} dispatch={mockDispatch} />);
describe('navigation didFocus', () => {
beforeAll(() => {
// get .addEventListener calls with 'didFocus'
mockNavigation.addEventListener.mock.calls
.filter(([eventName]) => eventName === 'didFocus')
// iterate over the "attached" handlers
.map(([eventName, eventHandler]) => {
// and trigger them
eventHandler();
});
});
it('should have called the dispatch with the result of fetchDevices', () => {
expect(mockDispatch).toHaveBeenCalledWith(
fetchDevices.mock.results[0].value
);
});
});
});
note: it's not tested, just a solution outline
edit: if the fetchDevices is a property instead of mocking the library you define a mock function
const fetchDevices = jest.fn();
// and pass it to the component
shallow(<Component navigation={mockNavigation} dispatch={mockDispatch} fetchDevices={fetchDevices} />);
and then you should have the same assertions for it