Invalid use of hooks when calling component with onPress - reactjs

I'm trying to work with modals when I click on a button from the header.
Say I have this component List, and List is using custom navigation options:
import { CustomModal } from './components/Modal';
const List = (props) => {
const [enteredUrl, setEnteredUrl] = useState('');
const urlInputHandler = (enteredUrl) => {
setEnteredUrl(enteredUrl);
};
const addUrlHander = () => {
console.log(enteredUrl);
}
return (
<View></View>
);
};
List.navigationOptions = (navData) => {
return {
headerTitle: 'Workouts',
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='Add'
iconName='md-add'
onPress={() => {
CustomModal(); //here is the modal
}}
/>
</HeaderButtons>
),
headerBackTitle: null
};
};
My Modal component has this:
export const CustomModal = (props) => {
const [modalVisible, setModalVisible] = useState(false);
console.log(props);
return (
<Modal
animationType='slide'
transparent={false}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{ marginTop: 22 }}>
<View>
<Text>Hello World!</Text>
<TouchableHighlight
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
);
}
But it is giving me the invalid hook error. Why is it that my onPress in my navigationOptions giving me this? Am I doing this wrong?

onPress is a callback, you can't put components in it. Probably what you want is something like this:
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<CustomModal/>
</HeaderButtons>
and the modal looks like
export const CustomModal = (props) => {
const [modalVisible, setModalVisible] = useState(false);
console.log(props);
return modalVisible?(
<Modal
animationType='slide'
transparent={false}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{ marginTop: 22 }}>
<View>
<Text>Hello World!</Text>
<TouchableHighlight
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
):(
<Item
title='Add'
iconName='md-add'
onPress={() => setModalVisible(!modalVisible)}
/>
);
}

Related

React Native - Passing navigation into a child component

I have a screen with the following:
function Intereset ({ navigation }) {
function ReturnMyFunction () {
if (!var.length) {
return ( <NoILikes /> )
} else {
return (
<FlatList
data={Ilike}
keyExtractor={item => item._id}
ItemSeparatorComponent={() => <Divider />}
renderItem={UserRow}
/>
)
}
}
return ( <ReturnILikeOrNoILike /> )
}
export default Interest
Here is my UserRow component below:
const UserRow = ({ item, navigation }) => (
<TouchableOpacity onPress={() => navigation.navigate("ProfileDetailScreenSingle", { userID: item.likeTo })}>
<View style={styles.row}>
<Image style={styles.avatar}
resizeMode={"cover"}
source={{ uri: item.likeToProfileImage }}
/>
<View style={styles.textContainer}>
<Text style={styles.name}>{item.likeToName}, <Text>{item.likeToAge}</Text></Text>
</View>
<Text style={styles.viewProfileText}>View Profile</Text>
<AntDesign name="right" size={14} color="black" />
</View>
</TouchableOpacity>
)
When I click on the UserRow to navigate I get the following issue.
I'm using useNavigation to redirect screen in stack:
import { useNavigation } from "#react-navigation/native";
const UserRow = ({ item }) => {
const navigation = useNavigation()
const onGoToProfileDetailScreenSingle = () => navigation.navigate("ProfileDetailScreenSingle", { userID: item.likeTo })
return (
<TouchableOpacity onPress={onGoToProfileDetailScreenSingle}>
...
</TouchableOpacity>
);
}
const renderItem = ({ item }) => <UserRow item={item} />
return (
<FlatList
data={Ilike}
renderItem={renderItem}
...
/>
)
Use onPress handler instead and perform navigation on the screen.
const UserRow = ({ item, onPress }) => (
<TouchableOpacity onPress={onPress}>
...
</TouchableOpacity>
);
const renderItem = ({ item }) => {
return (
<UserRow
item={item}
onPress={() => navigation.navigate(...)}
/>
);
};
<FlatList
data={Ilike}
renderItem={renderItem}
...
/>

Mobx store do not update with observer

