error in passing data through navigation in react native - reactjs

I'm new to react native world and I'm trying to integrate a calendar with a time slot picker, so I'm trying to pass the selected date from the calendar to the slot picker page but I'm having this Error when I press on a date in the calendar and I couldn't fix it.
TypeError: undefined is not an object (evaluating 'navigation.navigate')
This is my calendar function:
const RequestMeeting = ({ navigation }) => {
const [isModalVisible, setModalVisible] = useState(false);
const toggleModal = () => {
setModalVisible(!isModalVisible);
};
return (
<View style={{ margin: 100, }}>
<Button title="Show modal" onPress={toggleModal} />
<Modal isVisible={isModalVisible} avoidKeyboard={true} scrollHorizontal={true} propagateSwipe={true}>
<ScrollView>
<View style={{ margin: 50, backgroundColor: 'gray', borderRadius: 20, padding: 20, margin: 20 }}>
<Text style={styles.heading}>Request to Buy/Rent</Text>
<View style={{ paddingBottom: 10 }}></View>
<View >
<Calendar
onDayPress={(day) => navigation.navigate("Slot", { bookingDate: day })}
style={styles.calendar}
hideExtraDays
theme={{
selectedDayBackgroundColor: 'green',
todayTextColor: 'green',
arrowColor: 'green',
}}
/>
</View>
<Button
buttonStyle={styles.register}
title="Send Buy/Rent request"
/>
<Button
buttonStyle={styles.cancelbtn}
title="Cancel"
onPress={toggleModal}
/>
</View>
</ScrollView>
</Modal>
</View>
);
};
And this is my time slot picker function:
const jsonData = {
"slots": {
"slot1": "9:00am to 9:30am",
"slot2": "9:30am to 10:00am",
"slot3": "10:00am to 10:30am",
"slot4": "10:30am to 11:00am",
"slot5": "11:00am to 11:30am",
"slot6": "11:30am to 12:00pm"
}
}
const Slot = ({ navigation }) => {
const onPressBack = () => {
const { goBack } = navigation
goBack()
}
const slots = jsonData.slots
const slotsarr = Object.keys(slots).map(function (k) {
return (
<View key={k} style={{ margin: 5 }}>
<TouchableOpacity >
<Text>{slots[k]}</Text>
</TouchableOpacity>
</View>)
});
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" />
<View >
<TouchableOpacity onPress={() => onPressBack()}><Text >Back</Text></TouchableOpacity>
<Text ></Text>
<Text ></Text>
</View>
{ slotsarr}
</View>
);
}

This error means that wherever you are rendering RequestMeeting or Slot (whichever one has the error) it's not getting the navigation prop. If it is rendered as a Screen then it will get the prop from the Navigator. If it's not a top-level screen then you need to pass down the prop from a parent or access it with the useNavigation hook.
Docs: Access the navigation prop from any component

Related

#gorrhom React Native Bottom Sheet - Calling from another component

Im trying to implement this relatively popular bottom sheet in React Native by #gorhom.
My aim is to open the bottom sheet from another component. I've tried to follow this answer - here . However, i do not understand what goes in the touchable opacity onPress in my component where it is being called from.
Code below
BottomSheetModalComponent
export default function BottomSheetModalComponent({ref}) {
// ref
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
// variables
const snapPoints = useMemo(() => ['25%', '50%'], []);
// callbacks
const handlePresentModalPress = useCallback(() => {
ref.current?.present();
}, []);
const handleSheetChanges = useCallback((index: number) => {
console.log('handleSheetChanges', index);
}, []);
// renders
return (
<BottomSheetModalProvider>
<View>
<BottomSheetModal
ref={ref}
snapPoints={snapPoints}
onChange={handleSheetChanges}
>
<View>
<Text>Awesome 🎉</Text>
</View>
</BottomSheetModal>
</View>
</BottomSheetModalProvider>
);
};
Location Component
export default function LocationBar() {
// Create Ref
const userBottomSheetRef = useRef<BottomSheetModal>(null);
// Pass ref into the bottom sheet component
<LocationBottomSheet ref={userBottomSheetRef} snapPoints={["30%"]}/>
return (
<>
<View style={{ flexDirection: "row" }}>
<View style={styles.locationBar}>
<Octicons style={styles.locationIcon} name="location" size={18} />
<TouchableOpacity onPress={() => {
//WHAT GOES HERE?
}}>
<Text style={{fontSize: 17, fontWeight: '600'}}>{userLocation}</Text>
</TouchableOpacity>
<Ionicons style={styles.chevronIcon} name="chevron-down" size={12} />
</View>
</View>
</>
);
}
Thanks in advance

