With my current code, I have two input fields and a drop down menu. When ever a value is placed into the field or modified, it clears the rest of the fields. The only one that will stay consistent is the drop down menu. I have suspicions that my useEffect hooks may be doing something, but I'm quite unsure of why. Any suggestions would be great.
(FYI: storeArtic is the push to firebase)
CustomScreen.js
import React, { useState } from "react";
import { StyleSheet, Text, Keyboard, View, TouchableWithoutFeedback } from "react-native";
import { Button, Input } from "react-native-elements";
import { Dropdown } from "react-native-material-dropdown";
import { storeArtic } from '../helpers/fb-settings';
const CustomScreen = ({ route, navigation }) =>{
//create a screen with the ability to add a picture with text to the deck of artic cards
//add check box solution for selection of word type (maybe bubbles, ask about this)
const articDrop = [
{value: 'CV'},
{value: 'VC'},
{value: 'VV'},
{value: 'VCV'},
{value: 'CVCV'},
{value: 'C1V1C1V2'},
{value: 'C1V1C2V2'},
];
const [articCard, setCard] = useState({
word: '',
imageUrl: '',
aType:'',
cType: '',
mastery: false
})
return(
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View>
<Text>Please enter the information of your custom card!</Text>
<Input
placeholder="Enter valid image url"
value={articCard.imageUrl}
autoCorrect={false}
onChangeText={(val) => setCard({ imageUrl: val })}
/>
<Input
placeholder="Enter word or phrase"
value={articCard.word}
autoCorrect={false}
onChangeText={(val) =>
setCard({ word: val, aType: val.charAt(0).toUpperCase(), mastery: false})
}
/>
<Dropdown
value={articCard.cType}
onChangeText={(text) => setCard({cType: text})}
label="Artic Type"
data={articDrop}
/>
<Button
//this will save the cards to the database
title="Save"
onPress={() => {
storeArtic({articCard})
}}
/>
<Button
title="Clear"
onPress={() => {
setCard({word: '', aType: '', cType: '', imageUrl: '', mastery: false});
navigation.navigate('Home');
}}
/>
</View>
</TouchableWithoutFeedback>
)
}
export default CustomScreen;
HomeScreen.js
import React, { useState, useEffect } from "react";
import { StyleSheet, Text, Keyboard, TouchableOpacity, View, TouchableWithoutFeedback, Image } from "react-native";
import { Button } from "react-native-elements";
import { Feather } from "#expo/vector-icons";
import { initArticDB, setupArticListener } from '../helpers/fb-settings';
const HomeScreen = ({route, navigation}) => {
const [ initialDeck, setInitialDeck] = useState([]);
useEffect(() => {
try {
initArticDB();
} catch (err) {
console.log(err);
}
setupArticListener((items) => {
setInitialDeck(items);
});
}, []);
useEffect(() => {
if(route.params?.articCard){
setCard({imageUrl: state.imageUrl, word: state.word, aType: state.aType, cType: state.cType, mastery: state.mastery})
}
}, [route.params?.articType] );
navigation.setOptions({
headerRight: () => (
<TouchableOpacity
onPress={() =>
navigation.navigate('Settings')
}
>
<Feather
style={styles.headerButton}
name="settings"
size={24}
color="#fff"
/>
</TouchableOpacity>
),
headerLeft: () => (
<TouchableOpacity
onPress={() =>
navigation.navigate('About')
}
>
<Text style={styles.headerButton}> About </Text>
</TouchableOpacity>
),
});
return(
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.container}>
<Text style={styles.textmenu}>Welcome to Artic Cards</Text>
<Text style={styles.textsubmenu}>Press Start to Begin!</Text>
<Image source={require('../assets/5-snowflake-png-image.png')}
style={{width: 300, height: 300, alignSelf: 'center'}}/>
<Button
title="Start"
style={styles.buttons}
onPress={() => navigation.navigate('Cards',
{passDeck: initialDeck})}
/>
<Button
title="Progress"
style={styles.buttons}
onPress={() => navigation.navigate('Progress')}
/>
<Button
title="Customize"
style={styles.buttons}
onPress={() => navigation.navigate('Customize')}
/>
</View>
</TouchableWithoutFeedback>
);
};
const styles = StyleSheet.create({
container: {
padding: 10,
backgroundColor: '#E8EAF6',
flex: 1,
justifyContent: 'center'
},
textmenu: {
textAlign: 'center',
fontSize: 30
},
textsubmenu:{
textAlign: 'center',
fontSize: 15
},
headerButton: {
color: '#fff',
fontWeight: 'bold',
margin: 10,
},
buttons: {
padding: 10,
},
inputError: {
color: 'red',
},
input: {
padding: 10,
},
resultsGrid: {
borderColor: '#000',
borderWidth: 1,
},
resultsRow: {
flexDirection: 'row',
borderColor: '#000',
borderBottomWidth: 1,
},
resultsLabelContainer: {
borderRightWidth: 1,
borderRightColor: '#000',
flex: 1,
},
resultsLabelText: {
fontWeight: 'bold',
fontSize: 20,
padding: 10,
},
resultsValueText: {
fontWeight: 'bold',
fontSize: 20,
flex: 1,
padding: 10,
},
});
export default HomeScreen;
Unlike class based setState, with functional components when you do setState, it will override the state with what you provide inside setState function. It is our responsibility to amend state (not overrite)
So, if your state is an object, use callback approach and spread previous state and then update new state.
Like this
<Input
placeholder="Enter valid image url"
value={articCard.imageUrl}
autoCorrect={false}
onChangeText={(val) => setCard(prev => ({ ...prev, imageUrl: val }))} //<----- like this
/>
Do the same for all your inputs.
Related
I have created a React Context API, I have stored information such as first_name, hobbies, DOB etc in it. I have crated a state in the context file and a function that changes the state when called. I am using the context in SignUpForm1.js and SignUpForm2.js, I am trying to update the state of context variables when ever there is a change in the text field, but the data in the context is not updating.
UserData.js (Context)
import React from "react";
import { useState } from "react";
import {Text} from 'react-native';
import { createContext } from "react";
const SignUpContext = React.createContext({});
const UserData = (props)=>{
var state = {
pref_pronoun: "",
DOB: "",
course: "",
first_name: "",
hobby_1: "",
hobby_2: "",
hobby_3: "",
hobby_4: "",
hobby_5: "",
home_city: "",
last_name: "",
nationality: "",
student_email: "",
university: "",
}
const [userDetails , setDetails] = useState(state);
const updateFormData = (field, value) => {
setDetails({ [field]: value });
console.log(state);
};
return (
<SignUpContext.Provider value = {{state, updateFormData}}>
{props.children}
</SignUpContext.Provider>
)
}
export {SignUpContext, UserData} ;
SignUpForm1.js
import {
Image,
Text,
StyleSheet,
View,
StatusBar,
ScrollView,
RefreshControl,
} from "react-native";
import DropDown from "./DropDown";
import Input from "./Input";
import {
KeyboardAvoidingView,
TouchableWithoutFeedback,
Keyboard,
} from "react-native";
import { useCallback, useContext, useState } from "react";
import CustomButton from "./CustomButton";
import { useNavigation } from "#react-navigation/native";
import DateTimePickerModal from "react-native-modal-datetime-picker";
import { Button } from "react-native";
import { SignUpContext, UserData } from "./GlobalUtil/UserData";
const HideKeyboard = ({ children }) => (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
{children}
</TouchableWithoutFeedback>
);
function SignUpForm1() {
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const s_context = useContext(SignUpContext);
const showDatePicker = () => {
setDatePickerVisibility(true);
};
const hideDatePicker = () => {
setDatePickerVisibility(false);
};
const navigation = useNavigation();
const NationalityData = ["Football", "Cricket", "Programmming", "Coding"];
const [refreshing, setRefreshing] = useState(false);
const onRefresh = useCallback(() => {
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
}, 2000);
}, []);
const statusBarHeight = Platform.OS === "ios" ? 50 : StatusBar.currentHeight;
return (
<KeyboardAvoidingView behavior="padding">
<HideKeyboard>
<View
style={{
height: "100%",
width: "100%",
backgroundColor: "#f1be63",
}}
>
<View
style={{ backgroundColor: "#f1be63", height: statusBarHeight }}
>
<StatusBar barStyle="dark-content" />
</View>
<ScrollView
contentContainerStyle={styles.rootContainer}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
/>
}
>
<Image
source={require("../assets/aeroplane.png")}
style={styles.image}
resizeMode="contain"
/>
<Text style={styles.header}>Let's get you set up</Text>
<Text style={styles.lowerHeader}>
(we promise that it won't take long)
</Text>
<View style={[styles.textFieldsContainer]}>
<View style={{ alignItems: "center" }}>
<Input
isLabel={true}
label="Preferred Pronoun"
placeholder="He/Him"
onChangeText={(text) => {
s_context.updateFormData("pref_pronoun", text);
}}
/>
<Input
isLabel={true}
label="First Name"
placeholder="Cameron"
onChangeText={(text) => {
s_context.updateFormData("first_name", text);
}}
/>
<Input
isLabel={true}
label="Last Name"
placeholder="Cox"
onChangeText={(text) => {
s_context.updateFormData("last_name", text);
}}
/>
<View
style={{
backgroundColor: "white",
width: "80%",
borderRadius: 5,
marginTop: 10,
}}
>
<Button
title="Date of Birth"
onPress={showDatePicker}
color="gray"
/>
</View>
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode="date"
onConfirm={(date) => {
s_context.updateFormData('dob', date);
hideDatePicker();
}}
onCancel={hideDatePicker}
buttonTextColorIOS="white"
pickerContainerStyleIOS={{ backgroundColor: "#D89D35" }}
isDarkModeEnabled
/>
</View>
<View style={{ alignItems: "center" }}>
<DropDown
data={NationalityData}
placeholder="Nationality"
onSelect={(selectedItem, index) => {
s_context.updateFormData("nationality", selectedItem);
}}
/>
<DropDown
data={NationalityData}
placeholder="University"
onSelect={(selectedItem, index) => {
s_context.updateFormData("university", selectedItem);
}}
/>
<DropDown
data={NationalityData}
placeholder="Course"
onSelect={(selectedItem, index) => {
s_context.updateFormData("course", selectedItem);
}}
/>
<DropDown
data={NationalityData}
placeholder="HomeTown City"
onSelect={(selectedItem, index) => {
s_context.updateFormData("homeCity", selectedItem);
}}
/>
<CustomButton
isBorder={true}
title="Next"
onPress={() => {
navigation.navigate("SignUp2");
}}
/>
</View>
</View>
</ScrollView>
</View>
</HideKeyboard>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
rootContainer: {
height: "125%",
justifyContent: "flex-start",
alignItems: "center",
marginTop: 24,
},
textFieldsContainer: {
width: "100%",
flex: 1,
},
image: {
width: "25%",
height: "10%",
marginTop: 24,
},
header: {
color: "white",
fontSize: 26,
fontWeight: "bold",
marginVertical: 6,
},
lowerHeader: {
color: "white",
fontSize: 12,
marginBottom: 24,
},
});
export default SignUpForm1;
SignUpForm2.js
import {
View,
Text,
TouchableWithoutFeedback,
StyleSheet,
StatusBar,
ScrollView,
} from "react-native";
import { KeyboardAvoidingView } from "react-native";
import { Avatar } from "react-native-elements";
import Input from "./Input";
import DropDown from "./DropDown";
import { Keyboard } from "react-native";
import { Platform } from "react-native";
import { useNavigation } from "#react-navigation/native";
import CustomButton from "./CustomButton";
import { useState, useContext } from "react";
import { createNewUser } from "./util/auth";
import { SignUpContext, UserData } from "./GlobalUtil/UserData";
const HideKeyboard = ({ children }) => (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
{children}
</TouchableWithoutFeedback>
);
function SignUpForm2() {
const s_context = useContext(SignUpContext);
async function FinishBtnHandler()
{
console.log("Creating New User");
console.log("Email: " + emailText.trim());
console.log("Password: " + passwordText.trim());
await createNewUser(emailText.trim(), passwordText.trim());
}
const navigation = useNavigation();
const hobbiesData = ["Football", "Cricket", "Programmming", "Coding"];
const [emailText, setEmailText] = useState("");
function handleEmailText(newText) {
console.log("Email: " + newText);
setEmailText(newText);
}
const [passwordText, setPasswordText] = useState("");
function handlePasswordText(newText) {
console.log("Password: " + newText);
setPasswordText(newText);
}
function avatarPressHandler() {
console.log("Pressed!");
}
const statusBarHeight = Platform.OS === "ios" ? 50 : StatusBar.currentHeight;
return (
<KeyboardAvoidingView behavior="padding" style={{ flex: 1 }}>
<HideKeyboard>
<ScrollView>
<View
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
zIndex: 999,
}}
>
<View
style={{
backgroundColor: "#f1be63",
height: statusBarHeight,
}}
>
<StatusBar barStyle="dark-content" />
</View>
</View>
<View style={[styles.rootContainer, { paddingBottom: 48 }]}>
<View style={styles.lowerContainer}>
<Text style={styles.primaryText}>You're almost there!</Text>
<Avatar
rounded
size={170}
containerStyle={{ alignSelf: "center", marginTop: 24 }}
//icon={{ name: "user", type: "font-awesome" }}
overlayContainerStyle={{ backgroundColor: "#f1be63" }}
source={{
uri: "https://cdn.pixabay.com/photo/2019/11/03/20/11/portrait-4599553__340.jpg",
}}
>
<Avatar.Accessory size={20} onPress={avatarPressHandler} />
</Avatar>
<Text
style={[
styles.secondaryText,
{ marginBottom: 8, marginTop: 16 },
]}
>
Express yourself & customize your avatar
</Text>
<Input
isLabel={true}
label="Student Email"
placeholder="cce22rnu#uea.ac.uk"
onChangeText={handleEmailText}
defaultValue={emailText}
/>
<Input
isLabel={true}
label="Create Password"
placeholder="iLoveyoushakila123"
onChangeText={handlePasswordText}
defaultValue={passwordText}
/>
<Input
isLabel={true}
label="Confirm Password"
placeholder="iLoveyoushakila123"
/>
<Text style={styles.secondaryText}>
Now the exciting part - select your top 5 hobbies
</Text>
<View style={{ alignItems: "center", marginTop: 16 }}>
<DropDown
data={hobbiesData}
onSelect={(selectedItem, index) => {
s_context.updateFormData("hobby_1", selectedItem);
}}
/>
<DropDown data={hobbiesData} onSelect={(selectedItem, index) => {
s_context.updateFormData("hobby_2", selectedItem);
}}/>
<DropDown data={hobbiesData} onSelect={(selectedItem, index) => {
s_context.updateFormData("hobby_3", selectedItem);
}}/>
<DropDown data={hobbiesData} onSelect={(selectedItem, index) => {
s_context.updateFormData("hobby_4", selectedItem);
}}/>
<DropDown data={hobbiesData} onSelect={(selectedItem, index) => {
s_context.updateFormData("hobby_5", selectedItem);
}}/>
<CustomButton
isBorder={true}
title="Finish"
/*onPress={()=>{navigation.navigate("ConfirmId")}}*/ onPress={
FinishBtnHandler
}
/>
</View>
</View>
</View>
</ScrollView>
</HideKeyboard>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
rootContainer: {
flex: 1,
justifyContent: "flex-start",
alignItems: "center",
backgroundColor: "#f1be63",
marginTop: 48,
backgroundColor: "#f1be63",
},
lowerContainer: {
flex: 1,
width: "100%",
alignItems: "center",
},
primaryText: {
color: "white",
fontSize: 24,
fontWeight: "bold",
},
secondaryText: {
marginTop: 8,
color: "white",
fontSize: 12,
fontWeight: "bold",
},
});
export default SignUpForm2;
You need to pass your userDetails state variable to the context, not the initial state value
<SignUpContext.Provider value={{ state: userDetails, updateFormData }}>
{props.children}
</SignUpContext.Provider>
Passing the initial state object will not see any updates you make.
Also, you should use the functional updates format to dynamically set properties in your state
setDetails((prev) => ({ ...prev, [field]: value }));
Finally, console logging state is a pointless exercise that often gives unexpected results. Just don't do it
The issue is that you are using state instead of userDetails in the updateFormData function. The state variable is never updated and remains the same initial value. To fix this, you should use setDetails instead of setState in the updateFormData function.
const updateFormData = (field, value) => {
setDetails({ ...userDetails, [field]: value });
console.log(userDetails);
};
Also, you are passing state in the SignUpContext.Provider value. You should pass userDetails instead of state.
return (
<SignUpContext.Provider value={{ userDetails, updateFormData }}>
{props.children}
</SignUpContext.Provider>
);
I'm trying to figure out why my app isn't working properly. The basic premise is that that the user can choose the various types of cards they want to review. These are broken down into various types and are chosen via checkboxes. This array of settings is then passed to another screen where I take my data from firebase and convert it into an array. These arrays are then compared against one another and if the correct criteria is met, they are added to a new deck. This new deck is then rendered and moved through using a previous and next button. Any help would be appreciated.
My current error is saying length is undefined because when building the deck I need to go through the initial deck of cards (the firebase array).
Current error:
×
TypeError: Cannot read property 'length' of undefined
buildDeck
C:/Users/Langley/Desktop/ArticCards/screens/CardScreen.js:39
36 | let deck = new Array();
37 |
38 | for(var i = 0; i < passDeck.length; i++){
> 39 | for(var j = 0; j < currentSettings.length; j++){
| ^ 40 | if((passDeck.cType[i] == currentSettings[j].arType) && (currentSettings[j].addCV == true)){
41 | deck.push(passDeck[i]);
42 | }
HomeScreen
import React, { useState, useEffect } from "react";
import { StyleSheet, Text, Keyboard, TouchableOpacity, View, TouchableWithoutFeedback, Image } from "react-native";
import { Button } from "react-native-elements";
import { Feather } from "#expo/vector-icons";
import { initArticDB, setupArticListener } from '../helpers/fb-settings';
const HomeScreen = ({route, navigation}) => {
const [ deck, setDeck] = useState([]);
useEffect(() => {
try {
initArticDB();
} catch (err) {
console.log(err);
}
setupArticListener((items) => {
setDeck(items);
});
}, []);
useEffect(() => {
if(route.params?.articCard){
setCard({imageUrl: state.imageUrl, word: state.word, aType: state.aType, cType: state.cType, mastery: state.mastery})
}
if(route.params?.deck){
setDeck({imageUrl: state.imageUrl, word: state.word, aType: state.aType, cType: state.cType, mastery: state.mastery})
}
if(route.params?.articType){
setArticType({arType: state.arType, addCV: state.addCV})
}
}, [route.params?.articType, route.params?.deckeck, route.params?.articCard] );
navigation.setOptions({
headerRight: () => (
<TouchableOpacity
onPress={() =>
navigation.navigate('Settings')
}
>
<Feather
style={styles.headerButton}
name="settings"
size={24}
color="#fff"
/>
</TouchableOpacity>
),
headerLeft: () => (
<TouchableOpacity
onPress={() =>
navigation.navigate('About')
}
>
<Text style={styles.headerButton}> About </Text>
</TouchableOpacity>
),
});
return(
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.container}>
<Text style={styles.textmenu}>Welcome to Artic Cards</Text>
<Text style={styles.textsubmenu}>Press Start to Begin!</Text>
<Image source={require('../assets/5-snowflake-png-image.png')}
style={{width: 300, height: 300, alignSelf: 'center'}}/>
<Button
title="Start"
style={styles.buttons}
onPress={() => navigation.navigate('Cards',
{passDeck: deck})}
/>
<Button
title="Progress"
style={styles.buttons}
onPress={() => navigation.navigate('Progress')}
/>
<Button
title="Customize"
style={styles.buttons}
onPress={() => navigation.navigate('Customize')}
/>
</View>
</TouchableWithoutFeedback>
);
};
const styles = StyleSheet.create({
container: {
padding: 10,
backgroundColor: '#E8EAF6',
flex: 1,
justifyContent: 'center'
},
textmenu: {
textAlign: 'center',
fontSize: 30
},
textsubmenu:{
textAlign: 'center',
fontSize: 15
},
headerButton: {
color: '#fff',
fontWeight: 'bold',
margin: 10,
},
buttons: {
padding: 10,
},
inputError: {
color: 'red',
},
input: {
padding: 10,
},
resultsGrid: {
borderColor: '#000',
borderWidth: 1,
},
resultsRow: {
flexDirection: 'row',
borderColor: '#000',
borderBottomWidth: 1,
},
resultsLabelContainer: {
borderRightWidth: 1,
borderRightColor: '#000',
flex: 1,
},
resultsLabelText: {
fontWeight: 'bold',
fontSize: 20,
padding: 10,
},
resultsValueText: {
fontWeight: 'bold',
fontSize: 20,
flex: 1,
padding: 10,
},
});
export default HomeScreen;
SettingScreen
import React, {useState, useEffect} from 'react';
import {StyleSheet, TouchableOpacity, View} from "react-native";
import {Text, CheckBox} from "react-native-elements";
import {FlatList } from "react-native-gesture-handler";
//this is broken some how can't figure out why.
const SettingsScreen = ({route, navigation}) =>{
//create a screen with checkbox fields. One for the consonant-vowel field and the other for the alphabet.
//Both of which will be using flatlists preferably side by side
//A card will only be counted if it meets both values being marked true (category and alpha)
const [articType, setArticType] = useState([
{arType: 'CV', addCV: true},
{arType: 'VC', addCV: true},
{arType: 'VV', addCV: true},
{arType: 'VCV', addCV: true},
{arType: 'CVCV', addCV: true},
{arType: 'C1V1C1V2', addCV: true},
{arType: 'C1V1C2V2', addCV: true},
]);
navigation.setOptions({
headerRight: () => (
<TouchableOpacity onPress={() => navigation.navigate('Home')}>
<Text style={styles.headerButton}> Cancel </Text>
</TouchableOpacity>
),
headerLeft: () => (
<TouchableOpacity
onPress={() => {
// navigate back with new settings.
navigation.navigate('Home', {
currentSetting: articType
});
}}
>
<Text style={styles.headerButton}> Save </Text>
</TouchableOpacity>
),
});
const renderCVType = ({index, item}) =>{
return(
<CheckBox
title={item.arType}
checked={item.addCV}
onPress={() => {
let newArr = [... articType];
newArr[index] = {...item, addCV: !item.addCV};
setArticType(newArr);
console.log(newArr);
}}
/>
)
}
return(
<View style={styles.container}>
<Text style={styles.textmenu}>Artic Type</Text>
<Text style={styles.textsubmenu}>Select what words to include based on their cononants and vowels</Text>
<FlatList
keyExtractor={(item) => item.arType}
data={articType}
renderItem={renderCVType}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
padding: 10,
backgroundColor: '#E8EAF6',
flex: 1,
},
textmenu: {
textAlign: 'center',
fontSize: 30
},
textsubmenu:{
textAlign: 'center',
fontSize: 15
},
});
export default SettingsScreen;
CardScreen
import React, { useState, useRef, useEffect } from "react";
import { StyleSheet, Text, View } from "react-native";
import { Button, Card } from "react-native-elements";
import { updateArtic } from "../helpers/fb-settings";
import { State } from "react-native-gesture-handler";
//Cannot navigate to this page, not sure why
//general formatting and importing of the deck. Probably put shuffle function here with imported array
//Next and previous buttons should bascially refresh page and give next or previous card
//mastery probably will require setupArticListener (this needs clarification though)
//Deck will be imported here and the information from SettingScreen will be imported here as well
const CardScreen = ({route, navigation}) =>{
const { currentSettings, passDeck } = route.params;
const renderCard = ({index, item}) => {
<View>
<Card
title={item.word}
image={{uri: item.imageUrl}}>
<Text> {item.cType} </Text>
</Card>
</View>
}
const renderMastery = ({index, item}) =>{
return(
<CheckBox
title={'Mastered?'}
checked={!item.mastery}
onPress={() => {
updateArtic({ ...item, mastery: !item.mastery });
}}
/>
)
}
function buildDeck(){
let deck = new Array();
for(var i = 0; i < passDeck.length; i++){
for(var j = 0; j < currentSettings.length; j++){
if((passDeck.cType[i] == currentSettings[j].arType) && (currentSettings[j].addCV == true)){
deck.push(passDeck[i]);
}
}
}
return deck;
}
function nextCard(){
var k = deck.indexOf();
if( (k+1) <= deck.length){
return deck[k+1];
} else{
return deck[0];
}
}
function previousCard(){
var k = deck.indexOf();
if( (k-1) >= 0){
return deck[k-1];
} else{
return deck[(deck.length - 1)];
}
}
buildDeck();
return(
<View>
<Text>Cards</Text>
{renderCard(deck)}
<View style={styles.row}>
<Button
title='Next'
onPress={
nextCard()
}
/>
renderItem{renderMastery}
<Button
title='Previous'
onPress = {
previousCard()
}
/>
</View>
</View>
)
}
const styles= StyleSheet.create({
row: {
flexDirection: 'row',
flex: 1,
marginBottom: 1
},
})
export default CardScreen;
In my RN application, I have the following array.
const PDFItems = [
{ id: 1001, name: 'APPLICATION FORM.PDF', size: '2.77 MB' },
{ id: 1002, name: 'BENEFIT ILLUSTRATION.PDF', size: '368 KB' },
{ id: 1003, name: 'PRODUCT SUMMARY.PDF', size: '2.02 MB' },
{ id: 1004, name: 'TERMS AND CONDITIONS.PDF', size: '269 KB' },
];
I created the following function to render items.
renderPDFItems = () => {
return PDFItems.map(item => (
<ListItem
key={item.id.toString()}
icon={<Download />}
title={item.name}
label={item.size}
onPress={() => {}}
/>
));
}
This is my ListItem component.
import React, { Component } from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import colors from 'res/colors';
import SinglifeText from './text';
interface IProps {
title: string;
label: string;
icon: Object;
key: string;
onPress?: Function;
}
class ListItem extends Component<IProps> {
render() {
const { title, label, icon, key, onPress } = this.props;
return (
<TouchableOpacity onPress={onPress} key={key}>
<View style={styles.itemContainer}>
<View style={styles.titleContainer}>
<View style={styles.icon}>{icon}</View>
<SinglifeText type={SinglifeText.Types.BUTTON_LBL} label={title} />
</View>
<SinglifeText type={SinglifeText.Types.BODY_SMALL} label={label} />
</View>
</TouchableOpacity>
);
}
}
const styles = StyleSheet.create({
itemContainer: {
borderColor: colors.gray,
borderRadius: 4,
borderWidth: 0.5,
padding: 14,
paddingVertical: 24,
flexDirection: 'row',
alignContent: 'center',
alignItems: 'center',
marginBottom: 10,
},
titleContainer: {
flex: 1,
flexDirection: 'row',
alignContent: 'center',
alignItems: 'center',
},
icon: {
marginRight: 10,
},
});
export default ListItem;
When I run the app, it shows a warning saying that,
key is not a prop. Trying to access it will result in undefined being returned. If you need to access the same value within the child component, you should pass it as a different prop
What am I doing wrong here? My keys are unique and still gives this error.
Just dont name the prop key. Something like id has the same syntactic meaning.
renderPDFItems = () => {
return PDFItems.map(item => (
<ListItem
id={item.id.toString()}
icon={<Download />}
title={item.name}
label={item.size}
onPress={() => {}}
/>
));
}
ListItem.js
class ListItem extends Component<IProps> {
render() {
const { title, label, icon, id, onPress } = this.props;
return (
<TouchableOpacity onPress={onPress} key={id}>
<View style={styles.itemContainer}>
<View style={styles.titleContainer}>
<View style={styles.icon}>{icon}</View>
<SinglifeText type={SinglifeText.Types.BUTTON_LBL} label={title} />
</View>
<SinglifeText type={SinglifeText.Types.BODY_SMALL} label={label} />
</View>
</TouchableOpacity>
);
}
}
It seems your key prop defined is ambiguous to TouchableOpacity key={key}. Try renaming key prop to some other name.
As some guys have said above, we don't need the key prop. TouchableOpacity has the key by default, and we can't use key another prop as 'key'
I created a component called inputText.js, which serves to declare a particular input field according to my needs.
I use it inside login.js, passing it as Field props of redux-form.
The problem that when I begin to write even very slowly begins to start giving me problems, if I start writing faster, I get impatient, the text I write last in the field is mixed with the text already written and is found in the middle, even the stanchette that which indicates where the focus is inside the word is in the middle of the writing.
Link: Expo
Code: inputText.js
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import { TextInput, IconButton, Colors } from 'react-native-paper';
import Icon from 'react-native-vector-icons/MaterialIcons';
const propTypes = {
mapElement: PropTypes.func,
onSubmitEditing: PropTypes.func,
onChangeText: PropTypes.func,
value: PropTypes.string,
placeholder: PropTypes.string,
maxLength: PropTypes.number,
keyboardType: PropTypes.string,
secureTextEntry: PropTypes.bool,
label: PropTypes.string,
};
const defaultProps = {
mapElement: n => {},
onSubmitEditing: () => {},
onChangeText: () => {},
value: '',
placeholder: '',
maxLength: 200,
keyboardType: 'default',
secureTextEntry: false,
label: '',
};
const styles = StyleSheet.create({
inputBox: {
width: 300,
//backgroundColor: 'rgba(255, 255,255,0.2)',
//borderRadius: 25,
//paddingHorizontal: 16,
//fontSize: 16,
//color: '#ffffff',
//marginVertical: 10,
},
icon: {
position: 'absolute',
top: 20,
right: 10,
},
});
class InputText extends Component<{}> {
constructor(props) {
super(props);
this.state = {
vPwd: props.secureTextEntry,
};
}
changePwdType = () => {
this.setState(prevState => ({
vPwd: !prevState.vPwd,
}));
};
render() {
const {
label,
placeholder,
secureTextEntry,
iconPassword,
keyboardType,
maxLength,
value,
onChangeText,
onSubmitEditing,
} = this.props;
const { vPwd } = this.state;
let icEye = vPwd ? 'visibility-off' : 'visibility';
return (
<View>
<TextInput
style={styles.inputBox}
placeholder={placeholder}
secureTextEntry={vPwd}
keyboardType={keyboardType}
maxLength={maxLength}
returnKeyType="next"
value={value}
onSubmitEditing={onSubmitEditing}
onChangeText={onChangeText}
label={label}
underlineColor="#fff"
theme={{
colors: {
primary: '#f5a442',
background: 'transparent',
placeholder: '#fff',
},
}}
/>
{secureTextEntry && iconPassword && (
<Icon
style={styles.icon}
name={icEye}
size={25}
onPress={this.changePwdType}
/>
)}
</View>
);
}
}
InputText.defaultProps = defaultProps;
InputText.propTypes = propTypes;
export default InputText;
Code: login.js
import React, { Component } from 'react';
import { StyleSheet, View, Text, TouchableOpacity } from 'react-native';
import { Field, reduxForm } from 'redux-form';
import { HelperText } from 'react-native-paper';
import Logo from '../components/Logo';
import Form from '../components/Form';
import InputText from '../components/InputText';
import { Actions } from 'react-native-router-flux';
class Login extends Component<{}> {
signup() {
Actions.signup();
}
onSubmit = values => {
console.log(values);
};
renderTextInput = field => {
const {
meta: { touched, error },
label,
secureTextEntry,
iconPassword,
maxLength,
keyboardType,
placeholder,
input: { onChange, ...restInput },
} = field;
return (
<View>
<InputText
onChangeText={onChange}
maxLength={maxLength}
placeholder={placeholder}
keyboardType={keyboardType}
secureTextEntry={secureTextEntry}
iconPassword={iconPassword}
label={label}
{...restInput}
/>
{touched && error && (
<HelperText type="error" visible={true}>
{error}
</HelperText>
)}
</View>
);
};
render() {
const { handleSubmit } = this.props;
return (
<View style={styles.container}>
<Logo />
<View style={styles.inputContainerStyle}>
<Field
name="email"
label="Email"
placeholder="Email#host.com"
component={this.renderTextInput}
/>
<Field
name="password"
label="Password"
placeholder="***********"
secureTextEntry={true}
iconPassword={true}
component={this.renderTextInput}
/>
</View>
<TouchableOpacity
style={styles.button}
onPress={handleSubmit(this.onSubmit)}>
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
<View style={styles.signupTextCont}>
<Text style={styles.signupText}>Do not have an account yet?</Text>
<TouchableOpacity>
<Text style={styles.signupButton}> Signup</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#455a64',
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
signupTextCont: {
flexGrow: 1,
alignItems: 'flex-end',
justifyContent: 'center',
paddingVertical: 16,
flexDirection: 'row',
},
signupText: {
color: 'rgba(255,255,255,0.6)',
fontSize: 16,
},
signupButton: {
color: '#ffffff',
fontSize: 16,
fontWeight: '500',
},
button: {
width: 300,
backgroundColor: '#1c313a',
borderRadius: 25,
marginVertical: 10,
paddingVertical: 13,
},
buttonText: {
fontSize: 16,
fontWeight: '500',
color: '#ffffff',
textAlign: 'center',
},
inputContainerStyle: {
backgroundColor: 'rgba(255, 255, 255, 0.15)',
padding: 5,
paddingBottom: 10,
borderRadius: 5,
},
});
const validate = values => {
const errors = {};
if (!values.email) {
errors.email = 'Email is required';
}
if (!values.password) {
errors.password = 'Password is required';
}
return errors;
};
export default reduxForm({
form: 'login',
validate,
})(Login);
Im making a component and calling it in my app.js with props inside like { placeholder }, but it always returns a refrenceError: Can't find variable placeholder. I don't understand why.
Calling it:
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import * as firebase from 'firebase';
import { Input } from './components/input'
import { Button } from './components/button'
export default class App extends React.Component {
state = {
email: '',
password: '',
}
render() {
return (
<View style={styles.container}>
<Input
placeholder='Enter your email'
label='Email'
onChangeText={password => this.setState({ password })}
value={this.state.password}
/>
<Input
placeholder='Enter your password'
label='Password'
secureTextEntry
onChangeText={email => this.setState({ email })}
value={this.state.email}
/>
<Button>Login</Button>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 20,
},
});
And the component
import React from 'react';
import { View, StyleSheet, Text, TextInput } from 'react-native';
const Input = () => {
return (
<View>
<Text style={styles.label}>Label</Text>
<TextInput
style={styles.input}
placeholder={ placeholder }
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginTop: 10,
width: '100%',
borderColor: '#eee',
borderBottomWidth: 2,
},
label: {
padding: 5,
paddingBottom: 0,
color: '#eee',
fontSize: 17,
fontWeight: '700',
width: '100%'
},
input: {
paddingRight: 5,
paddingLeft: 5,
paddingBottom: 2,
backgroundColor: '#eee',
fontSize: 18,
fontWeight: '700',
width: '100%',
}
})
export { Input };
const Input = ({ placeholder }) => { //<==== here
return (
<View>
<Text style={styles.label}>Label</Text>
<TextInput
style={styles.input}
placeholder={ placeholder }
/>
</View>
);
}
props will not be passing in automatically. It will be passed in as an argument, and your input component doesnt accept any argument and you're trying to access a variable placeholder and hence getting the error stated
Your Input is taking no props at all. You need to pass the props as parameters of the component function:
const Input = (props) => {
return (
<View>
<Text style={styles.label}>Label</Text>
<TextInput
style={styles.input}
placeholder={ props.placeholder }
/>
</View>
);
}
Accept props as a parameter in the Input component, then use props to access placeholder. You need to change the code of Input component to
const Input = (props) => {
return (
<View>
<Text style={styles.label}>Label</Text>
<TextInput
style={styles.input}
placeholder={ props.placeholder }
/>
</View>
);
}
Hope this will help!