I have a simple react native app with two screens.
First screen is the list, where you see your selected groups, and you can remove them, by clicking on trash icon:
export const Settings: NavioScreen = observer(({ }) => {
...
return (
<View flex bg-bgColor padding-20>
<FlashList
contentInsetAdjustmentBehavior="always"
data={toJS(ui.savedGroups)}
renderItem={({item}) => <ListItem item={item} />}
estimatedItemSize={20}
/>
</View>
);
});
};
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<TouchableOpacity onPress={() => ui.deleteGroup(item)}>
<Icon name={'trash'}/>
</TouchableOpacity>
</View>
);
};
The second screen is also the list, where you can add and remove the subjects from the list:
export const Playground: NavioScreen = observer(() => {
...
const groupsToShow =
ui.search && ui.search.length > 0
? ui.groups.filter((p) =>
p.name.toLowerCase().includes(ui.search.toLowerCase())
)
: ui.groups;
return (
<View >
<FlashList
data={toJS(groupsToShow)}
renderItem={({item}) => <ListItem item={item} />}
/>
</View>
);
});
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<View>
<If
_={ui.isGroupSaved(item)}
_then={
<TouchableOpacity onPress={(e) => {ui.deleteGroup(item)}}>
<AntDesign name="heart" size={20} color={Colors.primary} />
</TouchableOpacity>
}
_else={
<TouchableOpacity onPress={(e) => {ui.addGroup(item)}}>
<AntDesign name="hearto" size={20} color={Colors.primary} />
</TouchableOpacity>
}
/>
</View>
</View>
);
};
And now when I remove the group from the first list, the heart icon do not update on the second list. But it should, because there is an if statement on second list, that checks if the group is saved. And if it is not, the heart should have the name="hearto"
I have tried to use the state instead mobx library but it does not also help.
Here is my store written with mobx:
export class UIStore implements IStore {
savedGroups = [];
constructor() {
makeAutoObservable(this);
makePersistable(this, {
name: UIStore.name,
properties: ['savedGroups'],
});
}
addGroup = (group: any) => {
if (true === this.isGroupSaved(group)) {
return;
}
this.savedGroups.push(group);
}
isGroupSaved = (group: any) => {
return -1 !== this.savedGroups.findIndex(g => g.id === group.id);
}
deleteGroup = (groupToDelete: any) => {
this.savedGroups = this.savedGroups.filter((group) => group.id !== groupToDelete.id);
}
}

How to update component from another one in react native

