How to create a custom alert using Redux ? (Warning: Cannot update during an existing state transition) - react-native

I want, instead of using the default Alert.alert method, to create my own so that I could change the background of my Alert, whether it's successful or not. To do so, I use a Modal, which put in my root component, App.js (so that the modal can appear on each screen) :
App.js
// ...
const Navigation = createAppContainer(MainNavigator);
// Render the app container component with the provider around it
class App extends React.Component {
render() {
return (
<Provider store={store}>
<Navigation />
<CustomAlert /> {/* <--- here */}
</Provider>
);
}
}
I do so because I don't want to place my <CustomAlert /> component on each Screen that I use. If someone has another approach I'll take it.
CustomAlert.js
// ... (imports)
class CustomAlert extends Component {
render() {
const { customStyle } = this.props;
const { title, description, enabled } = this.props;
const { reinit } = this.props; // dispatch
return (
<View>
<Modal
animationType="none"
transparent
visible={enabled}
>
<View style={styles.fullScreen}>
<View style={[styles.content, customStyle]}>
<View style={{ flex: 1 }}>
<View style={styles.title}>
<Text style={{ fontWeight: 'bold' }}>{title}</Text>
</View>
<View style={styles.desc}>
<Text style={{ textAlign: 'center' }}>{`${description}`}</Text>
</View>
<View style={styles.buttons}>
<TouchableOpacity
onPress={() => {
reinit();
}}
style={styles.button}
>
<Text>OK</Text>
</TouchableOpacity>
</View>
</View>
</View>
</View>
</Modal>
</View>
);
}
}
function mapStateToProps(state) {
return {
type: state.alert.type,
title: state.alert.title,
description: state.alert.description,
enabled: state.alert.enabled,
};
}
function mapDispatchToProps(dispatch) {
return {
reinit: () => dispatch(reinitState()),
};
}
export default stylesWrapper(connect(mapStateToProps, mapDispatchToProps)(CustomAlert));
I proceed like that:
In my function that shows the alert, I simply change my redux store:
helper.js
// ...
export const infoAlert = (title, msg, type = 'error') => {
store.dispatch(setTitle(title));
store.dispatch(setDescription(msg));
store.dispatch(setType(type));
store.dispatch(setEnabled(true));
};
// ...
The behaviour is good but I have this warning :
Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
I suspects this has one reason between those two:
Using store.dispatch as a general function doesn't know anything about the component that called him, so this message indicates that It might rerender it (which is bad)
infoAlert is called inside a functionnal component (Redux Form), and it's state shouldn't change (something like that)
Here an example of call infoAlert (at the bottom):
const renderField = ({
input,
label,
keyboardType,
secureTextEntry,
autoCapitalize,
autoCorrect,
meta: {
touched, error, warning, active, dirty,
},
}) => (
<View>
<TextInput
style={[styles.textInput, (active) && styles.active,
(touched) && (((error) && styles.error)
|| ((warning) && styles.warning))]}
{...input}
onChangeText={input.onChange}
onBlur={input.onBlur}
onFocus={input.onFocus}
value={input.value}
keyboardType={keyboardType}
placeholder={label}
secureTextEntry={secureTextEntry}
autoCapitalize={autoCapitalize}
autoCorrect={autoCorrect}
/>
{(touched && !active && dirty) && (((error) && infoAlert('Erreur', error))
|| ((warning) && infoAlert('Attention', warning)))}
</View>
);
Do someone has an idea of how to remove this Warning ?
Do someone has another way (maybe simpler) to create a custom alert (without putting the component itself everywhere) ?
Screens

Related

How can I hide/show components by touching not button but screen on React Native?

