How to pass a custom button in a drawer navigator, using the DrawerItemList in React Navigation v. 5? - react-native

I'm trying to migrate to react navigation 5, but I get an error where I use the DrawerItemList in place of the DrawerNavigatorItems.
My code used to look like this:
const MainNavigator = createDrawerNavigator(
// e.g.
Winners: {
screen: WinnersNavigator,
navigationOptions: {
drawerLabel: (
<BoldText style={NavigationStyles.arhiki}>Νικητές</BoldText>
),
drawerIcon: tabInfo => {
return (
<View style={NavigationStyles.winners}>
<FontAwesome
name="users"
size={iconMultiplier / 10}
color={tabInfo.tintColor}
/>
</View>
);
}
}
},
// ... more screens
contentComponent: props => {
const dispatch = useDispatch();
// This is for showing the Admin screen link, if user is an admin.
const userIdExists = useSelector(state => state.auth.userId);
return (
<View style={{ flex: 1 }}>
<SafeAreaView forceInset={{ top: "always", horizontal: "never" }}>
{/* These are the default drawer items */}
// THE PROBLEM IS HERE!!!
<DrawerNavigatorItems {...props} />
{/* Plus our custom buttons */}
{userIdExists && (
<View style={NavigationStyles.summary}>
<Ionicons.Button
name="ios-create"
backgroundColor={Colours.moccasin_light}
size={iconMultiplier / 10}
// style={{marginLeft: -20 }}
color="#888"
onPress={() => props.navigation.navigate("CreateWelcome")}
></Ionicons.Button>
<Text
onPress={() => props.navigation.navigate("CreateWelcome")}
style={[
NavigationStyles.exodos,
Platform.OS == "android" ? { marginLeft: -6 } : null
]}
>
Δημιουργία
</Text>
</View>
)}
...
Now it looks like this:
const CustomDrawerContent = props => {
const dispatch = useDispatch();
// This is for showing the Admin screen link, if user is an admin.
const userIdExists = useSelector(state => state.auth.userId);
return (
<View style={{ flex: 1 }}>
<SafeAreaView forceInset={{ top: "always", horizontal: "never" }}>
<DrawerContentScrollView {...props}>
{/* These are the default drawer items */}
<DrawerItemList {...props} />
{/* Plus our custom buttons */}
<Drawer.Section>
{userIdExists && (
<View style={NavigationStyles.summary}>
<DrawerItem
label={() => (
<Text
// onPress={() => props.navigation.navigate("CreateWelcome")}
style={[
NavigationStyles.exodos,
Platform.OS == "android" ? { marginLeft: -6 } : null
]}
>
Δημιουργία
</Text>
)}
icon={() => (
<Ionicons.Button
name="ios-create"
backgroundColor={Colours.moccasin_light}
size={iconMultiplier / 10}
// style={{marginLeft: -20 }}
color="#888"
// onPress={() => props.navigation.navigate("CreateWelcome")}
></Ionicons.Button>
)}
onPress={() => props.navigation.navigate("CreateWelcome")}
/>
</View>
)}
// ... more items
</Drawer.Section>
</DrawerContentScrollView>
</SafeAreaView>
</View>
);
};
const MainDrawerNavigator = createDrawerNavigator();
export const MainNavigator = () => {
return (
<MainDrawerNavigator.Navigator
drawerStyle={{
width: width < 900 ? 0.6 * width : 0.4 * width,
backgroundColor: Colours.moccasin_light,
overlayColor: Colours.maroonRGBA
}}
drawerContentOptions={{ activeTintColor: Colours.gr_brown }}
drawerContent={props => <CustomDrawerContent {...props} />}
>
<MainDrawerNavigator.Screen
name="GameNavigator"
component={GameNavigator}
options={{
drawerLabel: (
<BoldText style={NavigationStyles.arhiki}>Αρχική</BoldText>
),
drawerIcon: ({ color }) => (
<View style={NavigationStyles.shield}>
<MaterialCommunityIcons
name="shield-cross"
size={iconMultiplier / 8}
color={color}
/>
</View>
)
}}
/>
// ... more screens
</MainDrawerNavigator.Navigator>
);
};
The error I get is:
TypeError: label is not a function. (In 'label({
color: color,
focused: focused
})', 'label' is an instance of Object)
and it's generated at the <DrawerItemList {...props} />
In the docs I read:
import {
    DrawerContentScrollView,
    DrawerItemList,
  } from '#react-navigation/drawer';
  
  function CustomDrawerContent(props) {
    return (
      <DrawerContentScrollView {...props}>
        <DrawerItemList {...props} />
      </DrawerContentScrollView>
    );
  }
// To add additional items in the drawer, you can use the DrawerItem component:
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerItem
label="Help"
onPress={() => Linking.openURL('https://mywebsite.com/help')}
/>
</DrawerContentScrollView>
);
}
The first screen that I render is a Navigator, the GameNavigator.
Could that be a problem?
I read in a issue that:
"It's not possible to add navigators inside drawer content. You can achieve custom layouts using a custom router and custom navigator:" source,but I've learned from a course that it is possible! Or does this guy mean something else with drawer content?
The GameNavigator is:
const GameStackNavigator = createStackNavigator();
const GameNavigator = () => {
return (
<GameStackNavigator.Navigator
initialRouteName="Welcome"
screenOptions={defaultNavOptions}
>
<GameStackNavigator.Screen
name="Welcome"
component={WelcomeScreen}
options={WelcomeScreenOptions}
/>
...
</GameStackNavigator.Navigator>
);
};
And the WelcomeScreen is:
const WelcomeScreen = props => {
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [gridTileAnimValue] = useState(new Animated.Value(0));
const [isConnected, setIsConnected] = useState(false);
// For adding the points that are saved in memory, when connection is established.
useEffect(() => {
const unsub = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected);
});
return () => unsub();
}, []);
const getPoints = async () => {
let points = await AsyncStorage.getItem("savedPoints");
if (!!points) {
const getEmail = async () => {
const userData = await AsyncStorage.getItem("userData");
if (userData) {
const transformedData = JSON.parse(userData);
const { userEmail } = transformedData;
return userEmail;
}
};
const email = await getEmail();
// Give it some time to get the token and userId,
// because saveData needs them.
setTimeout(
async () => await dispatch(dataActions.saveData(email, +points)),
3000
);
await AsyncStorage.removeItem("savedPoints");
}
};
if (isConnected) getPoints();
useEffect(() => {
getFilters = async () => {
await dispatch(filtersActions.fetchDifficultyLevelFilters());
await dispatch(filtersActions.fetchCategoriesFilters());
};
}, [dispatch]);
useEffect(() => {
props.navigation.setOptions({
headerRight: () => (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="game-info"
iconName={
Platform.OS === "android"
? "md-information-circle-outline"
: "ios-information-circle-outline"
}
// style={{width: width / 8, height: height / 10, paddingTop: height / 35}}
onPress={() => setModalVisible(!modalVisible)}
/>
</HeaderButtons>
)
});
}, [modalVisible, setModalVisible]);
useEffect(() => {
const checkIfInfoNeeded = async () => {
return await AsyncStorage.getItem("NO_infoNeeded");
};
checkIfInfoNeeded().then(NO_infoNeeded => {
if (NO_infoNeeded === "NO") {
return;
} else {
setModalVisible(true);
}
});
}, []);
const animateGridTile = () => {
Animated.timing(gridTileAnimValue, {
toValue: 1,
duration: 1200,
useNativeDriver: false
}).start();
};
useEffect(() => {
animateGridTile();
const unsubscribe = props.navigation.addListener("focus", animateGridTile);
return () => unsubscribe();
}, [animateGridTile]);
useEffect(() => {
const unsubscribe = props.navigation.addListener("willBlur", () =>
gridTileAnimValue.setValue(0)
);
return () => {
unsubscribe();
};
}, []);
const cardStyle = { opacity: gridTileAnimValue };
const renderGridItem = itemData => {
return (
<Animated.View style={cardStyle}>
<CategoryGridTile
color={itemData.item.color}
title={itemData.item.title}
id={itemData.item.id}
onSelect={() => {
if (itemData.item.id == 0) {
props.navigation.navigate({
routeName: "MixedChoicesScreen"
});
} else if (itemData.item.id == 1) {
props.navigation.navigate({
routeName: "MultiChoiceCategories",
params: {
gameType: itemData.item.title
}
});
} else if (itemData.item.id == 2) {
props.navigation.navigate({
routeName: "TrueFalseCategories",
params: {
gameType: itemData.item.title
}
});
}
}}
/>
</Animated.View>
);
};
if (isLoading) {
return (
<CustomLinearGradient>
<View style={styles.centered}>
<ActivityIndicator size="large" color={Colours.moccasin_light} />
</View>
</CustomLinearGradient>
);
}
return (
<CustomLinearGradient>
<View style={styles.flatListContainer}>
{modalVisible && (
<CustomModal
modalVisible={modalVisible}
setModalVisible={setModalVisible}
onRequestClose={() => {
Alert.alert(
"Επιλογές",
"Παρακαλώ επιλέξτε μία από τις δύο επιλογές της καρτούλας: Ναι ή Όχι.",
[{ text: "Εντάξει", style: "default" }]
);
// Alert.alert("Παρακαλώ επιλέξτε μία από τις δύο επιλογές της καρτούλας: Πληροφορίες ή Δεν χρειάζεται.");
}}
textOne="Θέλετε να διαβάσετε τις οδηγίες χρήσεως και τις πληροφορίες σχετικά με τις
δοκιμαστικές εκδόσεις της εφαρμογής."
buttonOneTitle="Ναι"
buttonTwoTitle="Όχι"
onPressOne={async () => {
AsyncStorage.setItem("NO_infoNeeded", "NO");
setModalVisible(false);
props.navigation.navigate("GameInfo");
}}
onPressTwo={async () => {
AsyncStorage.setItem("NO_infoNeeded", "NO");
setModalVisible(false);
}}
/>
)}
<FlatList
// numColumns={2}
keyExtractor={(item, index) => item.id}
data={GAME_TYPES}
renderItem={renderGridItem}
/>
</View>
</CustomLinearGradient>
);
};
export const WelcomeScreenOptions = ({ route, navigation }) => {
return {
title: "ΕΝ ΤΟΥΤΩ ΝΙΚΑ",
headerLeft: () => (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
onPress={() => navigation.toggleDrawer()}
title="Menu"
iconSize={73}
iconName={Platform.OS === "android" ? "md-menu" : "ios-menu"}
// style={{
// width: width / 8,
// height: height / 10,
// paddingTop: height / 35
// }}
/>
</HeaderButtons>
)
};
};
Any suggestion would be appreciated.
Thanks

