How to update component from another one in react native - reactjs

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

Related

react-native-reanimated, pass component as prop with animated close

I have a component which uses react-native-reanimated, how can I add hideSheet() to the component I am passing as a prop?
import Animated, { Easing } from "react-native-reanimated";
...
const BottomSheet = ({
children,
...
renderHeader,...
}) => {
const style = useMemo(
() => getStyleObj({ backgroundColor, secondSnapshot }),
[backgroundColor, secondSnapshot]
);
const [alignment] = useState(new Animated.Value(0));
const [alignmentChildren] = useState(new Animated.Value(0));
const [open, setOpen] = useState(true);
const WrapperComponent = tapToOpenEnabled ? Pressable : View;
const openSheet = () => {
Animated.timing(alignment, {
toValue: 1,
duration: 400,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}).start();
};
const hideSheet = () => {
Animated.timing(alignment, {
toValue: 0,
duration: 400,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}).start();
};
const toggleOpen = () => {
if (open) {
hideSheet();
setOpen(false);
} else {
openSheet();
setOpen(true);
}
};
I am passing the header component as a prop:
const renderSheetHeader = useCallback(() => {
return (
<View style={styles.headerCont}>
<Text style={styles.headerTXT}>Setup your reminders</Text>
<Icon name="closecircle" size={20} color={colors.GREYONE} *add hideSheet here* />
</View>
);
}, []);
return (
<BottomSheet
...
renderHeader={renderSheetHeader()}
...
>
return (
<WrapperComponent>
...
{renderHeader}
You could pass it as a parameter to your function.
const renderSheetHeader = useCallback((onHide) => {
return (
<View style={styles.headerCont}>
<Text style={styles.headerTXT}>Setup your reminders</Text>
<View style={styles.headerCont}>
<Text style={styles.headerTXT}>Setup your reminders</Text>
<Pressable onPress={() => onHide()}>
<Icon name="closecircle" size={20} color={colors.GREYONE} />
</Pressable>
</View>
</View>
);
}, []);
Then, call it as usual.
<WrapperComponent>
...
{(onHide) => renderHeader(onHide)}
In your WrapperComponent:
renderHeader(hideSheet)
However, if it is not necessary that you define this as a function (and I do not see a reason in your current code), then you should be better of to just create a normale JSX component.
export function SheetHeader(props) {
return (
<View style={styles.headerCont}>
<Text style={styles.headerTXT}>Setup your reminders</Text>
<View style={styles.headerCont}>
<Text style={styles.headerTXT}>Setup your reminders</Text>
<Pressable onPress={() => onHide()}>
<Icon name="closecircle" size={20} color={colors.GREYONE} />
</Pressable>
</View>
</View>
);
}
and call it in your WrapperComponent directly.
<SheetHeader onHide={hideSheet} />

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);
}
}

Invalid use of hooks when calling component with onPress

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)}
/>
);
}

createDrawerNavigator and back button

We're using drawer navigator in our app to show the routes in the drawer. Our routes have stacks of pages and users can go into a page. E.g.:
Home
HomeChild1
HomeChild2
About
When user goes to the HomeChild1 or HomeChild2 we want to show back button in the header. We don't want to add a back button in each page's navigation options like below:
HomeScreen1.navigationOptions = ({ navigation }) => {
const { state } = navigation;
return {
title: `Home Screen 1`,
headerLeft: (
<Icon
name="ios-arrow-back"
type="ionicon"
color="#FFF"
underlayColor="transparent"
iconStyle={{ paddingRight: 5 }}
onPress={() => {
navigation.navigate.goBack();
}}
/>
)
};
}
Is there a way to put this config at the global level - like in the defaultNavigationOptions.
const defaultNavigationOptions = ({ navigation }) => {
return {
hideStatusBar: false,
headerStyle: {
backgroundColor: Colors.baseColor
},
headerTintColor: Colors.titleColor,
headerBackImage: ( //this has no affect
<Icon
name="ios-arrow-back"
type="Ionicons"
style={{ color: "#D8025E", fontSize: 30, paddingHorizontal: 10 }}
/>
),
headerLeft: (
<Icon
name="menu"
size={30}
style={{ marginStart: 25 }}
color="#FFF"
backgroundColor="#FFF"
onPress={() => navigation.openDrawer()}
/>
)
};
};
You can set up using navigation path values.
defaultNavigationOptions: ({ navigation }) => ({
headerLeft: () => {
const { routeName } = navigation.state;
let iconName;
if (routeName === "HomeChild1") {
iconName = "ios-arrow-back";
} else if (routeName === "HomeChild2") {
iconName = "ios-arrow-back";
}
....
return (
<Icon
name={iconName}
size={30}
style={{ marginStart: 25 }}
color="#FFF"
backgroundColor="#FFF"
onPress={() => navigation.goBack()}
/>
);
...

Resources