I'm learning React Native for the first time. I want to implement a function to show/hide the component by touching the screen, not a specific button.
(Please check the attached file for the example image.)
enter image description here
In this code, I've tried to make a function. if I touch the screen (<View style={style.center}>, then show/hide the renderChatGroup() and renderListMessages() included in <View style={style.footer}>. The source code is below.
In my code, it works. However, the two <View> tag is not parallel. the footer view is center View's child.
I want to make them parallel. but I couldn't find the contents about controlling another <View> tag, not a child. In this code, I used setState, then I couldn't control another the below <View>.
Of course, I tried Fragment tag, but it didn't render anything.
How could I do implement this function? Please help me!
export default class Streamer extends React.Component {
constructor(props) {
super(props);
this.state = {
isVisibleFooter: true,
};
}
renderChatGroup = () => {
const { isVisibleFooter } = this.state;
if (isVisibleFooter) {
return (
<ChatInputGroup
onPressHeart={this.onPressHeart}
onPressSend={this.onPressSend}
onFocus={this.onFocusChatGroup}
onEndEditing={this.onEndEditing}
/>
);
}
return null;
};
onPressVisible = () => {
const { isVisibleFooter } = this.state;
this.setState(() => ({ isVisibleFooter: !isVisibleFooter }));
};
render() {
return (
<SafeAreaView style={styles.container}>
<SafeAreaView style={styles.contentWrapper}>
<View style={styles.header} />
<TouchableWithoutFeedback onPress={this.onPressVisible}>
<View style={styles.center}>
<View style={styles.footer}>
{this.renderChatGroup()}
{this.renderListMessages()}
</View>
</View>
</TouchableWithoutFeedback>
</SafeAreaView>
</SafeAreaView>
);
}
}
Firstly I would highly recommend you use react native with functional components and React Hooks as they alternative will soon will be deprecated.
Since onPress is not available on the View Component, you would need to replace it with TouchableWithoutFeedback as you have already done in your code.
For Showing/Hiding a view you would need to use a conditional operator.
export default class Streamer extends React.Component {
constructor(props) {
super(props);
this.state = {
isVisibleFooter: true,
};
}
renderChatGroup = () => {
const { isVisibleFooter } = this.state;
if (isVisibleFooter) {
return (
<ChatInputGroup
onPressHeart={this.onPressHeart}
onPressSend={this.onPressSend}
onFocus={this.onFocusChatGroup}
onEndEditing={this.onEndEditing}
/>
);
}
return null;
};
onPressVisible = () => {
this.setState(() => ({ isVisibleFooter: !isVisibleFooter }));
const { isVisibleFooter } = this.state;
};
render() {
return (
<SafeAreaView style={styles.container}>
<SafeAreaView style={styles.contentWrapper}>
<View style={styles.header} />
<TouchableWithoutFeedback onPress={this.onPressVisible}>
<View style={styles.center}>
{isVisibleFooter && <View style={styles.footer}>
{this.renderChatGroup()}
{this.renderListMessages()}
</View>}
</View>
</TouchableWithoutFeedback>
</SafeAreaView>
</SafeAreaView>
);
}
}
Here you can see i have replaced
<View style={styles.footer}>
{this.renderChatGroup()}
{this.renderListMessages()}
</View>
with
{isFooterVisible && <View style={styles.footer}>
{this.renderChatGroup()}
{this.renderListMessages()}
</View>}
stating that to only display the Footer View when
const isFooterVisible = true;

Passing Navigation to a Function Component

