How to add a "reset values" button to a react-admin edit form - react-admin

Is there a way to have a button in a react-admin form so that when I click the button, the values are reset to the edited record's initial values?
I don't mean a Cancel button (that would close the form and redirect). I mean a reset-to-initial-values button that would discard any changes from the last save.

New implementation related to replacing 'redux-form 'with' react-final-form':
import React, {
useCallback,
} from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { Button } from 'react-admin'
import { useForm } from 'react-final-form'
import { makeStyles } from '#material-ui/core/styles'
import IconClear from '#material-ui/icons/Clear'
const useStyles = makeStyles(theme => ({
button: {
marginLeft: '10px',
position: 'relative',
},
leftIcon: {
marginRight: theme.spacing(1),
},
icon: {
fontSize: 18,
},
}), { name: 'ClearButton' })
const sanitizeRestProps = ({
basePath,
invalid,
pristine,
//reset,
saving,
submitOnEnter,
handleSubmit,
handleSubmitWithRedirect,
undoable,
onSave,
...rest
}) => rest
const ClearButton = ({ className, icon, ...rest}) => {
const classes = useStyles()
const form = useForm()
const handleClick = useCallback(() => {
form.setConfig('keepDirtyOnReinitialize', false)
form.reset()
form.setConfig('keepDirtyOnReinitialize', true)
}, [form])
return (
<Button
className={classnames(classes.button, className)}
onClick={handleClick}
{...sanitizeRestProps(rest)}
>
{ icon ? React.cloneElement(icon, { className: classnames(classes.leftIcon, classes.icon) }) : null }
</Button>
)
}
ClearButton.propTypes = {
className: PropTypes.string,
classes: PropTypes.object,
icon: PropTypes.element,
}
ClearButton.defaultProps = {
icon: <IconClear />,
label: 'Clear',
}
export default ClearButton

Related

React-Native: pressing the button twice only updates the this.setState

The App is simple.. Conversion of gas.. What im trying to do is multiply the Inputed Amount by 2 as if it is the formula. So i have a index.js which is the Parent, and the Calculate.component.js who do all the calculations. What i want is, index.js will pass a inputed value to the component and do the calculations and pass it again to the index.js to display the calculated amount.
Index.js
import React, { Component } from 'react';
import { Text } from 'react-native';
import styled from 'styled-components';
import PickerComponent from './Picker.Component';
import CalculateAVGAS from './Calculate.component';
export default class PickerAVGAS extends Component {
static navigationOptions = ({ navigation }) => ({
title: navigation.getParam('headerTitle'),
headerStyle: {
borderBottomColor: 'white',
},
});
state = {
gasTypeFrom: 'Gas Type',
gasTypeTo: 'Gas Type',
input_amount: '',
pickerFrom: false,
pickerTo: false,
isResult: false,
result: '',
};
inputAmount = amount => {
this.setState({ input_amount: amount });
console.log(amount);
};
onResult = value => {
this.setState({
result: value,
});
console.log('callback ', value);
};
render() {
return (
<Container>
<Input
placeholder="Amount"
multiline
keyboardType="numeric"
onChangeText={amount => this.inputAmount(amount)}
/>
<ResultContainer>
<ResultText>{this.state.result}</ResultText>
</ResultContainer>
<CalculateContainer>
<CalculateAVGAS
title="Convert"
amount={this.state.input_amount}
total="total"
titleFrom={this.state.gasTypeFrom}
titleTo={this.state.gasTypeTo}
// isResult={this.toggleResult}
result={value => this.onResult(value)}
/>
</CalculateContainer>
</Container>
);
}
}
CalculateAVGAS / component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
export default class CalculateAVGAS extends Component {
static propTypes = {
amount: PropTypes.string.isRequired,
titleFrom: PropTypes.string.isRequired,
titleTo: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
};
state = {
totalVisible: true,
result: '',
};
onPressConversion = () => {
const formula = this.props.amount * 2;
const i = this.props.result(this.state.result);
this.setState({ result: formula });
// console.log(this.state.result);
console.log('func()');
}
render() {
return (
<ButtonContainer onPress={() => this.onPressConversion()}>
<ButtonText>{this.props.title}</ButtonText>
</ButtonContainer>
);
}
}
After doing this, the setState only updates when pressing the Convert button twice
your issue here is that you want to display information in the parent component, but you are saving that info in the child component's state.
Just pass amount, and result, to the stateless child component (CalculateAVGAS).
It's usually best to keep child components "dumb" (i.e. presentational) and just pass the information they need to display, as well as any functions that need to be executed, as props.
import React, {Component} from 'react';
import styled from 'styled-components';
export default class CalculateAVGAS extends Component {
onPressConversion = () => {
this.props.result(this.props.amount * 2);
};
render() {
return (
<ButtonContainer onPress={() => this.onPressConversion()}>
<ButtonText>{this.props.title}</ButtonText>
</ButtonContainer>
);
}
}
const ButtonContainer = styled.TouchableOpacity``;
const ButtonText = styled.Text``;
Parent component looks like:
import React, {Component} from 'react';
import {Text} from 'react-native';
import styled from 'styled-components';
import CalculateAVGAS from './Stack';
export default class PickerAVGAS extends Component {
static navigationOptions = ({navigation}) => ({
title: navigation.getParam('headerTitle'),
headerStyle: {
borderBottomColor: 'white',
},
});
state = {
gasTypeFrom: 'Gas Type',
gasTypeTo: 'Gas Type',
input_amount: null,
pickerFrom: false,
pickerTo: false,
isResult: false,
result: null,
};
inputAmount = amount => {
this.setState({input_amount: amount});
};
onResult = value => {
this.setState({
result: value,
});
};
render() {
return (
<Container>
<Input
placeholder="Amount"
multiline
keyboardType="numeric"
onChangeText={amount => this.inputAmount(amount)}
/>
<ResultContainer>
<ResultText>{this.state.result}</ResultText>
</ResultContainer>
<CalculateContainer>
<CalculateAVGAS
title="Convert"
amount={this.state.input_amount}
total="total"
titleFrom={this.state.gasTypeFrom}
titleTo={this.state.gasTypeTo}
result={value => this.onResult(value)}
/>
</CalculateContainer>
</Container>
);
}
}
const Container = styled.View``;
const ResultContainer = styled.View``;
const ResultText = styled.Text``;
const CalculateContainer = styled.View``;
const Input = styled.TextInput``;