Preventing state item in all components from changing with useContext()?

I'm trying to control the state of 2 components use useContext() in React-Native. I have a flatmap of cards that render on a screen. Each card has a press-able 'interested' icon. If I click on a card, it shows the details of the card and also the press-able 'interested' icon. Both should share the same state of being pressed and also send a 'interested bool to the backend.
Right now, if I press 'interested' on one card, the all get pressed. Same with the inside icon of the details page. Obviously the 'interested' state in the Context Provider is to broad and changing all of them. How can I filter out the state change?
Here is the card component that gets rendered from a flatlist:
import React, { useState, useEffect, useContext } from 'react';
import { Image, View, Text, TouchableOpacity } from 'react-native';
import { localDate } from 'utils/formatDate';
import PropTypes from 'prop-types';
import { ConcertContext } from '../../../context/ConcertContextProvider';
import LocationIcon from '../../../assets/images/locationIcon.svg';
import InterestedFilledIcon from '../../../assets/images/interested-filled.svg';
import InterestedEmptyIcon from '../../../assets/images/interested-empty.svg';
import ShareIcon from '../../../assets/images/share.svg';
import styles from './styles';
export default function ConcertCard({
gotoConcertPage,
artist,
cover,
concertCity,
scheduledAt,
description,
concertObj,
}) {
const [clickInterest, setClickInterest] = useState(concertObj.is_interested);
const { interested, setInterested, concertInterest } = useContext(ConcertContext);
const handleInterest = () => {
setInterested(prevState => !prevState);
concertInterest(concertObj);
};
useEffect(() => {
setClickInterest(interested);
}, [interested]);
return (
<TouchableOpacity onPress={() => gotoConcertPage(concertObj)}>
<View style={styles.card}>
<View style={styles.cardContent}>
<View style={styles.cropped}>
<Image style={styles.image} source={{ url: cover.url }} />
</View>
<View style={styles.textWrapper}>
<Text style={styles.date}>{localDate(scheduledAt, 'MMM D, h:mm A z')}</Text>
<View style={styles.locationWrapper}>
<LocationIcon style={styles.locationIcon} />
<Text style={styles.location}>{concertCity.name}</Text>
<Text style={styles.location}>{concertCity.address}</Text>
</View>
<Text style={styles.name}>{artist.name}</Text>
<Text style={styles.description}>{description}</Text>
</View>
<View style={styles.border} />
</View>
<View style={styles.icons}>
<View style={styles.sharedIcon}>
<ShareIcon />
</View>
{!clickInterest ? (
<InterestedEmptyIcon onPress={handleInterest} />
) : (
<InterestedFilledIcon onPress={handleInterest} />
)}
</View>
</View>
</TouchableOpacity>
);
}
ConcertCard.propTypes = {
gotoConcertPage: PropTypes.func,
artist: PropTypes.object,
cover: PropTypes.object,
concertCity: PropTypes.object,
scheduledAt: PropTypes.string,
description: PropTypes.string,
concertObj: PropTypes.object,
};
Here is the card details page:
import React, { useState, useEffect, useContext } from 'react';
import { Text, View, Image, ImageBackground, ScrollView, FlatList } from 'react-native';
import PropTypes from 'prop-types';
import Button from 'components/button';
import { localDate } from 'utils/formatDate';
import { ConcertContext } from '../../context/ConcertContextProvider';
import LocationIcon from '../../assets/images/locationIconTwo.svg';
import LittleFriendIcon from '../../assets/images/little-friend.svg';
import Star from '../../assets/images/Star.svg';
import QuestionMarkIcon from '../../assets/images/question-mark.svg';
import ShareIcon from '../../assets/images/ShareIconLarge.svg';
import styles from './concertStyles';
export default function ConcertPageScreen({ navigation, route }) {
const {
concertObj,
name,
artist,
scheduled_at,
interests,
concert_city,
other_cities,
description,
ticket_base_price,
} = route.params;
const { interested, setInterested, concertInterest } = useContext(ConcertContext);
const formatLocation = city => {
return city.slice(0, city.length - 5);
};
const handleInterest = () => {
setInterested(prevState => !prevState);
concertInterest(concertObj);
};
const buyTicket = concertObj => {
console.log('Buy ticked: ', concertObj.id);
};
return (
<>
<View style={styles.wrapper}>
<ImageBackground
style={styles.imageBackground}
imageStyle={{ opacity: 0.3 }}
source={{ url: artist.media }}
/>
<ScrollView vertical>
<View style={styles.headerWrapper}>
<Text style={styles.headerTitle}>{name}</Text>
<Text style={styles.smallText}>{artist.name}</Text>
<Text style={styles.date}>{localDate(scheduled_at, 'MMM D, h:mm A z')}</Text>
<View style={styles.locationWrapper}>
<LocationIcon style={styles.locationIcon} />
<Text style={styles.location}>{concert_city.name}</Text>
<Text style={styles.location}>{concert_city.address}</Text>
</View>
<Text style={styles.description}>{description}</Text>
<View style={styles.interestedWrapper}>
<LittleFriendIcon style={styles.littleMan} />
<Text style={styles.interested}>{interests} Interested</Text>
</View>
<View
style={{
borderTopColor: '#DADADA',
borderTopWidth: 0.2,
borderStyle: 'solid',
alignSelf: 'center',
height: 5,
marginTop: 32,
marginBottom: 28.5,
}}
/>
<View style={styles.tableWrapper}>
<View style={styles.tableHeaderWrapper}>
<Text style={styles.headerTitle}>DATES</Text>
<Text style={styles.headerTitle}>LOCATIONS</Text>
</View>
<View>
<FlatList
data={other_cities}
initialNumToRender={1}
renderItem={({ item }) => (
<View style={styles.tableItemsWrapper}>
<Text style={styles.dateItem}>
{localDate(item.date, 'MMM DD - h:mm A z')}
</Text>
<Text style={styles.locationItem}>{formatLocation(item.city)}</Text>
</View>
)}
concertCityId={item => `item${item.concert_city_id}`}
concertScheduleId={item => `item${item.concert_schedule_id}`}
/>
</View>
</View>
<View style={styles.iconWrapper}>
<View style={{ backgroundColor: '#292929', width: 44, height: 44, borderRadius: 30 }}>
<ShareIcon style={styles.shareIcon} />
</View>
{!interested ? (
<View
style={{ backgroundColor: '#292929', width: 44, height: 44, borderRadius: 30 }}>
<Star style={styles.starIcon} onPress={handleInterest} />
</View>
) : (
<View
style={{ backgroundColor: '#007AFF', width: 44, height: 44, borderRadius: 30 }}>
<Star style={styles.starIcon} onPress={handleInterest} />
</View>
)}
<Image style={styles.roundAvatar} source={{ url: artist.media }} />
</View>
<View style={styles.iconWrapper}>
<Text style={styles.shareIconText}>Share</Text>
<Text style={styles.interestedIconText}>I'm Interested</Text>
<Text style={styles.artistIconText}>Add Artist</Text>
</View>
<View style={styles.ticketWrapper}>
<Text style={styles.ticketPrice}>TICKET PRICES</Text>
<QuestionMarkIcon style={styles.ticketPrice} />
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text style={styles.ticketLocation}>In-location</Text>
<Text style={styles.ticketLocation}>$ {ticket_base_price}</Text>
</View>
<View
style={{
borderTopColor: '#DADADA',
borderTopWidth: 0.5,
borderStyle: 'solid',
height: 20,
marginTop: 32,
marginBottom: 28.5,
}}
/>
<View style={styles.footerWrapper}>
<Text style={styles.headerTitle}>TICKET GUIDE</Text>
<Text style={styles.ticketDescription}>
This ticket is for a livestream concert. Sign In at www.colortv.me on your browser
to watch on our TV or laptop. On the go? Watch from the COLOR TV mobile app.
</Text>
</View>
</View>
<Button
rightIcon={false}
text="Buy Ticket"
style={styles.button}
onPress={() => buyTicket(concertObj)}
/>
</ScrollView>
</View>
</>
);
}
ConcertPageScreen.propTypes = {
navigation: PropTypes.object,
route: PropTypes.object,
};
and here is the Context Provider:
import React, { createContext, useState } from 'react';
import { concertInterestApi } from 'utils/apiRoutes';
import useFetcher from 'hooks/useFetcher';
import parseError from '../utils/parseError';
export const ConcertContext = createContext(null);
const ConcertContextProvider = ({ children }) => {
const { isLoading, error, fetcher } = useFetcher();
const [interested, setInterested] = useState([]);
const concertInterest = async concertObj => {
try {
await fetcher({
url: concertInterestApi(concertObj.id),
method: concertObj.is_interested ? 'DELETE' : 'POST',
});
} catch ({ response }) {
throw parseError(response);
}
};
return (
<ConcertContext.Provider value={{ interested, setInterested, concertInterest }}>
{children}
</ConcertContext.Provider>
);
};
export default ConcertContextProvider;
Perhaps I'm not wiring this up correctly at all and any suggestions would be welcome to manage state.
You are confused about the shape of your own context. In your Provider, interested is an array (I'm not sure of what) and setInterested is a function to replace that array. With that in mind, hopefully you can see the problem with this:
const { interested, setInterested, concertInterest } = useContext(ConcertContext);
const handleInterest = () => {
setInterested(prevState => !prevState);
concertInterest(concertObj);
};
You are treating interested as a boolean when it is an array. I think it's all of the concerts that this user is interested it? Or perhaps an array of all userIds who are interested in this concert? Or perhaps useState([]) is a mistake and it was always meant to be a boolean?
Either way, I would recommend moving the shared logic from the ConcertCard and ConcertPageScreen into a custom hook that consumes your context and return an onClickInterested handler function. Your hook can take the concert id and/or user id as an argument.

React Native: Keyboard keeps closing on key press when typing in TextInput

I have a functional component Login Screen and have 4 Input Fields in them, along with a login button.
Here is the Full Code for my component Screen:
export default function Login() {
//Configs
const navigation = useNavigation();
const orientation = useDeviceOrientation();
const { colors, dark } = useTheme();
const InputTheme = {
colors: {
placeholder: colors.accent,
primary: colors.accent,
error: "red",
},
};
/** State Codes */
//States
const [login, setLogin] = useState({
email: "",
password: "",
licenseKey: "",
deviceName: "",
});
const [loading, setLoading] = useState(false);
const [secureEntry, setSecureEntry] = useState(true);
//Errors
const [errorEmail, setEmailError] = useState(false);
const [errorPWD, setPWDError] = useState(false);
const [errorLicense, setLicenseError] = useState(false);
const [errorDevice, setDeviceError] = useState(false);
//Error Messages
const [messageEmail, setEmailMessage] = useState("Looks Good");
const [messagePWD, setPWDMessage] = useState("All Good");
async function VerifyInputs() {
var pattern = /^[a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+)*#[a-z0-9]+(\-[a-z0-9]+)*(\.[a-z0-9]+(\-[a-z0-9]+)*)*\.[a-z]{2,4}$/;
if (login.email == "") {
//Email cannot be empty
setEmailMessage("Email cannot be Blank!");
setEmailError(true);
return;
} else if (login.email != "" && !pattern.test(login.email)) {
//Email is not valid
setEmailMessage("This is not a valid email address!");
setEmailError(true);
return;
} else {
console.log("resolved email");
setEmailMessage("");
setEmailError(false);
}
if (login.password == "") {
//Password cannot be empty
setPWDMessage("Password cannot be Empty!");
setPWDError(true);
return;
} else if (login.password.length < 5) {
//Password must be minimum 5 characters.
setPWDMessage("Password must be of minimum 5 characters!");
setPWDError(true);
return;
} else {
console.log("resolved password");
setPWDMessage("");
setPWDError(false);
}
if (login.licenseKey == "") {
//License Key can't be Empty
setLicenseError(true);
return;
} else {
console.log("License resolved");
setLicenseError(false);
}
if (login.deviceName == "") {
//Device Name can't be empty as well
setDeviceError(true);
return;
} else {
console.log("Device name resolved");
setDeviceError(false);
}
Toast.show("Validation Successful");
}
function MobileContent() {
console.log("mobile_content rerendered");
return (
<View style={{ flex: 1, backgroundColor: colors.accent }}>
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}></View>
<View style={{ flex: 3, justifyContent: "center" }}>
{/**Main Content */}
<View style={[styles.content, { backgroundColor: colors.primary }]}>
<View style={{ flex: 1 }}>
{/**For Header */}
<Header />
</View>
<View style={{ flex: 5 }}>
{/**For Content */}
<ScrollView style={{ flex: 1 }}>
<LoginContent />
</ScrollView>
</View>
<View style={{ flex: 1 }}>
{/**For Footer */}
<Footer />
</View>
</View>
</View>
</View>
);
}
function TabContent() {
console.log("tab_content rerendered");
return (
<View
style={{
flex: 1,
backgroundColor: colors.accent,
flexDirection: orientation.landscape ? "row" : "column",
}}>
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}></View>
<View style={{ flex: 1.5, justifyContent: "center" }}>
{/**Main Content */}
<View style={[styles.content, { backgroundColor: colors.primary }]}>
{/**Header Wrapper */}
<View style={{ justifyContent: "center" }}>
{/**Header Title */}
<Header />
</View>
{/**Content Wrapper */}
<LoginContent />
{/**Footer Wrapper */}
<View style={{ justifyContent: "center" }}>
{/** Login Button */}
<Footer />
</View>
</View>
</View>
</View>
);
}
function Header() {
console.log("header_component rerendered");
return (
<View style={{ margin: "5%" }}>
<Title>Welcome User</Title>
<Subheading>Please Sign In to Continue..</Subheading>
</View>
);
}
function Footer() {
console.log("footer_component rerendered");
return (
<View style={{ margin: isTablet ? "5%" : "3.5%" }}>
<Button
title="Login"
loading={false}
ViewComponent={LinearGradient}
containerStyle={{ maxWidth: isTablet ? "45%" : "100%" }}
buttonStyle={{ height: 50, borderRadius: 10 }}
linearGradientProps={{
colors: [colors.accent, colors.accentLight],
start: { x: 1, y: 1 },
end: { x: 1, y: 0 },
}}
onPress={() => {
VerifyInputs();
//navigation.navigate("verify");
}}
/>
</View>
);
}
function LoginContent() {
console.log("login_component rerendered");
return (
<View style={{ margin: "3%" }}>
{/**Login & Email Wrapper */}
<View style={{ flexDirection: isTablet ? "row" : "column" }}>
<View style={styles.input}>
<TextInput
mode="outlined"
label="Email"
value={login.email}
error={errorEmail}
theme={InputTheme}
onChangeText={(text) => setLogin({ ...login, email: text })}
/>
{errorEmail ? (
<HelperText visible={true} type="error" theme={{ colors: { error: "red" } }}>
{messageEmail}
</HelperText>
) : null}
</View>
<View style={styles.input}>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<TextInput
mode="outlined"
label="Password"
value={login.password}
error={errorPWD}
theme={InputTheme}
secureTextEntry={secureEntry}
style={{ flex: 1, marginBottom: 5, marginEnd: isTablet ? 15 : 5 }}
onChangeText={(text) => setLogin({ ...login, password: text })}
/>
<Button
icon={
<Icon
name={secureEntry ? "eye-off-outline" : "eye-outline"}
size={30}
color={colors.primary}
/>
}
buttonStyle={{
width: 55,
aspectRatio: 1,
backgroundColor: colors.accent,
borderRadius: 10,
}}
containerStyle={{ marginStart: 5 }}
onPress={async () => setSecureEntry(!secureEntry)}
/>
</View>
{errorPWD ? (
<HelperText visible={true} type="error" theme={{ colors: { error: "red" } }}>
{messagePWD}
</HelperText>
) : null}
</View>
</View>
{/**License & Device Wrapper */}
<View style={{ flexDirection: isTablet ? "row" : "column" }}>
<View style={styles.input}>
<TextInput
mode="outlined"
label="License Key"
value={login.licenseKey}
error={errorLicense}
theme={InputTheme}
onChangeText={(text) => setLogin({ ...login, licenseKey: text })}
/>
{errorLicense ? (
<HelperText visible={true} type="error" theme={{ colors: { error: "red" } }}>
License Key cannot be Empty!
</HelperText>
) : null}
</View>
<View style={styles.input}>
<TextInput
mode="outlined"
label="Device Name"
value={login.deviceName}
error={errorDevice}
theme={InputTheme}
onChangeText={(text) => setLogin({ ...login, deviceName: text })}
/>
{errorDevice ? (
<HelperText visible={true} type="error" theme={{ colors: { error: "red" } }}>
Device Name cannot be empty
</HelperText>
) : null}
</View>
</View>
</View>
);
}
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle={"dark-content"} backgroundColor={colors.accent} />
{isTablet ? <TabContent /> : <MobileContent />}
</SafeAreaView>
);
}
As you can see by looking at the code.. my Login component's rendering responsive component layouts based on device type being phone or tablet, the following components MobileContent or TabContent.
MobileContent functional component wraps my main content in a ScrollView & TabContent functional component doesn't need a scrollview but has responsive scaling views.
Both of these parent components display my common components, Header,LoginContent & Footer.
My Header component just displays a standard title & subheading. My LoginContent component has all the TextInput fields in them with validation logic setup as well. My Footer component has the Submit/Login button. That's All.
So we can summarize the components tree for screen as:
Login Screen/Parent Component => Mobile Content/Tab Content => Header, LoginContent & Footer (Common Components)
Alright, so now what is the issue? the major issue for me lies for TextInputs in LoginContent component, but I assume the underlying issue is applicable for entire Login Screen component.
The ISSUE: When I click on TextInput fields, the input gets focus and Keyboard appears. Everything is fine till this point. As soon as I type even a single letter, single key press on keyboard.. The Keyboard closes immediately and the text input is lost focus.
Why it could be happening? I believe this might be the classic 'React Component Re-render' problem, which causes by TextInput to be re-rendered when state is updated by the onChangeText for the TextInput and hence it loses focus & that's why the Keyboard closes.
So, I hope to resolve atleast first issue from these two issues.
Issue 1: How do I prevent the Keyboard from constantly closing when I try to type something in any of the TextInput fields.
Issue 2: How can I better optimize my Parent & Children components rendering on every state update using the any of these two React Hooks useCallback() and useMemo()?
I've read the documentation for these two hooks useCallback & useMemo multiple times and still haven't grasped the concept of these hooks. All posted examples deal with a Counter Exmaple optimized using useCallback or useMemo but I'm not working with Counters here in my Screen.
I need a more practical exmaple of useCallback() & useMemo() in screen components. Perhaps an implementation of these two hooks in my current Login Screen component will help me understand & grasp the concept better.
Like I mentioned, solving Issue 1 is my highest priority atm, but I know a solution for Issue 2 will help me in the long run as well. It will be very helpful if you can resolve both of my issues here. Thanks for helping.