This Is My Home Page Code:
import React from "react";
//More Imports
export default class Home extends React.Component {
//Some Code
render() {
const { navigation } = this.props;
return (
<ScrollView>
//Some Code
<View style={styles.barContainer}>
<Button
title="Add Lesson"
onPress={() => navigation.navigate("ThisLesson")}
/>
</View>
//Some Code
{ScrollViewWithCards}
//Some Code
</ScrollView>
);
}
}
const styles = StyleSheet.create({
//Some Style
});
const cards = [
{
day: "3",
month: "Jan",
numberOfPeople: "4",
time: "17:00-18:00",
title: "Dance Class",
image: require("../../../assets/images/image1.jpeg"),
},
//More Cards...
];
const ScrollViewWithCards = (
<ScrollView>
{cards.map((card, index) => (
<View key={index} style={styles.cardContainer}>
<TouchableOpacity
onPress={() =>
navigation.navigate("ThisLesson", {
image: card.image,
day: card.day,
month: card.month,
time: card.time,
title: card.title,
numberOfPeople: card.numberOfPeople,
})
}
>
<HomeCard
image={card.image}
day={card.day}
month={card.month}
time={card.time}
title={card.title}
numberOfPeople={card.numberOfPeople}
/>
</TouchableOpacity>
</View>
))}
</ScrollView>
);
I'm mapping through an array of static data and rendering cards unto the screen
I made the cards pressable so that they take me to another page,
when I click the card it Returns an error:Reference Error: Can't find variable: navigation
But the Button Above the Cards Works Just Fine
What Am I Doing Wrong?
I tried the useNavigation Hook but it didn't work either
Update
This is my HomeCard component:
import React from "react";
//More Imports
const HomeCard = (props) => {
return (
<View style={styles.container}>
//Some Code
</View>
);
};
export default HomeCard;
const styles = StyleSheet.create({
//Some Style
});
const smallAvatars = [
//Some Array
];
I passed {navigation} to ScrollViewWithCards like so:
const ScrollViewWithCards =({navigation})=>()
but now I'm Getting another Error TypeError: undefined is not an object (evaluating 'navigation.navigate')
Solution
The Solution for this Problem is to transform ScrollViewWithCards to a function component, then pass props to it and add return:
const ScrollViewWithCards = (props) => {
return (
<ScrollView>
{cards.map((card, index) => (
<View key={index} style={styles.cardContainer}>
<TouchableOpacity
onPress={() =>
props.navigation.navigate("ThisLesson", {
image: card.image,
day: card.day,
month: card.month,
time: card.time,
title: card.title,
numberOfPeople: card.numberOfPeople,
})
}
>
<HomeCard
image={card.image}
day={card.day}
month={card.month}
time={card.time}
title={card.title}
numberOfPeople={card.numberOfPeople}
/>
</TouchableOpacity>
</View>
))}
</ScrollView>
);
};
and then in the main render:
<ScrollViewWithCards navigation={this.props.navigation} />
You are setting the const navigation inside the render function, and it wont be accessible inside other functions, so you have to use
this.props.navigation.navigate
Then you can simply do
const ScrollViewWithCards =()=> (
<ScrollView>
{cards.map((card, index) => (
<View key={index} style={styles.cardContainer}>
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate("ThisLesson", {
image: card.image,
day: card.day,
month: card.month,
time: card.time,
title: card.title,
numberOfPeople: card.numberOfPeople,
})
}
>
<HomeCard
image={card.image}
day={card.day}
month={card.month}
time={card.time}
title={card.title}
numberOfPeople={card.numberOfPeople}
/>
</TouchableOpacity>
</View>
))}
</ScrollView>
);
In the routing section, you need to mention the both component like this,
<Stack.Screen name="<your component name>" component={your component class} />
please don't forget to import the files at the above.
and then you can use the navigation props like,
this.props.navigation //for class component
props.navigation //for functional component
or if you have parent child relation in your compoent try this one:
<YOUR_COMPONENT navigation={props.navigation}/> // functional component
<YOUR_COMPONENT navigation={this.props.navigation}/> // class component

How to send data to another page in react-native

I have Main component and Bar component. I want to send some info to Bar component.
This is my code:
render() {
<View>
<View>
<Bar />
</View>
<View>
<TouchableOpacity onPress={() => this.props.navigation.navigate('Bar', { Info:'test' }) }>
</TouchableOpacity>
</View>
<View/>
}
I cant send like this because Bar component is in this component. How Can I fix this.
Since your're in the same component ... setState is your answer
class Comp extends React.Component {
state = {
info: '',
};
render() {
const { info } = this.state;
return (
<View>
<View>
<Bar info={info} />
</View>
<View>
<TouchableOpacity
onPress={() => {
this.setState({ info: 'test' });
}}
/>
</View>
</View>
);
}
}
State There are two types of data that control a component: props and
state. props are set by the parent and they are fixed throughout the
lifetime of a component. For data that is going to change, we have to
use state.

How to change text in sibling component in react-native?