The component for route 'ActivityFeed' must be a React component

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 it's 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 it's 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 it's 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?
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'
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.

How can I pass the navigator prop from react-native-navigation v2 to my splash screen via Navigation.setRoot

I am trying to migrate from react-native-navigation v1 to react-native-navigation v2. I am struggling to move from
Navigation.startSingleScreenApp
to
Navigation.setRoot
When I switch from Navigation.startSingleScreenApp (v1) to Navigation.setRoot (v2), I no longer have the navigator prop that I was relying on to navigate around the application.
I have copy and pasted all relevant code below
RegisterScreens
import { Navigation } from 'react-native-navigation';
import SplashScreenScreen from './components/SplashScreen';
import { Provider } from 'react-redux';
import React from "react";
import SCREEN from './screenNames';
export default function registerScreens(store) {
Navigation.registerComponent(
SCREEN.SPLASH_SCREEN,
() => props => (<Provider store={store}><SplashScreenScreen {...props} /></Provider>), () => SplashScreenScreen);
App
import { Platform } from 'react-native';
import { Navigation } from 'react-native-navigation';
import registerScreens from './registerScreens';
import { Colors, Fonts } from './themes';
import { store } from './configureStore';
import NavigationListener from './NavigationEventListener';
import configureNotification from './configureNotification';
import SCREEN from './screenNames';
import Reactotron from 'reactotron-react-native';
const navBarTranslucent = Platform.OS === 'ios';
configureNotification();
registerScreens(store);
new NavigationListener(store);
const STARTING_SCREEN = SCREEN.SPLASH_SCREEN;
Navigation.events().registerAppLaunchedListener(() => {
Reactotron.log('5');
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
id: STARTING_SCREEN,
name: STARTING_SCREEN
}
}],
}
},
layout: {
orientation: 'portrait',
},
});
});
SplashScreen
import React from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { PersistGate } from 'redux-persist/es/integration/react';
import { navigateToFirstScreen } from '../redux/splash';
import { Colors, Fonts, Metrics } from '../themes';
import { persistor } from '../configureStore';
export class SplashScreen extends React.Component {
navigateTo = (screen) =>
this.props.navigator.push({
screen,
overrideBackPress: true,
backButtonHidden: true,
animated: false,
navigatorStyle: {
disabledBackGesture: true,
},
});
render() {
const { dispatchNavigateToFirstScreen } = this.props;
return (
<PersistGate
persistor={persistor}
onBeforeLift={() => setTimeout(() => dispatchNavigateToFirstScreen(this.navigateTo), 2000)}><View style={styles.bodyContainer}
>
<Text>Jono</Text>
</View>
</PersistGate>
);
}
}
const styles = StyleSheet.create({
bodyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: Colors.splashScreen,
},
appTitleText: {
fontSize: Fonts.size.splashScreenTitle,
fontFamily: Fonts.type.extraBold,
lineHeight: Metrics.lineHeight.appTitle,
textAlign: 'center',
color: Colors.textLightColor,
},
});
SplashScreen.propTypes = {
navigator: PropTypes.shape({
push: PropTypes.func.isRequired,
}).isRequired,
dispatchNavigateToFirstScreen: PropTypes.func.isRequired,
};
const mapDispatchToProps = (dispatch) => {
return {
dispatchNavigateToFirstScreen: (navigateTo) =>
dispatch(navigateToFirstScreen(navigateTo)),
};
};
export default connect(null, mapDispatchToProps)(SplashScreen);
I spent multiple hours trying to solve this problem so I am going to post my conclusion as an answer.
this.props.navigator is not used anymore in 2.x.
You need to use Navigation
This dude had the same problem and reached the same conclusion: https://github.com/wix/react-native-navigation/issues/3795