For drawerLabel you have directly set an object which is wrong
drawerLabel: (
<BoldText style={NavigationStyles.arhiki}>Αρχική</BoldText>
),
This should be either a string or a function that returns a component, so you should change it like below
drawerLabel: {()=>(
<BoldText style={NavigationStyles.arhiki}>Αρχική</BoldText>
)},
You can refer the docs
https://reactnavigation.org/docs/drawer-navigator/#drawerlabel

Related

react-native, conditional render does not show on state change. using react navigation setOptions here

in react native, i am using react navigation ,and when using setOptions i am setting up headerright, and onPress of headerRight, i am updating a boolean state to true, to show up the popup, but it does not showup untill i click on the screen once.
this view Media component is passed to a Flatlist as a Renderitem. does it make any problem here.
import { AntDesign, Feather, Ionicons, Octicons } from "#expo/vector-icons";
import { useNavigation } from "#react-navigation/native";
import { Video } from "expo-av";
import { LinearGradient } from "expo-linear-gradient";
import React, { useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
import {
Image,
ImageBackground,
LayoutAnimation,
Text,
TouchableOpacity,
TouchableWithoutFeedback,
View,
Alert
} from "react-native";
import Animated from "react-native-reanimated";
import { colors, radius } from "../../../../styles/variables";
import { AuthContext } from "../../../components/layout/layout";
import MemberModal from "../../../components/member-modal/member-modal";
import { DEFAULT_PROFILE, SUCCESS } from "../../../utils/constants/user-constants";
import { concatMediaData } from "../../../utils/helpers/concatQueryPages";
import { VIDEO } from "../../add-content/utils/constants";
import { useMediaListQuery } from "../../add-content/utils/hooks";
import { POST } from "../../report/utils/constants";
import DeletePostModal from "../deletePostModal";
import { likeKeyframe, styles } from "./style";
import {
downloadMediaSendRequest
} from "../utils/api";
import { LIKE, LIKED_BY, UNLIKE } from "../utils/constants";
import { useDeleteMediaQuery, useLikedListQuery, useLikeUnlikeMediaQuery } from "../utils/hooks";
import { updateImgixCDN } from "../../../utils/helpers/updateImgixCDN";
const ViewMedia = ({ media }) => {
const navigation = useNavigation()
const { authContext, state } = useContext(AuthContext);
const video = useRef(null);
const [mediaDetail, setMediaDetail] = useState(null)
const [mediaLoaded, setLoaded] = useState(false);
const [status, setStatus] = React.useState({});
const [showModal, setModal] = useState(false);
const [showDeleteModal, setDeleteModal] = useState(false);
const [showKebabModal, setKebabModal] = useState(false);
const [profileURI, setURI] = useState(null);
const [liked, setLiked] = useState(false);
const [totalLikes, setTotalLikes] = useState(0);
const [totalComments, setTotalComments] = useState(null);
const [likedBy, setLikedBy] = useState([]);
const [showMediaText, setMediaText] = useState(true);
const [showPlayPause, setPlayPauseIcon] = useState(null);
const [doubleTapCount, setDoubleTapCount] = useState(0);
const [showDoubleTapIcon, setDoubleTapIcon] = useState(false);
const {
data: likedListData,
isLoading: isLikesLoading,
status: likedStatus,
fetchStatus: likedFetchStatus,
} = useLikedListQuery({
mediaId: media?._id,
circleId: media.circleId._id,
pageParam: 1,
feature: LIKED_BY,
authToken: state.authToken,
});
// console.log(media.s3BucketObject)
const { mutateAsync: mutateDeleteMedia } = useDeleteMediaQuery(mediaDetail)
const { mutateAsync: mutateLikeUnlikedMedia } = useLikeUnlikeMediaQuery(mediaDetail)
// useEffect(() => {
// if (!mediaLoaded) {
// authContext.loading(true);
// }
// return () => {
// authContext.loading(false);
// };
// }, [mediaLoaded]);
useEffect(() => {
// setMediaDetail(state?.selectedMediaData)
setMediaDetail(media)
setTotalLikes(media.likes);
setTotalComments(media.comments);
setURI({ uri: updateImgixCDN(media?.uploadedBy?.profile, 'w=200&h=200') });
}, [state])
useEffect(() => {
navigation.setOptions({
headerLeft: () => (
<Ionicons
name="ellipsis-vertical"
size={26}
color={colors.color_white}
onPress={() => {
setKebabModal(!showKebabModal)
console.log(showKebabModal);
}}
/>
),
headerShown: showMediaText,
});
});
useEffect(() => {
if (likedStatus == SUCCESS && likedListData && likedListData.pages.length)
setLiked(!!likedListData?.pages[0]?.isLikedByUser);
}, [isLikesLoading, media]);
useEffect(() => {
if (mediaDetail) setPlayPause();
}, [status.isPlaying]);
const setPlayPause = () => {
if (mediaDetail?.mediaType == VIDEO) {
if (status.isPlaying) {
setPlayPauseIcon(
<Ionicons name="play" size={60} color={colors.color_white} />
);
setTimeout(() => setPlayPauseIcon(null), 1500);
} else {
setPlayPauseIcon(
<AntDesign name="pause" size={60} color={colors.color_white} />
);
}
}
};
const toggle = () => {
setModal(!showModal);
};
const toggleDeleteModal = () => {
setDeleteModal(!showDeleteModal);
setKebabModal(false);
};
const setLikes = async (likeFlag) => {
setLiked(likeFlag);
const args = {
mediaId: mediaDetail._id,
circleId: mediaDetail?.circleId._id,
authToken: state.authToken,
};
if (likeFlag) {
authContext.setSelectedMediaData({ ...state?.selectedMediaData, likes: totalLikes + 1 })
setTotalLikes((prevState) => prevState + 1);
mutateLikeUnlikedMedia({ ...args, feature: LIKE })
} else {
setDoubleTapCount(0)
authContext.setSelectedMediaData({ ...state?.selectedMediaData, likes: totalLikes - 1 })
setTotalLikes((prevState) => prevState - 1);
mutateLikeUnlikedMedia({ ...args, feature: UNLIKE })
}
};
const deleteMedia = async () => {
const args = {
mediaId: mediaDetail._id,
circleId: mediaDetail?.circleId._id,
authToken: state.authToken,
};
authContext.loading(true);
Alert.alert('Deleting post');
await mutateDeleteMedia(args)
navigation.navigate("AddContent");
authContext.loading(false);
};
return (
<TouchableOpacity activeOpacity={1}
onLongPress={() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
setMediaText(false);
}}
onPressOut={() => setMediaText(true)}
onPress={() => {
if (showKebabModal) setKebabModal(!showKebabModal);
if (mediaDetail?.mediaType == VIDEO)
status.isPlaying
? video.current.pauseAsync()
: video.current.playAsync();
// Added Double Tap Like feature
let doubleTapTimer;
setDoubleTapCount((prevCount) => prevCount + 1)
if (doubleTapCount % 2 == 1 && !showDoubleTapIcon) {
setDoubleTapIcon(true)
if (doubleTapCount == 1 && !liked) setLikes(true)
clearTimeout(doubleTapTimer)
} else {
doubleTapTimer = setTimeout(() => {
(function () {
setDoubleTapCount(prevCount => prevCount + 1)
setDoubleTapIcon(false)
})()
}, 1000)
}
}} style={styles.media_screen}>
<>
<TouchableWithoutFeedback
>
<>
{mediaDetail?.mediaType == VIDEO ? (
<Video
ref={video}
style={styles.card}
source={{
uri: mediaDetail?.s3BucketObject,
}}
useNativeControls
isLooping
shouldPlay={true}
resizeMode={"contain"}
onReadyForDisplay={() => setLoaded(true)}
onPlaybackStatusUpdate={(status) => setStatus(() => status)}
/>
) : (
<Image
source={{ uri: updateImgixCDN(mediaDetail?.s3BucketObject, '?auto=compress&w=720') }}
borderRadius={radius.border_radius_ternary}
style={styles.card}
resizeMode={"contain"}
// resizeMode={FastImage.resizeMode.contain}
onLoadEnd={() => setLoaded(true)}
/>
)}
<TouchableOpacity
activeOpacity={0.7}
style={styles.play_pause}
onPress={() => {
mediaDetail?.mediaType == VIDEO &&
(status.isPlaying
? video.current.pauseAsync()
: video.current.playAsync());
}}
>
{showPlayPause}
</TouchableOpacity>
{showDoubleTapIcon ? <Animated.View
entering={likeKeyframe} style={[styles.play_pause, styles.center_like_icon]}>
<AntDesign
name="heart"
size={60}
color={colors.color_green}
/>
</Animated.View> : null}
<LinearGradient
colors={[
colors.color_modal_black_bg,
colors.color_transparent,
colors.color_transparent,
colors.color_modal_black_bg,
]}
style={[showMediaText && styles.linearGradient]}
/>
</>
</TouchableWithoutFeedback>
{showMediaText && (
<>
{showKebabModal && (
<View style={styles.kebab}>
<TouchableOpacity
activeOpacity={0.7}
style={styles.kebab_feature}
onPress={async () => {
authContext.loading(true);
setKebabModal(false);
await downloadMediaSendRequest(
updateImgixCDN(mediaDetail?.s3BucketObject, 'auto=compress'),
mediaDetail?.mediaType
);
authContext.loading(false);
}}
>
<Feather
name="download"
size={24}
color={colors.color_blue}
/>
<Text style={[styles.feature_text, styles.download]}>
Download
</Text>
</TouchableOpacity>
{mediaDetail?.uploadedBy?._id == state?.userData?._id ? (
<>
<TouchableOpacity
activeOpacity={0.7}
style={styles.kebab_feature}
onPress={toggleDeleteModal}
>
<AntDesign
name="delete"
size={24}
color={colors.color_error}
/>
<Text style={[styles.feature_text, styles.delete]}>
Delete
</Text>
</TouchableOpacity>
</>
) : (
<TouchableOpacity
activeOpacity={0.7}
style={styles.kebab_feature}
onPress={() => {
navigation.navigate("Report", {
mediaId: mediaDetail?._id,
circleId: mediaDetail?.circleId?._id,
reportType: POST,
});
setKebabModal(false);
}}
>
<Octicons name="stop" size={24} color={colors.color_blue} />
<Text style={[styles.feature_text, styles.download]}>
Report post
</Text>
</TouchableOpacity>
)}
</View>
)}
<View style={styles.detail}>
<TouchableOpacity
activeOpacity={0.7}
style={styles.detail_top}
onPress={toggle}
>
<Image
source={profileURI || DEFAULT_PROFILE}
style={styles.profile}
/>
<Text style={styles.name}>
{mediaDetail?.uploadedBy?.name || "Earth Cups User"}
</Text>
</TouchableOpacity>
<View style={[styles.detail_bottom, styles.bottom]}>
<View style={styles.detail_bottom}>
<TouchableOpacity
activeOpacity={0.9}
style={styles.like_icon}
// taking current feature as a param after hitting the icon.
onPress={() => setLikes(!liked)}
>
{liked ? (
<Animated.View entering={likeKeyframe} exiting={likeKeyframe}>
<AntDesign
name="heart"
size={15}
color={colors.color_green}
/>
</Animated.View>
) : (
<Animated.View entering={likeKeyframe} exiting={likeKeyframe}>
<AntDesign
name="hearto"
size={15}
color={colors.color_black_500}
/>
</Animated.View>
)}
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.8}
onPress={() =>
navigation.navigate("MembersList", {
mediaDetail: mediaDetail,
circleId: mediaDetail?.circleId._id,
likePage: true,
})
}
>
<Text style={styles.name}>
{totalLikes} like{totalLikes > 1 && "s"}
</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
activeOpacity={0.8}
style={[
styles.detail_bottom,
styles.bottom_right,
styles.like_icon,
]}
onPress={() =>
navigation.navigate("Comments", {
mediaDetail: media,
circleId: media?.circleId._id,
})
}
>
<Image
source={require("../../../../assets/icons/comment.png")}
/>
<Text style={[styles.name, styles.comments]}>
{totalComments}
</Text>
</TouchableOpacity>
</View>
</View>
</>
)}
<MemberModal
toggle={toggle}
showModal={showModal}
memberDetail={mediaDetail?.uploadedBy}
/>
<DeletePostModal
toggle={toggleDeleteModal}
showModal={showDeleteModal}
deleteMedia={() => deleteMedia()}
/>
</>
</TouchableOpacity>
);
};
export default React.memo(ViewMedia);

