Cannot Open/Close Drawer Layout react-native-gesture-handler - javascript

Im trying to use for my first time the Drawer Layout from react-native-gesture-handler, but everytime i try to use it wont open.
Im getting this error(both in the openDrawer):
const newBet: React.FC = () => {
let drawer: any = null;
var renderDrawer = () => {
return (
<Button onPress={drawer.closeDrawer()} title='Close Drawer'></Button>
);
};
var render = () => {
return (
<View style={{ flex: 1 }}>
<DrawerLayout
ref={(refDrawer) => {
refDrawer = refDrawer;
}}
drawerWidth={200}
drawerPosition='right'
drawerType='front'
drawerBackgroundColor='#ddd'
renderNavigationView={renderDrawer}
>
<Button onPress={drawer.openDrawer} title='Open Drawer'></Button>
</DrawerLayout>
</View>
);
};
return (
<TouchableOpacity>
<Text onPress={drawer.openDrawer()}>Render Drawer Layout</Text>)
</TouchableOpacity>
);
};
export default newBet;

You set drawer to null, and never assign a value to it. You have a typo in ref={}:
ref={(refDrawer) => {
// refDrawer = refDrawer; Assigning value to itself here :)
drawer = refDrawer
}}
Also, I'm not sure since I didn't check this but I think ref={drawer} also works.
Next, note how you sometimes write
onPress={drawer.closeDrawer()} // ending with '()'
// vs
onPress={drawer.openDrawer} // ending without '()'
You can't directly call a function or it will execute it immediately. So either write:
onPress={drawer.closeDrawer}
// or
onPress={() => drawer.closeDrawer()}
That should do the trick hopefully!

Related

React Native Error : Non-serializable values were found in the navigation state. onPress(Function)

