Component not unmounting when user navigates to new screen - react-native

new to React Native here.
I have a simple component/screen with this code:
useEffect(() => {
console.log("OPENING LOGIN");
AppState.addEventListener("change", testFunction);
return (() => {
console.log("Removing HELLO LISTENER");
AppState.removeEventListener("change", testFunction);
});
}, []);
const testFunction = () => {
console.log("APPSTATE CHANGE FUNCTION RUNNING");
};
const changeScreen = () => {
return props.navigation.navigate("MainView", {});
};
This starts a eventListener when the component is mounted. When the component is unmounted, I log something else out, and would like to remove the same listener.
However, when the changeScreen function is fired, which navigates the user to a whole new component/screen, I do not receive the "Removing HELLO LISTENER" log, and the listener still fires.
My question is:
Why is this component not unmounted when naivagting to a new Screen?
Thank you for your time!

Related

Why this navigation.goBack is not working as I intended ? How can I get the Last Active State?

I got 3 pages
homepage, productList and productDetails
When going from homepage to productList I pass a route param,
navigation.navigate('productList', { showCategory: 'productListA'} )
InitialProcess when component mounted
Inside the productList page when the component is mounted. I am declaring use state like this.
const {showCateory} = route.params;
const [activeTab, setActiveTab] = useState(showCateory);
and calling api using that activeTab
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
activeTab,
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, []);
User Interaction
But I also add the button in the productList so that user can change the current active tab
<TouchableOpacity onPress={() => changeTab()}></TouchableOpacity>
const changeTab = async () => {
await setActiveTab('productListB'),
await dispatch(fetchProductList(activeTab)
}
Take note that right now active tab and data coming from api is different from when the component is start mounted.
Navigation Change again
When use goes from productList to productDetails. All thing is fine.
But inside the product details I am going back to productList with this.
navigation.goBack().
When I am back in productList page The activeTab is change back to productListA and the data is change back to when component is mounted
Can I pass or change the route params when calling navigation.goBack()?
add activeTab in useEffect depedineces.
as docs say
The array of dependencies is not passed as arguments to the effect function. Conceptually, though, that’s what they represent: every value referenced inside the effect function should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically.
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
//this value will always updated when activeTab change
activeTab,
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, [activeTab]); //<<<<< here
also you need to know setState() does not always immediately update the component. see here
so change this
const changeTab = async () => {
//await setActiveTab('productListB'),
//await dispatch(fetchProductList(activeTab)
setActiveTab('productListB')
dispatch(fetchProductList('productListB'))
}
This might be happening because route.params is still set to { showCategory: 'productListA'} when you are coming back to the screen.
If this is the case, you can fix it by Changing params object in changeTab() like
navigation.setParams({
showCategory: 'productListB',
});
I hope this will fix your problem.
This happens because the callback function inside the focus listener uses the initial value of the state when the function was defined (at initial page render) . Throughout the lifespan of listener the callback function uses this stale state value.You can read more about this behaviour in this answer
Although the answer by Ahmed Gaber works in this case as the listener is cleared and redefined after each state change.Another common work-around is to use an useRef instead of useEffect.A ref is basically a recipe that provides a mutable object that can be passed by reference.
In your case you can initialise activeTab with navigation param value using useRef hook as :
const activeTab = useRef(showCateory);
and the focus listener callback function should be changed to use the Reference current value as
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
activeTab.current, //<<<<<<---------here
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, []);
and the changeTab function can directly update reference current value
const changeTab = async () => {
setActiveTab.current = 'productListB';
dispatch(fetchProductList('productListB'))
}

navigation.getparam latest value is not accessible inside didFocus listener

I am having 2 screens in a react native app, both the screens are functional components.
A button on screen 1 leads to screen 2, I select a few checkboxes on screen 2 and click on a button to navigate to screen 1 with adding navigation params.
coming back on screen 1 runs didFocus listener, but param values are undefined, when I hit ctrl+s on in code editor, useEffect runs and values become accessible.
After this, going back to screen 1 from screen 2 runs didfocus listener (as expected) but the param values do not update.
below is useEffect code in screen 1.
useEffect(() => {
navigation.getParam('from') == 'TagFiltersScreen' ? getAllQuestions('mostInsightful', navigation.getParam('tagsFilter')) : getAllQuestions();
const listener = navigation.addListener('didFocus', () => {
navigation.getParam('from') == 'TagFiltersScreen' ? getAllQuestions('mostInsightful', navigation.getParam('tagsFilter')) : getAllQuestions();
});
return () => {
listener.remove();
}
}, []);
I faced the same issue, and here is how I am doing it.
useEffect(() => {
const isFocused = props.navigation.isFocused();
if (isFocused) {
const { params } = props.navigation.state;
navigationFocus(params);
}
const navigationFocusListener = props.navigation.addListener('willFocus', (payload) => {
const params = payload.state.params;
navigationFocus(params);
});
return () => {
navigationFocusListener.remove();
};
}, []);
const navigationFocus = (params) => {
if (params) {
}
}
I'll be curios to know if there is a better way of doing this.

How to use useFocusEffect hook