How do I add items to array in following format and display it in flatlist?

I am trying to add items to redux state array. I can add items but my flatlist doesn't display them. It's most likely because they are like this ['abc-123, bcd-234'] etc. instead of [{license: abc-123}] so I could call the item.license in my flatlist. And how would I add an id to these items. How can I fix my structure a bit to get the [{id: 0, license: 'abc-123'}] ?
This is my action file:
const ADD_NEW_CAR = 'ADD_NEW_CAR'
const DELETE_EXISTING_CAR = 'DELETE_EXISTING_CAR'
export const addNewCar = (text) => ({
type: ADD_NEW_CAR,
payload: text
})
export const deleteExistingCar = (license) => ({
type: DELETE_EXISTING_CAR,
payload: license
})
this is my reducer:
const ADD_NEW_CAR = 'ADD_NEW_CAR'
const DELETE_EXISTING_CAR = 'DELETE_EXISTING_CAR'
const initialState = {
cars: [],
}
const carsListReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_NEW_CAR:
return {
...state,
cars: [...state.cars, action.payload],
}
case DELETE_EXISTING_CAR:
return {
cars: [
...state.cars.filter(license => license !== action.payload)
]
}
default:
return state
}
}
export default carsListReducer
and this is my flatlist:
const totalCars = props.cars.length
<FlatList
style={{ marginTop: 0 }}
data={cars}
keyExtractor={(item) => item.id}
renderItem={({ item }) => {
return (
<View style={licenseContainer}>
<View style={{ width: '20%' }}>
<Ionicons
style={carIcon}
name='car-outline'
size={30}
color={colors.black}
/>
</View>
<View style={{ width: editing ? '60%' : '80%' }}>
<Text key={item.license} style={licenseText}>
{item.license}
</Text>
</View>
{editing ? (
<View style={{ width: '20%' }}>
<Ionicons
name='ios-close-circle-outline'
size={30}
color={colors.black}
style={removeIcon}
onPress={() => removeCar(item.license)}
/>
</View>
) : null}
</View>
)
}}
ItemSeparatorComponent={() => {
return <View style={divider} />
}}
/>
const mapStateToProps = (state) => ({
signedIn: state.authReducer.signedIn,
cars: state.cars
})
const mapDispatchToProps = (dispatch) => ({
authActions: bindActionCreators(authAction, dispatch),
addNewCar: text => dispatch(addNewCar(text)),
deleteExistingCar: car => dispatch(deleteExistingCar(car))
})
export default connect(mapStateToProps, mapDispatchToProps)(ProfileScreen)
If your array contains string values then instead of using item.license just use item
<View style={{ width: editing ? '60%' : '80%' }}>
<Text key={item} style={licenseText}>
{item}
</Text>
</View>