I am trying to resolve the error Non-serializable values were found in the navigation state. Alert > params.action[0].onPress (Function) of React Native navigation. I don't think the function is not passed to the param like the error points out, but it kept returning this same error every time I pressed the icon. I'd appreciate any suggestions or comments.
export default function Alert({ route, navigation }) {
const { colors } = useTheme();
const { t } = useTranslation();
const { title, message, action, option, type } = route?.params;
const success = type === "success";
useEffect(() => {
const backHandler = BackHandler.addEventListener(
"hardwareBackPress",
() => !option?.cancelable
);
return () => backHandler.remove();
}, [option?.cancelable]);
const renderButtonFirst = () => {
const firstTitle = action?.[0]?.text ?? t("close");
const onPressNo = action?.[0];
return (
<TouchableOpacity
onPress={() => {
onPressNo?.onPress();
if (option?.cancelable) navigation.goBack();
}}
>
<Text>
{firstTitle}
</Text>
</TouchableOpacity>
);
};
const renderButtonSecond = () => {
const secondTitle = action?.[1]?.text;
const onPressYes = action?.[1];
if (title && onPressYes) {
return (
<TouchableOpacity
onPress={() => {
onPressYes?.onPress();
if (option?.cancelable) navigation.goBack();
}}
>
<Text>
{secondTitle}
</Text>
</TouchableOpacity>
);
}
};
return (
<View>
<Icon name={success ? "check-circle" : "question-circle"} />
</View>
<View>
<Text>
{title}
</Text>
<Text>
{message}
</Text>
</View>
<View >
{renderButtonFirst()}
{renderButtonSecond()}
</View>
</View>
</View>
);
}
And this is the parent component just in case. But this error is from the Alert component as it says.
const onOpen = (type, title, link) => {
Alert.alert({
title: title,
message: `${t("do_you_want_open")} ${title} ?`,
action: [
{
text: t("cancel"),
onPress: () => console.log("Cancel Pressed"),
style: "cancel",
},
{
text: t("done"),
onPress: () => {
switch (type) {
case "web":
Linking.openURL(link);
break;
case "phone":
Linking.openURL("tel://" + link);
break;
case "email":
Linking.openURL("mailto:" + link);
break;
case "address":
Linking.openURL(link);
break;
}
},
},
],
});
};
{product?.website.length > 0 && (
<TouchableOpacity
onPress={() => {
onOpen("web", t("Website"), product?.website);
}}
>
<View>
<Image
source={Images}
/>
</View>
</TouchableOpacity>
)}
UPDATE 4/1
This is the Navigation component just in case;
import AlertScreen from "#screens/Alert";
export default function Navigator() {
...
return (
<AppearanceProvider>
<NavigationContainer theme={theme}>
<RootStack.Screen
name="Alert"
component={AlertScreen}
gestureEnabled: false,
}}
/>
</RootStack.Navigator>
</NavigationContainer>
</AppearanceProvider>
);
}
From the react navigation docs
This can happen if you are passing non-serializable values such as
class instances, functions etc. in params. React Navigation warns you
in this case because this can break other functionality such state
persistence, deep linking etc.
If you don't use state persistence or deep link to the screen which
accepts functions in params, then the warning doesn't affect you and
you can safely ignore it. To ignore the warning, you can use
YellowBox.ignoreWarnings.
If you are using react-native version > 0.63, use:
import { LogBox } from 'react-native';
LogBox.ignoreLogs([ 'Non-serializable values were found in the
navigation state', ]);
I also got bitten by this. You cannot pass non-simple objects to the navigation.
The problem is not "directly" in the code you posted but somewhere else. Either the go-back triggered the problem "once more" or there is somewhere a line like:
navigation.navigate('Alert', { action: {onPress: some_function }, /* rest */ }
In any case, the problem is that action comes from the parameters and is expected to have am onPress function. You cannot serialize a function an thus cannot model it like that.
Solution: Put that logic into a service and the parameters into the route, something like:
export Service {
do(actionDescription: {type: string, payload: any}) {
if (actionDescription.type === 'log') console.log(actionDescription.payload); // you get the idea
}
}
// in Alert
const onPressNo = () => Service.do(action?.[0].action);
// somewhere:
navitation.navigate('Alert', {action: [{action: {type: 'log', payload: 'Cancel Pressed'} /* complete... */]
So, past only simple objects to the navigation route. Use a pseudo-Command pattern, where the command state is passed into the route and the trigger is centralized somewhere else.

Can't find variable when setting value to selected image in React Native

Here are my functions. They check the selected image by the user and assign an ID to them.
setCharacter = (props) => {
this.setState({
character:props
})
}
const pressHandler = (character) => {
this.setCharacter(character)
console.log(character);
}
I'm using the functions here to assign the ID.
TouchableOpacity onPress={(pressHandler('1'))}>
<Image style={{height: 120,
width: 120, alignSelf: 'center'}} source={require('../assets/characters/001-superhero.png')}
/>
</TouchableOpacity>
<TouchableOpacity onPress={(pressHandler('2'))}>
<Image style={{height: 120,
width: 120, alignSelf: 'center'}} source={require('../assets/characters/003-superhero.png')}
/>
</TouchableOpacity>
This is my error.
ReferenceError: Can't find variable: setCharacter
Thank you very much in advance! I used another stack overflow link to create this:
How to get value of text in state.when i clicked on TouchableOpacity in react-native?
I hope I'm following all the rules on stackoverflow :)
Try using like
const setCharacter = (props) => {
// .. use state from `useState` for hooks
}
const pressHandler = (character) => {
setCharacter(character)
console.log(character);
}
TouchableOpacity onPress={() => pressHandler('1')}>
...
</TouchableOpacity>
<TouchableOpacity onPress={() => pressHandler('2')}>
...
</TouchableOpacity>
Three things:
What Nooruddin said -- onPress has to point at a function and (pressHandler('1') is not a function. Change it to onPress={()=>pressHandler('1')}
You need to declare setCharacter properly, with const or something similar: const setCharacter = ...
Try replacing this.setCharacter(character) with setCharacter(character) -- I can't tell for sure if that's an issue but it may be.
You can use Bind or an arrow function but the below should work.
In your constructor also init the state with the character property.
constructor(props) {
super(props);
this.state = {
character: '',
};
}
<TouchableOpacity onPress={this.pressHandler.bind(this, '1')}>
Then you can just update your state with whatever value you passed from the button press in your case either a 1 or a 2.
const pressHandler = (c) => {
this.setState({character: c});
console.log(c);
}
To use the character that was passed in you would just access it via this.state.character
<Text>Current Image ID: {this.state.character}</Text>

Array of text from a JSON object inserted into <Text> elements via forEach/map not rendering in React Native

I'm making a Choose Your Own Adventure style game in React Native to learn how everything works. I figured it makes sense to keep the story in a separate JSON file and render it on one screen that updates depending on what arc the user is in.
Problem: when I write a forEach or map or other function to go through the array of paragraphs, the part of the screen it's supposed to be on is blank. Text from other parts of the return area displays fine, but what is supposed to be displayed from the forEach does not. Here's my code:
const StoryDisplayScreen = props => {
const theStory = require('../data/TestStory.json')
const theArc = 'intro'
const storyText = () => {
theStory[theArc].story.forEach((paragraph, i) => {
<Text style={styles.body}>{paragraph}</Text>
})
}
return (
<View style={styles.screen}>
<ScrollView>
<View>
{storyText()}
</View>
<View style={styles.promptNextArcArea}>
<TouchableOpacity style={styles.promptNextArc}>
<Text style={styles.promptNextArcText}>What do you do?</Text>
</TouchableOpacity>
</View>
</ScrollView>
</View>
)
}
In case you're wondering the structure of the JSON file and its contents, I'm using this one to test with: https://github.com/gophercises/cyoa/blob/master/gopher.json
I've tried using map instead, tried putting things in Views, tried putting the forEach/map functions directly into the return section of the code, tried console.log-ing to confirm that my functions are working properly (which they appear to be), and... yeah, I'm at a loss.
Any suggestions?
Consider using map instead of forEach.
Full Working Example: Expo Snack
import * as React from 'react';
import {
Text,
View,
StyleSheet,
ScrollView,
TouchableOpacity,
} from 'react-native';
import Constants from 'expo-constants';
import { Card } from 'react-native-paper';
const StoryDisplayScreen = (props) => {
const theStory = require('./data/TestStory.json');
const theArc = 'intro';
const storyText = () => {
return theStory[theArc].story.map((paragraph, i) => (
<Card style={{margin: 5, padding:5}}>
<Text style={styles.body}>{paragraph}</Text>
</Card>
));
};
return (
<View style={styles.screen}>
<ScrollView>
<View>{storyText()}</View>
<View style={styles.promptNextArcArea}>
<TouchableOpacity style={styles.promptNextArc}>
<Text style={styles.promptNextArcText}>What do you do?</Text>
</TouchableOpacity>
</View>
</ScrollView>
</View>
);
};
export default StoryDisplayScreen;
const styles = StyleSheet.create({
screen: {
flex: 1,
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
firstly, if you want your sort is a function, it must return something, just like the render method
const sort = () => {
return (<View></View>)
}
you also can make the sort is a view, use it as a variable. like that, both they are can works.
const sort = (<View></View>)
but the sort must have something return. your way use for each and map dont return any. there are two ways can do that.
const storyText = () => {
//,first way,define a array, push it in the array
let storyViews= []
theStory[theArc].story.forEach((paragraph, i) => {
sortyviews.push(<Text style={styles.body}>{paragraph}</Text>)
})
return soortView;
}
the second way is to return directly a array
const storyText = () => {
//,first way,define a array, push it in the array
let storyViews= []
storyViews = theStory[theArc].story.map((paragraph, i) => {
return (<Text style={styles.body}>{paragraph}</Text>)
})
return soortView;
}
for more about the array operate to see the doc

Showing customer spinner for 5 seconds after which show content

I am developing a react-native project. I have a spinner as a custom component:
const MySpinner = ({hide = false}) => {
...
if (hide) {
return null;
} else {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Animated.View>
<MyIcon />
</Animated.View>
</View>
);
}
}
As you can see above, there is a hide property which decides whether I show MySpinner or not in parent component.
In MyScreen, I would like to show MySpinner for 5 seconds, after which show the actual content (No networking callback involved). I try to use setTimeOut function to achieve it.
This is what I tried:
const MyScreen = () {
...
return (
<View style={styles.container}>
{setTimeout(() => { <MySpinner hide={true}/>}, 3000)}
<ActualContent />
</View>
)
}
At runtime, I got error Error: Text strings must be rendered within a <Text> component.
Also, the above code doesn't show actual content after MySpinner disappear.
So, how can I achieve what I need? That's showing MySpinner at first for 5 seconds after that show the actual content.
You can achieve this in multiple ways, here is a simple example to help you get started.
Code Sandbox => https://codesandbox.io/s/wonderful-chebyshev-io6pd?file=/src/App.js
[UPDATED ANSWER]
import { useEffect, useState } from "react";
export default function App() {
const [spinner, setSpinner] = useState(true);
useEffect(() => {
setTimeout(() => {
setSpinner(false);
}, 5000);
}, []);
const component = spinner ? (
<span>Loading...</span>
) : (
<h1>Component Ready</h1>
);
return <div className="App">{component}</div>;
}

react native rendering a list of buttons can't figure out what's wrong

I'm trying to make a list of buttons based on the input from user input type is an array of options like so multipleOptions = ['1', '2', '3'] then we loop through each option to show a Button can't figure out why it's not working here's my code :
import React, { useState } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
const InputButton = (multipleOptions, likertScale, onPress) => {
const [options, setOptions] = useState([]);
if (likertScale) {
setOptions([...new Array(likertScale).keys()].map((i) => i));
} else if (multipleOptions) setOptions(multipleOptions);
return (
<View style={styles.container}>
{options ? (
options.map((option, i) => (
<View style={[styles.button]} key={`${i}`}>
<TouchableOpacity onPress={() => onPress(option)}>
<Text>{option}</Text>
</TouchableOpacity>
</View>
))
) : (
<Text>no options</Text>
)}
</View>
);
};
const App = () => {
return (
<View>
<InputButton multipleOptions={['1', '2','3']} />
</View>
)
}
const styles = StyleSheet.create({})
export default App;
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
},
button: {
margin: 3,
flex: 1,
backgroundColor: '#EEF6FA',
minHeight: 72,
borderRadius: 2,
justifyContent: 'center',
alignItems: 'center',
},
});
the error message is
Too many re-renders. React limits the number of renders to prevent an infinite loop.
or sometimes this
options.map is not a function
TypeError: options.map is not a function
at InputButton
(All kind of optimisations are welcome)
Thanks in Advance guys.
code demo https://snack.expo.io/#mansouriala/dac832
You put set state in every re-render so you get a loop. So you have two options use useEffect to just set state one time or set the first state directly.
https://snack.expo.io/ZvLQM9FEF
const InputButton = ({multipleOptions, likertScale, onPress}) => {
const [options, setOptions] = useState(likertScale?[...new Array(likertScale).keys()].map((i) => i):[ ...multipleOptions]);
return (
<View style={styles.container}>
{options ? (
options.map((option, i) => (
<View style={[styles.button]} key={`${i}`}>
<TouchableOpacity onPress={() => onPress(option)}>
<Text>{option}</Text>
</TouchableOpacity>
</View>
))
) : (
<Text>no options</Text>
)}
</View>
);
};
export default InputButton;
You have several issues here.
The first, which leads to Too many re-renders. React limits the number of renders to prevent an infinite loop. is because you're calling setOptions at each render, which triggers another render, etc… This is infinite loop, because when you're setting a state, React re-renders the component. To avoid that, you have to wrap your expression with useEffect and the correct dependencies.
React.useEffect(() => {
if (likertScale) {
setOptions([...new Array(likertScale).keys()].map((i) => i));
} else if (multipleOptions) {
setOptions(multipleOptions);
}, [multipleOptions, likertScale]);
This way, this expression would only run when multipleOptions or likertScale change.
https://reactjs.org/docs/hooks-reference.html#useeffect
The other problem is that InputButton props argument is wrong: you forgot the props destructuring. It should be const InputButton = ({ multipleOptions, likertScale, onPress }) => { /* body function */ }.
Finally, it's a bad practice to use an array index as a key prop, because array order could change. You should use a stable key, like the option value key={option}.
https://reactjs.org/docs/lists-and-keys.html#keys

Categories

Resources