问题
I have looked through various similar posts here on SO regarding similar issue, but none of the answers solved it for me.
This is the full error:
So in my src/navigation/feed/stack.js
its being defined like so:
import React from 'react';
import {StackNavigator} from 'react-navigation';
import ActivityFeed from 'activity-feed/session-user/screens/Main';
import HamburgerButton from 'navigation-components/HamburgerButton';
import HeaderTitle from 'navigation-components/HeaderTitle';
import ActionAlertIndicator from 'navigation-components/ActionAlertIndicator';
import * as navConfig from '../config';
import * as cache from 'utils/cache';
const stack = StackNavigator(
{
ActivityFeed: {
screen: ActivityFeed,
navigationOptions: ({navigation}) => ({
header: (
<HeaderTitle
headerLeft={() => (
<HamburgerButton
onPress={() => navigation.navigate('DrawerOpen')}
/>
)}
headerRight={() => (
<ActionAlertIndicator
onPress={() => {
cache.setRouteStarter('MainDrawer');
navigation.navigate('ActionAlertsStack');
}}
/>
)}
/>
),
}),
},
},
{
navigationOptions: {
...navConfig.defaultStackConfig,
},
}
);
export default stack;
The actual component or screen is defined like so inside of src/activity-feed/session-user/screens/Main.js
:
import React, {PureComponent} from 'react';
import {
FlatList,
StyleSheet,
AppState,
Platform,
Dimensions,
View,
Alert,
} from 'react-native';
import PropTypes from 'prop-types';
import OneSignal from 'react-native-onesignal';
import {Loading, SwippableCard, BottomAlert} from 'common-components';
import EmptyState from 'activity-feed/session-user/components/EmptyState';
import EventFeedCard from 'events/components/EventFeedCard';
import SurveyBallotFeedCard from 'surveys-ballots/components/FeedCard';
import MicroSurvey from 'surveys-ballots/components/MicroSurvey';
import ActionAlertFeedCard from 'action-alerts/components/ActionAlertFeedCard';
import MissingAddressCard from 'action-alerts/components/MissingAddressCard';
import ArticleFeedCard from 'articles/components/ArticleFeedCard';
import GetInvolvedFeedCard from 'account-settings/components/GetInvolvedFeedCard';
import {connect} from 'react-redux';
import {
fetchFeed,
handleContentSwipe,
undoSwipeAction,
hideUndoAlert,
} from 'activity-feed/actions';
import {setSelectedEvent} from 'events/actions';
import {setSelectedSurvey} from 'surveys-ballots/actions';
import {setSelectedAlert, getCampaignDetails} from 'action-alerts/actions';
import * as cache from 'utils/cache';
import {setSelectedArticle} from 'articles/actions';
import {
handleUpdateTopics,
handleUpdateGetInvoved,
} from 'account-settings/preferencesActions';
import {scale} from 'react-native-size-matters';
import {emptyStateStyles} from 'theme';
const {height} = Dimensions.get('window');
export class ActivityFeed extends PureComponent {
static propTypes = {
displayAlert: PropTypes.bool,
feed: PropTypes.array,
fetchFeed: PropTypes.func,
getCampaignDetails: PropTypes.func,
handleContentSwipe: PropTypes.func,
handleUpdateGetInvoved: PropTypes.func,
handleUpdateTopics: PropTypes.func,
hideUndoAlert: PropTypes.func,
lastSwippedElement: PropTypes.object,
loading: PropTypes.bool,
navigation: PropTypes.object,
setSelectedAlert: PropTypes.func,
setSelectedArticle: PropTypes.func,
setSelectedEvent: PropTypes.func,
setSelectedSurvey: PropTypes.func.isRequired,
undoSwipeAction: PropTypes.func,
userEmailIsValidForVoterVoice: PropTypes.bool,
};
constructor(props) {
super(props);
this.prompted = false;
this.state = {
refreshing: false,
appState: AppState.currentState,
};
}
async componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
if (!this.props.loading) {
const doRefresh = await cache.shouldRefresh('feed');
if (this.props.feed.length === 0 || doRefresh) {
this.props.fetchFeed();
}
cache.incrementAppViews();
}
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = async appState => {
if (
this.state.appState.match(/inactive|background/) &&
appState === 'active'
) {
cache.incrementAppViews();
const doRefresh = await cache.shouldRefresh('feed');
if (doRefresh) {
this.props.fetchFeed();
}
}
this.setState({appState});
};
_keyExtractor = ({Entity}) =>
(Entity.Key || Entity.Id || Entity.CampaignId || Entity.Code).toString();
_gotoEvent = event => {
cache.setRouteStarter('MainDrawer');
this.props.setSelectedEvent(event);
const title = `${event.LegislatureType} Event`;
this.props.navigation.navigate('EventDetails', {title});
};
_gotoSurveyBallot = survey => {
cache.setRouteStarter('MainDrawer');
this.props.setSelectedSurvey(survey);
this.props.navigation.navigate('SurveyDetails');
};
_gotoArticle = article => {
cache.setRouteStarter('MainDrawer');
this.props.setSelectedArticle(article);
this.props.navigation.navigate('ArticleDetails');
};
_onAlertActionButtonPress = async item => {
cache.setRouteStarter('MainDrawer');
await this.props.setSelectedAlert(item.Entity);
this.props.getCampaignDetails();
if (this.props.userEmailIsValidForVoterVoice) {
this.props.navigation.navigate('Questionnaire');
} else {
this.props.navigation.navigate('UnconfirmedEmail');
}
};
_onSwipedOut = (swippedItem, index) => {
this.props.handleContentSwipe(this.props, {swippedItem, index});
};
_handleGetInvolved = (response, entity) => {
if (response !== entity.IsSelected) {
const isTopic = entity.Category !== 'GetInvolved';
const items = [
{
...entity,
IsSelected: response,
},
];
if (isTopic) {
this.props.handleUpdateTopics({topics: items});
} else {
this.props.handleUpdateGetInvoved({involved: items});
}
}
};
renderItem = ({item, index}) => {
const {Type, Entity} = item;
if (Type === 'EVENT') {
return (
<SwippableCard onSwipedOut={() => this._onSwipedOut(item, index)}>
<EventFeedCard
style={styles.push}
mainActionButtonPress={() => this._gotoEvent(Entity)}
event={Entity}
/>
</SwippableCard>
);
}
if (['SURVEY_SURVEY', 'SURVEY_BALLOT'].includes(Type)) {
return (
<SwippableCard onSwipedOut={() => this._onSwipedOut(item, index)}>
<SurveyBallotFeedCard
style={styles.push}
survey={Entity}
handleViewDetails={() => this._gotoSurveyBallot(Entity)}
/>
</SwippableCard>
);
}
if (Type === 'SURVEY_MICRO') {
return (
<SwippableCard onSwipedOut={() => this._onSwipedOut(item, index)}>
<MicroSurvey style={styles.push} selectedSurvey={Entity} />
</SwippableCard>
);
}
if (Type === 'ALERT') {
return (
<SwippableCard onSwipedOut={() => this._onSwipedOut(item, index)}>
<ActionAlertFeedCard
datePosted={Entity.StartDateUtc}
style={styles.push}
title={Entity.Headline}
content={Entity.Alert}
mainActionButtonPress={() => this._onAlertActionButtonPress(item)}
secondaryActionButtonPress={() => {
this.props.setSelectedAlert(Entity);
// eslint-disable-next-line
this.props.navigation.navigate("ActionAlertDetails", {
content: Entity.Alert,
id: Entity.CampaignId,
title: Entity.Headline,
});
}}
/>
</SwippableCard>
);
}
if (Type === 'ARTICLE') {
return (
<SwippableCard onSwipedOut={() => this._onSwipedOut(item, index)}>
<ArticleFeedCard
content={Entity}
style={styles.push}
mainActionButtonPress={() => this._gotoArticle(Entity)}
/>
</SwippableCard>
);
}
//prettier-ignore
if (Type === 'NOTIFICATION' && Entity.Code === 'INDIVIDUAL_ADDRESS_HOME_MISSING') {
return (
<MissingAddressCard
style={styles.push}
navigate={() => this.props.navigation.navigate('HomeAddress')}
/>
);
}
if (['PREFERENCE_TOPIC', 'PREFERENCE_INVOLVEMENT'].includes(Type)) {
return (
<SwippableCard onSwipedOut={() => this._onSwipedOut(item, index)}>
<GetInvolvedFeedCard
style={styles.push}
title={Entity.DisplayText}
onPress={response => this._handleGetInvolved(response, Entity)}
/>
</SwippableCard>
);
}
return null;
};
_onRefresh = async () => {
try {
this.setState({refreshing: true});
this.props
.fetchFeed()
.then(() => {
this.setState({refreshing: false});
})
.catch(() => {
this.setState({refreshing: false});
});
} catch (e) {
this.setState({refreshing: false});
}
};
_trackScroll = async event => {
try {
if (this.prompted) {
return;
}
const y = event.nativeEvent.contentOffset.y;
const scrollHeight = height * 0.8;
const page = Math.round(Math.floor(y) / scrollHeight);
const alert = await cache.shouldPromtpPushNotificationPermissions();
const iOS = Platform.OS === 'ios';
if (alert && iOS && page > 1) {
this.prompted = true;
this._openPromptAlert();
}
} catch (e) {
return false;
}
};
_openPromptAlert = () => {
Alert.alert(
'Push Notifications Access',
'Stay engaged with NFIB on the issues and activities you care about by allowing us to notify you using push notifications',
[
{
text: 'Deny',
onPress: () => {
cache.pushNotificationsPrompted();
},
style: 'cancel',
},
{
text: 'Allow',
onPress: () => {
OneSignal.registerForPushNotifications();
cache.pushNotificationsPrompted();
},
},
],
{cancelable: false}
);
};
_getAlertTitle = () => {
const {lastSwippedElement} = this.props;
const {Type} = lastSwippedElement.swippedItem;
if (Type.startsWith('PREFERENCE')) {
return 'Preference Dismissed';
}
switch (Type) {
case 'EVENT':
return 'Event Dismissed';
case 'SURVEY_BALLOT':
return 'Ballot Dismissed';
case 'SURVEY_SURVEY':
return 'Survey Dismissed';
case 'SURVEY_MICRO':
return 'Micro Survey Dismissed';
case 'ARTICLE':
return 'Article Dismissed';
case 'ALERT':
return 'Action Alert Dismissed';
default:
return 'Dismissed';
}
};
render() {
if (this.props.loading && !this.state.refreshing) {
return <Loading />;
}
const contentStyles =
this.props.feed.length > 0 ? styles.content : emptyStateStyles.container;
return (
<View style={styles.container}>
<FlatList
contentContainerStyle={contentStyles}
showsVerticalScrollIndicator={false}
data={this.props.feed}
renderItem={this.renderItem}
keyExtractor={this._keyExtractor}
removeClippedSubviews={false}
onRefresh={this._onRefresh}
refreshing={this.state.refreshing}
ListEmptyComponent={() => (
<EmptyState navigation={this.props.navigation} />
)}
scrollEventThrottle={100}
onScroll={this._trackScroll}
/>
{this.props.displayAlert && (
<BottomAlert
title={this._getAlertTitle()}
onPress={this.props.undoSwipeAction}
hideAlert={this.props.hideUndoAlert}
/>
)}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
paddingHorizontal: scale(8),
paddingTop: scale(16),
paddingBottom: scale(20),
},
push: {
marginBottom: 16,
},
});
const mapState2Props = ({
activityFeed,
auth: {userEmailIsValidForVoterVoice},
navigation,
}) => {
return {
...activityFeed,
userEmailIsValidForVoterVoice,
loading: activityFeed.loading || navigation.deepLinkLoading,
};
};
export default connect(mapState2Props, {
fetchFeed,
getCampaignDetails,
handleUpdateGetInvoved,
handleUpdateTopics,
setSelectedAlert,
setSelectedArticle,
setSelectedEvent,
setSelectedSurvey,
handleContentSwipe,
undoSwipeAction,
hideUndoAlert,
})(ActivityFeed);
I don't see anything apparent with my code and I am wondering if its some change that the react-navigation
team did.
I am using react-navigation
version 1.5.11 with react-native
version 0.60.4.
Is this a compatibility issue with the RN version? Do I have no choice but to upgrade?
And this problem seems prevalent throughout my application. I also get the error here:
This is the src/auth/screens/ResetLinkConfirmationAlert.js
file:
import React from 'react';
import {connect} from 'react-redux';
import ResetPasswordLinkConfirmationAlert from 'auth/components/ResetPasswordLinkConfirmationAlert';
import PropTypes from 'prop-types';
const ResetLinkConfirmationAlert = ({resetEmail, navigation}) => {
const {params} = navigation.state;
return <ResetPasswordLinkConfirmationAlert email={resetEmail} {...params} />;
};
ResetLinkConfirmationAlert.propTypes = {
navigation: PropTypes.object,
resetEmail: PropTypes.string,
};
const mapStateToProps = ({registrations}) => {
const {resetEmail} = registrations.resetPasswordData;
const email = resetEmail || registrations.verificationEmail;
return {resetEmail: email};
};
export default connect(mapStateToProps)(ResetLinkConfirmationAlert);
and its being imported here in src/navigation/auth/stack.js
:
import React from "react";
import { StackNavigator, NavigationActions } from "react-navigation";
import { Intro } from "auth/screens/Intro";
import { Login } from "auth/screens/Login";
import { PasswordReset } from "auth/screens/PasswordReset";
import { RegisterNoEmail } from "auth/screens/RegisterNoEmail";
import AskForMembership from "auth/screens/AskForMembership";
import { CompleteAccount } from "auth/screens/CompleteAccount";
import { ConfirmMemberAccount } from "auth/screens/ConfirmMemberAccount";
import { Register } from "auth/screens/Register";
import SetNewPassword from "auth/screens/SetNewPassword";
import { RegisterEmailPassword } from "auth/screens/RegisterEmailPassword";
import ResetLinkConfirmationAlert from "auth/screens/ResetLinkConfirmationAlert";
import DetailsConfirmation from "auth/screens/DetailsConfirmation";
import AccountCreated from "auth/screens/AccountCreated";
import BackButton from "navigation-components/BackButton";
import CustomHeader from "navigation-components/CustomHeader";
import HeaderTitle from "navigation-components/HeaderTitle";
import { v2Colors } from "theme";
import { defaultStackConfig, defaultHeaderStyles } from "../config";
const leftRegiterNavOptions = {
title: "Register",
headerStyle: defaultStackConfig.authHeaderStyle
};
const stack = StackNavigator(
{
Intro: {
screen: Intro,
navigationOptions: {
header: null
}
},
Register: {
screen: Register,
navigationOptions: ({ navigation }) => ({
header: <CustomHeader onPress={() => navigation.goBack(null)} />,
headerStyle: defaultStackConfig.authHeaderStyle
})
},
RegisterNoEmail: {
screen: RegisterNoEmail,
navigationOptions: leftRegiterNavOptions
},
RegisterEmailPassword: {
screen: RegisterEmailPassword,
navigationOptions: leftRegiterNavOptions
},
AskForMembership: {
screen: AskForMembership,
navigationOptions: {
header: <HeaderTitle />,
headerStyle: defaultStackConfig.authHeaderStyle
}
},
ConfirmMemberAccount: {
screen: ConfirmMemberAccount,
navigationOptions: ({ navigation }) => ({
header: (
<HeaderTitle
headerLeft={() => (
<BackButton onPress={() => navigation.goBack(null)} />
)}
/>
),
headerStyle: defaultStackConfig.authHeaderStyle
})
},
CompleteAccount: {
screen: CompleteAccount,
navigationOptions: {
header: <HeaderTitle />,
headerStyle: defaultStackConfig.authHeaderStyle
}
},
Login: {
screen: Login,
navigationOptions: ({ navigation }) => ({
title: "Log In",
headerLeft: <BackButton onPress={() => navigation.goBack(null)} />,
headerStyle: defaultStackConfig.authHeaderStyle
})
},
PasswordReset: {
screen: PasswordReset,
navigationOptions: ({ navigation }) => ({
title: "Password Reset",
headerLeft: <BackButton onPress={() => navigation.goBack(null)} />,
headerStyle: defaultStackConfig.authHeaderStyle
})
},
ResetLinkConfirmationAlert: {
screen: ResetLinkConfirmationAlert,
navigationOptions: ({ navigation }) => ({
title: "Password Reset",
headerLeft: (
<BackButton
onPress={() => {
const resetNavAction = NavigationActions.reset({
index: 0,
key: null,
actions: [NavigationActions.navigate({ routeName: "Intro" })]
});
navigation.dispatch(resetNavAction);
}}
/>
),
headerStyle: defaultStackConfig.authHeaderStyle
})
},
Upgrading to react-navigation
2.0.0 is not the answer because I already tried that and if you are going to suggest upgrading to 3.x please explain how that will solve this issue.
It was suggested that in the changelog for react-redux 7.1.0 they mention a note stating PropTypes.func
has to be changed to PropTypes.elementType
if an element is being passed as a prop
github.com/reduxjs/react-redux/releases/tag/v7.0.1
So in the case where I am getting the error for SetNewPassword
, I refactored it like so:
export class CompleteAccount extends PureComponent {
static propTypes = {
loading: PropTypes.bool,
newConfirmResetPassword: PropTypes.string,
newResetPassword: PropTypes.string,
resetUserPassword: PropTypes.elementType.isRequired,
setConfirnResetPassword: PropTypes.elementType.isRequired,
setNewResetPassword: PropTypes.elementType.isRequired,
validationErrors: PropTypes.object
};
and then in navigation/auth/stack.js
I added curly braces to the import statement like so:
import { SetNewPassword } from "auth/screens/SetNewPassword";
but I am still getting that error message, although I am not sure if I applied that correctly. At the same time I have noticed that SetNewPassword.js
file only has the named export of CompleteAccount
:
export class CompleteAccount extends PureComponent {
static propTypes = {
loading: PropTypes.bool,
newConfirmResetPassword: PropTypes.string,
newResetPassword: PropTypes.string,
resetUserPassword: PropTypes.elementType.isRequired,
setConfirnResetPassword: PropTypes.elementType.isRequired,
setNewResetPassword: PropTypes.elementType.isRequired,
validationErrors: PropTypes.object
};
.......
export default connect(
mapStateToProps,
{
resetUserPassword,
setNewResetPassword,
setConfirnResetPassword
}
)(CompleteAccount);
Not sure how this file was working before in that manner. I usually name my files the same name as the class or functional screen and import it with the same name.
On further inspection I see that there are two screens with the same class name function.
CompleteAccount.js
:
export class CompleteAccount extends PureComponent {
static propTypes = {
cellPhone: PropTypes.string,
cellPhoneChanged: PropTypes.func.isRequired,
city: PropTypes.string,
cityChanged: PropTypes.func.isRequired,
errors: PropTypes.object,
firstName: PropTypes.string.isRequired,
homeAddress: PropTypes.string,
homeAddressChanged: PropTypes.func.isRequired,
homePhone: PropTypes.string,
homePhoneChanged: PropTypes.func.isRequired,
registeredUser: PropTypes.object,
registerUser: PropTypes.func.isRequired,
state: PropTypes.string,
stateChanged: PropTypes.func.isRequired,
zipCode: PropTypes.string.isRequired,
zipCodeChanged: PropTypes.func.isRequired,
};
which is exported as:
export default connect(mapStateToProps, {
cityChanged,
homeAddressChanged,
homePhoneChanged,
cellPhoneChanged,
stateChanged,
zipCodeChanged,
registerUser,
})(CompleteAccount);
and then there is SetNewPassword.js
:
which is also named:
export class CompleteAccount extends PureComponent {
static propTypes = {
loading: PropTypes.bool,
newConfirmResetPassword: PropTypes.string,
newResetPassword: PropTypes.string,
resetUserPassword: PropTypes.func.isRequired,
setConfirnResetPassword: PropTypes.func.isRequired,
setNewResetPassword: PropTypes.func.isRequired,
validationErrors: PropTypes.object
};
.....
export default connect(
mapStateToProps,
{
resetUserPassword,
setNewResetPassword,
setConfirnResetPassword
}
)(CompleteAccount);
even though the file name is completely different. That is confusing, why didn't they just give the second one the class name of SetNewPassword
?
回答1:
The immediate issue looks like the multiple exports in the component files. Try removing the export
before the class definition and only keep export default
at the end.
Regarding the confusion about SetNewPassword.js
and CompleteAccount.js
having the same exports, that'll not cause an issue as long you import the default exported
component.
To put it simply,
If you export a component as default
, then you can import it without the {}
curly braces, like
import CompleteAccount from '.../CompleteAccount.js'
Here you can name the import anything you want.
If you use the curly braces, that'll import the named export
, like
import {CompleteAccount} from '.../CompleteAccount.js'
回答2:
After a long grueling 6 days at this, and attempting fixes that went against our understanding of using curly braces when you got named exports, I always suspected that the problem was with react-navigation
because I did not mess with the react-navigation
version or the codebase.
The problem is how react-navigation
works or does not work with react-redux
version 7.
React Navigation does not recognize the object returned by React-Redux version 7.
The solution was to downgrade to React-Redux version 5.1.1.
来源:https://stackoverflow.com/questions/57189982/the-component-for-route-activityfeed-must-be-a-react-component