I'm new to react native and i am struggling with the idea of sharing state - i started by creating a contacts app with 2 components - ContactList (class component) , ContactDetail (functional component).
Everything went well until i wanted to share something from the child (functional ) to the parent (class) - the opposite works
I am trying to Splice a contact from the array however, when passing the contact on the function "RemoveContact" on "ContactDetail" i am getting undefined on console.log.
this will not crash my app and the splicing works but it will splice from the end of the array and not the specific index of object i wanted.
can you please guide me with what i have done wrong?
Here is my ContactDetail class component
import React, { Component } from 'react';
import { ScrollView, View } from 'react-native';
import ContactDetail from './ContactDetail.js';
import { Header, Item, Input, Container, Button, Icon, Text } from 'native-base';
const contacts = [
{ firstname: 'a', lastname: 'b', dogname: 'c', image: 'https://s3.amazonaws.com/uifaces/faces/twitter/brynn/128.jpg', id: 1 },
{ firstname: 'd', lastname: 'e', dogname: 'f', image: 'https://s3.amazonaws.com/uifaces/faces/twitter/brynn/128.jpg', id: 2 },
{ firstname: 'g', lastname: 'h', dogname: 'j', image: 'https://s3.amazonaws.com/uifaces/faces/twitter/brynn/128.jpg', id: 3 },
{ firstname: 'k', lastname: 'l', dogname: 'm', image: 'https://s3.amazonaws.com/uifaces/faces/twitter/brynn/128.jpg', id: 4 },
{ firstname: 'n', lastname: 'o', dogname: 'p', image: 'https://s3.amazonaws.com/uifaces/faces/twitter/brynn/128.jpg', id: 5 }
];
export default class ContactList extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
contactsNew: [],
};
}
render() {
const inputValueLet = this.state.inputValue.trim().toLowerCase();
const removeContact = ({ contact }) => {
let index = this.state.contactsNew.indexOf(contact);
console.log(contact);
let nextContacts = this.state.contactsNew;
nextContacts.splice(index, 1);
this.setState({
contactsNew: nextContacts,
});
};
if (contacts.length > 0) {
this.state.contactsNew.filter(contact => contact.dogname.toLowerCase().match(inputValueLet));
const dataRow = this.state.contactsNew.map((contact, index) => (
<ContactDetail key={contact.firstname} contact={contact} removeContact={removeContact} />
));
return (
<View>
<Header searchBar rounded style={{ marginBottom: 10 }} >
<Item>
<Icon name="ios-search" />
<Input
placeholder="find friends"
value={this.state.inputValue}
onChangeText={inputValue => this.setState({ inputValue })}
/>
<Icon name="ios-people" />
</Item>
<Button transparent>
<Text>find friends</Text>
</Button>
</Header>
<ScrollView style={{ height: 400 }} >
{dataRow}
</ScrollView>
</View>
);
}
}
the 2nd Component - ContactDetail - functional component
import React from 'react';
import { View, Image, ListView, TouchableOpacity } from 'react-native';
import { Card, ListItem, Divider } from 'react-native-elements';
import { Button, Icon, Title, Text, Item, Input, Right, SwipeRow } from 'native-base';
const ContactDetail = ({ contact, removeContact }) => {
const { firstname,
dogname,
image } = contact;
return (
<Card style={{ marginTop: 3, marginBottom: 15 }} >
<View style={styles.headerContentStyle}>
<SwipeRow
leftOpenValue={55}
rightOpenValue={-55}
left={
<Button primary onPress={() => { removeContact(contact); }}>
<Icon active name="person" />
</Button>
}
body={
<TouchableOpacity>
<View style={styles.viewContainer}>
<Image
style={styles.thumbnailStyle}
source={{ uri: image }}
/>
<Text style={{ textAlign: 'right' }} >{dogname}</Text>
</View>
</TouchableOpacity>
}
right={
<Button danger onPress={() => {}}>
<Icon active name="trash" />
</Button>
}
/>
<Divider style={{ backgroundColor: 'blue', borderBottomWidth: 0, marginTop: 3 }} />
</View>
</Card>
);
};
export default ContactDetail;
You have a syntax error I believe. You are sending a single object but then trying deconstruct it rather than using it directly.
This,
const removeContact = ({ contact }) => { ... }
should be this
const removeContact = (contact) => { ... }
or this
onPress={() => { removeContact(contact); }}
should be this
onPress={() => { removeContact({contact}); }}
Related
I am following a tutorial but as I code along I am stuck,
Why isn't my FlatList not showing?
App.js is simply returning MessagesScreen "screen".
This is my code for a MessagesScreen:
import React from "react";
import { FlatList } from "react-native";
import ListItem from "../components/ListItem";
const messages = [
{
id: 1,
title: "T1",
description: "D1",
image: require("../assets/mosh.jpg"),
},
{
id: 2,
title: "T2",
description: "D2",
image: require("../assets/mosh.jpg"),
},
{
id: 3,
title: "T3",
description: "D3",
image: require("../assets/mosh.jpg"),
},
{
id: 4,
title: "T4",
description: "D4",
image: require("../assets/mosh.jpg"),
},
];
const MessagesScreen = () => {
return (
<FlatList>
data={messages}
keyExtractor={(message) => message.id.toString()}
renderItem=
{({ item }) => {
return (
<ListItem
title={item.title}
subTitle={item.subTitle}
image={item.image}
/>
);
}}
</FlatList>
);
};
export default MessagesScreen;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This is my component for the ListItem:
import React from "react";
import { View, StyleSheet, Image } from "react-native";
import AppText from "./AppText";
import colors from "../config/colors";
function ListItem({ title, subTitle, image }) {
return (
<View style={styles.container}>
<Image style={styles.image} source={image} />
<View>
<AppText style={styles.title}>{title}</AppText>
<AppText style={styles.subTitle}>{subTitle}</AppText>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: "row",
},
image: {
width: 70,
height: 70,
borderRadius: 35,
marginRight: 10,
},
subTitle: {
color: colors.medium,
},
title: {
fontWeight: "500",
},
});
export default ListItem;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
What I've tried is to use the return inside the "renderItem", but with no work, also done it without. The code doesn't give me any errors, so I can't see where the problem is.
You're accessing a property that doesn't exist on your items: subTitle. Did you mean to use description?
Also, I don't believe you can pass in the result of a require call like that. Try passing the raw url and placing the require in the image={} prop instead
Oh it's just a quick fix.
FlatList should be one tag:
const MessagesScreen = () => {
return (
<FlatList // <--- edit here
data={messages}
keyExtractor={(message) => message.id.toString()}
renderItem=
{({ item }) => {
return (
<ListItem
title={item.title}
subTitle={item.subTitle}
image={item.image}
/>
);
}}
/> // <--- and here
);
};
Here's a codesandbox to demonstrate
im writing a todolist with reactnative using hooks , however when rendering todo items , its not showing up, any advice to fix this
thank you so much for your help!!!
import React, { useState } from 'react';
import { StyleSheet, FlatList, Text, View } from 'react-native';
export default function App() {
const [todos, setTodos] = useState([
{ text: 'budddy', key: '1' },
{ text: 'helloddd', key: '2' },
{ text: "hellddo", key: '3' }
])
return (
<View style={styles.container} >
<View style={styles.content}>
<FlatList data={todos} renderItem={({ item }) => {
<Text> {item.text}</Text>
}} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
paddingTop: 30,
},
content: {
padding: 40
},
})
Check the renderItem code you need to add return or use the implicit return of arrow function
<FlatList data={todos} renderItem={({ item }) => (
<Text> {item.text}</Text>
)} />
Use the extraData property on your FlatList component.
As per documentation:
Bypassing extraData={this.state} to FlatList we make sure FlatList will re-render itself when the state.selected changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.
here is the example code (documentation)
import React from 'react';
import {
SafeAreaView,
TouchableOpacity,
FlatList,
StyleSheet,
Text,
} from 'react-native';
import Constants from 'expo-constants';
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
},
];
function Item({ id, title, selected, onSelect }) {
return (
<TouchableOpacity
onPress={() => onSelect(id)}
style={[
styles.item,
{ backgroundColor: selected ? '#6e3b6e' : '#f9c2ff' },
]}
>
<Text style={styles.title}>{title}</Text>
</TouchableOpacity>
);
}
export default function App() {
const [selected, setSelected] = React.useState(new Map());
const onSelect = React.useCallback(
id => {
const newSelected = new Map(selected);
newSelected.set(id, !selected.get(id));
setSelected(newSelected);
},
[selected],
);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={DATA}
renderItem={({ item }) => (
<Item
id={item.id}
title={item.title}
selected={!!selected.get(item.id)}
onSelect={onSelect}
/>
)}
keyExtractor={item => item.id}
extraData={selected}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: Constants.statusBarHeight,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});
I have an inbox component which fetches all the notifications from the server and lists in a view. The code for which looks like,
import React, { Component } from 'react'
import {
View,
FlatList,
ActivityIndicator,
TouchableOpacity
} from 'react-native'
import {List, ListItem, SearchBar} from 'react-native-elements'
import Header from '../common/Header'
import { Container } from 'native-base'
import PushNotifications from '../../fcm/notifications/PushNotifications'
import NotificationDetails from './NotificationDetails';
export const Navigator = new StackNavigator({
NotificationList: { screen: NotificationList },
NotificationDetail: { screen: NotificationDetail },
},{
initialRouteName: 'NotificationList',
})
class NotificationList extends Component {
constructor(props) {
super(props)
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false
}
this.loadNotificationDetails = this.loadNotificationDetails.bind(this)
}
componentDidMount() {
const{dispatch,actions} = this.props
dispatch(actions.getNotification())
}
handleRefresh = () => {
this.setState(
{
page: 1,
seed: this.state.seed + 1,
refreshing: true
},
() => {
const{dispatch,actions} = this.props
dispatch(actions.getNotification())
}
)
}
handleLoadMore = () => {
this.setState(
{
page: this.state.page + 1
},
() => {
const{dispatch,actions} = this.props
dispatch(actions.getNotification())
}
);
}
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "86%",
backgroundColor: "#CED0CE",
marginLeft: "14%"
}}
/>
);
};
renderHeader = () => {
return <SearchBar placeholder="Type Here..." lightTheme round />
}
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<ActivityIndicator animating size="large" />
</View>
)
}
loadNotificationDetails = () => {
this.props.navigation.navigate('NotificationDetails')
}
render() {
return (
<Container >
<Header />
<List containerStyle={{ marginTop: 0, borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.props.listNotification}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => this.loadNotificationDetails()}>
<ListItem
roundAvatar
title={`${item.text}`}
subtitle={item.dateTime}
// avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
</TouchableOpacity>
)}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
onRefresh={this.handleRefresh}
refreshing={this.state.refreshing}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={50}
/>
</List>
<PushNotifications />
</Container>
)
}
}
export default NotificationList;
Now what i want to achieve is on clicking any of the listItem, i want to load the complete detailed notification.
Whats happening is when i click it seems to be missing the navigation object. Hence its complaining cannot find property of navigate.
The props is having only the items from the redux store, i am not able to understand how do i get the navigation props into this component which is already having props from the redux store?
How do i achieve this? Any help is much appreciated.
Thanks,
Vikram
StackNavigator is a factory function instead of a constructor. Have you try
const Navigator = StackNavigator({
NotificationList: { screen: NotificationList },
NotificationDetail: { screen: NotificationDetail },
},{
initialRouteName: 'NotificationList',
})
It is a bit confusing, however in v2 the team change the api to createStackNavigator.
I create a component Confirm.js with react-native <Modal /> that has two DropDownMenu and two <Button />.
I want user click the Yes <Button /> than i can get the DropDownMenu onOptionSelected value. I think i should use this.props but i have no idea how to do it.
I want get the value in MainActivity.js from Confirm.js
Any suggestion would be appreciated. Thanks in advance.
Here is my MainActivty.js click the <Button /> show <Confirm />:
import React, { Component } from 'react';
import { Confirm } from './common';
import { Button } from 'react-native-elements';
class MainActivity extends Component {
constructor(props) {
super(props);
this.state = { movies: [], content: 0, showModal: false, isReady: false };
}
// close the Confirm
onDecline() {
this.setState({ showModal: false });
}
render() {
return (
<View style={{ flex: 1 }}>
<Button
onPress={() => this.setState({ showModal: !this.state.showModal })}
backgroundColor={'#81A3A7'}
containerViewStyle={{ width: '100%', marginLeft: 0 }}
icon={{ name: 'search', type: 'font-awesome' }}
title='Open the confirm'
/>
<Confirm
visible={this.state.showModal}
onDecline={this.onDecline.bind(this)}
>
</Confirm>
</View>
);
}
}
export default MainActivity;
Confirm.js:
import React from 'react';
import { Text, View, Modal } from 'react-native';
import { DropDownMenu } from '#shoutem/ui';
import TestConfirm from './TestConfirm';
import { CardSection } from './CardSection';
import { Button } from './Button';
import { ConfirmButton } from './ConfirmButton';
const Confirm = ({ children, visible, onAccept, onDecline }) => {
const { containerStyle, textStyle, cardSectionStyle } = styles;
return (
<Modal
visible={visible}
transparent
animationType="slide"
onRequestClose={() => {}}
>
<View style={containerStyle}>
<CardSection style={cardSectionStyle}>
{/* Here is my DropDownMenu */}
<TestConfirm />
</CardSection>
<CardSection>
<ConfirmButton onPress={onAccept}>Yes</ConfirmButton>
<ConfirmButton onPress={onDecline}>No</ConfirmButton>
</CardSection>
</View>
</Modal>
);
};
const styles = {
cardSectionStyle: {
justifyContent: 'center'
},
textStyle: {
flex: 1,
fontSize: 18,
textAlign: 'center',
lineHeight: 40
},
containerStyle: {
backgroundColor: 'rgba(0, 0, 0, 0.75)',
position: 'relative',
flex: 1,
justifyContent: 'center'
},
buttonStyle: {
flex: 1,
alignSelf: 'stretch',
backgroundColor: '#fff',
borderRadius: 5,
borderWidth: 1,
borderColor: '#007aff',
marginLeft: 5,
marginRight: 5
}
};
export { Confirm };
TestConfirm.js
import React, { Component } from 'react';
import { View } from 'react-native';
import { DropDownMenu, Title, Image, Text, Screen, NavigationBar } from '#shoutem/ui';
import {
northCities,
centralCities,
southCities,
eastCities,
islandCities
} from './CityArray';
class TestConfirm extends Component {
constructor(props) {
super(props);
this.state = {
zone: [
{
id: 0,
brand: "North",
children: northCities
},
{
id: 1,
brand: "Central",
children: centralCities
},
{
id: 2,
brand: "South",
children: southCities
},
{
id: 3,
brand: "East",
children: eastCities
},
{
id: 4,
brand: "Island",
children: islandCities
},
],
}
}
render() {
const { zone, selectedZone, selectedCity } = this.state
return (
<Screen>
<DropDownMenu
style={{
selectedOption: {
marginBottom: -5
}
}}
styleName="horizontal"
options={zone}
selectedOption={selectedZone || zone[0]}
onOptionSelected={(zone) =>
this.setState({ selectedZone: zone, selectedCity: zone.children[0] })}
titleProperty="brand"
valueProperty="cars.model"
/>
<DropDownMenu
style={{
selectedOption: {
marginBottom: -5
}
}}
styleName="horizontal"
options={selectedZone ? selectedZone.children : zone[0].children} // check if zone selected or set the defaul zone children
selectedOption={selectedCity || zone[0].children[0]} // set the selected city or default zone city children
onOptionSelected={(city) => this.setState({ selectedCity: city })} // set the city on change
titleProperty="cnCity"
valueProperty="cars.model"
/>
</Screen>
);
}
}
export default TestConfirm;
If i console.log DropDownMenu onOptionSelected value like the city it would be
{cnCity: "宜蘭", enCity: "Ilan", id: 6}
I want to get the enCity from MainActivity.js
ConfirmButton.js:
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
const ConfirmButton = ({ onPress, children }) => {
const { buttonStyle, textStyle } = styles;
return (
<TouchableOpacity onPress={onPress} style={buttonStyle}>
<Text style={textStyle}>
{children}
</Text>
</TouchableOpacity>
);
};
const styles = {
{ ... }
};
export { ConfirmButton };
You can pass a function to related component via props and run that function with the required argument you got from the dropdowns. Because you have couple of components in a tree it is going to be a little hard to follow but if you get the idea I'm sure you'll make it simpler. Also after getting comfortable with this sort of behavior and react style coding you can upgrade your project to some sort of global state management like redux, flux or mobx.
Sample (removed unrelated parts)
class MainActivity extends Component {
onChangeValues = (values) => {
// do some stuf with the values
// value going to have 2 properties
// { type: string, value: object }
const { type, value } = values;
if(type === 'zone') {
// do something with the zone object
} else if(type === 'city') {
// do something with the city object
}
}
render() {
return(
<Confirm onChangeValues={this.onChangeValues} />
)
}
}
const Confirm = ({ children, visible, onAccept, onDecline, onChangeValues }) => {
return(
<TestConfirm onChangeValues={onChangeValues} />
)
}
class TestConfirm extends Component {
render() {
return(
<Screen>
<DropDownMenu
onOptionSelected={(zone) => {
// run passed prop with the value
this.props.onChangeValues({type: 'zone', value: zone});
this.setState({ selectedZone: zone, selectedCity: zone.children[0] });
}}
/>
<DropDownMenu
onOptionSelected={(city) => {
// run passed prop with the value
this.props.onChangeValues({type: 'city', value: city});
this.setState({ selectedCity: city })
}}
/>
</Screen>
)
}
}
How can I close SwipeRow inside FlatList?
This is the easy thing to do with ListView, but since the next release is going to use FlatList it should be possible.
From reading their source code, it seems like it is not implemented yet.
can you try this
import React, { Component } from 'react';
import { FlatList } from 'react-native';
import { Container, Header, Content, SwipeRow, View, Text, Icon, Button } from 'native-base';
export default class App extends Component {
constructor(props) {
super(props)
this.state = { data: [{ key: 'a' }, { key: 'b' }] }
this.component = [];
this.selectedRow;
}
render() {
return (
<Container>
<Header />
<Content>
<FlatList
data={[{ key: 'a', value: 'Row one' }, { key: 'b', value: 'Row two' }, { key: 'c', value: 'Row three' }, { key: 'd', value: 'Row four' }, { key: 'e', value: 'Row five' }]}
keyExtractor={(item) => item.key}
renderItem={({ item }) => <SwipeRow
ref={(c) => { this.component[item.key] = c }}
leftOpenValue={75}
rightOpenValue={-75}
onRowOpen={() => {
if (this.selectedRow && this.selectedRow !== this.component[item.key]) { this.selectedRow._root.closeRow(); }
this.selectedRow = this.component[item.key]
}}
left={
<Button success onPress={() => alert('Add')}>
<Icon active name="add" />
</Button>
}
body={
<View style={{ paddingLeft: 20 }}>
<Text>{item.value}</Text>
</View>
}
right={
<Button danger onPress={() => alert('Delete')}>
<Icon active name="trash" />
</Button>
}
/>}
/>
<Button style={{ margin: 20 }} onPress={() => this.selectedRow._root.closeRow()}><Text>Close row</Text></Button>
</Content>
</Container>
);
}
}