So into the cartscreen, I am importing a dropdown component.
Now, there is a different dropdown inside the cartscreen called renderDropdown, and I'm using it to display two different data lists as two dropdowns for the user to choose from. Although this renderDropdown is functioning properly, selecting an imported dropdown causes the selected value in my renderDropdown to be cleared from the dropdown bar.
I verified that renderDropdown's value remains in the current state and I noticed that when I click on this imported drop-down, I get a message printed to the console(I have added console.log in cart screen whenever screen renders), which indicates that my entire screen is rendering. Could someone please look into this and let me know what's wrong?
Here is the code for a dropdown menu.
[I've also used the same component in a lot of other screens, and it works just fine]
import { StyleSheet, View } from 'react-native'
import React from 'react'
import { Dropdown} from 'react-native-element-dropdown'
import { COLORS } from '../constants'
const DropdownComponent = ({text, extractorValue, external_Id, data:mainData, extraData, value, setValue, isFocus, setIsFocus,area=false, retailer=false, retailerC=false, extraStyling}) => {
return (
<View style={[styles.container, {...extraStyling}]}>
<Dropdown
style={[styles.dropdown, isFocus && { borderColor: COLORS.blue90 }]}
placeholderStyle={styles.placeholderStyle}
selectedTextStyle={styles.selectedTextStyle}
inputSearchStyle={styles.inputSearchStyle}
iconStyle={styles.iconStyle}
data={mainData}
containerStyle={{
backgroundColor:COLORS.white10,
borderRadius:10,
marginTop:10,
borderColor:COLORS.gray10,
shadowColor:COLORS.blue90,
shadowOffset:{
height:15,
width: 15
},
elevation:19,
}}
search
maxHeight={300}
labelField={extractorValue ? extractorValue :"name"}
valueField={external_Id ? external_Id :"name"}
placeholder={!isFocus ? `Select ${text ? text : 'Item'}` : '...'}
searchPlaceholder="Search..."
value={value}
onFocus={() => setIsFocus(true)}
onBlur={() => setIsFocus(false)}
onChange={item => {
retailer ?
retailerC ?
(
setValue({...extraData, retailerClass:external_Id ? item[external_Id] : item.name})
):
(
( area ?
setValue({...extraData, area:external_Id ? item[external_Id] : item.name})
:
setValue({...extraData, route:external_Id ? item[external_Id] : item.name})
)
)
:
(
setValue(external_Id ? item[external_Id] : item.name)
)
setIsFocus(false)
}}
/>
</View>
)
}
export default DropdownComponent
and here is the cartScreen code which is causing trouble , also i want to include that i have to show 3 dropdowns on the screen so i was using the same component renderDropdown for all of them but i was having the same issue so i though it is because of third dropwdown (namely value scheme ) so i used external one but having the same issue.
const CartDetailsScreen = ({navigation}) => {
const [loading, setLoading] = React.useState(false)
const {cartItems, setCartItems } = useContext(mapContext)
const { createOrders, createOrderStatus, distributors, setDistributors, getDistributors, schemes, setSchemes, getSchemes} = useContext(commonUrlsContext)
useLayoutEffect(()=>{
navigation.setOptions({
headerShown:true,
headerTitle:'CART DETAILS',
...HeaderStyles,
headerLeft: () =>(
<HeaderLeft navigation={navigation} />
)
})
},[])
const [valueSchemeFromDropDown, setValueSchemeFromDropDown] = React.useState('')
React.useEffect(()=>{
getSchemes()
getDistributors()
return () =>{
setDistributors([])
setSchemes([])
}
},[])
React.useEffect(()=>{
if(createOrderStatus){
ToastAndroid.show('Order created successfully', ToastAndroid.CENTER)
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{ name: 'OrderSuccessful' },
],
})
);
setTimeout(()=>{{navigation.navigate('OrderSuccessful'), setCartItems([])}},1000)
}
},[createOrderStatus])
function RenderDropDown({index, text, data, external_Id, extractorValue, width, qtyScheme, valueScheme}){
return(
<View style={[styles.container, width && {width: width}]}>
<Dropdown
style={[styles.dropdown]}
placeholderStyle={styles.placeholderStyle}
selectedTextStyle={styles.selectedTextStyle}
inputSearchStyle={styles.inputSearchStyle}
iconStyle={styles.iconStyle}
data={data}
containerStyle={{
borderRadius:10,
marginTop:10,
borderColor:COLORS.gray10,
shadowColor:COLORS.gray90,
shadowOffset:{
height:10,
width: 10
},
elevation:15,
}}
search
maxHeight={300}
labelField={extractorValue ? extractorValue :"name"}
valueField={external_Id ? external_Id :"name"}
placeholder={`Select ${text ? text : 'item'}`}
searchPlaceholder="Search..."
// value={qtyScheme ? cartItems[index]?.distributorName}
onChange={item => {
qtyScheme ?
(
console.log(1),
cartItems[index].qtyScheme = item[external_Id],
setCartItems(cartItems)
)
:
(
console.log(2),
cartItems[index].distributorName = item[extractorValue],
cartItems[index].distributorId = item[external_Id],
setCartItems(cartItems)
)
}}
/>
</View>
)
}
const removeFromCart = (id) =>{
let updatedCart = cartItems.filter(product => product?.products?.Id !== id)
setCartItems(updatedCart)
ToastAndroid.show('Item Removed From the Cart.', ToastAndroid.SHORT)
if(updatedCart.length === 0){
navigation.goBack()
}
}
const submitHandler = () =>{
setLoading(true)
createOrders(cartItems, valueSchemeFromDropDown)
}
const reduceHandler = (item, index) =>{
if(item.qty === 1){
let newResults = cartItems.filter(product => product.products.Id !== item.products.Id)
setCartItems(newResults)
if(newResults.length === 0){
navigation.goBack()
}
}
else{
item.qty = item.qty - 1
let newResults = cartItems.filter(product => product.products.Id !== item.products.Id ? product : item)
setCartItems(newResults)
}
}
const [testValue, setTestValue] = React.useState('')
const [testFocus, setTestFocus] = React.useState(false)
const incrementHandler = (item, index) =>{
let newResults = cartItems.map(product =>
product.products.Id === item.products.Id ? {...item, qty: product.qty + 1} : product)
setCartItems(newResults)
}
return (
<>
{ loading ?
<RowCenter style={{flex:1, flexDirection: 'column', alignItems:'center'}}>
<StyledText fontSize={'20px'}>{createOrderStatus ? 'Redirecting..' : 'Loading..'}</StyledText>
</RowCenter> :
<>
<View style={{flex:0.8}}>
<ScrollView showsVerticalScrollIndicator={false}>
{cartItems?.map((item, index)=>{
return(
<View key={item?.products?.Id} style={{flexDirection:'row', padding:13, borderRadius:10, margin:10, alignItems:'center', backgroundColor:COLORS.green20, ...customShadow }}>
<View style={{width:'100%'}}>
<StyledText>Product Name : {item?.products?.Name}</StyledText>
<ExtraSpace />
{item?.products?.Description &&
<StyledText>Product Description : {item?.products?.Description?.length > 30 ? item?.products?.Description?.slice(0,30) + '...': item?.products?.Description }</StyledText>
}
<ExtraSpace />
<View style={{flexDirection:'row', justifyContent:'space-between', alignItems:'center', padding:10}}>
<TouchableOpacity onPress={()=>removeFromCart(item?.products?.Id)} style={{flexDirection:'row', justifyContent:'center', alignItems:'center'}}>
<SimpleLineIcons name="trash" size={15} color={COLORS.error} />
<StyledText color={COLORS.error}>Remove</StyledText>
</TouchableOpacity>
<View style={{width:'40%', flexDirection:'row', justifyContent:'space-between', alignItems:'center',}}>
<TouchableOpacity onPress={()=>reduceHandler(item, index)}>
<Feather name="minus-circle" size={22} color={COLORS.gray90} />
</TouchableOpacity>
<StyledText>Qty : {item?.qty}</StyledText>
<TouchableOpacity onPress={()=>incrementHandler(item, index)} >
<Feather name="plus-circle" size={22} color={COLORS.gray90} />
</TouchableOpacity>
</View>
</View>
<RenderDropDown index={index} text={'Distributor'} data={distributors?.response} external_Id='external_Id' extractorValue='name' />
{item.distributorName &&
<Label color={'#131821'}> Clone Selected : {item.distributorName}</Label>
}
<RenderDropDown id={item?.products?.id} index={index} text={'Quantity Scheme'} data={schemes?.response?.filter(scheme => scheme.Type__c === 'Quantity Scheme')} external_Id='Id' extractorValue='Name' qtyScheme={true}/>
</View>
</View>
)
})}
</ScrollView>
</View>
<View style={{flex:0.2, justifyContent:'center', alignItems:'center', borderWidth:1, borderTopLeftRadius:10, borderTopRightRadius:10, marginBottom:2, marginHorizontal:1, borderTopColor:'gray', backgroundColor:"transparent"}}>
<DropdownComponent text={'Value Scheme'} data={schemes?.response?.filter(scheme => scheme.Type__c === 'Value Scheme')} external_Id='Id' extractorValue={'Name'} value={testValue} setValue={setTestValue} isFocus={testFocus} setIsFocus={setTestFocus} extraStyling={{width: '100%', marginTop:10, marginBottom:1}}/>
{/* <RenderDropDown text={'Value Scheme'} data={valueScheme} external_Id='Id' extractorValue='Name' valueScheme={true}/> */}
<TextButton onPress={()=>{cartItems?.length === 0 ? null : submitHandler()}} componentStyling={{width:'94%', padding:8, marginHorizontal:10, marginBottom:10, marginTop:1}} title='Place Order'/>
</View>
</>
}
</>
)
}
Hey you have added these
onFocus={() => setIsFocus(true)}
onBlur={() => setIsFocus(false)}
and these are setStates , so it will re render when dropdwon opens or closes
Hope it helps. feel free for doubts
I'm trying to create a library app and I want to be able to search stories by their title. I created a search bar from the SearchBar component, but I don't know when to call a used defined function that allows me to get the books with the searched word.
Could anyone tell me the prop we need to use? More specifically, how do I know the little magnifying glass on the left is clicked? Thank you!
render() {
var count = 0
var title = "";
var author = "";
return(
<ScrollView>
<View>
<Header
backgroundColor = {"#b7e4c7"}
centerComponent = {{
text:"Available stories",
style: {
color:'white',
fontSize:20,
fontWeight:'bold'
}
}}/>
<SearchBar
lightTheme
containerStyle = {{marginTop:5}}
placeholder="Which book would you like to search"
onChangeText={text =>
{this.setState({search:text})}}
value ={this.state.search}
/>
{this.state.tas.map((element) => {
count = count + 1
if(count%2 == 1) {
title = element;
}
else {
author = element;
return(
<TouchableOpacity style = {styles.storyButton} key = {element}>
<Text style = {styles.buttonText}>Title: {title}</Text>
<Text style = {styles.buttonText}>Author: {author}</Text>
</TouchableOpacity>
)
}
})}
</View>
</ScrollView>
)
}
Please have a look at fetchDetails and onChangeText
import React from "react";
import "./styles.css";
export default class App extends React.Component {
// state init
fetchDetails = () =>{
const search = this.state.search
//DO fetch
}
render() {
var count = 0
var title = "";
var author = "";
return(
<ScrollView>
<View>
<Header
backgroundColor = {"#b7e4c7"}
centerComponent = {{
text:"Available stories",
style: {
color:'white',
fontSize:20,
fontWeight:'bold'
}
}}/>
<SearchBar
lightTheme
containerStyle = {{marginTop:5}}
placeholder="Which book would you like to search"
onChangeText={text =>{
this.setState({search:text})
this.fetchDetails()
}}
value ={this.state.search}
/>
{this.state.tas.map((element) => {
count = count + 1
if(count%2 == 1) {
title = element;
}
else {
author = element;
return(
<TouchableOpacity style = {styles.storyButton} key = {element}>
<Text style = {styles.buttonText}>Title: {title}</Text>
<Text style = {styles.buttonText}>Author: {author}</Text>
</TouchableOpacity>
)
}
})}
</View>
</ScrollView>
)
}
}
I've two functional components CustomerDetails and other is MainDetails i want the state value of textInputName and textInputId, so i pass props in MainDetails component
But im getting error :
const CustomerDetails = ({ navigation }) => {
const [textInputName, setTextInputName] = React.useState("");
const [textInputId, setTextInputId] = React.useState("");
return (
<View style={styles.container}>
<NavigationBar
componentCenter = { () => <ComponentCenter /> }
navigationBarStyle= {{ backgroundColor:'#0095ff87' }}
statusBarStyle = {{ barStyle: 'light-content', backgroundColor: '#215e79' }}
/>
<Text style = { styles.heading }>Customer Details</Text>
{ error ?
<Text style={{color: 'red', textAlign:'center'}}>{error}
</Text> : null
}
<TextInput
style = {styles.input}
placeholder ="Enter Doctor Id"
onChangeText ={
(value) => setTextInputId(value)
}
value = { textInputId }
underlineColorAndroid="transparent"
/>
<TextInput
style = {styles.input}
placeholder = "Enter Name"
onChangeText = {
(value) => setTextInputName(value)
}
value = { textInputName }
underlineColorAndroid="transparent"
/>
<TextInput
style = {styles.input}
placeholder = "Enter Email(optional)"
underlineColorAndroid="transparent"
/>
<View style={{ marginTop: 15 }}>
<TouchableOpacity
style = {styles.submitButton}
onPress={() => {
if (textInputId.trim() === "") {
setError("Id Required.")
}
else if( textInputName.trim() === ""){
setError("Name Required.")
}
else{
navigation.navigate('MainDetails')}}
}>
<Text style = {styles.submitButtonText}> Connect </Text>
</TouchableOpacity>
</View>
</View>
)}
const MainDetails = ({ navigation, props }) => {
var idData = props.textInputId
var nameData = props.textInputName
return (
)}
function Customer() {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="CustomerDetails" component={CustomerDetails} />
<Stack.Screen name="MainDetails" component={MainDetails} />
</Stack.Navigator>
</NavigationContainer>
);
}
You need to pass params to MainDetails via route (assuming you're using React Navigation): https://reactnavigation.org/docs/params/
So your code should look like:
const CustomerDetails = ({ navigation }) => {
const [textInputName, setTextInputName] = React.useState("");
const [textInputId, setTextInputId] = React.useState("");
...
navigation.navigate('MainDetails', { textInputId, textInputName })
...
const MainDetails = ({ navigation, route }) => {
const { textInputId, textInputName } = route.params;
return (
)
}
I want to make a generic handler for setstate rather than having two different handlers? How can I do that?
I am thinking of something like:
setStateHandler= (stateToBeChanged) =>{
this.setState({
stateToBeChanged: !this.state.stateToBeChanged
});
}
handleHappyToggle = () =>{
this.setState({
Happy: !this.state.Happy
});
}
handleSadToggle = () => {
this.setState({
Sad: !this.state.Sad
});
}
My two touchable opacities:
<TouchableOpacity
onPress={this.handleHappyToggle}
>
<Text> {this.state.happy ? 'Yes' : 'No' } </Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.handleSadToggle}
>
<Text> {this.state.sad? 'Yes' : 'No' } </Text>
</TouchableOpacity>
EDIT: added demo StackSnippet...
EDIT 2: added 3 total ways to handle this "generically"
You could use a generic handler to accomplish this - just supply the "mood" as a parameter.
handleMoodToggle = mood => event => {
if(!["Happy", "Sad"].includes(mood)){
return null;
}
this.setState({
[mood]: !this.state[mood]
})
}
<TouchableOpacity onPress={this.handleMoodToggle("Happy")}>
<Text> {this.state.happy ? 'Yes' : 'No'} </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.handleMoodToggle("Sad")}>
<Text> {this.state.sad ? 'Yes' : 'No'} </Text>
</TouchableOpacity>
DEMO:
const { Component } = React;
const { render } = ReactDOM;
class App extends Component {
state = {
Happy: true,
Sad: false
};
handleMoodToggle1 = mood => event => {
if (!["Happy", "Sad"].includes(mood)) {
return null;
}
this.setState({
[mood]: !this.state[mood]
})
};
handleMoodToggle2 = event => {
this.setState({
[event.target.innerHTML]: !this.state[event.target.innerHTML]
});
}
handleMoodToggle3 = event => {
this.setState({
[event.target.name]: !this.state[event.target.name]
});
}
render() {
return (
<div>
<button onClick={this.handleMoodToggle1("Happy")}>Toggle Happy</button>
<button onClick={this.handleMoodToggle1("Sad")}>Toggle Sad</button>
<br />
<button onClick={this.handleMoodToggle2}>Happy</button>
<button onClick={this.handleMoodToggle2}>Sad</button>
<br />
<button name="Happy" onClick={this.handleMoodToggle3}>Happy 3</button>
<button name="Sad" onClick={this.handleMoodToggle3}>Sad 3</button>
<pre>{JSON.stringify(this.state, null, 2)}</pre>
</div>
);
}
}
render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
you can check my exmaple for you:
export default class App extends Component {
state = {
emotions: [
{ name: 'happy', status: true },
{ name: 'sad', status: false },
{ name: 'angry', status: true },
]
}
handleEmotionToggle = (index) => {
let emotions = [...this.state.emotions]
emotions[index].status = !emotions[index].status
this.setState({
emotions
})
}
render() {
return (
<View style={styles.container}>
{
this.state.emotions.map((emotion, index) => {
return (
<TouchableOpacity key={index}
onPress={()=>this.handleEmotionToggle(index)}
>
<Text style={{margin:10,fontSize:20}}>{emotion.name} - {emotion.status ? "YES":"NO"} </Text>
</TouchableOpacity>
)
})
}
</View>
);
}
}
I want to skip a specific account from picker item , So i have passed the account to be skipped into the child component from parent , But its generating error saying
error occured:TypeError: TypeError: TypeError: second argument to Function.prototype.apply must be an Array-like object (evaluating '[].concat.apply([],t.children)')
I have refered the solution provided on this
link.
I have From account and To account drop down , i want to skip the selected from accounts from the To accounts drop down.
This is my child component
import React, { Component } from "react";
import { Picker, Icon } from "native-base";
import { Dimensions, Platform } from "react-native";
export default class accountsDropDown extends Component {
constructor(props) {
super(props);
}
render() {
console.log('skipped Acct:'+JSON.stringify(this.props.skipAcct))
let filteredItems = null ;
if(this.props.accounts !== undefined && this.props.accounts.acctId != null){
filteredItems = this.props.accounts;
}
if(filteredItems != null && this.props.skipAcct !== undefined && this.props.skipAcct.acctId != null){
filteredItems = filteredItems.filter(acct=>{
return acct.acctId == this.props.skipAcct.acctId ? false :true;
})
}
console.log('filteredItems:'+JSON.stringify(filteredItems))
return (
<Picker
selectedValue={this.props.selectedValue}
mode="dropdown"
iosHeader="Choose To Account"
style={{ width: Platform.OS === "ios" ? undefined : Dimensions.get("window").width }}
iosIcon={<Icon name="arrow-down" />}
onValueChange={value => this.props.onValueChange(value)}
>
{filteredItems != null &&
filteredItems.map(acct => {
return <Picker.Item key={acct.acctId} label={acct.desc} value={acct} />;
})}
</Picker>
);
}
}
This is my parent component
<View style={styles.item}>
<Text note>From Account</Text>
<AccountsDropDown
selectedValue={this.state.fromAcct}
accounts={this.state.xferSrcAccts}
navigation={this.props.navigation}
onValueChange={itemValue => this.setState({ fromAcct: itemValue })}
/>
</View>
<View style={styles.item}>
<Text note>To Account</Text>
<AccountsDropDown
selectedValue={this.state.toAcct}
skipAcct = {this.state.fromAcct}
accounts={this.state.xferDestAccts}
navigation={this.props.navigation}
onValueChange={itemValue => this.setState({ toAcct: itemValue })}
/>
</View>
I was able to fix the issue by changing the render method like below
render() {
console.log('skipped Acct:'+JSON.stringify(this.props.skipAcct))
let filteredItems = [] ;
if(this.props.accounts.length > 0){
filteredItems = this.props.accounts;
}
if(filteredItems != null && this.props.skipAcct !== undefined && this.props.skipAcct.acctId != null){
filteredItems = filteredItems.filter(acct=>{
return acct.acctId == this.props.skipAcct.acctId ? false :true;
})
}
console.log('filteredItems:'+JSON.stringify(filteredItems))
if(filteredItems.length >0){
return (
<Picker
selectedValue={this.props.selectedValue}
mode="dropdown"
iosHeader="Choose To Account"
style={{ width: Platform.OS === "ios" ? undefined : Dimensions.get("window").width }}
iosIcon={<Icon name="arrow-down" />}
onValueChange={value => this.props.onValueChange(value)}
>
{filteredItems.map(acct => {
return <Picker.Item key={acct.acctId} label={acct.desc} value={acct} />;
})}
</Picker>
);
}
return null;
}
}