I am using React Native App, I have a main page that displays the categories, and a button that navigates to another page to add a new category,
the issue is that when I add a new one I want to update the main page as well to include the new one,
is there a way to do so, this my code,
this the navigation
const HomeStack = createStackNavigator();
const Tab = createMaterialBottomTabNavigator();
const MainTabScreen = () => (
<Tab.Navigator
initialRouteName="Home"
activeColor="#fff"
>
<Tab.Screen
name="Home"
component={HomeStackScreen}
options={{
tabBarLabel: 'Home',
tabBarColor: '#009387',
tabBarIcon: ({ color }) => (
<Icon name="ios-home" color={color} size={26} />
),
}}
/>
);
export default MainTabScreen;
const HomeStackScreen = ({navigation}) => (
<HomeStack.Navigator screenOptions={{
headerStyle: {
backgroundColor: '#009387',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
}
}}>
<HomeStack.Screen name="Home" component={HomeScreen} options={{
title:'Overview',
headerLeft: () => (
<Icon.Button name="ios-menu" size={25} backgroundColor="#009387" onPress={() => navigation.openDrawer()}></Icon.Button>
)
}} />
<HomeStack.Screen name="Home_Provider" component={HomeScreen_Provider} options={{
title:'Overview',
headerLeft: () => (
<Icon.Button name="ios-menu" size={25} backgroundColor="#009387" onPress={() => navigation.openDrawer()}></Icon.Button>
)
}} />
<HomeStack.Screen name="AddServiceScreen" component={AddServiceScreen} options={{
title:'Overview',
headerLeft: () => (
<Icon.Button name="ios-menu" size={25} backgroundColor="#009387" onPress={() => navigation.openDrawer()}></Icon.Button>
)
}} />
</HomeStack.Navigator>
);
this is the home page:
const HomeScreen_Provider = ({navigation}) => {
const [services, setServices] = React.useState([]);
const [status, setStatus] = React.useState([]);
async function fetchdata(){
api.GetMyServices({headers:{'Accept': "application/json", 'content-type': "application/json"}})
.then(function (response) {
console.log(response.data);
setServices(response.data);
})
.catch(function (error) {
console.error(error);
});
}
React.useEffect(() => {
fetchdata();
}, []);
return (
<View style={styles.container}>
<StatusBar backgroundColor='#009387' barStyle="light-content"/>
<View style={{marginTop:20}}>
<Text style={styles.text_header}>My Services</Text>
</View>
<TouchableOpacity
style={{alignItems:'flex-end',marginLeft:250}}
onPress={() => navigation.navigate('AddServiceScreen')}
>
<Text style={styles.button}>Add New..</Text>
</TouchableOpacity>
{services.length == 0 ? <Text >You Have No Services Yet, Click Here To Add..</Text> : <View/>}
<ScrollView>
{services.map (service => (
<View style ={styles.rowContainer}>
<Text style={styles.text_footer}>{service.name} </Text>
<Text style={styles.text_footer}>{service.status} </Text>
<TouchableOpacity
onPress={handleToggle}
>
<Text style={styles.button}>Change Status</Text>
</TouchableOpacity>
</View>
))}
</ScrollView>
</View>
)
};
export default HomeScreen_Provider;
````````````
here is the add new page :
const AddServiceScreen = ({navigation}) => {
const { colors } = useTheme();
const [availableServices, setAvailableServices] = React.useState([]);
const [userID, setUserID] = React.useState(0);
const [serviceId, setServiceId] = React.useState(0);
const [serviceName, setServiceName] = React.useState(0);
const [serviceStatus, setServiceStatus] = React.useState('');
const [serviceCost, setServiceCost] = React.useState(0);
async function handleAddServiceButton () {
api.AddNewCategory(Category,{headers:{'Accept': "application/json", 'content-type': "application/json"}})
.then(function (response) {
})
.catch(function (error) {
console.error(error);
});
}
return (
<View style={styles.container}>
<StatusBar barStyle= { theme.dark ? "light-content" : "dark-content" }/>
<View style={styles.rowContainer} >
{availableServices.length !== 0 ? (
<View>
<Text style={{marginTop:15,marginBottom:30}}>Select From Existing Services</Text>
<Picker
selectedValue={serviceId}
style={{ height: 50, width: 150 }}
onValueChange={(itemValue, itemIndex) => setServiceId(itemValue)}
>
{availableServices.map((service)=>{
return(
<Picker.Item label={service.name} value={service.id} />
)
})}
</Picker>
</View>
): <View/>}
</View>
<Button
title="Submit"
onPress={handleAddServiceButton}
/>
</View>
);
};
export default AddServiceScreen;
You can either use react context to update the state directly and use the spread operator in the API call
or
You can maintain your state using redux to centralize your application state

Return JSX component onPress react-native

