How can I create method of my own component in react-native? - reactjs

For example, in react-native, I can pass a ref to a regular TextInput and then, through this ref, call the methods:
const inputRef = useRef(null);
const myCallback = () => {
inputRef?.current?.focus();
}
<>
<TouchableOpacity onPress={() => myCallback()}>
<Text>
Press here to focus the input!
</Text>
</TouchableOpacity>
<TextInput
ref={inputRef}
{...props} // Doesn't matter, nothing special
>
</>
So, my question is, how can I create methods on my components, so I can call them from outside of component using ref.
Of course, I'm interested in creating a method in a functional component.

You can use useImperativeHandle hook to expose the methods you need to have with the input element.
Try like this.
import React, { useImperativeHandle, forwardRef, useRef } from "react";
import { Button, StyleSheet, View, TextInput } from "react-native";
const MyTextInput = (props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
doFocus: () => {
inputRef.current.focus();
},
doBlur: () => {
inputRef.current.blur();
}
}));
return <TextInput ref={inputRef} style={props.style} />;
};
const MyCustomTextInput = forwardRef(MyTextInput);
const App = () => {
const myInputRef = useRef();
return (
<View style={styles.app}>
<MyCustomTextInput ref={myInputRef} style={styles.input} />
<View style={styles.button}>
<Button
onPress={() => {
myInputRef?.current?.doFocus();
}}
title="focus"
style={styles.button}
/>
</View>
<View style={styles.button}>
<Button
onPress={() => {
myInputRef?.current?.doBlur();
}}
title="blur"
style={styles.button}
/>
</View>
</View>
);
};
Code sandbox => https://codesandbox.io/s/react-native-web-forked-mnwee?file=/src/App.js

Related

React native page keeps crashing, I can't see why