I have an 'OutputBox' component and want to change the text being displayed in the component when I click a button.
I've read about props and state and i can't seem to get them working the way i need them too. I just started react-native and have a heavy background c++. I thought i could just declare a variable, 'text' in the 'OutputBox' component and then call a 'setOutputBoxText' function and change the 'text' var. Getters and Setters paradigm. I just cant wrap my head around how to use props to 'pass args' to components and the such.
export default class HelloWorldApp extends Component {
render() {
return (
<View style={{ flex: 1, alignItems: "flex-end" }}>
<CustomButton
text="N"
onPress={() => {
OutputBox.setOutputBox('You head North');
alert("You head North");
}}
/>
<OutputBox></OutputBox>
</View>
);
}
}
class CustomButton extends Component {
render() {
const { text, onPress} = this.props;
return (
<TouchableOpacity style={styles.buttonStyle}
onPress={() => onPress()}
>
<Text style={styles.textStyle}>{text}</Text>
</TouchableOpacity>
);
}
}
class OutputBox extends Component {
constructor( props ) {
super( props );
var displayText = 'Hello Traveller';
}
setOutputBox( newText ){
displayText = newText;
}
render() {
return (
<View style={ styles.outputBox }>
<Text>{this.displayText}</Text>
</View>
);
}
}
I would expect to be able to do something similar to what i have, however i just keep getting a typeError: OutputBox.setOutputBox is not a function. I know this is the wrong paradigm for react-native. I can't seem to wrap my head around doing something like this with props and state.
UPDATE: I no longer get the error typeError: OutputBox.setOutputBox is not a function. Now, the OutputBox just doesn't display anything. How I do I get the <Text/> component of the OutputBox to change and display.
Here is the correct way to do it.
export default class HelloWorldApp extends Component {
constructor( props ) {
super( props );
this.state={
displayText : 'Hello Traveller',
}
}
render() {
return (
<View style={{ flex: 1, alignItems: "flex-end" }}>
<CustomButton
text="N"
onPress={() => {
this.setState({displayText:'You head North'})
}}
/>
<OutputBox text={this.state.displayText}/>
</View>
);
}
}
class CustomButton extends Component {
render() {
const { text, onPress} = this.props;
return (
<TouchableOpacity style={styles.buttonStyle}
onPress={() => onPress()}
>
<Text style={styles.textStyle}>{text}</Text>
</TouchableOpacity>
);
}
}
class OutputBox extends Component {
render() {
return (
<View style={ styles.outputBox }>
<Text>{this.props.displayText}</Text>
</View>
);
}
}
I just removed the OutputBox component and put a <Text> component in there. I realized i didn't need the OutputBox component, since it's only purpose was to display text. This is what i ended up with
export default class HelloWorldApp extends Component {
constructor( props ) {
super( props );
this.state={
displayText : 'Hello Traveller',
}
}
render() {
return (
<View style={{ flex: 1, alignItems: "flex-end" }}>
<CustomButton
text="N"
onPress={() => {
this.setState({displayText:'You head North'})
}}
/>
<Text>{this.state.displayText}</Text>
</View>
);
}
}
class CustomButton extends Component {
render() {
const { text, onPress} = this.props;
return (
<TouchableOpacity style={styles.buttonStyle}
onPress={() => onPress()}
>
<Text style={styles.textStyle}>{text}</Text>
</TouchableOpacity>
);
}
}

Better solution to open the Menu when 3 dots are clicked in React Native