I want to prompt users to get input onPress but returning JSX from an onPress never worked so whats a possible workaround to return JSX based on button click. Here's my code:
import React, { useState } from 'react';
import {
StyleSheet,
KeyboardAvoidingView,
View,
Text,
TouchableOpacity
} from 'react-native';
import InputPrompt from './InputPrompt'
const Newact = (props) => {
const [visible, setVisible] = useState(false)
return(
<View>
<View style={styles.button} >
<TouchableOpacity style={styles.center} onPress={getTitle}>
<Text style={styles.plusSign}>+</Text>
</TouchableOpacity>
</View>
</View>
);
}
const getTitle = () =>{
return(
<InputPrompt />
)
}
Update:
Now thats how my code looks:
const Newact = props => {
const [prompt, setPrompt] = useState(false);
return(
<View style={styles.button} >
<TouchableOpacity style={styles.center} onPress={() => setPrompt(true)}>
<Text style={styles.plusSign}>+</Text>
</TouchableOpacity>
{prompt ? <InputPrompt setPrompt={setPrompt} /> : null}
</View>
);
}
and InputPrompt component is:
const InputPrompt = (props) => {
const [name, setName] = useState('');
return(
<View>
<DialogInput
title={"New Activity"}
submitText={"Add"}
hintInput ={"Enter activity name....."}
submitInput={ (inputText) => {setName(inputText), props.setPrompt(false)} }
closeDialog={ () => {props.setPrompt(false)}}>
</DialogInput>
<Text>{name}</Text>
</View>
);
}
When they press, you should set state. This causes the component to rerender, and on that new render you can return JSX describing what you want the screen to look like. I'm not sure exactly where you want to render the input prompt, but maybe something like this:
const Newact = (props) => {
const [visible, setVisible] = useState(false)
const [prompt, setPrompt] = useState(false);
return (
<View>
<View style={styles.button} >
<TouchableOpacity style={styles.center} onPress={() => setPrompt(true)}>
<Text style={styles.plusSign}>+</Text>
</TouchableOpacity>
</View>
{prompt && <InputPrompt />}
</View>
);
}
Updating the state onPress is a simple way of achieving this as it will then re-render the component and you can run any jsx based on that state that you updated.
You can use a ternary expression
{isPressed ? <return your jsx here> : null}
this is what it will look like in your case
const Newact = (props) => {
const [visible, setVisible] = useState(false)
const [prompt, setPrompt] = useState(false);
return (
<View>
<View style={styles.button} >
<TouchableOpacity style={styles.center} onPress={() => setPrompt(true)}>
<Text style={styles.plusSign}>+</Text>
</TouchableOpacity>
</View>
{prompt ? <InputPrompt /> : null}
</View>
);
}
You need to change your code to something like this:
const Newact = props => {
const [visible, setVisible] = useState(false);
const getTitle = () => {
setVisible(true);
}
return (
<View>
<View style={styles.button}>
<TouchableOpacity style={styles.center} onPress={getTitle}>
<Text style={styles.plusSign}>+</Text>
</TouchableOpacity>
{
visible && <InputPrompt />
}
</View>
</View>
);
};

React Native: Render different component based on button press