Call a function from static method

I have something like this:
const BasketButton2 = ({ isWhite, style, navigation }) => (
<TouchableOpacity
style={[styles.button, style]}
onPress={() => Header.addInstaPost()}
>
<Icon
family="Entypo"
size={16}
name="new-message"
color={theme.COLORS[isWhite ? 'WHITE' : 'ICON']}
/>
</TouchableOpacity>
);
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
notifications: false,
loading: true,
error: null,
modalVisible: false,
modalThanksVisible: false,
reportSubmitted: false,
reportError: false,
};
}
handleLeftPress = () => {
const { back, navigation } = this.props;
return back ? navigation.goBack() : navigation.openDrawer();
};
renderRight = () => {
const { white, title, navigation, scene } = this.props;
if (global.loggedUser === true) {
return [
<BasketButton2
key="basket-search"
navigation={navigation}
isWhite={white}
/>,
<ChatButton
key="chat-search"
navigation={navigation}
isWhite={white}
/>,
];
} else {
return [
<BasketButton
key="basket-search"
navigation={navigation}
isWhite={white}
/>,
<ChatButton
key="chat-search"
navigation={navigation}
isWhite={white}
/>,
];
}
};
renderSearch = () => {
const { navigation } = this.props;
return (
<Input
right
color="black"
style={styles.search}
placeholder="What are you looking for?"
onFocus={() => navigation.navigate('Search')}
iconContent={
<Icon
size={16}
color={theme.COLORS.MUTED}
name="magnifying-glass"
family="entypo"
/>
}
/>
);
};
renderOptions = () => {
const { navigation, optionLeft, optionRight } = this.props;
return (
<Block row style={styles.tabs}>
<Button
shadowless
style={[styles.tab, styles.divider]}
onPress={() => navigation.navigate('Categories')}
>
<Block row middle>
<Icon name="globe" family="feather" style={{ paddingRight: 8 }} />
<Text size={16} style={styles.tabTitle}>
{optionLeft || 'Locations'}
</Text>
</Block>
</Button>
<Button
shadowless
style={styles.tab}
onPress={() => navigation.navigate('Deals')}
>
<Block row middle>
<Icon name="grid" family="feather" style={{ paddingRight: 8 }} />
<Text size={16} style={styles.tabTitle}>
{optionRight || 'Categories'}
</Text>
</Block>
</Button>
</Block>
);
};
renderTabs = () => {
const { tabs, tabIndex, navigation } = this.props;
const defaultTab = tabs && tabs[0] && tabs[0].id;
if (!tabs) return null;
return (
<Tabs
data={tabs || []}
initialIndex={tabIndex || defaultTab}
onChange={(id) => navigation.setParams({ tabId: id })}
/>
);
};
renderHeader = () => {
const { search, tabs, options } = this.props;
if (search || tabs || options) {
return (
<Block center>
{search ? this.renderSearch() : null}
{options ? this.renderOptions() : null}
{tabs ? this.renderTabs() : null}
</Block>
);
}
return null;
};
addInstaPost = () => {
this.setState({ modalVisible: true });
};
render() {
const { back, title, white, transparent, navigation, scene } = this.props;
const noShadow = ['Profile'].includes(title);
const noShadowWhite = ['Search'].includes(title);
const headerStyles = [
!noShadow ? styles.shadow : null,
transparent ? { backgroundColor: 'rgba(0,0,0,0)' } : null,
];
var myHeaderStyle = styles.shadow;
if (noShadow) {
var myHeaderStyle = '';
} else if (transparent) {
var myHeaderStyle = "{ backgroundColor: 'rgba(0,0,0,0)' }";
} else if (noShadowWhite) {
var myHeaderStyle = styles.searchShadow;
}
return (
<Block style={myHeaderStyle}>
<View style={styles.imageContainer} transparent={transparent}>
{
this.renderInstaPostButton()
}
</View>
<View style={styles.item}>
<NavBar
back={back}
title={title}
style={styles.navbar}
transparent={transparent}
right={this.renderRight()}
rightStyle={{ alignItems: 'center' }}
leftStyle={{ paddingTop: 3, flex: 0.3 }}
leftIconName={back ? 'leftcircle' : 'menu-fold'}
leftIconFamily="AntDesign"
leftIconSize="1.6"
leftIconColor={
white ? materialTheme.COLORS.NAVICON : theme.COLORS.ICON
}
titleStyle={[
styles.title,
{ color: theme.COLORS[white ? 'WHITE' : 'ICON'] },
]}
onLeftPress={this.handleLeftPress}
/>
</View>
{this.renderHeader()}
</Block>
);
}
}
So basically inside BasketButton2 I am trying to make a call to a function which is inside the class Header.
onPress={() => Header.addInstaPost() is not working
as well as onPress={() => this.addInstaPost()
I am getting Header.addInstaPost is not defined.
How I can refer to function inside class?
Thanks!!
You can pass addInstaPost as a property to BasketButton2
class Header extends React.Component {
addInstaPost = () => {
this.setState({ modalVisible: true });
};
renderRight = () => {
const { white, title, navigation, scene } = this.props;
if (global.loggedUser === true) {
return [
<BasketButton2
key="basket-search"
navigation={navigation}
isWhite={white}
onPress={this.addInstaPost}
/>,
<ChatButton
key="chat-search"
navigation={navigation}
isWhite={white}
/>,
];
} else {
return [
<BasketButton
key="basket-search"
navigation={navigation}
isWhite={white}
/>,
<ChatButton
key="chat-search"
navigation={navigation}
isWhite={white}
/>,
];
}
};
}
const BasketButton2 = ({ isWhite, style, navigation, onPress }) => (
<TouchableOpacity
style={[styles.button, style]}
onPress={onPress}
>
<Icon
family="Entypo"
size={16}
name="new-message"
color={theme.COLORS[isWhite ? 'WHITE' : 'ICON']}
/>
</TouchableOpacity>
);
IMO this is the most prefered way for such case

React child prop doesn't change when parent state updates

I have a react native app and I want to update some states and pass a prop to other components. I am passing the focus state and setFocus to the "Search" component. The focus state on the "Vault" component updates properly but it doesn't affect the "Search" component. The code is like below. What am I doing wrong?
const Vault = ({ navigation }: VaultStackNavigationProps<"Vault">) => {
const [focus, setFocus] = useState(false);
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<Right isFocus={focus}>
<Search handleFocus={setFocus} focus={focus} />
</Right>
),
});
}, [navigation]);
return (
<Box style={{ flex: 1, backgroundColor: "#1A1A1A" }}>
<Text color="white">hello</Text>
</Box>
);
};
export const Search = ({ handleFocus, focus }) => {
const [value, setValue] = useState("");
const inputRef = useRef<TextInput>();
const { width } = useWindowDimensions();
const onHandleFocus = (value) => {
handleFocus(value);
};
useEffect(() => {
if (focus) {
inputRef.current.focus();
} else {
Keyboard.dismiss();
clearTextState();
}
}, [focus]);
const clearTextState = () => {
setValue("");
};
const onClear = () => {
clearTextState();
inputRef.current.clear();
};
const onClose = () => {
onHandleFocus(false);
};
return (
<>
{focus && (
<TouchableOpacity onPress={onClose}>
<MaterialIcons name="arrow-back" size={24} color="white" />
</TouchableOpacity>
)}
{focus && (
<TextInput
ref={inputRef}
style={{
flex: 1,
color: "white",
paddingLeft: 15,
fontFamily: "CrimsonRegular",
}}
onChangeText={(text) => setValue(text)}
placeholder="Type here"
/>
)}
{focus && value.length > 0 && (
<SearchIconButton
onPress={onClear}
style={{ width: width / 9 }}
icon="close"
/>
)}
</>
);
};
Your layoutEffect is listening changes only from navigation
Try adding focus to useLayoutEffect array like
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<Right isFocus={focus}>
<Search handleFocus={setFocus} focus={focus} />
</Right>
),
});
}, [navigation, focus]);

