React Navigation is not a function? - react-native

I'm using React Navigation. I'm directing you to the conversation after the record insert page. I provide automatic renewal when I redirect. I'm sending a parameter. But that doesn't work. I don't know where I'm making a mistake.
I get this error.
Uncaught typeerror. TypeError: s.handleRefresh is not a function. (In 's.handleRefresh()', 's.handleRefresh' is undefined
LeadDetail.js Page
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
title: 'Müşteri Adayı Detay',
headerRight: (
<TouchableHighlight activeOpacity={0.4} underlayColor='transparent' onPress={() => navigation.navigate('CreateMeeting', { parentId: params.id, parentType: 'Lead' })} style={{marginRight: 12 }}>
<Icon
name="calendar-check-o"
size={25}
color="white"
/>
</TouchableHighlight>
)
};
};
CreateMeeting.js Page
dataSuccess(response) {
this.setState({ buttonLoading: false });
Alert.alert(
'Tebrikler!',
'Meeting başarılı bir şekilde kayıt edildi',
[
{ text: 'Tamam', onPress: () => this.props.navigation.navigate('Meeting', { refreshMeeting: true }) }
]
);
}
Meeting.js Page
static navigationOptions = ({ navigation }) => {
const { params } = navigation.state;
let refresh = navigation.getParam('refreshMeeting', false);
if(refresh) {
params.handleRefresh();
}
return {
title: 'Görüşmeler',
headerRight: (
<View style={{ flexDirection: 'row' }}>
<TouchableHighlight activeOpacity={0.4} underlayColor='transparent' onPress={navigation.getParam('setOverlay')}>
<Icon
name="filter"
size={25}
color="white"
/>
</TouchableHighlight>
<TouchableHighlight activeOpacity={0.4} underlayColor='transparent' onPress={navigation.getParam('setModalVisible')} style={{ marginRight: 12, marginLeft: 20 }}>
<Icon
name="plus"
size={25}
color="white"
/>
</TouchableHighlight>
</View>
)
};
};

Are you setting the param handleRefresh? Instead of params.handleRefresh(); shouldn't it be params.refresh();?
Also, s.handleRefresh where is this in your code? I think there is a typo or something like that and I'm considering that s.handleRefresh is actually params.handleRefresh
Please edit your question if none of this is correct.
I don't know where you are setting the param handleRefresh, but if you are setting it (please check that and add the code where you do that), maybe here is the reason why this happens.
If you look at the docs they have a not
React Navigation doesn't guarantee that your screen component will be mounted before the header. Because the increaseCount param is set in componentDidMount, we may not have it available to us in navigationOptions. This usually will not be a problem because onPress for Button and Touchable components will do nothing if the callback is null. If you have your own custom component here, you should make sure it behaves as expected with null for its press handler prop.
This means that the header can render before the component calls setParams and that doens't load you params in the first time. What you need to do some checking to avoid that time when the header renders first.
// added some checking
if(refresh && params && params.handleRefresh) {
params.handleRefresh();
}

Related

Could not find "client" in the context or passed in as an option. Wrap the root component in an <ApolloProvider>, or pass an ApolloClient instance