I am new to React/React Native and am currently building a screen that consists of a section consisting of 3 buttons and a space below to render different components depending on which tab button is pressed. The tabs are add, remove, and history.
Basically the intent is to recreate something like the tabs component from React Bootstrap, but for React Native. I have seen the React Navigation tabs component, but I do not want the tabs to navigate to a different screen, just render a different component depending on which is clicked and have it fade into the section below the buttons.
Below is a rough idea of how I am thinking of approaching the problem.
const ChoiceContainer = props => {
const {children} = props;
render(<View>{children}</View>);
};
const AddEntry = () => (
<ChoiceContainer>
<Card>
<View style={{paddingLeft: 5}}>
<Text style={styles.cardTitle}>Component 1</Text>
</View>
</Card>
</ChoiceContainer>
);
const RemoveEntry = () => (
<ChoiceContainer>
<Card>
<View style={{paddingLeft: 5}}>
<Text style={styles.cardTitle}>Component 2</Text>
</View>
</Card>
</ChoiceContainer>
);
const EntryHistory = () => (
<ChoiceContainer>
<Card>
<View style={{paddingLeft: 5}}>
<Text style={styles.cardTitle}>Component 3</Text>
</View>
</Card>
</ChoiceContainer>
);
export default class EntryTabs extends Component {
showAdd = () => {
this.setState({sceneType: 'add'});
};
showRemove = () => {
this.setState({sceneType: 'receive'});
};
showHistory = () => {
this.setState({sceneType: 'history'});
};
renderScene = type => {
if (type === 'add') {
return <AddEntry />;
}
if (type === 'remove') {
return <RemoveEntrty />;
}
if (type === 'history') {
return <EntryHistory />;
}
};
render() {
const {sceneType} = this.state;
render(
<SafeAreaView>
<View style={{flex: 1}}>
return (
<EntryCard
/>
);
})}
<View style={{flex:1}}>
<View>
<TouchableOpacity onPress={this.showAdd}>
<Text> Add </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showRemove}>
<Text> Remove </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showHistory}>
<Text> History </Text>
</TouchableOpacity>
</View>
<View>{this.renderScene(sceneType)}</View>
</View>,
</View>
</SafeAreaView>
);
}
}
Any help/guidance would huge hugely appreciated. Thanks!
You have not defined the state, And you have to use map to show the entries.
The working version of you code should be as below.
const value1 = 1,
value2 = 2,
value3 = 3;
const entries = [
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
];
const ChoiceContainer = (props) => {
const { children } = props;
return <View>{children}</View>;
};
const AddEntry = () => (
<ChoiceContainer>
<Card>
<View style={{ paddingLeft: 5 }}>
<Text style={styles.cardTitle}>Add Entry</Text>
</View>
</Card>
<TextInput value={entries.key1} />
</ChoiceContainer>
);
const RemoveEntry = () => (
<ChoiceContainer>
<Card>
<View style={{ paddingLeft: 5 }}>
<Text style={styles.cardTitle}>Remove Entry</Text>
</View>
</Card>
<TextInput value={entries.key2} />
</ChoiceContainer>
);
const EntryHistory = () => (
<ChoiceContainer>
<Card>
<View style={{ paddingLeft: 5 }}>
<Text style={styles.cardTitle}>Entry History</Text>
</View>
</Card>
<TextInput value={entries.key3} />
</ChoiceContainer>
);
class EntryTabs extends React.Component {
state = {
sceneType: 'add',
};
showAdd = () => {
this.setState({ sceneType: 'add' });
};
showRemove = () => {
this.setState({ sceneType: 'receive' });
};
showHistory = () => {
this.setState({ sceneType: 'history' });
};
renderScene = (type) => {
if (type === 'add') {
return <AddEntry />;
}
if (type === 'remove') {
return <RemoveEntry />;
}
if (type === 'history') {
return <EntryHistory />;
}
};
render() {
const { sceneType } = this.state;
return (
<SafeAreaView>
<View style={{ flex: 1 }}>
{entries.map((item) => {
return (
<EntryHistory
baseAsset={item.key1}
logo={item.key2}
screen={item.key3}
/>
);
})}
<View style={{ flex: 1 }}>
<View>
<TouchableOpacity onPress={this.showAdd}>
<Text> Add </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showRemove}>
<Text> Remove </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showHistory}>
<Text> History </Text>
</TouchableOpacity>
</View>
<View>{this.renderScene(sceneType)}</View>
</View>
,
</View>
</SafeAreaView>
);
}
}
function CustomText(props) {
const [showMore, setShowMore] = React.useState(false);
const [lines, setLines] = React.useState(props.numberOfLines);
const onTextLayout = (e) => {
setShowMore(
e.nativeEvent.lines.length > props.numberOfLines && lines !== 0
);
};
return (
<View>
<Text numberOfLines={lines} onTextLayout={onTextLayout}>
{props.children}
</Text>
&&{' '}
{showMore && (
<Button
title="Show More"
onPress={() => {
setLines(0);
setShowMore(false);
}}
/>
)}
{!showMore && (
<Button
title="Hide More"
onPress={() => {
setLines(props.numberOfLines);
}}
/>
)}
</View>
);
}

Resources