React.useMemo doesn't prevent re-rendering

I have a functional component UserInfoInput and another functional component UserInfo including many UserInfoInput components. Here my code:
const UserInfoInput = React.memo((props) => {
useEffect(() => {
console.log('changed ');
})
return (
<View style={styles.container} >
<View style={{ ...styles.iconContainer, backgroundColor: props.color }}>
{props.name ? <Icon type={props.type} name={props.name} size={props.size} color='white' /> :
<Image source={props.image} style={props.imageStyle} />}
</View>
//input goes there
<TextInput style={styles.input} placeholder={props.title} onChangeText={props.changeText} />
{props.divider && <View style={{ ...RootStyle.divider, position: 'absolute', width: '85%', bottom: 0, right: 5 }}></View>}
</View>
)
});
and UserInfo component:
const [text, setText] = useState('');
const [second, setSecond] = useState('');
const update = text => setText(text);
const updt = text1 => setSecond(text1);
return (
// <UserMenu />
<View style={{ flex: 1 }}>
<View style={styles.navigator}>
</View>
<View style={styles.inputList}>
<UserInfoInput name="bookmark" color='#FF6C88' type='font-awesome' size={18} title={UserMenuTitle.bookmark} divider={true} changeText={update} />
<UserInfoInput name="star" color='#8BD8FB' type='font-awesome' size={18} title={UserMenuTitle.achieve} divider={true} changeText={updt} />
...
</View>
</View>
Whenever I enter one of those inputs, both two UserInfoInput components still re-render even I already wrap it inside React.memo function. I want my UserInfoInput component only re-render when I enter exactly it, not from other UserInfoInput components but I don't know how to do it. Could anyone help me to solve this problem ?
According to react official docs:
By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument.
This is the reason why your component is re-rendering again.
What you can do pass custom callback function and check your self:
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
// prevProps.title===nextProps.title(add your condition return true if values are equal. It ensure it will not re-render again)
}
const UserInfoInput = React.memo((props) => {
useEffect(() => {
console.log('changed ');
})
return (
<View style={styles.container} >
<View style={{ ...styles.iconContainer, backgroundColor: props.color }}>
{props.name ? <Icon type={props.type} name={props.name} size={props.size} color='white' /> :
<Image source={props.image} style={props.imageStyle} />}
</View>
//input goes there
<TextInput style={styles.input} placeholder={props.title} onChangeText={props.changeText} />
{props.divider && <View style={{ ...RootStyle.divider, position: 'absolute', width: '85%', bottom: 0, right: 5 }}></View>}
</View>
)
},areEqual);