This is my first Apollo Project, but I have been working with it for awhile, and things had been pretty smooth in terms of Apollo, until recently.
While I understand how Apollo works, I obviously do not know all the minutae involved with it, and I am unclear I suppose on exactly how the project is supposed to be wrapped.
Below is my App.js, when it worked as anticipated...
// Auth for token
const authLink = setContext((_, { headers }) => {
const token = state
return {
headers: {
...headers,
authorization: token ? `${token}` : ''
}
}
})
// Initialize Apollo Client
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
const Stack = createNativeStackNavigator();
export default function App() {
// Other irrelevant things like fonts...
const ErrorPrompt = (props) => {
return(
<View>
<View style={{height: 50, width: '100%', backgroundColor: "#534FFF"}}/>
<Text style={{...Template.title, fontSize: 30}}>Something happened!</Text>
<Text style={{...Template.subTitle, fontSize: 20}}>We apologize for the inconvenience</Text>
<Text style={{...Template.title, fontSize: 20, color: '#534FFF'}}>{props.error.toString()}</Text>
<View style={{marginLeft: 30}}>
<Text style={{...Template.subTitle, fontSize: 15, marginRight: 30, marginLeft: 0}}>An email has been sent to the Support Team to attempt to prevent this error from occurring again. We thank you for your support and patience</Text>
</View>
<View style={{marginTop: 50}}>
<Button onPress={props.resetError} title={'Reload Application'} />
</View>
</View>
)
}
if(!loaded){
return null
}
try{
return (
<ErrorBoundary
FallbackComponent={ErrorPrompt}
>
<NavigationContainer>
<ApolloProvider client={client}>
<RecoilRoot>
<IconRegistry icons={EvaIconsPack} />
<ApplicationProvider {...eva} theme={{...eva.light, ...theme}}>
<PaperProvider>
<KeyboardAvoidingView
behavior="padding"
enabled
style={{flexGrow:1,height:'110%'}}
>
<View style={AppStyles.container}>
<Stack.Navigator screenOptions={{headerShown: false}}>
{/* {loggedIn === false ? ( */}
<Stack.Screen name="/">
{props => <LandingPage {...props} handleLoggedIn={handleLoggedIn} rememberMe={rememberMe} setRememberMe={setRememberMe} />}
</Stack.Screen>
{/* ) : null} */}
<Stack.Screen name="home">
{props => <Home {...props} handleLoggedIn={handleLoggedIn} />}
</Stack.Screen>
{/* About 40 more Stack Screens */}
</Stack.Navigator>
</View>
</KeyboardAvoidingView>
</PaperProvider>
</ApplicationProvider>
</RecoilRoot>
</ApolloProvider>
</NavigationContainer>
</ErrorBoundary>
)
}
catch(error){
return(<ErrorPrompt code={error} />)
}
I apologize for the amount of code, but my primary question is, does the order of the wrapping components matter? I know I need my NavContainer, ErrorBoundary, and RecoilRoot wrapping all of the pages, but do the orders of these matter? Because the code I have about was working perfectly until I added a mutation to the ErrorPrompt Component. The new ErrorPrompt looked like this...
const ErrorPrompt = (props) => {
useEffect(() => {
sendErrorEmail({
variables: {
errorCode: props.error.toString()
}
})
}, [])
return(
// The same return as before
)
}
After this addition, I was given the Could not find "client" in the context or passed in as an option. Wrap the root component in an <ApolloProvider>, or pass an ApolloClient instance error. The message isn't particularly helpful, as both everything is wrapped in AND I have a client passed in. Does anyone know the solution / if order of these wrappings matters, and it it does, what order it should be in?
I found the issue. It was not a matter of the order in which things were wrapped but rather that I had the declaration for the sendErrorEmail mutation OUTSIDE of a component that was wrapped with the Apollo Provider. I had it out floating in App.js, but when I moved the line to under ErrorPrompt = () => { the error was removed

Strange behavior using useState to load data into a object state

i have broken my head trying to understand a problem with my code. I'm new with React Native so there may be a standard behavior that i am unaware of. This is my problem:
In my component i have a useEffect() to load my data like "componentDidMount":
useEffect( () => {
async function loadDadosLista(){
let listaRecebida = await getListaByID(route.params.idLista);
setLista(listaRecebida);
};
loadDadosLista();
}, []);
My function works correctly, the function getListaById accesses my realm.db and return my object lista. After that I can access the data and associate it with components of type TextInput. My real problem is that any change the i do in any component using properties of lista, overwrites all data leaving only the one that has been modified. I'm using spread operator but apparently it doesn't work. Below is my complete code for better understanding.
function ListConfig(){
const [lista, setLista] = useState({});
useEffect( () => {
async function loadDadosLista(){
let listaRecebida = await getListaByID(route.params.idLista);
setLista(listaRecebida);
};
loadDadosLista();
}, []);
return(
<View style={styles.container}>
<View style={[styles.containerLinha, styles.linha2]}>
<View style={styles.inputLocal}>
<TextInput
name='estabelecimento'
placeholder='Venda do seu Francisco'
placeholderTextColor={theme.colors.cinzaPrimario}
style={styles.textInputLocal(theme)}
value={lista.estabelecimento}
maxLength={25}
onChangeText={ (value) => {
setLista({
...lista,
estabelecimento: value
})
}}
textAlignVertical='bottom'
/>
<IconLocation width={20} height={24} />
</View>
</View>
<View style={styles.containerNotif}>
<Text style={styles.textoNotif(theme)}>
Me notifique 20 minutos antes
</Text>
<ToggleSwitch
isOn={lista.notificacaoAtiva}
onColor={theme.colors.cinzaSecundario}
offColor={theme.colors.cinzaSecundario}
thumbOnStyle={{
backgroundColor: theme.colors.destaque
}}
size="medium"
onToggle={(isOn) => {
setLista({
...lista,
notificacaoAtiva: isOn
});
}}
/>
</View>
</View>
);
}
export default ListConfig;
My object lista have this properties:
{
estabelecimento: 'nameOfEstabelecimento',
notificacaoAtiva: true
}

useEffect not working in custom drawer component without refresh

So I am using react-navigation 5 and I have a custom drawer component for my app. I want to display the name of the logged-in user in the drawer for which I am using a state variable and I am updating the state from firestore. I am calling a function in useEffect which accesses firestore and gets the name of the user. But I think the useEffect is not working without refresh because unless I save the project and refresh the application the state is not getting updated in the application and I cannot see the name of the user without refreshing but it is visible after a refresh. Any ideas why this is happening? Any help would be appreciated. Thank you.
Custom drawer
export default function CustomDrawer(props) {
const paperTheme = useTheme();
const [name,setName]=useState('');
useEffect(() => {
doStuff();
}, []);
const doStuff = async () => {
var phone=global.phone;
await firestore().collection("Users").where('Phone Number', '==', phone).get().then(querySnapshot=>{
querySnapshot.forEach(documentSnapshot => {
console.log("in drawer");
console.log(documentSnapshot.data());
setName(documentSnapshot.data().Name);
})
})
};
return(
<View style={{flex:1}}>
<DrawerContentScrollView {...props}>
<View style={styles.drawerContent}>
<View style={styles.userInfoSection}>
<View style={{flexDirection:'row',marginTop: 15}}>
<Avatar.Image
source={{
uri: ''
}}
size={50}
/>
<View style={{marginLeft:15, flexDirection:'column'}}>
<Title style={styles.title}>{name}</Title>
</View>
</View>
</View>
</View>
</DrawerContentScrollView>
</View>
);
}
Looks like you have doStuff function defined outside the useEffects.
Either you need to put it inside useEffects or add it in dependency list
useEffect(() => {
doStuff();
}, [doStuff]);

Calling modal on a list of products opens the modal for all of them instead of just the one being clciked

I am making a react native app that loads data from google firebase and then display it on a page, when a user clicks on any of the products aa modal will open to show more datails.
I am using useEffect to load the data on page load then display then results:
const fetchData = async () => {
const categories = db.collection("productsDB");
const collections = await categories
.limit(6)
.onSnapshot((querySnapshot) => {
const items = [];
querySnapshot.forEach((documentSnapshot) => {
items.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setItems(items);
setLoading(false);
});
return () => collections();
};
useEffect(() => {
fetchData();
}, []);
and the show them like this:
{loading ? (
<ActivityIndicator />
) : (
items.map((item) => (
<TouchableOpacity
style={styles.queryResult}
key={item.key}
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<View style={styles.queryResultContent}>
<Image
style={{ width: 100, height: 100 }}
source={{ uri: String(item.images) }}
/>
<View>
<Text style={styles.queryInfoHeader}>{item.name}</Text>
</View>
</View>
<View>
<ProductModal
isModalVisible={modalVisible}
setModalVisible={setModalVisible}
navigation={navigation}
{...item}
/>
</View>
</TouchableOpacity>
))
)}
when I open the modal, it opens the modal for all of the products and doesnt really matter if I click on the first product or what, it opens all of the modals, and I am not sure how to get rid of this!
is there any better way to write this function?
You're using the same modalVisible flag for all of your modals; therefore, they either are all visible or all hidden.
Why not have a single modal rather than rendering a bunch of them in the loop, and pass the item as a prop to it?

React native updates state "on its own"

I have two screens, one list (Flatlist) and one filter screen where I want to be able to set some filters for the list. the list screen has the states "data" and "usedFilters". When I am switching to the filters screen, the states are set as navigation parameters for react navigation and then passed via navigation.navigate, together with the onChange function, as props to the filter screen. There they are read, and the filters screen class' state is set (usually with passed filters from the list screen, if no valid filters has been passed, some are initialized).
After that the filters can be changed. If that happens, the state of the filter screen gets updated.
If then the apply button is clicked the filter screens' state is passed to the onChange function and via that back to the list screen, the onChange function updates the state "usedFilters" state of the list screen. If the cancel button is pressed null is passed to the onChange function and there is no setState call.
Setting new states for the list screen works perfectly fine. the problem is, that when i press the cancel button (or the back button automatically rendered by react navigation) the changes are kept nevertheless. That only happens if the state has been changed before. So if there has never been applied a change and hence the "usedFitlers" state of the list screen is null, this behavior does not occur. Only if I already made some changes and hence the "usedFitlers" state of the list screen has a valid value which is passed to the filters screen the cancel or go back buttons won't work as expected.
I am using expo-cli 3 and tried on my android smartphone as well as the iOS simulator. Same behavior. I looked into it with chrome dev tools as well but i simply couldn't figure out where the "usedFitlers" state was updated.
I am using react native 0.60 and react navigation 3.11.0
My best guess is that for some reason the two states share the same memory or one is pointer to the other or sth like that. (Had problems like that with python some time ago, not knowing the it uses pointers when assigning variables).
Anyone got an idea?
List Screen:
export default class ListScreen extends React.Component {
state = { data: [], usedFilters: null };
static navigationOptions = ({ navigation }) => {
let data = navigation.getParam('data')
let changefilter = navigation.getParam('changeFilter')
let currfilter = navigation.getParam('currFilter')
return {
headerTitle:
<Text style={Styles.headerTitle}>{strings('List')}</Text>,
headerRight: (
<TouchableOpacity
onPress={() => navigation.navigate('FilterScreen', {
dataset: data, onChange: changefilter, activeFilters:
currfilter })} >
<View paddingRight={16}>
<Icon name="settings" size={24} color=
{Colors.headerTintColor} />
</View>
</TouchableOpacity>
),
};
};
_onChangeFilter = (newFilter) => {
if (newFilter) {
this.setState({ usedFilters: newFilter })
this.props.navigation.setParams({ currFilter: newFilter });
} // added for debugging reasons
else {
this.forceUpdate();
let a = this.state.usedFilters;
}
}
_fetchData() {
this.setState({ data: fakedata.results },
() => this.props.navigation.setParams({ data: fakedata.results,
changeFilter: this._onChangeFilter }));
}
componentDidMount() {
this._fetchData();
}
render() {
return (
<ScrollView>
<FlatList/>
// Just data rendering, no problems here
</ScrollView>
);
}
}
Filter Screen:
export default class FilterScreen extends React.Component {
static navigationOptions = () => {
return {
headerTitle: <Text style={Styles.headerTitle}> {strings('filter')}
</Text>
};
};
state = { currentFilters: null }
_onChange = (filter, idx) => {
let tmp = this.state.currentFilters;
tmp[idx] = filter;
this.setState({ currentFilters: tmp })
}
_initFilterElems() {
const filters = this.props.navigation.getParam('activeFilters');
const dataset = this.props.navigation.getParam('dataset');
let filterA = [];
let filterB = [];
let filterC = [];
if (filters) {
// so some checks
} else {
// init filters
}
const filterElements = [filterA, filterB, filterC];
this.setState({ currentFilters: filterElements })
}
componentDidMount() {
this._initFilterElems()
}
render() {
const onChange = this.props.navigation.getParam('onChange');
return (
<ScrollView style={Styles.screenView}>
<FlatList
data={this.state.currentFilters} // Listeneinträge
keyExtractor={(item, index) => 'key' + index}
renderItem={({ item, index }) => (
<FilterCategory filter={item} name={filterNames[index]}
idx={index} onChange={this._onChange} />
)}
ItemSeparatorComponent={() => <View style=
{Styles.listSeperator} />}
/>
<View style={Layout.twoHorizontalButtons}>
<TouchableOpacity onPress={() => {
onChange(this.state.currentFilters);
this.setState({ currentFilters: null });
this.props.navigation.goBack();
}}>
<View style={Styles.smallButton}>
<Text style={Styles.buttonText}>{strings('apply')} </Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => {
onChange(null);
this.setState({ currentFilters: null });
this.props.navigation.goBack();
}}>
<View style={Styles.smallButton}>
<Text style={Styles.buttonText}>{strings('cancel')}
</Text>
</View>
</TouchableOpacity>
</View>
</ScrollView >
);
}
}
So when I press the cancel button, null is returned to the _onChangeFilter function of the list screen. This part works, and according to console.log and the debugger, the setState is not called. But if i set a breakpoint within the else part, i can see that this.state.usedFilters has changed.
Ok after a while i figured it out. The problem was that the whole filters list was always just referenced since react native (js) seems to always use references, even when changing sub-parts of the lists.
fixed that by using lodash cloneDeep.