state get null after disptach - react-native

I am using redux in my react native project and I get this error when dispatched sth.
error:
TaskQueue: Error with a task: Invariant Violation: Invariant Violation: Invariant Violation: Tried to get the frame for out of range index NaN
extra information which I prefer to say is that I have navigation with the first screen has connected to *that list and the second screen is connected to *room_Messages
My Reducer:
case 'RECEIVE_MESSAGE':
{
index=0;
msg = JSON.parse(action.msg)
msgItem = {
id: Math.random(),
message: msg.message
}
return {...state,
chatList: {
...state.chatList
, [0]: {
...state.chatList[0], room_messages: {
...state.chatList[0].room_messages.slice(0)
}
}
},
}
when I navigate to the second screen and then dispatch RECEIVE_MESSAGE action, in the first screen my props get null(i noticed this in componentDidUpdate of the first screen)

Related

Type error in getting route params within nested navigator using Typescript [react-navigation v6]

Having a navigation type definition as bellow, when I navigate from e.g AOne to BTwo with id:99 the console log of props.route.params shows correct info. But props.route.params.id throws type error
TypeError: undefined is not an object (evaluating 'props.route.params.id')
// navigation related imports in all components
import {BottomTabScreenProps} from '#react-navigation/bottom-tabs';
import {CompositeScreenProps, NavigatorScreenParams} from '#react-navigation/core';
import {StackScreenProps} from '#react-navigation/stack';
// type defenitions
export type StackOneParams = {
AOne:undefined,
BOne: undefined,
// some other screens
};
export type StackTwoParams = {
ATwo: undefined;
BTwo:{id:number};
// some other screens
};
export type TabParams = {
StackOne: NavigatorScreenParams<StackOneParams>;
StackTwo: NavigatorScreenParams<StackTwoParams>;
// ... some other stacks each represent a tab
};
export type RootParamList = {
ROne: undefined; // these screens should stand alone and not blong to any tab
RTwo: undefined;
Tabs: NavigatorScreenParams<TabParams>
}
// navigation from AOne to BTwo
props.navigation.navigate('Tabs', {
screen: 'StackTwo',
params: {screen: 'BTwo', params: {id: 99}}
}); // this part give correct auto complete hints in VSCode and no compilation error
// BTwo component (screen)
//--------------------------------
type Props = CompositeScreenProps<
StackScreenProps<RootParamList, 'Tabs'>,
CompositeScreenProps<
BottomTabScreenProps<TabPrams, 'StackTwo'>,
StackScreenProps<StackTwoParams, 'BTwo'>
>
>;// using CompositeScreenProps to be able to navigate to screens in another tabs
// otherwise just `type Props=StackScreenProps<StackTwoParams, 'BTwo'>` works fine but cannot navigate to other tabs
const BTwo:React.FC<Props> = (props) =>{
console.log(props.route.params)// the log shows {id:23}
// but props.route.params.id gives error and also no auto completion hint
return(...)
}
is this the correct way to define screen props for a specific screen, like what I have in BTwo screen? or the sequence of the composition should be different?
most of the examples (and the official documentation) show the most simple composition where the target screen is not even in second level of nesting (in the official doc profile is not really in nested bottom tabs)
How should I solve the type error in this case?
the image shows the VSCode auto-complete suggestions
Solution using CompositeScreenProps
My other explanations were not quite accurate. The way you have defined the CompositeScreenProp is not correct. Here is the correct way to implement this.
type ScreenProps = CompositeScreenProps<
StackScreenProps<StackTwoParams, "BTwo">,
CompositeScreenProps<
BottomTabScreenProps<TabParams, "StackTwo">,
StackScreenProps<RootParamList>
>
>
The first parameter of CompositeScreenProps contains the type of the navigator that owns the screen. In this case BTwo is owned by StackTwo and this determines the primary navigator, which is a Stack.
The above yields to the correct types as well.
const BTwo = (props: ScreenProps) => {
return <></>
}
Solution using separate types for navigation and route
We can type the navigation object and the route object separately as follows.
type NavigationProps = CompositeNavigationProp<
StackNavigationProp<StackTwoParams, "BTwo">,
CompositeNavigationProp<
BottomTabNavigationProp<TabParams, "StackTwo">,
StackNavigationProp<RootParamList>
>
>
type ScreenPropsA = {
navigation: NavigationProps
route: RouteProp<StackTwoParams, "BTwo">
}
Notice the usage of CompositeNavigationProp and RouteProp here.
Then, use it as follows.
const BTwo = ({ route, navigation }: ScreenProps) => {
return <></>
}
Both, route and navigation are now correctly typed.

Cannot Prevent Async on GraphQL Mutation and Recoil State updating process

I am working on a Chat Room portion of a larger React Native app and am facing issues with the updating of the page after a text has been sent. Currently, when the user compiles a text in a TextInput and hits the send button, it triggers a mutation that is supposed to add a message object to the chatroom model, which is linked to all of the users that are currently in the chatroom. It is then supposed to take the result from this mutation, which is the updated chatroom connected to all the users (the current user included obviously) and render its contents. It is intended to rerender the page after the activeThread atom is updated, since the page used the contents of activeThread to render everything on the page, new messages included. However, this occurs asyncronously and it tries to render a promise.... which you can't do. I've tried everything I'm capable of, using thens and awaits everywhere I could but JavaScript's giving me the middle finger pretty hard on this one. My code is below...
const handleSendMessage = async () => {
console.log(activeThread.id)
if (newMessage.length > 0){
return sendMessage({
variables: {
chatroomId: activeThread.id,
content: newMessage
}
}).then( async (newMessageThread) => {
await setUpdating(true)
await setNewMessage("")
await setKeyboardVisible(false);
await setActiveThread(newMessageThread)
}).then( async() => {
await console.log(activeThread)
await setUpdating(false)
})
}
else{
}
}
setUpdating is part of a useState. This is defaulted to false and when true the main page is not set to render. It is intended as a guard against attempting to render the promise. Didn't work, obviously
setNewMessage is defaulted to "" and is responsible for keeping track of the text the user has entered into the TextInput. Pretty irrelevant here.
setKeyBoardVisible is pretty self explanatory and also not necessary
setActiveThread is the heavy lifter here. Pretty much all of the contents rendered are going to be pulling data from activeThread, which is, again; a recoil state. For example, everything below looks essentially something like
<View>
<Text> {activeThread.someMethodOrValue} </Text>
</View>
I can only assume this has something to do with the async-ing. I have a console.log(error) statement in my backend GraphQL mutation resolver that would catch any errors there, and it's not triggering anything. The error I get everytime is the following...
TypeError: undefined is not an object (evaluating 'activeThread.chatroomName.split')
This error is located at:
in MessageThread (created by SceneView)
in StaticContainer
in EnsureSingleNavigator (created by SceneView)
in SceneView (created by SceneView)
in {/* keeps going down the stack you get the idea */}
[Unhandled promise rejection: TypeError: undefined is not an object (evaluating 'activeThread.chatroomName.split')]
at Pages/CommunicationPage/MessageThread.js:210:37 in MessageThread
Any solutions?
While the code I had still looks like it should work to me, we all know how finnicky code can be sometimes. What ended up working was separating the handleSendMessage function and the mutation, creating a whole new function for the mutation.
My new code looks like this...
const handleSendMessage = () => {
if (newMessage.length > 0){
handleMutation().then( (resolved) => { // This line fixed all the promise issues
setNewMessage("") // clears current message input
let newActiveThread = resolved.data.driverSendMessage.chatroom // creates new thread JSON from mutation data
console.log(newActiveThread) // testing out a different bug now lolllll
setActiveThread(newActiveThread) // Sets current thread to match the new one
// changes the entire user state, leaving all over threads untouched but updating the current one
let updatedThreads = [newActiveThread]
user.chatrooms.forEach( (chat) => {
if (chat.id == newActiveThread.id){
console.log("Skipping " + newActiveThread.chatroomName)
}
else {
updatedThreads.push(chat)
}
})
// changes the main recoil state
setUser({...user, chatrooms: updatedThreads})
})
}
else{
// Throw Error Handling for no input or just do nothing, we'll see
}
}
const handleMutation = async () => {
return sendMessage({
variables: {
chatroomId: activeThread.id,
content: newMessage
}
})
}

How unmount a hook after going to new screen with navigate

The context is a simple React Native app with React Navigation.
There are 3 screens.
The first simply displays a button to go to second screen using navigation.navigate("SecondScreen").
The Second contains a hook (see code below) that adds a listener to listen the mouse position. This hook adds the listener in a useEffect hook and removes the listener in the useEffect cleanup function. I just added a console.log in the listener function to see when the function is triggered.
This screen contains also a button to navigate to the Third screen, that only shows a text.
If I go from first screen to second screen: listener in hook start running. Good.
If I go back to the first screen using default react navigation 's back button in header. the listener stops. Good.
If I go again to second screen, then listener runs again. Good.
But if I now go from second screen to third screen, the listener is still running. Not Good.
How can I unmount the hook when going to third screen, and mount it again when going back to second screen?
Please read the following before answering :
I know that:
this is due to the fact that react navigation kills second screen when we go back to first screen, and then trigger the cleanup function returned by the useEffect in the hook. And that it doesn't kill second screen when we navigate to third screen, and then doesn't trigger the cleanup function.
the react navigation's hook useFocusEffect could be used to resolve this kind of problem. But it can't be used here because it will involve to replace the useEffect in the hook by the useFocusEffect. And I want my hook to be usable in every context, even if react navigation is not installed. More, I'm using here a custom hook for explanation, but it's the same problem for any hook (for example, the native useWindowDimensions).
Then does anyone know how I could manage this case to avoid to have the listener running on third screen ?
This is the code of the hook sample, that I take from https://github.com/rehooks/window-mouse-position/blob/master/index.js, but any hook could be used.
"use strict";
let { useState, useEffect } = require("react");
function useWindowMousePosition() {
let [WindowMousePosition, setWindowMousePosition] = useState({
x: null,
y: null
});
function handleMouseMove(e) {
console.log("handleMouseMove");
setWindowMousePosition({
x: e.pageX,
y: e.pageY
});
}
useEffect(() => {
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, []);
return WindowMousePosition;
}
module.exports = useWindowMousePosition;
the react navigation's hook useFocusEffect could be used to resolve this kind of problem. But it can't be used here because it will involve to replace the useEffect in the hook by the useFocusEffect. And I want my hook to be usable in every context, even if react navigation is not installed
So your hook somehow needs to know about the navigation state. If you can't use useFocusEffect, you'll need to pass the information about whether the screen is focused or not (e.g. with an enabled prop).
function useWindowMousePosition({ enabled = true } = {}) {
let [WindowMousePosition, setWindowMousePosition] = useState({
x: null,
y: null
});
useEffect(() => {
if (!enabled) {
return;
}
function handleMouseMove(e) {
console.log("handleMouseMove");
setWindowMousePosition({
x: e.pageX,
y: e.pageY
});
}
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, [enabled]);
return WindowMousePosition;
}
And then pass enabled based on screen focus:
const isFocused = useIsFocused();
const windowMousePosition = useWindowMousePosition({ enabled: isFocused });
Note that this approach will need the screen to re-render when it's blurred/focused unlike useFocusEffect.

Check if navigation state params object has been set

I'm trying to do some validation to see if the params object has been set when navigating to my GetPromoScreen but it does not work. Here is my sample code:
HomeScreen (navigating to GetPromoScreen)
this.props.navigation.navigate('GetPromoScreen', { promo: true })
GetPromoScreen (validating the params object)
if(this.props.navigation.state.params.promo != undefined){
this.setState({ promo: true })
} else {
this.setState({ promo: false })
}
I have an alert dialog from HomeScreen where when the user press the button, it will trigger the function where I set the params promo. But, If the user closes the alert dialog and straight away entering GetPromoScreen from the menu button, the params promo will not be set and when that happen, an error will appear saying:
TypeError: undefined is not an object (evaluating
'this.props.navigation.state.params.promo')
My validation code above is not working. Any idea?
if(this.props.route.params){
formData.push('profile_id' + "=" + this.props.route.params.profile_id);
}

React Native Expo: Cannot add child that doesn't have a YogaNode - Error on Android

I am stuck with this error:
Cannot add a child that doesn't have a YogaNode to a parent without a measure function!
(Trying to add a 'ReactRawTextShadowNode' to a 'LayoutShadowNode')
The app is on expo and works fine in iOS
but on Android I always get this error when pressing the button for authentication.
Earlier it was working fine, I tried to reset my commits to track the error but for no help.
I think, whenever this function is executed, the error arises:
onButtonPress = async () => {
const { code } = this.props;
await this.props.loginUser({ code });
if (this.props.error) {
await AsyncStorage.removeItem('code');
this.props.navigation.goBack();
} else {
await AsyncStorage.setItem('code', code);
await this.props.orderUpdate();
await this.props.menuFetch();
this.props.navigation.navigate('main');
}
};
Note that the props are accessing redux state and calling redux actions.
This issue having a different reason:
Might be the comments inside the render method of component so try to remove comments inside render method of component component.
Might be because of that you have not closed a tag correctly
Might be using of && operator inside render method so remove '&&'
operator and use ternary operator.
Instead { foobar && <View/> }
Use this { foobar ? <View/> : null }