I'm making a react native app on Expo, but the whole app crashes everytime I try to get into this one page. This is my first time using react native, and I'm still a beginner to react, but I can't see what's causing the issue. The error I'm getting is Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it., but I can't see where
import { StyleSheet, Text, View, TouchableOpacity, ScrollView, TextInput } from 'react-native'
import React, { useState, useEffect } from 'react'
// import CartCard from '../Components/CartCard'
import ItemCard from '../Components/ItemCard'
const Cart = ({navigation}) => {
const [details, setDetails] = useState([])
useEffect(()=>{
getData()
}, [])
const getData = async() => {
try {
const getValues = await AsyncStorage.getItem('object')
if(getValues !== null){
console.log(JSON.parse(getValues))
setDetails(getValues)
// const objectValues = JSON.parse(getValues)
// setDetails(objectValues)
// getValues.forEach(object => {
// console.log("id:", object.id)
// setToyId(object.id)
// })
}
}catch(error){
console.log('getData didnt work')
}
}
const [cardholder, setCardholder] = useState('');
const [cardNumber, setCardNumber] = useState('');
const [expiryDate, setExpiryDate] = useState('');
const [cvc, setCvc] = useState('');
const [allItems, setAllItems] = useState(0);
const [total, setTotal] = useState(0)
const clearCart = () => {
console.log('cc')
}
const countAllItems = () => {
console.log('cai')
}
const countTotal = () => {
console.log('ct')
}
return (
<ScrollView style={{backgroundColor: 'white', height: '100%'}}>
<ItemCard />
<View>
<View style={styles.payment}>
<Text>Your Shopping Cart</Text>
<View>
<Text value={allItems} onChangeText={(value)=>{setAllItems(value)}}>Overall Items: {countAllItems}</Text>
<Text value={total} onChangeText={(value)=>{setTotal(value)}}>Total Price: ${countTotal}.00</Text>
</View>
</View>
<Text>Payment</Text>
<TextInput placeholder='Cardholder Name' style={styles.inputsLong} placeholderTextColor='black' value={cardholder} onChangeText={(value)=>setCardholder(value)}/>
<TextInput placeholder='Card Number' style={styles.inputsLong} placeholderTextColor='black' value={cardNumber} onChangeText={(value)=>setCardNumber(value)}/>
<View style={styles.shortForm}>
<TextInput placeholder='MM/YY' style={styles.inputsShort} placeholderTextColor='black' value={expiryDate} onChangeText={(value)=>setExpiryDate(value)} />
<TextInput placeholder='CVC' style={styles.inputsShort} placeholderTextColor='black' value={cvc} onChangeText={(value)=>setCvc(value)}/>
</View>
<View style={styles.buttonRow}>
<TouchableOpacity>
<Text style={styles.cancelButton} onPress={clearCart}>Cancel Order</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text style={styles.orderButton} onPress={()=>navigation.navigate('ConfirmScreen')}>Place Order</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
)
}
export default Cart
const styles = StyleSheet.create({ // styles })
ItemCard component doesn't take any parameters yet because I'm having problems storing data and passing data between pages, but this is what the component looks like in its current state:
import { StyleSheet, Text, View, ScrollView } from 'react-native'
import React from 'react'
const ItemCard = () => {
return (
<ScrollView>
<View style={styles.itemSection}>
<View style={styles.card}></View>
</View>
</ScrollView>
)
}
export default ItemCard
const styles = StyleSheet.create({ // styles })
For this warning
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
you have to call the functions
eg. countTotal()
<Text value={total} onChangeText={(value)=>{setTotal(value)}}>Total Price: ${countTotal()}.00</Text>
if you are using onPress then like this
<Text style={styles.cancelButton} onPress={()=>clearCart()}>Cancel Order</Text>

How can pass state variable between components REACT NATIVE

I have 2 components A and B.
How can pass a variable from component A to component B ?
ComponentA.js
const ComponentA = () => {
//INSET
const insets = useSafeAreaInsets();
//GET HEADER HEIGHT
const [heightHeader, setHeightHeader] = useState(false)
return (
<View
onLayout={({ nativeEvent }) => {
const { height } = nativeEvent.layout
setHeightHeader(height)
}}>
</View>
)
}
export default ComponentA
I want to get from const [heightHeader, setHeightHeader] = useState(false)
the heightHeader variable
ComponentB.js
import ComponentA from './ComponentA';
const ComponentB = () => {
return (
<View style={{
flex:1
}}>
<View style={{
flex:1,
paddingTop: heightHeader,
}}>
</View>
</View>
)
}
export default ComponentB
I want to get heightHeader variable from ComponentA to ComponentB.
*The 2 Components isn't in the same file
Use redux for maintaining global state.
OR
Use pass props feature provided by React Navigation like this :
function HomeScreen({ navigation: { navigate } }) {
return (
<View>
<Text>This is the home screen of the app</Text>
<Button
onPress={() =>
navigate('Profile', {heightHeader:heightHeader })
}
title="Go to Brent's profile"
/>
</View>
);
}
For more follow this link

react native navigation - update list in screen on add/update/delete items

I am building a mobile app using react native. I am using the stack navigator from react-native-navigation library.
the stack has two screens, the first screen ItemsListScreen displays a list of items with add button, clicking the add button navigates to the second screen AddItemScreen which displays some inputs for the item with a save button. clicking the save button will navigate back to the list screen.
Items are saved currently on a local SQLite database, but in future will be saved on a server.
ItemsListScreen.js
import React, { useEffect, useState, useContext } from "react";
import { Button, Image, Platform, StyleSheet, View, TouchableOpacity } from "react-native";
import { ScrollView } from "react-native-gesture-handler";
import { Ionicons } from "#expo/vector-icons";
import DatabaseContext from "../db/DatabaseContext";
export default function ItemsListScreen({ navigation }) {
const db = useContext(DatabaseContext);
const [items, setItems] = useState([]);
useEffect(() => {
navigation.setOptions({
headerRight: () => {
return (
<TouchableOpacity activeOpacity={0.5} style={{ paddingRight: 10 }} onPress={() => navigation.navigate("AddItem")}>
<Ionicons name="md-add" size={30} />
</TouchableOpacity>
);
},
});
db.getItems().then((data) => {
setItems(data);
});
}, []);
return (
<View style={styles.container}>
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
{items.map((item, index) => (
<View key={index} style={styles.item}>
<Text style={styles.subSection}>{item.title}</Text>
</View>
))}
</ScrollView>
</View>
);
}
AddItemScreen.js
import React, { useState, useEffect, useContext } from "react";
import { StyleSheet, View, Dimensions, TextInput, ScrollView, TouchableOpacity, Picker, Switch } from "react-native";
import DateTimePicker from "#react-native-community/datetimepicker";
import { Ionicons } from "#expo/vector-icons";
import DatabaseContext from "../db/DatabaseContext";
export default function AddItemScreen({ navigation }) {
const db = useContext(DatabaseContext);
const [item, setItem] = useState({
title: "",
description: "",
// more properties
});
const saveItem = () => {
db.saveItem(item);
navigation.navigate("ItemsList");
};
useEffect(() => {
navigation.setOptions({
headerRight: () => {
return (
<TouchableOpacity activeOpacity={0.5} style={{ paddingRight: 10 }} onPress={saveItem}>
<Ionicons name="md-save" size={30} />
</TouchableOpacity>
);
},
});
}, [item]);
return (
<View style={styles.scene}>
<ScrollView>
<Text style={styles.label}>Title</Text>
<TextInput style={styles.field} value={item.title} onChangeText={(text) => setItem((prevState) => ({ ...prevState, title: text }))} />
<Text style={styles.label}>Description</Text>
<TextInput style={styles.field} value={item.description} onChangeText={(text) => setItem((prevState) => ({ ...prevState, description: text }))} />
{/* more inputes */}
</ScrollView>
</View>
);
}
My problem is that the list on ItemsListScreen is not updated when a new item is added. I need to reload the app to get the list updated.
I tried to remove the second parameter (the empty array) from the useEffect in ItemsListScreen, it works but I think it is a bad solution as it keeps reading from the db on each re-render.
How can I refresh the list whenever the user navigates back to the list screen? I thought the useEffect will be executed whenever the screen is activated but it seems not.
You will be resolved by adding navigation focus listener to ItemsListScreen.js. Replace your useEffet in this way
useEffect(() => {
navigation.setOptions({
headerRight: () => {
return (
<TouchableOpacity activeOpacity={0.5} style={{ paddingRight: 10 }} onPress={() => navigation.navigate("AddItem")}>
<Ionicons name="md-add" size={30} />
</TouchableOpacity>
);
},
});
const unsubscribe = navigation.addListener('focus', () => {
db.getItems().then((data) => {
setItems(data);
});
});
return unsubscribe;
}, []);

How to replace component did update using react hooks in a functional component?

I have developed a react native application. In there I am updating a state variable through redux-thunk action dispatch. Now what I want is I want to navigate to another component when It is updated. How I have written is like this.
useEffect(() => {
_navigateOnSuccessOtp(props.navigation);
}, [props.success, props.navigation]);
This is navigated to next view even when navigation is changed because It also has been added as a dependency to this effect. This is my full code of the component.
import React, {useEffect} from 'react';
import {
View,
TouchableOpacity,
KeyboardAvoidingView,
Switch,
TextInput,
BackHandler,
} from 'react-native';
import I18n from 'react-native-i18n';
import {Formik} from 'formik';
import * as Yup from 'yup';
import {connect} from 'react-redux';
import {signinActions} from '_store/actions';
import SubmitButton from '_components/submitButton/';
import AppText from '_components/appText';
import {strings} from '_translations/i18n';
import styles from './loginstyle';
import Icon from 'react-native-vector-icons/FontAwesome';
const keepMe = (e) => {
console.log(e);
};
const handleBackButton = () => {
return true;
};
const _onPress = (values, sendOTP) => {
console.log(values.mobileNo);
sendOTP(values.mobileNo);
// navigation.navigate('Otp');
};
const _navigateOnSuccessOtp = (navigation) => {
navigation.navigate('Otp');
};
const changeLanguage = (navigate) => {
navigate.navigate('Home');
};
const getCurrentLocale = () => {
let locale;
locale = I18n.locale;
if (locale === 'en') {
return <AppText styles={styles.bottomLinkText}>English</AppText>;
} else if (locale === 'ta') {
return <AppText styles={styles.bottomLinkText}>தமிழ்</AppText>;
}
};
const Login = (props) => {
useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', handleBackButton);
return () => {
BackHandler.removeEventListener('hardwareBackPress', handleBackButton);
};
}, []);
useEffect(() => {
_navigateOnSuccessOtp(props.navigation);
}, [props.success, props.navigation]);
return (
<KeyboardAvoidingView style={{flex: 1}} enabled>
<View style={styles.signinView}>
<View style={styles.signinTitleView}>
<AppText styles={styles.signinTitle}>
{strings('login.title')}
</AppText>
</View>
<View style={styles.colempty} />
<View style={styles.signIn}>
<View style={styles.signInContainer}>
<AppText styles={styles.loginFormTitle}>
{strings('login.title')}
</AppText>
<View style={styles.formContainer}>
<Formik
initialValues={{
mobileNo: '',
toggle: true,
}}
validationSchema={Yup.object({
mobileNo: Yup.string().required('Mobile number required'),
})}
onSubmit={(values, formikActions) => {
_onPress(values, props.sendOTP);
setTimeout(() => {
formikActions.setSubmitting(false);
}, 500);
}}>
{(formprops) => (
<View>
<View
style={
(!formprops.touched.mobileNo
? styles.inputView
: null) ||
(formprops.values.mobileNo && !formprops.errors.mobileNo
? styles.validInputView
: null) ||
(formprops.touched.mobileNo && formprops.errors.mobileNo
? styles.inputViewError
: null)
}>
<TextInput
style={styles.textField}
placeholder={strings('login.mobile')}
placeholderTextColor="#bbbbbb"
value={formprops.values.mobileNo}
onChangeText={formprops.handleChange('mobileNo')}
onBlur={formprops.handleBlur('mobileNo')}
keyboardType="numeric"
/>
{formprops.touched.mobileNo &&
formprops.errors.mobileNo ? (
<Icon name="times" size={20} style={styles.errorIcon} />
) : null}
{formprops.touched.mobileNo &&
formprops.values.mobileNo ? (
<Icon name="check" size={20} style={styles.validIcon} />
) : null}
</View>
{formprops.touched.mobileNo && formprops.errors.mobileNo ? (
<View style={styles.errorMessage}>
<AppText styles={styles.errorMessageText}>
{formprops.errors.mobileNo}
</AppText>
</View>
) : null}
<View style={styles.togglebuttoncontainer}>
<View style={styles.toggleTextView}>
<AppText styles={styles.toggleText}>
{strings('login.keep-login')}
</AppText>
</View>
<View style={styles.toggleView}>
<Switch
trackColor={{false: '#dddddd', true: '#c1d6ee'}}
thumbColor={{false: '#ffffff', true: '#007aff'}}
ios_backgroundColor="#dddddd"
onValueChange={(value) =>
formprops.setFieldValue('toggle', value)
}
value={formprops.values.toggle}
style={styles.toggle}
/>
</View>
</View>
<SubmitButton
onpress={formprops.handleSubmit}
btext={strings('login.button-text')}
/>
</View>
)}
</Formik>
</View>
</View>
</View>
<View style={styles.hr} />
<View style={styles.signInBottomContainer}>
<View style={styles.signInBottomView}>
<View style={styles.signInBottomContainerTextView}>
<AppText styles={styles.signInBottomContainerText}>
{strings('login.language-change')}?
</AppText>
</View>
<View style={styles.signInBottomLinkView}>
<TouchableOpacity
onPress={() => changeLanguage(props.navigation)}>
{getCurrentLocale()}
</TouchableOpacity>
</View>
</View>
</View>
</View>
</KeyboardAvoidingView>
);
};
const mapStateToProps = (state) => {
console.log(state);
return {
success: state.signin.success,
};
};
const mapDispatchToProps = (dispatch) => {
return {
sendOTP: (number) => dispatch(signinActions.sendOtpActionCreator(number)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);
I tried a lot to figure out how to do it correctly but I was unable to do so. So can someone help me with this problem? Thank you.
Since , _navigateOnSuccessOtp only uses navigation.navigate you can simply pass props.navigation.navigate as a dependency for useEffect and also as an argument to the function. Now navigate does not change and hence your useEffect won't be triggered unnecessarily.
const _navigateOnSuccessOtp = (navigate) => {
navigate('Otp');
};
.....
useEffect(() => {
_navigateOnSuccessOtp(props.navigation.navigate);
}, [props.success, props.navigation.navigate]);
You could also remove props.navigation from the dependency as you don't want useEffect to trigger on props.navigation change. Please check this post for more details: How to fix missing dependency warning when using useEffect React Hook?

React navigation with hooks and header function - state is not updating

Trying to use react navigation with hooks and a button in the navigation header.
I can pass the handleShowModalLogin function to the navigation header and I can see the button is clicked, but the problem is the setShowLoginModal is not updating the showLoginModal state to true. Not sure why this is not working.
import React, { useState, useEffect, useLayoutEffect } from "react";
import {
Image,
Platform,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
Button,
} from 'react-native';
import LoginModal from './users/LoginModal';
const HomeScreen = ({navigation}, props) => {
const [showLoginModal, setShowLoginModal] = useState(false);
const handleShowModalLogin = (value) => {
console.log("showLoginModal button clicked: ", value)
if(value === "on"){
setShowLoginModal(true);
}else{
setShowLoginModal(false);
}
}
useEffect(() => {
console.log('navigation handler set with showLoginModal set:', showLoginModal)
navigation.setParams({ handleShowModalLogin: handleShowModalLogin });
}, []);
useEffect(() => {
console.log("showLoginModal value changed: ", showLoginModal), [showLoginModal]
})
return (
<View style={styles.container}>
<LoginModal showLoginModal={showLoginModal} />
<ScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}>
</ScrollView>
</View>
);
};
HomeScreen.navigationOptions = ({ navigation }) => ({
title: "Home",
headerRight: (
<View style={styles.headerComContainer}>
<Button
onPress={() => {
navigation.getParam('handleShowModalLogin')('on')
}}
title="Login"
color="#841584"
accessibilityLabel="Login" />
</View>
)
});
Here's the login modal component.
import React, { useState } from "react";
import { Text, TouchableOpacity, View, ScrollView } from "react-native";
import Modal from 'modal-enhanced-react-native-web';
export default function LoginModal(props){
const [visibleModal, setModalVisible] = useState(props.showLoginModal);
return (
<View>
<Modal
isVisible={visibleModal}
onBackdropPress={() => setModalVisible(false)}
>
<View>
<Text>Hello!</Text>
<TouchableOpacity onPress={() => setModalVisible(false)}>
<View>
<Text>Close</Text>
</View>
</TouchableOpacity>
</View>
</Modal>
</View>
);
}
const [visibleModal, setModalVisible] = useState(props.showLoginModal);
This code in the LoginModal creates a state, who's initial initial value is props.showLoginModal. After that initial value though, there's no connection with the prop. Changing the prop later will not cause the state to change.
You seem to be trying to mix having LoginModal be a controlled component (where a parent handles the logic, and then controls it through props) and an uncontrolled component (where the component manages its own state). Instead, i'd recommend picking one or the other.
From the fact that you're trying to control it externally, it looks like you want to create a controlled component. So your login modal will need modification to have additional props to notify the parent of the clicks. Perhaps an "onBackdropPressed" and an "onClosePressed", as in:
export default function LoginModal(props){
return (
<View>
<Modal
isVisible={props.showLoginModal}
onBackdropPress={() => props.onBackdropPressed()}
>
<View>
<Text>Hello!</Text>
<TouchableOpacity onPress={() => props.onClosePressed()>
<View>
<Text>Close</Text>
</View>
</TouchableOpacity>
</View>
</Modal>
</View>
);
}
Don't forget to modify the home screen to pass those additional props in, as in:
<LoginModal
showLoginModal={showLoginModal}
onBackdropPressed={() => setShowLoginModal(false)}
onClosePressed={() => setShowLoginModal(false)}
/>

Resources