react navigation v3 cannot read property 'default' of undefined

I'm trying to define the navigation in my app in react native but I get a weird error.
the root stack on an app
import React from 'react'
import { Easing, Animated } from 'react-native'
import { createAppContainer, createStackNavigator, createSwitchNavigator } from 'react-navigation'
import { UserStore } from '../stores'
import { Playground, Welcome, Login, Register, ConfirmationCode, App, AuthLoading } from '../screens'
// import TabStack from './TabStack'
const transitionConfig = () => {
return {
transitionSpec: {
duration: 750,
easing: Easing.out(Easing.poly(4)),
timing: Animated.timing,
useNativeDriver: true,
},
screenInterpolator: sceneProps => {
const { layout, position, scene } = sceneProps
const thisSceneIndex = scene.index
const width = layout.initWidth
const translateX = position.interpolate({
inputRange: [thisSceneIndex - 1, thisSceneIndex],
outputRange: [width, 0],
})
return { transform: [{ translateX }] }
},
}
}
const AuthStack = createStackNavigator(
{
Welcome,
Login,
Register,
ConfirmationCode,
},
{
initialRouteName: 'Welcome',
headerMode: 'none',
lazy: true,
transitionConfig,
defaultNavigationOptions: {
gesturesEnabled: false,
},
}
)
let MainStack = createSwitchNavigator(
{
AuthLoading,
Auth: AuthStack,
App,
},
{
initialRouteName: 'AuthLoading',
headerMode: 'none',
lazy: true,
defaultNavigationOptions: {
gesturesEnabled: false,
},
}
)
export default createAppContainer(MainStack)
App.js
import React, { Component } from 'react'
import {
View,
Text,
} from 'react-native'
import { inject, observer } from 'mobx-react/native'
import style from './style'
import AppStack from '../../routes/AppStack';
#inject('UserStore')
#observer
class App extends Component {
constructor(props) {
super(props)
this.state = {
}
}
componentDidMount() {
}
render() {
const { container } = style;
return (
<View style={container}>
<AppStack/>
</View>
)
}
}
export default App
AppStack
import React from 'react'
import { Easing, Animated } from 'react-native'
import { createAppContainer, createStackNavigator, createSwitchNavigator } from 'react-navigation'
import { UserStore } from '../stores'
import { Playground, Welcome, Login, Register, ConfirmationCode, App, Search, SearchResult, AuthLoading, BusinessDetail, BusinessMap, MyFavouriteBusiness, MakeAppointment, EditUserProfile, TermsAndConditions } from '../screens'
import TabStack from './TabStack'
const transitionConfig = () => {
return {
transitionSpec: {
duration: 750,
easing: Easing.out(Easing.poly(4)),
timing: Animated.timing,
useNativeDriver: true,
},
screenInterpolator: sceneProps => {
const { layout, position, scene } = sceneProps
const thisSceneIndex = scene.index
const width = layout.initWidth
const translateX = position.interpolate({
inputRange: [thisSceneIndex - 1, thisSceneIndex],
outputRange: [width, 0],
})
return { transform: [{ translateX }] }
},
}
}
export default createStackNavigator(
{
TabStack,
SearchResult: {
screen: SearchResult
},
// SearchResult: SearchResult,
BusinessDetail: {
screen: BusinessDetail
},
BusinessMap: {
screen: BusinessMap
},
MakeAppointment: {
screen: MakeAppointment
},
TermsAndConditions: {
screen: TermsAndConditions
},
EditUserProfile: {
screen: EditUserProfile
}
},
{
initialRouteName: 'TabStack',
headerMode: 'none',
lazy: true,
}
)
TabStack
import React from 'react';
import { createBottomTabNavigator, createAppContainer } from 'react-navigation';
import {
Search,
UserProfile,
MyFavouriteBusiness,
MyAppointments
} from '../screens'
import Icon from 'react-native-vector-icons/Feather';
import Colors from '../utils/Colors'
export default TabStack = createBottomTabNavigator(
{
// Search: {
// screen: Search
// },
Search:Search,
MyFavouriteBusiness: MyFavouriteBusiness,
MyAppointments: MyAppointments,
UserProfile,
},
{
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, horizontal, tintColor }) => {
const { routeName } = navigation.state;
let icon_name;
switch (routeName) {
case 'Search': {
icon_name = 'search'
break;
}
case 'MyFavouriteBusiness': {
icon_name = 'heart'
break;
}
case 'MyAppointments': {
icon_name = 'calendar'
break;
}
case 'UserProfile': {
icon_name = 'user'
break;
}
default: {
icon_name = 'search'
break;
}
}
return <Icon name={icon_name} size={horizontal ? 20 : 25} color={tintColor} />;
},
}),
initialRouteName: 'Search',
tabBarOptions: {
activeTintColor: Colors.pink,
inactiveTintColor: Colors.black,
showLabel: false,
style: {
backgroundColor: 'white'
}
},
}
)
I tried to separate AppStack to another file and then import it in screen 'App.js' and call the stack there and then I get this issue.
in addition, I'm tried to understand what to difference between
declare navigation this way
export default TabStack = createBottomTabNavigator(
{
Search:Search,
MyFavouriteBusiness: MyFavouriteBusiness,
MyAppointments: MyAppointments,
UserProfile,
},
to this way
export default TabStack = createBottomTabNavigator(
{
Search:{
screen:Search,
}
},
what the declaration of 'screen:'Search'' do? when to use this way and when the other way?
I had the same issue today. It seems that in my case the problem was a wrong import of a screen declared in the StackNavigator.
Cheers! Nicu

How to make a custom alert dialog that is called using a method instead of embedding into parent view?

How to make a custom alert dialog that is self destroyed when dismissed and called from any where in the app with a static method. Same as in react-native method
alert()
My current code:
import { View } from 'react-native';
import { Portal, Dialog } from 'react-native-paper';
static function showDialog(title, paragraph, buttonLabelText, onDismissHandler, canDismiss) {
return (
<View style={{ flex: 1 }}>
<Portal>
<Dialog dismissable={canDismiss} visible={need a value here} onDismiss={() => {this.onDismissHandler()}}>
<Dialog.Title>{title}</Dialog.Title>
<Dialog.Content>
<Paragraph>{paragraph}</Paragraph>
</Dialog.Content>
<Dialog.Actions>
<Button onPress={() => {this.onDismissHandler()}}>{buttonLabelText}}</Button>
</Dialog.Actions>
</Dialog>
</Portal>
</View>
)
}
export default { showDialog };
React-native has it's on Alert and you can use like below.
import { Alert } from 'react-native';
import _ from 'lodash';
export const showPopupAlert = (message, onOKPressed: _.noop) => {
Alert.alert(
'',
message,
[
{ text: 'OK', onPress: onOKPressed },
],
);
};
export const showOptionAlert = (message, buttonOk, buttonCancel, action, title) => {
Alert.alert(
title,
message,
[
{ text: buttonOk, onPress: action },
{ text: buttonCancel },
],
{ cancelable: false },
);
};
Save this as ShowAlert.js as root laval component and call like below.
import { showPopupAlert } from 'yourpath/ShowAlert';
showPopupAlert('Your message'); // Call like this