Tabbed navigation between screens in react native

I use a StackNavigator based on the react-native-elements example and I want to enable something similar to a link with an ID as a parameter. I want to link to this screen:
const FontsTab = StackNavigator({
Home: {
screen: FontsTabView,
path: '/',
navigationOptions: ({ navigation }) => ({
title: 'Fonts',
headerLeft: (
<Icon
name="menu"
size={30}
type="entypo"
style={{ paddingLeft: 10 }}
onPress={() => navigation.navigate('DrawerOpen')}
/>
),
}),
},
Detail: {
screen: FontsDetailTabView,
path: 'fonts_detail',
navigationOptions: {
title: 'Fonts Detail',
},
},
});
I have this screen where I want the click of the text of an item to open the FontsTabView with the ID as a parameter. I would like to achieve something like the following:
<Text onPress={ (navigation)=> navigation.navigate('FontsTabView ', { id: {item.id} }) } style={styles.listHeader} >{item.title}</Text>
How can it be done?
class Icons extends Component {
constructor() {
super();
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
});
this.state = {
selectedIndex: 0,
value: 0.5,
dataSource: ds.cloneWithRows(list1),
isLoading: true
};
this.updateIndex = this.updateIndex.bind(this);
this.renderRow = this.renderRow.bind(this);
}
updateIndex(selectedIndex) {
this.setState({ selectedIndex });
}
renderRow(rowData, sectionID) {
return (
<ListItem
key={sectionID}
onPress={log}
title={rowData.title}
icon={{ name: rowData.icon }}
/>
);
}
_renderList = ({ item, navigation }) => {
return (
<TouchableWithoutFeedback onPress={(event) => this._selectedItem(item.key)}>
<View style={styles.listRowContainer}>
<View style={styles.listinside1Container}>
<Image style={styles.listImage} source={item.icon} />
<View style={styles.listContainer} onPress={(event) => this._selectedItem(item.text)} >
<Text onPress={ (navigation)=> navigation.navigate('DrawerOpen') } style={styles.listHeader} >{item.title}</Text>
<Text style={styles.listValue} >{item.value}</Text>
<Image
style={{width: 50, height: 50}}
source={{uri: item.img}}
/>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
}
componentDidMount(){
return fetch('https://www.koolbusiness.com/in.json')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson.movies,
}, function(){
});
})
.catch((error) =>{
console.error(error);
});
}
render() {
if(this.state.isLoading){
return(
<View style={{flex: 1, padding: 20}}>
<ActivityIndicator/>
</View>
)
}
const { navigation } = this.props;
const buttons = ['Button1', 'Button2'];
const { selectedIndex } = this.state;
if(this.state.isLoading){
return(
<View style={{flex: 1, padding: 20}}>
<ActivityIndicator/>
</View>
)
}
return (
<ScrollView>
<View style={styles.headerContainer}>
<Icon color="white" name="invert-colors" size={62} />
<Text style={styles.heading}>Trending Ads India</Text>
</View>
<View style={styles.MainContainer}>
</View>
<View style={styles.mainWrapper} >
<FlatList data={this.state.dataSource} renderItem={this._renderList} keyExtractor={(item, index) => index.toString()} />
</View>
</ScrollView>
);
}
}
You were close but not quite there,
Try to render your item like below;
<Text onPress={ ()=> this.props.navigation.navigate('FontsTabView', { id: item.id }) } style={styles.listHeader} >
{item.title}
</Text>
Then in FontsTabView you can read the parameter like below and render your screen accordingly.
this.props.navigation.state.params.id