I am able to open menu when 3 dots icon is clicked for each item. But can the code be written in a better way..
Right now menu is getting created for each card item but ideally it would have been good to create single Menu View and dynamically associate it to some card where ever the 3 dots is clicked.
Expo Source Code Link
Code
export default class App extends React.Component {
constructor(props, ctx) {
super(props, ctx);
this.state = {
list: [
{ name: "Michael", mobile: "9292929292", ref: React.createRef() },
{ name: "Mason Laon Roah", mobile: "1232313233", ref: React.createRef() },
{ name: "Constructor", mobile: "4949494949", ref: React.createRef() },
{ name: "Rosling", mobile: "4874124584", ref: React.createRef() }
],
};
}
_menu = null;
hideMenu = () => {
this._menu.hide();
};
showMenu = (ref) => {
this._menu = ref;
this._menu.show();
};
render() {
const renderItem = ({ item, index }) => (
<ListItem
title={
<View>
<Text style={{ fontWeight: "bold" }}>{item.name}</Text>
<Text>{item.mobile}</Text>
</View>
}
subtitle={
<View>
<Text>445 Mount Eden Road, Mount Eden, Auckland. </Text>
<Text>Contact No: 134695584</Text>
</View>
}
leftAvatar={{ title: 'MD' }}
rightContentContainerStyle={{ alignSelf: 'flex-start'}}
rightTitle={this.getMenuView(item.ref)}
/>
);
return (
<View style={styles.container}>
<View style={{ flex: 1, marginTop: 30 }}>
<FlatList
showsVerticalScrollIndicator={false}
keyExtractor={(item, index) => index.toString()}
data={this.state.list || null}
renderItem={renderItem}
ItemSeparatorComponent={() => (
<View style={{ marginBottom: 5 }} />
)}
/>
</View>
</View>
);
}
getMenuView(ref) {
return (
<Menu
ref={ref}
button={<Icon onPress={() => this.showMenu(ref.current)} type="material" color="red" name="more-vert" />}
>
<MenuItem onPress={this.hideMenu}>Menu item 1</MenuItem>
<MenuItem onPress={this.hideMenu}>Menu item 2</MenuItem>
<MenuItem onPress={this.hideMenu} disabled>
Menu item 3
</MenuItem>
<MenuDivider />
<MenuItem onPress={this.hideMenu}>Menu item 4</MenuItem>
</Menu>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#ecf0f1',
padding: 8,
},
});
Sample Output
As mentioned here, you can find an undocumented UIManager.java class that allows you to create Popups with its showPopupMenu method.
This currently works only for Android.
import React, { Component } from 'react'
import { View, UIManager, findNodeHandle, TouchableOpacity } from 'react-native'
import Icon from 'react-native-vector-icons/MaterialIcons'
const ICON_SIZE = 24
export default class PopupMenu extends Component {
constructor (props) {
super(props)
this.state = {
icon: null
}
}
onError () {
console.log('Popup Error')
}
onPress = () => {
if (this.state.icon) {
UIManager.showPopupMenu(
findNodeHandle(this.state.icon),
this.props.actions,
this.onError,
this.props.onPress
)
}
}
render () {
return (
<View>
<TouchableOpacity onPress={this.onPress}>
<Icon
name='more-vert'
size={ICON_SIZE}
color={'grey'}
ref={this.onRef} />
</TouchableOpacity>
</View>
)
}
onRef = icon => {
if (!this.state.icon) {
this.setState({icon})
}
}
}
Then use it as follows.
render () {
return (
<View>
<PopupMenu actions={['Edit', 'Remove']} onPress={this.onPopupEvent} />
</View>
)
}
onPopupEvent = (eventName, index) => {
if (eventName !== 'itemSelected') return
if (index === 0) this.onEdit()
else this.onRemove()
}
Source: https://cmichel.io/how-to-create-a-more-popup-menu-in-react-native
There is now a React Native plugin for this. I'm not sure it was around when the question was originally asked. But I'm leaving this here for anyone else looking for the answer.
https://www.npmjs.com/package/react-native-popup-menu
The example worked for me. I wanted to use the vertical ellipsis, so I did this modification to the MenuTrigger part of the example to an icon instead of text:
<MenuTrigger>
<Icon name="more-vert" size={25} color={colors.rustRed} />
</MenuTrigger>
As a side note, I had difficulty finding and using the ellipsis. I eventually went with using react-native-vector-icons by using 'npm -i react-native-vector-icons' and importing the Material Icons like this:
import Icon from 'react-native-vector-icons/MaterialIcons';
Use React Portals
https://reactjs.org/docs/portals.html
In short the receipts is:
You define your dynamic menu at sibling level only once in the parent i.e. in your case it would be adjacent to App.
Handle Click at each item level to open your component. You can pass some specific event days to achieve the dynamism.
Easier example https://codeburst.io/reacts-portals-in-3-minutes-9b2efb74e9a9
This achieves exactly what you are trying to do which is defer the creation of component untill clicked.