I created a button in flat list, when user click an specific item, it's button should change state and increment button should appear, but button changing state for all the items. I pass id too but it's not working, can someone please help me... below is my code
Items.js
<FlatList
data={this.props.items}
extraData={this.props}
keyExtractor={(items) => items.id.toString()}
numColumns={2}
renderItem={({ item }) => (
<CardBuyItem>
<Image style={styles.image} source={item.image} />
<View style={styles.detailContainer}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subTitle} numberOfLines={1}>
{item.subTitle}
</Text>
<Text style={styles.price}>Rs {item.price}</Text>
</View>
{this.props.button && this.props.added.length > 0 ? (
<View style={styles.add}>
<Text style={styles.quantity}>{item.quantity}</Text>
<MaterialCommunityIcons
style={styles.iconUp}
size={20}
name="plus-circle-outline"
onPress={() => this.props.addQuantity(item.id)}
/>
<MaterialCommunityIcons
style={styles.iconDown}
size={20}
name="minus-circle-outline"
onPress={() => this.props.subtractQuantity(item.id)}
/>
</View>
) : (
<View style={styles.buy}>
<Text
style={styles.buyonce}
onPress={() => {
this.props.addToCart(item.id);
this.props.showCart();
this.props.showButton(item.id);
}}
>
Buy Once
</Text>
</View>
)}
</CardBuyItem>
)}
/>
const mapStateToProps = (state) => {
return {
items: state.clothes.jeans,
button: state.clothes.showButton,
added: state.clothes.addedItems,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (id) => dispatch(addToCart(id)),
addQuantity: (id) => dispatch(addQuantity(id)),
subtractQuantity: (id) => dispatch(subtractQuantity(id)),
showCart: () => dispatch(showCart()),
showButton: (id) => dispatch(showButton(id)),
};
};
That's my item list with mapStateToProsp and mapDispatchToProps here button should change it's state
reducer.js
if (action.type === SHOW_BUTTON) {
let addedItem = state.jeans.find((item) => item.id === action.id);
return {
...state,
addedItem: addedItem,
showButton: action.showButton,
};
}
const initialstate = { showButton: false}
it's my reducer function with initial state of that button
action.js
export const showButton = (id) => {
return {
type: SHOW_BUTTON,
showButton: true,
id,
};
};
it's my action where I'm describing action for my reducer
You are having a common state variable for this which causes it to show all buttons.
You can do a simple solution like this.
In your flatlist you can have a logic to display the button
{this.props.added.find(x=>x.id==item.id) !=null ? (
Or if you have to use the reducer, you will have to have a property in the array and update it which would be complex to maintain.
Related
I have a component which changes the state when checkbox is checked and the data needs to be updated of the object in the array.
The component state looks something like this
{
key:1,
todo:"Something",
isChecked:false
}
i have 3 files:
AddTodo.js Which passes state & setState to an component TodoList which passes it the subcomponent TodoItem.
I am unable to update the state from TodoItem , I need to implement a function that finds the object from array and updates its isChecked state.
AddTodo.js
function AddTodo() {
const [state, setState] = useState(false);
const [todos, addTodos] = useState([]);
var keys = (todos || []).length;
return (
<View style={styles.container}>
<Modal
animationType="slide"
transparent={true}
visible={state}
statusBarTranslucent={true}
>
<View style={styles.itemsContainer}>
<GetInfoDialog
state={state}
stateChange={setState}
addItem={addTodos}
numKeys={keys}
/>
</View>
</Modal>
{(todos || []).length > 0 ? (
<TodoList data={todos} updateState={addTodos} />
) : null}
<TouchableOpacity
style={styles.btn}
onPress={() => {
setState(true);
}}
>
<Text style={styles.text}>Add New</Text>
</TouchableOpacity>
</View>
);
}
TodoList.js
function TodoList(props) {
return (
<View style={styles.todoList}>
<FlatList
data={props.data}
renderItem={({ item }) => {
console.log(item);
return (
<TodoItem
list={props.data}
itemKey={item.key}
todo={item.todo}
isChecked={item.isChecked}
updateState={props.updateState}
/>
);
}}
backgroundColor={"#000000"}
alignItems={"center"}
justifyContent={"space-between"}
/>
</View>
);
}
TodoItem.js
function TodoItem(props) {
const [checked, setCheck] = useState(props.isChecked);
return (
<View style={styles.todoItem}>
<Checkbox
value={checked}
onValueChange={() => {
setCheck(!checked);
}}
style={styles.checkbox}
/>
<Text style={styles.text}>{props.todo}</Text>
</View>
);
}
renderItem={({ item, index }) => {
console.log(item);
return (
<TodoItem
list={props.data}
itemKey={item.key}
todo={item.todo}
isChecked={item.isChecked}
updateState={props.updateState}
setChecked={(value)=>{
let updatedList = [...yourTodosList]
updatedlist[index].isChecked=value
setTodos(updatedList)
}}
/>
);
}}
and in your todo item
onValueChange={(value) => {
props.setChecked(value);
}}
i also don't think that you need an is checked state in your todo component since you are passing that through props (so delete const [checked, setCheck] = useState(props.isChecked) line and just use the value you are getting from props.isChecked)
didn't pay much attention to your variable names but this should put you on the right track
as per React Native Hooks you have to call
useEffect(() => {
setCheck(checked);
}, [checked]) // now this listens to changes in contact
in TodoItem.tsx
I am trying to change the style of individual TouchableOpacity components that have been returned from a map function.
Here is the component:
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<React.Fragment key={id}>
<TouchableOpacity
style={styles.button}
onPress={() => console.log(id)}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
</React.Fragment>
);
})}
</View>
);
};
Let TouchableOpacity = TO.
The map function returns about 30 TOs with unique IDs. When I click the TOs, I can see their unique ID in the console log. I want to know how I can modify the style of an individual TO.
Here is my render function which uses the functional component Example.
render() {
return (
<View style={styles.body}>
<ScrollView>
<View style={styles.column}>
<this.Example props={{ listExample: this.getList() }} />
</View>
</ScrollView>
</View>
);
}
What I have tried:
referencing this stackoverflow post, I tried to create a function which changed the style of the TO when it is clicked. But the result of this changed all the TOs in the UI since of the way it is mapped.
I tried something like the following.
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
let buttonStyle = this.state.pressed ? styles.button : styles.buttonClicked
return (
<React.Fragment key={id}>
<TouchableOpacity
style={buttonStyle}
onPress={() => console.log(id)}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
</React.Fragment>
);
})}
</View>
);
};
But as previously stated, this changed all of the Touchable Opacitys. Is there a way to only change one?
Thanks
Edit - to show entire class
class Page extends Component {
constructor(props) {
super(props)
}
MyButton = ({ onButtonPressed = () => {} }) => {
const [isPressed, setIsPressed] = useState(false);
const onPressed = () => {
setIsPressed(!isPressed);
onButtonPressed();
}
return (<TouchableOpacity style={isPressed ? styles.pressedButton: styles.button}
onPress={onPressed}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
);
}
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<MyButton key={id}/>
);
})}
</View>
);
};
render() {
return (
<View style={styles.body}>
<ScrollView>
<View style={styles.column}>
<this.Example props={{ listExample: this.getList()}} />
</View>
</ScrollView>
</View>
);
}
}
It is easier to separate the component inside map to a separate component and then handle style changes on press there
const MyButton = ({ onButtonPressed = () => {} }) => {
const [isPressed, setIsPressed] = useState(false);
const onPressed = () => {
setIsPressed(!isPressed);
onButtonPressed();
}
return (<TouchableOpacity style={isPressed ? styles.pressedButton: styles.button}
onPress={onPressed}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
)
}
so you can use in the map like this
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<MyButton key={id} />
);
})}
</View>
);
};
I have a component that looks like this:
const criteriaList = [
'Nur Frauen',
'Freunde Zweiten Grades',
];
export const FilterCriteriaList: React.FunctionComponent = () => {
const [state, setState] = useState(false);
useEffect(() => {
console.log('state is,', state);
});
const myFunction = () => {
console.log('checking state', state);
if (state == false) {
setState(true);
} else {
setState(false);
}
};
return (
<View style={styles.container}>
<View style={styles.horizontalLine} />
{criteriaList.map((item: string, index: number) => (
<View key={index}>
<View style={styles.criteriaRow}>
<TouchableOpacity
onPress={() => {
myFunction();
}}>
<Icon
name="circle-thin"
color="#31C283"
size={moderateScale(20)}
/>
</TouchableOpacity>
<Text style={styles.text}>{item}</Text>
</View>
<View style={styles.horizontalLine} />
</View>
))}
</View>
);
};
Currently, I am using the circle-thin icon. I want to change it such that everytime I click on an icon, it changes to the dot-circle-o icon. Like radio buttons. However, I am not quite sure how to do so.
I thought of using ternary operators but since I am mapping my fields Idk how to setStates collectively. Maybe using the index? I don't want to make a separate state for each field. Here's a similar snack demo:
https://snack.expo.io/toTSYc2fD
I want to be able to select multiple/unselect options. I don't want to apply the same rule on all fields together.
Note: the onPress function can also be used on the Icon directly instead of the TouchableOpacity (though it is not preferred)
Using a ternary sounds like the right approach to me. Can you not do something like:
name={state ? 'dot-circle-o' : 'circle-thin'}
You could also refactor your function:
const myFunction = () => {
console.log('checking state', state);
setState(!state)
};
If you have multiple fields then there are many ways to handle it. You could call useState multiple times, eg:
const [field1, setField1] = useState(false);
const [field2, setField2] = useState(false);
You could also store all fields in the same state:
const [state, setState] = useState({field1: false, field2: false});
...
const myFunction = (fieldName) => {
console.log('checking state', state);
setState({...state, [fieldName]: !state[fieldName]})
};
I guess you'd then use the item as the "fieldName? In which case:
return (
<View style={styles.container}>
<View style={styles.horizontalLine} />
{criteriaList.map((item: string, index: number) => (
<View key={index}>
<View style={styles.criteriaRow}>
<TouchableOpacity
onPress={() => {
myFunction(item);
}}>
<Icon
name={state[item] ? 'dot-circle-o' : 'circle-thin'}
color="#31C283"
size={moderateScale(20)}
/>
</TouchableOpacity>
<Text style={styles.text}>{item}</Text>
</View>
<View style={styles.horizontalLine} />
</View>
))}
</View>
);
And to create the initial state:
const initialState = {}
criteriaList.forEach(item => initialState[item] = false)
const [state, setState] = useState(initialState);
The code would be something like below.
You have to set the index of the selected item as the state and use it to chose the icon.
const criteriaList = [
{title:'My List',checked:false},
{title:'Friends listt',checked:false},
{title:'Not Good',checked:false},
{title:'Sweet and sour',checked:false},
{title:'Automatic',checked:false},
];
export const FilterCriteriaList: React.FunctionComponent = () => {
const [state, setState] = useState(criteriaList);
useEffect(() => {
console.log('state is,', state);
});
const myFunction = (index) => {
console.log('checking state', state);
const arr=[...state];
arr[index].checked=arr[index].checked?false:true;
setState(arr);
};
return (
<View style={styles.container}>
<View style={styles.horizontalLine} />
{criteriaList.map((item: Any,index:number) => (
<View key={item}>
<View key={item} style={styles.criteriaRow}>
<Icon
style={styles.icon}
name={item.checked?"circle":"circle-thin"}
color="#31C283"
size={moderateScale(20)}
onPress= {()=>myFunction(index)}
/>
<Text style={styles.text}>{item.title}</Text>
</View>
<View style={styles.horizontalLine} />
</View>
))}
</View>
);
};
I'm rendering a few items in my map in ContactListand upon clicking on the thumbnail, I want to navigate to a new screen UserDetailsScreen such that the data about the clicked item is also passed along.
Previously I was using modals, but now I trying to switch to react-navigation.
ContactList.tsx:
export const ContactList: React.FunctionComponent<UserProps> = ({
data,
onDeleteContact,
}) => {
const [isUserVisible, setIsUserVisible] = useState(false);
//const [visibleUser, setVisibleUser] = useState<any>();
const navigation = useNavigation();
return (
<View style={styles.users}>
{data.users.nodes[0].userRelations.map(
(item: { relatedUser: RelatedUser; id: number }) => {
const numberOfFriends = item.relatedUser.userRelations.length;
const numberPlate = 'WHV AB 123';
return (
<View style={styles.item} key={item.id}>
{/* <TouchableOpacity onPress={() => setIsUserVisible(true)}> */}
<TouchableOpacity
onPress={() =>
navigation.navigate('UserDetailsScreen', {
firstName: item.relatedUser.firstName,
rating: item.relatedUser.rating,
numberOfFriends: numberOfFriends,
onDeleteContact: onDeleteContact,
isUserVisible: isUserVisible,
setIsUserVisible: setIsUserVisible,
numberPlate: numberPlate,
navigation: navigation,
})
}>
<Thumbnail
}}></Thumbnail>
</TouchableOpacity>
<View style={styles.nameNumber}>
<Text style={styles.userName}>{userName}</Text>
</View>
{/* <UserDetails
firstName={item.relatedUser.firstName}
rating={item.relatedUser.rating}
numberOfFriends={numberOfFriends}
onDeleteContact={onDeleteContact}
isUserVisible={isUserVisible}
setIsUserVisible={setIsUserVisible}
numberPlate={numberPlate}>
</UserDetails> */}
</View>
);
},
)}
</View>
);
};
UserDetailsScreen:
export const UserDetailsScreen: React.FunctionComponent<UserProps> = ({
firstName,
rating,
numberOfFriends,
numberPlate,
onDeleteContact,
navigation,
// isUserVisible,
// setIsUserVisible,
}) => {
//const navigation = useNavigation();
const fName = navigation.getParam('firstName')
return (
// <Modal visible={isUserVisible}>
<View style={styles.container}>
<View>
<TouchableOpacity
style={styles.cross}
//onPress={() => setIsUserVisible(false)}>
onPress={() => navigation.navigate('Whitelist')}>
<Thumbnail></Thumbnail>
</TouchableOpacity>
</View>
<View style={styles.searchLocationContainer}>
<UserInfoContainer
firstName={firstName}
rating={rating}
numberPlate={numberPlate}
numberOfFriends={numberOfFriends}></UserInfoContainer>
</View>
</View>
// </Modal>
);
};
Similarly, when I click on the thumbnail on this screen, I want to go back to my previous page where I can click on another object.
However, I keep getting an error that navigation.getParam is undefined. How can I fix this?
Hi you will get the data sent in the navigation params in
props.route.params
Problem:
I am rendering a set of toggle buttons through a map. Now I want to make it true or false each when the user is changing the value of each toggle. This is how I have created the toggle component.
const AnswerToggle = (props) => {
const {styles, name} = props;
return (
<View style={styles.answerContentContainer}>
<View style={styles.answerTextContainer}>
<AppText styles={styles.answerText}>{name}</AppText>
</View>
<View style={styles.container}>
<Switch
trackColor={{false: '#dddddd', true: '#c1d6ee'}}
thumbColor={{false: '#ffffff', true: '#007aff'}}
ios_backgroundColor="#dddddd"
// ref={name}
onValueChange={
(value) => {
// ref[name].value = true;
}
// console.log(
// '>>>>>> value',
// this[`${name}`].value,
// )
}
style={styles.toggle}
/>
</View>
</View>
);
};
And I am loading it through map like this.
return answers.map((answer, i) => {
return (
<AnswerToggle
key={i}
styles={styles}
name={name}
/>
);
});
I try to do it by giving reference to the Switch component. Then It says you cannot use ref without forwardRef so then I put it to the AnswerToggle component but it still giving me the error can some help me to solve this issue?. I tried lot to find out a solution to this problem. But I was unable to do so
Define the onChange handler in the parent component and pass it in as a prop. When the switch is flipped update the state in the parent accordingly and pass the new value to AnswerToggle as a prop.
// pseudo code
const [switchValues, setSwitchValues] = useState([]);
const onChange = (index, value) => setSwitchValues( ... );
answers.map((a, i) => <AnswerToggle value={switchValues[i]} onChange={newValue => onChange(i, newValue) />
This will work just fine:
const AnswerToggle = (props) => {
const {styles, name} = props;
const [toggleStatus, setToggle] = React.useState(false)
const onChange = () => setToggle(status => !status)
return (
<View style={styles.answerContentContainer}>
<View style={styles.answerTextContainer}>
<AppText styles={styles.answerText}>{name}</AppText>
</View>
<View style={styles.container}>
<Switch
trackColor={{false: '#dddddd', true: '#c1d6ee'}}
thumbColor={{false: '#ffffff', true: '#007aff'}}
ios_backgroundColor="#dddddd"
onChange={onChange}
value={toggleStatus}
style={styles.toggle}
/>
</View>
</View>
);
};
EDIT:
If you need to set the statuses of toggles into the parent component, this is my solution for you:
const AnswerToggle = (props) => {
const {styles, name, onChange, value} = props;
return (
<View style={styles.answerContentContainer}>
<View style={styles.answerTextContainer}>
<AppText styles={styles.answerText}>{name}</AppText>
</View>
<View style={styles.container}>
<Switch
trackColor={{false: '#dddddd', true: '#c1d6ee'}}
thumbColor={{false: '#ffffff', true: '#007aff'}}
ios_backgroundColor="#dddddd"
onChange={() => onChange(name)}
value={value}
style={styles.toggle}
/>
</View>
</View>
);
};
const Parent = props => {
// ... other code
// set all toggles to false
const [toggleStatuses, setToggle] = React.useState(
answers.reduce((toggles,answer) => {
toggles[answer.name] = false
return toggles
},{})
);
const onChange = name => setToggle(state => ({
...state,
[name]: !state[name],
}));
return answers.map((answer, i) => {
return (
<AnswerToggle
value={toggleStatuses[answer.name]}
onChange={onChange}
key={i}
styles={styles}
name={answer.name}
/>
);
});
}