As the docs https://reactnavigation.org/docs/en/next/use-focus-effect.html,
"Sometimes we want to run side-effects when a screen is focused. A side effect may involve things like adding an event listener, fetching data, updating document title, etc."
I'm trying to use useFocusEffect to fetch data everytime that the user go to that page.
on my component I have a function which dispatch an action with redux to fetch the data:
const fetchData = ()=>{
dispatch(companyJobsFetch(userDetails.companyId));
};
Actually I'm using useEffect hook to call fetchData(), but I'd like to fetch data everytime that the user go to that page and not only when rendered the first time.
It's not clear from the documentation how to use useFocusEffect and I'm not having success on how to do it.
Any help?
The docs show you how to do it. You need to replace API.subscribe with your own thing:
useFocusEffect(
React.useCallback(() => {
dispatch(companyJobsFetch(userDetails.companyId));
}, [dispatch, companyJobsFetch, userDetails.companyId])
);
For version react navigation 4.x, you can use addEvent listener
useEffect(() => {
if (navigation.isFocused()) {
resetReviews(); // replace with your function
}
}, [navigation.isFocused()]);
OR
useEffect(() => {
const focusListener = navigation.addListener('didFocus', () => {
// The screen is focused
// Call any action
_getBusiness({id: business?.id}); // replace with your function
});
return () => {
// clean up event listener
focusListener.remove();
};
}, []);
For later version 5.x, you can use hooks to achieve this
import { useIsFocused } from '#react-navigation/native';
// ...
function Profile() {
const isFocused = useIsFocused();
return <Text>{isFocused ? 'focused' : 'unfocused'}</Text>;
}

The Event component keeps calling componentDidMount but never load at all

There are 3 bottom tabs in my react native 0.59 app with react navigation 3.7. The tabs are Event, Group and Contact. Event is the initial tab.
After initial loading, I click Group tab and the screen is as below:
There are 2 groups HT and MKL shown. After I click one group, either HT or MKL, this should load Event component. The problem is that after clicking a group, Event is never loaded. On both app and server monitoring screen, there are crazy loading Event and Group non stop. The screen is stuck at Group and stops responding to any click. I have no clue about this behavior as Event module has been fully tested and Group is newly added.
Here is the onPress code in Group module:
async _onPress(id) {
try {
console.log("saved group id in Group : ", id);
await helper.setMyValue("group_id", id.toString());
//update group_id in App.js
this.props.updateGroup(id); //<<<===this causes the problem
} catch (err) {
console.log("error in saving group_id to local disk", err);
};
this.props.navigation.goBack();
//this.props.navigation.navigate("Event"); //, {group_id:element1.id});
}
What the above onPress does is to save the id user clicked and call updateGroup to update the parent's group_id and call goBack which is Event component.
The componentDidMount in Event is:
async componentDidMount(){
this._isMounted = true;
if (!this.props.group_id) {
alert("Select a group");
return;
};
try {
//retrieving token
let result = this.state.token;
if (!result) {
result = await helper.getJwtToken();
if (this._isMounted) this.setState({
token: result
});
};
//console.log("secure store : ", result);
if (!result) {
this.props.navigation.navigate("Signup");
return;
};
this.refreshData(result);
//refresh data after goBack()
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus', () => {
this.refreshData(result);
}
);
//get user
let myself = this.state.user;
if (!myself) {
myself = await helper._getUser(result.password, result.username);
if (this._isMounted) this.setState({
user: myself
});
};
console.log("out of did mount event", this.state);
}
catch (err) {
console.log("服务器错误(et),请再试一次!", err);
this.props.navigation.navigate.goBack(); //("Event");
return;
};
Here is the app console output of in Event componentDidMount:
09-19 20:16:44.551 21659 21743 I ReactNativeJS: 'event url : ', 'http://192.168.2.133:3000/api/events/active?group_id=1&_group_id=1&_device_id=0cfce7b7e86fa397&_timezone=America%2FLos_Angeles'
09-19 20:16:44.552 21659 21743 I ReactNativeJS: 'out of did mount event', { activeEvents: [],
}
UPDATE:
The problem is caused by this.updateGroup(id) which updates the state of group_id in App.js
updateGroup = (group_id) => {
if (this._isMounted) this.setState({group_id:group_id});
};
It is passed in to Group as a props in App.js:
const GroupWithSelf = (props) => (<Group {...props} myself={data.myself} token={data.result} updateGroup={this.updateGroup} updateToken={this.updateToken} />);
Not sure why this method causes racing.

How to send data from one Electron window to another with ipcRenderer using vuejs?

I have in one component this:
openNewWindow() {
let child = new BrowserWindow({
modal: true,
show: false,
});
child.loadURL('http://localhost:9080/#/call/' + this.chatEntityId + '?devices=' + JSON.stringify(data));
child.on('close', function () { child = null; });
child.once('ready-to-show', () => {
child.show();
});
child.webContents.on('did-finish-load', () => {
console.log("done loading");
ipcRenderer.send('chanel', "data");
});
}
And then in child window component:
mounted() {
ipc.on('chanel', (event, message) => {
console.log(message);
console.log(event);
});
}
I tried that .on in created() and beforeCreate() and with this.$nextTick(), withsetTimeout` but nothing works.
I don't want to send some string data but object but as you can see not event simple string "data" works. I am out of ideas.
I can see that this only works in parent component for component where that emit came from if i do this:
send
listen in main process
send back to event.sender
So, question is how to pass any form of data from one window to another?
Ok, after long night, morning after solution.
In some vuejs componenet some action on button click for example
ipcRenderer.send('chanel', someData);
In main process
ipcMain.on('chanel', (event, arg) => {
let child = new BrowserWindow()
// other stuff here
child.loadURL(arg.url)
child.on('show', () => {
console.log("done loading");
child.webContents.send('data', arg);
});
})
In vuejs component for other route arg.url
mounted() {
ipc.on('chanel', (event, message) => {
console.log(message);
console.log(event);
});
}