Im using a flat list on 2 different screens.
On the EventListScreen:
this is the main screen and should display all events.
and on the 2nd page UserProfile.js this page should only display that users events.
in both flat lists I'm using a pure component stored in a seperate class, to where the flat lists are i.e
My Question is, I want to display an "Edit" button on the Event.js child component only if the User is on the
UserProfileScreen.js
I have looked up a lot of example but cant really find any that show how to do it
with a child pure component like I'm doing.
Any Help would be greatly appreciated! Thank you
EventListScreen.js
<FlatList
data={this.state.events}
// Get the item data by referencing as a new function to it
renderItem={({item}) =>
<Event
openEventDetail={() => this.openEventDetail(item)}
{...item}
/>}
/>
UserProfileScreen.js
<FlatList
data={this.state.events}
// Get the item data by referencing as a new function to it
renderItem={({item}) =>
<Event
openEventDetail={() => this.openEventDetail(item)}
openEditEvent={() => this.openEditEvent(item)}
{...item}
/>}
/>
Event.js
export default class Event extends Component {
render() {
return (
<Card>
<CardSection>
<Text>{this.props.eventName}</Text>
//I want this button to be displayed only if user is viewing
//from the UserProfile.js
<Button onPress={() =>this.props.openEditEvent()}>
{this.props.displayButton}
</Button>
</CardSection>
<TouchableOpacity
onPress={() => this.props.openEventDetail()}
>
}
You don't need additional properties.
We can assume that the "Edit" button should be available when openEditEvent prop is defined.
Condition in event (using convertion to bool, false for undefined):
<CardSection>
<Text>{this.props.eventName}</Text>
{!!this.props.openEditEvent &&
<Button onPress={() =>this.props.openEditEvent()}>
{this.props.displayButton}
</Button>
}
</CardSection>
Use propTypes to define openEditEvent prop as a function, optional (not required).
If I understand your problem correctly an option to solve this problem would be to pass a boolean "showable prop" to show the edit button only when required:
EventListScreen.js (Stays the same, we don't show the edit button here)
<FlatList
data={this.state.events}
// Get the item data by referencing as a new function to it
renderItem={({item}) =>
<Event
openEventDetail={() => this.openEventDetail(item)}
{...item}
/>}
/>
UserProfileScreen.js (we add the shouldShowEditButton prop to event in order to show the button)
<FlatList
data={this.state.events}
// Get the item data by referencing as a new function to it
renderItem={({item}) =>
<Event
openEventDetail={() => this.openEventDetail(item)}
openEditEvent={() => this.openEditEvent(item)}
shouldShowEditButton
{...item}
/>}
/>
Event.js (We add some propTypes and defaultProps to handle the new prop, it won't show the edit button if not specified)
export default class Event extends Component {
render() {
return (
<Card>
<CardSection>
<Text>{this.props.eventName}</Text>
//I want this button to be displayed only if user is viewing
//from the UserProfile.js
{this.props.shouldShowEditButton && <Button onPress={() =>this.props.openEditEvent()}>
{this.props.displayButton}
</Button>}
</CardSection>
<TouchableOpacity
onPress={() => this.props.openEventDetail()}
>
...
...
);
...
}
}
// We add some default propTypes and definitions
Event.propTypes = {
shouldShowEditButton: PropTypes.bool
};
Event.defaultProps = {
shouldShowEditButton: false
};
In this way you're only showing the edit button for the components that have the prop shouldShowEditButton defined, and because its default value is defined as false, the components that don't have the property will behave in the same way they were before.
Related
Right now I have a flatlist that contains a bunch of firebase objects (books), when one of the book are clicked, I want to return a page with more data about that specific book. Right now each book it an object, where title is one of the values, this is what is shown on the flatlist. I want to be able to show all of the other object attributes when the new detailed page is opened. If there is a better way of doing this, let me know but this is the logic that I was trying to go with.
(this is in the flatlist)
renderItem={({ item }) => (
<TouchableOpacity onPress={bookOnPressHandler} activeOpacity={0.9} style={styles.flatListStyle}>
<View>
<Text>{item.title}</Text>
</View>
</TouchableOpacity>
)}
/>
then the handler:
const bookOnPressHandler = (item) => {
//this holds the title of the book
title = item.title
console.log(title)
navigation.navigate('booknotes')
}
I obviously need to pass item into the function, what it is right now wont work. How would I get access to "Item" in the function? once I set the item = to something, I can use it on the new page.
I feel like there is a better method than this and that this might even not work? I know this is a common thing to do in apps, all help is really appreciated. Also sorry if its obvious, Im pretty new to this language and framework!
Since you already have access to the required object that is to be passed to the next screen, it's very easy, you can pass it along with the navigation object as a route param.
It's always good to isolate the prop functions to separate functions to avoid unnecessary re-render of the component.
Here is an example.
<FlatList
ref={ref}
contentContainerStyle={styles.contentContainer}
scrollEventThrottle={16}
numColumns={2}
data={exploreData}
initialNumToRender={2}
renderItem={renderItem}
keyExtractor={(item: any) => item.id.toString()}
})}
/>
Here is the renderItem function:
const renderItem = ({ item }) => {
const handleOnPress = () => navigation.navigate("Profile", { item });
return (
<TouchableWithoutFeedback
onPress={handleOnPress}
>
<ImageBackground
source={{ uri: img }}
style={styles.image}
imageStyle={styles.background}
/>
</TouchableWithoutFeedback>
);
};
//Profile Screen
const Profile = ({ navigation, route }) => {
const { item } = route.params;
console.log(item);
};
I am new to react-native. Here I am trying to add two buttons on headerRight. I did add one button but I could not figure out how to put more than one. Something like this.
I am using react-navigaiton and react-navigation-header-buttons.
This is how I added one button.
mainScreen
headerRight: (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title={"Search"}
iconName={"md-search"}
onPress={() => {
console.log('Search')
}}
/>
</HeaderButtons>
),
CustomHeaderButton.js
import {HeaderButton, Item} from 'react-navigation-header-buttons';
import {Ionicons} from '#expo/vector-icons';
const CustomHeaderButton = props => {
return(
<HeaderButton
{...props}
IconComponent={Ionicons}
iconSize={23}
color={'black'}
/>
)
};
export default CustomHeaderButton;
You're on the right track. You should be able to simply add another Item with whatever title, icon, onPress functionality you want wrapped in the HeaderButtons component like this:
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title={"Search"}
iconName={"md-search"}
onPress={() => {
console.log('Search')
}}
/>
<Item
title={"Other Button"}
iconName={"other-icon-name"}
onPress={() => {
console.log('The other header icon was pressed.')
}}
/>
</HeaderButtons>
You are able to nest multiple React elements within a React element, which is what this example uses. For instance, you can nest multiple Text elements inside of a View.
It looks like you are using the react-navigation-header-buttons package, here is their example with multiple header icons for your reference as well: https://github.com/vonovak/react-navigation-header-buttons/blob/master/example/screens/UsageCustom.tsx
I am wondering which is the right way to assign onPress function for the FlatList item in React-Native. My mentor has explained for me that maybe I missed the knowledge of "delegate/closure/block" definition in OOP, I had read it but until now I still cannot figure it out by myself. This is the detail:
I have a screen named Menu – this menu has a <FlatList> which contains multiple <MenuItem>, each <MenuItem> has a toggle button to "add item to cart" or "remove item for cart" (click to add – click again to remove). I seperate <MenuItem> to another file so my current folder-tree looks like this:
__Menu
| |_MenuItem
| |__index.js
|
|__index.js
And this is my mentor's approach:
He defined a function named _onToggleCart in Menu/index.js like this:
_onToggleCart = (selected) => {
if(selected == false){
this.props.addItemToCart()
}else{
this.props.removeItemFromCart()
}
}
<FlatList
data={data}
initialNumToRender={6}
extraData={this.state.data}
keyExtractor={(item) => item.id}
renderItem={({ item, index }) => <MenuItem item={item} isFinalItem={index == data.length - 1} navigation={navigation} onPress={this._onToggleCart} />}
/>
Menu/MenuItem/index.js looks like this:
...
const [selected, setSelected] = useState(false)
const { id, imgURL, name, desciption, total } = item
_onChangeCart = () => {
setSelected(!selected)
onPress(selected)
}
...
<TouchableOpacity>
...
<TouchableWithoutFeedback onPress={this._onChangeCart}>
...
</TouchableWithoutFeedback>
...
</TouchableOpacity>
And this is my approach:
In Menu/index.js I do not assign any function to <MenuItem>, so I don't have a function _onToggleCart and also don't have onPress props. It looks like this:
<FlatList
data={data}
initialNumToRender={6}
extraData={this.state.data}
keyExtractor={(item) => item.id}
renderItem={({ item, index }) => <MenuItem item={item} isFinalItem={index == data.length - 1} navigation={navigation} />}
/>
In Menu/MenuItem/index.js to check the condition in _onChageCart like this:
...
const [selected, setSelected] = useState(false)
const { id, imgURL, name, desciption, total } = item
_onChangeCart = () => {
if(selected == false){
this.props.addItemToCart()
}else{
this.props.removeItemFromCart()
}
}
...
<TouchableOpacity>
...
<TouchableWithoutFeedback onPress={this._onChangeCart}>
...
</TouchableWithoutFeedback>
...
</TouchableOpacity>
Can anyone help me what is different between them and which is better?
The main difference from you component and the one of your mentor is that he passes a onPress prop to MenuItem. What difference does this makes?
Well, in your example, if you need to add more action on the MenuItem press, how do you do? You can't do it, but the way your mentor did, you can add more functionality (call one more function) to the MenuItem because he call props.onPress.
You mentor did a more flexible component, wich maybe can grow easier, your component is more fixed.
Wich one is better? Now that is up to you and the structure of your project.
I created a basic component such as:
export default (props) => (
<TouchableOpacity {...props} style={styles.button}>
{props.title && <Text style={styles.text}>{props.title}</Text>}
{props.icon && <Icon name={props.icon} />}
</TouchableOpacity>
);
I can then call it with <Component title="Home" icon="home" /> for instance.
The problem is that passing {...props} to TouchableOpacity generate errors because it does not recognize title nor icon properly.
For instance:
JSON value 'Home' of type NSString cannot be converted to...
Is there a way to filter props so that I only pass valid ones for TouchableOpacity?
Transferring Props
Sometimes it's fragile and tedious to pass every property along. In that case you can use destructuring assignment with rest properties to extract a set of unknown properties.
List out all the properties that you would like to consume, followed by ...other.
var { checked, ...other } = props;
This ensures that you pass down all the props EXCEPT the ones you're
consuming yourself.
function FancyCheckbox(props) {
var { checked, ...other } = props;
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
// `other` contains { onClick: console.log } but not the checked property
return (
<div {...other} className={fancyClass} />
);
}
ReactDOM.render(
<FancyCheckbox checked={true} onClick={console.log.bind(console)}>
Hello world!
</FancyCheckbox>,
document.getElementById('example')
);
Like Paul Mcloughlin, I would recommend using object destructuring along with a rest parameter. You can destructure your props object directly in your function parameters like so:
({title, icon, ...remainingProps}) => (...)
This extracts the title and icon props from your props object and passes the rest as remainingProps.
Your complete component would be:
export default ({title, icon, ...remainingProps}) => (
<TouchableOpacity {...remainingProps} style={styles.button}>
{title && <Text style={styles.text}>{title}</Text>}
{icon && <Icon name={icon} />}
</TouchableOpacity>
);
i just start to learn react native i want to make ui like this,
i want the buttons on the image work like radio button, so user just can choose a button and change the button style choosed by user, and bellow is my code
class ButtonOption extends Component {
constructor(props) {
super(props);
this.state = {
data : [],
active : 0
};
}
loadButton(temp){
let result = [];
for (let i =0;i<temp.length;i++){
if(i==this.state.active){
result.push(
<View key={i} style={buttonOption.main}>
<TouchableOpacity style={buttonOption.tabActive} onPress={()=>{this.setState({active:i});alert(this.state.active)}}>
<Text style={buttonOption.labelActive}>{temp[i]}</Text>
</TouchableOpacity>
</View>)
}else{
result.push(
<View key={i} style={buttonOption.main}>
<TouchableOpacity style={buttonOption.tab} onPress={()=>{this.setState({active:i});alert(this.state.active)}}>
<Text style={buttonOption.label}>{temp[i]}</Text>
</TouchableOpacity>
</View>)
}
}
return result;
}
componentDidMount(){
this.setState({data:this.loadButton(this.props.arrButton)})
}
render() {
return (
<View style={{flexDirection:'row',padding:10}}>
{this.state.data}
</View>
);
}
}
export default ButtonOption;
on code above i was try to make button with looping, in looping function i use active as a state for check active button, and add function onPress so everytime user click button, active state will change, but when i run and choose button the active state won't change, what is wronge with my onPress code, can someone help please?
I think I might know the answer, You have generated the view using this.loadButton and saved it in this.state.data. This is done one time only. when the render method is called. It is expecting a new view based on states but this.state.data is not changing since you create it in componentDidMount. What you need to do is to call this.loadButton(this.props.arrButton) in render method. this will create new view every time render method is called.
render() {
return (
<View style={{flexDirection:'row',padding:10}}>
{this.loadButton(this.props.arrButton)}
</View>
);
}
This might do it, If you still have any error, please report.