How can I update text of dynamic component on react native?

I´m new to react and react-native, I want to show a component that is composed of:
so when the user checks the corresponding checkbox I transform the mentioned component adding another view getting something like this:
The problem comes when the user clicks on the Text element because I show a modal to pick the desired hour and minutes and when this modal closes I'm pretending to update the state and this updates the Text element with the selected time but it´s not working, any advice?
this is my code:
import React from 'react';
import {View,Text, TextInput, StyleSheet,TouchableOpacity} from 'react-native';
import { CheckBox } from 'react-native-elements';
import DateTimePicker from 'react-native-modal-datetime-picker';
let frmHoras;
export default class CustomChkHorario extends React.Component{
constructor(props){
super(props);
this.state={
chk:false,
fontColor:'black',
title:'Closed',
isDateTimePickerVisible: false,
sTimeMonday:'00:00',
eTimeMonday:'00:00'
}
}
_showDateTimePicker = () => {
this.setState({ isDateTimePickerVisible: true });
}
_hideDateTimePicker = () => this.setState({ isDateTimePickerVisible: false });
_handleDatePicked = (date) => {
console.log('A date has been picked: ', date);
this.setState({sTimeMonday:date});
this._hideDateTimePicker();
};
pressedChk = () => {
//console.log('checked:',this.state.checked);
this.setState({chk:!this.state.chk}, () =>{
if(this.state.chk==false){
this.setState({fontColor:'black', title:'Closed'});
frmHoras=null;
}else{
this.setState({fontColor:'green', title:'Open'});
frmHoras=
<View style={{flexDirection:'row',alignItems:'center'}}>
<Text>from:</Text>
<TouchableOpacity onPress={this._showDateTimePicker}>
<Text style={styles.input}>{this.state.sTimeMonday}</Text>
</TouchableOpacity>
<Text>to:</Text>
<TouchableOpacity onPress={this._showDateTimePicker}>
<Text style={styles.input}></Text>
</TouchableOpacity>
</View>;
}
});
}
render(){
return(
<View style={{alignItems:'center'}}>
<View style={{flexDirection:'row',justifyContent:'center',alignItems:'center'}}>
<Text>{this.props.dia}</Text>
<CheckBox title={this.state.title}
checked={this.state.chk}
onPress={this.pressedChk}
checkedColor='green'
textStyle={{color:this.state.fontColor}}></CheckBox>
</View>
<View>
{frmHoras}
</View>
<DateTimePicker
isVisible={this.state.isDateTimePickerVisible}
onConfirm={this._handleDatePicked}
onCancel={this._hideDateTimePicker}
mode={'time'}
/>
</View>
);
}
}
const styles = StyleSheet.create({
input: {
marginHorizontal: 10,
height:25,
width:60,
borderBottomColor:'black',
borderStyle:'solid',
borderBottomWidth:1,
margin:10
}
});
_handleDatePicked = date => {
const time = new Date(date);
const timePicked = `${time.getHours()}:${time.getMinutes()}`;
this.setState({ sTimeMonday: timePicked });
this._hideDateTimePicker();
};
pressedChk = () => {
this.setState({ chk: !this.state.chk }, () => {
if (this.state.chk) {
this.setState({ fontColor: 'green', title: 'Open' });
} else {
this.setState({ fontColor: 'black', title: 'Closed' });
}
});
};
render() {
return (
<View style={{ alignItems: 'center' }}>
<View
style={{
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text>{this.props.dia}</Text>
<CheckBox
title={this.state.title}
checked={this.state.chk}
onPress={this.pressedChk}
checkedColor="green"
textStyle={{ color: this.state.fontColor }}
/>
</View>
{this.state.chk && (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text>from:</Text>
<TouchableOpacity onPress={this._showDateTimePicker}>
<Text style={styles.input}>{this.state.sTimeMonday}</Text>
</TouchableOpacity>
<Text>to:</Text>
<TouchableOpacity onPress={this._showDateTimePicker}>
<Text style={styles.input} />
</TouchableOpacity>
</View>
)}
<DateTimePicker
isVisible={this.state.isDateTimePickerVisible}
onConfirm={this._handleDatePicked}
onCancel={this._hideDateTimePicker}
mode={'time'}
/>
</View>
);
}
}
You should render frmHoras in a function, but you are currently doing that in the setState callback, which is a bit misleading in your case, as setState callback is called when setState is completed and the component is re-rendered. Look at the document which clearly mentioned about this behaviour
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered.
So, changes will as follows
Create a new function which will render frmHoras, let it be renderFrmHoras
renderFrmHoras = () => {
const { chk } = this.state;
if(!chk) {
return null;
}
return (
<View style={{flexDirection:'row',alignItems:'center'}}>
<Text>from:</Text>
<TouchableOpacity onPress={this._showDateTimePicker}>
<Text style={styles.input}>{this.state.sTimeMonday}</Text>
</TouchableOpacity>
<Text>to:</Text>
<TouchableOpacity onPress={this._showDateTimePicker}>
<Text style={styles.input}></Text>
</TouchableOpacity>
</View>;
);
}
Next, update the pressedChk to set fontColor, title and chk values
pressedChk = () => {
this.setState((prevState) => {
return ({
chk: !prevState.chk,
fontColor: !prevState.chk ? 'green' : 'black',
title: !prevState.chk ? 'Open' : 'Closed'
});
})
}
Next, in the render method, just call the renderFrmHoras function
render() {
return (
...
</View>
<View>
{this.renderFrmHoras()}
</View>
<DateTimePicker
...
/>
...
);
}
Hope this will help!

Resources