I am studying React Naive by making a ToDo list.
I want to change the ToDo app to a Comment List that has comment one to another.
First attempt worked correctly:
First attempt
But, all users changed after second attempt.
Second attempt
Now, parent passes props to child by using {this.state.pick} and {this.state.key} , but child would change if parent's props changed.
Is there any way to change the parent props without changing child props?
Comment.js:
export default class CommentIndex extends Component<{}> {
constructor(props) {
super(props);
this.state = {
head: [],
list: [],
pick: [],
};
}
_onPress = (text) => {
const list = [].concat(this.state.list);
list.push({
key: Date.now(),
text: text,
done: false,
});
this.setState({
list,
});
}
render() {
const {
head,
list,
pick,
} = this.state;
var data = [["User1", "User2", "User3"],];
return (
<View style={styles.container}>
<View style={styles.dropdownHeader}>
<View style={styles.dropdown}>
<DropdownMenu
style={{flex: 1}}
bgColor={'white'}
tintColor={'#666666'}
activityTintColor={'green'}
handler={(selection, row) => this.setState({head: data[selection][row]})}
data={data}
>
<View style={styles.userHeader}>
{ this.state.head === 'User1' && <User1 /> }
{ this.state.head === 'User2' && <User2 /> }
{ this.state.head === 'User3' && <User3 /> }
</View>
</DropdownMenu>
</View>
</View>
<Text>To Do</Text>
<View style={styles.main}>
<View style={styles.post}>
<View style={styles.dropdown}>
<View style={{height: 0}} />
<DropdownMenu
bgColor={'white'}
tintColor={'#666666'}
activityTintColor={'green'}
handler={(selection,row) => this.setState({pick: data[selection][row]})}
data={data}
>
<View style={styles.user}>
{ this.state.pick === 'User1' && <User1_photo /> }
{ this.state.pick === 'User2' && <User2_photo /> }
{ this.state.pick === 'User3' && <User3_photo /> }
</View>
</DropdownMenu>
</View>
<View style={styles.postinput}>
<CommentInput onPress={this._onPress} />
</View>
</View>
<View style={styles.CommentListContainer}>
<FlatList
style={styles.CommentList}
data={list}
renderItem={({ item }) => <CommentItem {...item} head={this.state.head} pick={this.state.pick}/>}
/>
</View>
</View>
</View>
);
}
}
CommentInput:
export default class CommentInput extends Component {
constructor(props) {
super(props);
this.ref = {};
}
_onPress = () => {
this.props.onPress(this.ref._lastNativeText);
this.ref.setNativeProps({ text: '' });
}
render() {
const {
onPress,
} = this.props;
return (
<View style={styles.container}>
<TextInput
style={styles.textInput}
ref={(ref) => { this.ref = ref; }}
/>
<TouchableOpacity
style={styles.button}
onPress={this._onPress}
>
<Text style={styles.buttonText}>追加</Text>
</TouchableOpacity>
</View>
);
}
}
You could implement the shouldComponentUpdate method on the child. This will block a re-render (with the new properties) when you want.
Related
How to limit the quantity of View inside of a scrollview.
My component take too much time to render, because the map function renders too many views. I need to show only 10 views, and when scroll up, renders more 10.
I'm using react native, hooks and typescript.
First of all, if you have a large number of list data don't use scrollview. Because initially, it loads all the data to scrollview component & it costs performance as well.
Use flatlist in react-native instead of scrollview & you can limit the number of items to render in the initially using initialNumToRender. When you reach the end of the scroll position you can call onEndReached method to load more data.
A sample will like this
import React, { Component } from "react";
import { View, Text, FlatList, ActivityIndicator } from "react-native";
import { List, ListItem, SearchBar } from "react-native-elements";
class FlatListDemo extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const { page, seed } = this.state;
const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
this.setState({ loading: true });
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: page === 1 ? res.results : [...this.state.data, ...res.results],
error: res.error || null,
loading: false,
refreshing: false
});
})
.catch(error => {
this.setState({ error, loading: false });
});
};
handleRefresh = () => {
this.setState(
{
page: 1,
seed: this.state.seed + 1,
refreshing: true
},
() => {
this.makeRemoteRequest();
}
);
};
handleLoadMore = () => {
this.setState(
{
page: this.state.page + 1
},
() => {
this.makeRemoteRequest();
}
);
};
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>
);
};
render() {
return (
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={`${item.name.first} ${item.name.last}`}
subtitle={item.email}
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
keyExtractor={item => item.email}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
onRefresh={this.handleRefresh}
refreshing={this.state.refreshing}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={50}
/>
</List>
);
}
}
export default FlatListDemo;
Check this for more informations.
I changed to Flatlist! But initialNumToRender is not working as expected.
The flatlist is rendering all 150 transactions, not only 15, and i have no idea what to do.
I'm running .map() from another array with all others transactions to create newMonths with only those transactions that i want to use data={newMonths}.
let newMonths = [];
const createArrayMonth = histInfos.map(function (info) {
if (info.created_at.slice(5, 7) === month) {
newMonths = [info].concat(newMonths);
}
});
them, i created my component:
function Item({ value }: { value: any }) {
let createdDay = value.item.created_at.slice(8, 10);
let createdMonth = value.item.created_at.slice(5, 7);
let createdYear = value.item.created_at.slice(2, 4);
function dateSelected() {
if (
month === createdMonth &&
year === createdYear &&
(day === '00' || day == createdDay)
) {
console.log('foi dateSelected');
const [on, setOn] = useState(false);
const Details = (on: any) => {
if (on === true) {
return (
<View style={styles.infos}>
<Text style={styles.TextInfos}>
{' '}
CPF/CNPJ: {value.item.cpf_cnpj}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Criado em: {value.item.created_at}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Endereço da carteira: {value.item.address}{' '}
</Text>
<Text style={styles.TextInfos}> Valor: {value.item.amount}BTC </Text>
</View>
);
} else {
return <View />;
}
};
return (
<View>
<TouchableOpacity
style={styles.card}
onPress={() => setOn(oldState => !oldState)}>
<View style={styles.dateStyleView}>
<Text style={styles.dateStyle}>{createdDay}</Text>
</View>
<View style={styles.left}>
<Text style={styles.title}>Venda rápida</Text>
<Text style={styles.semiTitle}>
{
{
0: 'Pendente',
1: 'Aguardando conclusão',
2: 'Cancelado',
100: 'Completo',
}[value.item.status]
}
</Text>
</View>
<View style={styles.right2}>
<Text style={styles.price}>R$ {value.item.requested_value}</Text>
</View>
</TouchableOpacity>
<View>{Details(on)}</View>
</View>
);
}
}
return dateSelected();}
and i call it here
return (
<ScrollView>
...
<View style={styles.center}>
...
<View style={styles.middle2}>
...
<FlatList
extraData={[refresh, newMonths]}
data={newMonths}
renderItem={(item: any) => <Item value={item} />}
keyExtractor={(item, index) => index.toString()}
initialNumToRender={15}
/>
</View>
</View>
</ScrollView>);}
The scroll bar in the right, start to increase until renders all transactions from the data:
App scroll bar
I have a modal that shown on a button click and the modal is fetched from another component using prop.
In the modal there is a button to close the modal, How to close the modal onclick the button. I have tried but doesn't worked.
//InvalidUser
const InvalidUser = (props) => (
<Modal
visible={props.display}
animationType="slide"
onRequestClose={() => console.log('closed')}
>
<View style={styles.modalBox}>
<View style={{width: 300}}>
<Text style={styles.text}>
{props.data}
</Text>
<TouchableOpacity
onPress={() =>
this.closeModal()
}>
<Text style={styles.buttonOk}>Ok</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
);
//Login
export default class LoginFirst extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
modalVisible: false
};
}
nextBtn = () => {
let reg = /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/;
if (this.state.email !== '') {
if (reg.test(this.state.email) === false) {
}
else {
this.setState({modalVisible: true});
}
}
}
render() {
let notRegistered = this.state.email+' is not recognized as a registered user. Please contact us for further assistance.';
return (
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.scroller}>
<View
style={styles.inputSection}
>
<Text style={styles.label}>Email Address:</Text>
<View style={styles.section}>
<Image
style={styles.icon}
source={require('../../../../assets/user.png')}
/>
<TextInput
style={styles.input}
placeholder='johnsmith#gmail.com'
underlineColorAndroid='transparent'
onChangeText={(text) => this.setState({email:text})}
/>
</View>
<TouchableOpacity
style={styles.button}
onPress={() =>
this.nextBtn()
}
>
<Text style={styles.next}>Next</Text>
</TouchableOpacity>
</View>
</ScrollView>
<InvalidUserModal
data={notRegistered}
display={this.state.modalVisible}
/>
</View>
);
}
}
The above code is perfectly displaying the modal, but I cannot close the modal. Is there any way to close.
Please have a look into below image.
from parent component, create closeModal function and pass it to InvalidUserModal
closeModal = () => {
this.setState({modalVisible: false});
}
<InvalidUserModal
data={notRegistered}
display={this.state.modalVisible}
closeModal={this.closeModal}
/>
and call it inside InvalidUserModal on press the button
<TouchableOpacity onPress={props.closeModal}>
<Text style={styles.buttonOk}>Ok</Text>
</TouchableOpacity>
For parent, pass the closeModal method as a props to your component
class LoginFirst extends Component {
closeModal = () => {
this.setState({modalVisible: false});
}
render() {
return (
<InvalidUserModal
data={notRegistered}
display={this.state.modalVisible}
closeModal={this.closeModal}
/>
)
}
}
For your modal component
<Modal
visible={props.display}
animationType="slide"
onRequestClose={() => console.log('closed')}
>
<TouchableOpacity onPress={props.closeModal}>
<Text style={styles.buttonOk}>Ok</Text>
</TouchableOpacity>
</Modal>
I have connect socket successfully and getting proper response also from socket connection. State is updating successfully but issue is data is not updating in screen (renderTabSection not render updated data).
class Test extends Component{
constructor (props) {
super(props);
this.animatedValue = new Animated.Value(0);
}
componentDidMount(){
const { restaurantId } = this.props;
this.socket = new WebSocket('wss://test.com/' + id);
this.socket.onopen = (data) =>{
console.log('Socket connected');
}
this.socket.onmessage = (e) => {
this.props.socketData(e.data);
//this.socketUpdate();
};
this.socket.onclose = () => {
this.socket = new WebSocket('wss://test.com/' + id);
}
}
componentWillMount(){
this.props.resetVenueDetails();
this.props.showLoader();
}
componentWillReceiveProps(nextProps){
this.renderTabSection();
}
componentWillUnmount() {
this.socket.close();
}
onTabClick(tabId){
this.props.onTabChange(tabId);
}
renderTabSection(){
let playlist = this.props.playlists;
const {
container,
tabHeaderStyle,
tabHeadStyle,
tabDetailBox,
albumTextStyle,
selectedTabStyle,
hideTabContentStyle
} = styles;
return (
<View style={{flex:1, borderBottomColor: '#000', borderBottomWidth: 2 }}>
<View style={{flex:0,flexDirection:'row'}}>
<View style={tabHeadStyle}>
<TouchableOpacity onPress={this.onTabClick.bind(this,1)}>
<Text style={ [tabHeaderStyle, this.props.tabSelected == 1 ? selectedTabStyle : '' ] }>
Playlist
</Text>
</TouchableOpacity>
</View>
</View>
<View style={[container, this.props.tabSelected == 1 ? "" : hideTabContentStyle]} >
<ScrollView horizontal={true}>
<View style={{ flex: 1,flexDirection: 'row' }}>
{ this.renderPlaylistAlbums('user', playlist) }
</View>
</ScrollView>
</View>
</View>
);
}
renderPlaylistAlbums(type, playlist){
let songsArray = [];
const { tabDetailBox, albumTextStyle } = styles;
playlist = playlist.length > 0 ? playlist : PLAYLIST_SECTION;
playlist.filter( (data, i) => {
if( data.type == type ){
if(data.songs.length > 0 ){
let image = data.songs[0] && data.songs[data.songs.length -1].imageUrl ?
{ uri : data.songs[data.songs.length -1].imageUrl } :
require('../assets/images/playlist.png');
let imageStyle = data.songs[0] && data.songs[data.songs.length -1].imageUrl ?
{ width: '70%',height:'60%', borderRadius: 10 } :
{ width: '60%',height:'60%' };
songsArray.push(
<TouchableOpacity key={i}>
<View style={ tabDetailBox }>
<Image
source={ image }
style={ imageStyle } />
<Text style={ albumTextStyle }>{ data.name }</Text>
</View>
</TouchableOpacity>
);
}
}
});
return songsArray;
}
loadSpinner(){
if( this.props.loading ){
return ( <View style={{ flex:1, position:'absolute', width:'100%' }} ><Spinner /></View> );
}
}
render(){
const { backgroundImage } = styles;
return (
<View style={{ flex: 1 }}>
<ImageBackground source={APP_BACKGROUND_IMAGE.source} style={backgroundImage}>
{ this.loadSpinner() }
<View style={{ flex:1, backgroundColor:'rgba(0,0,0,.7)' }}>
{ this.renderTabSection() }
</View>
</ImageBackground>
<Footer headerText='Home' />
</View>
);
}
}
const mapStateToProps = state => {
return {
error:state.home.error,
loading:state.home.loading,
tabSelected:state.restaurantDetail.tabSelected,
playlists: state.restaurantDetail.playlists
}
}
export default connect(mapStateToProps, { onTabChange, socketData, showLoader, resetVenueDetails })(Test)
I have connect socket successfully and getting proper response also from socket connection. State is updating successfully but issue is data is not updating in screen (renderTabSection not render updated data).
I think you are not setting any state because setState will cause the component to re-render, this is how you can setState
constructor(props){
this.state = {
dataUpdated: false,
anyUseFullVariable: '',
}
}
you can setState when you get response from sockets like this
this.setState({dataUpdated: true})
then you can use the state within render component like this
this.state.dataUpdated
this will re-render the component. This is just an example try doing something similar.
Update
You can also use componentWillRecieveProps, this callback function is triggered whenever there is some change in props
componentWillRecieveProps(nextProps){
if(!isFetching && this.props.data !== nextProps.data) // or implement your preferred condition
this.setState({UpdateList: this.props.data}) // this would call the render function
}
}
When I close the modal, I need to detect that it has been closed to change the state of the parent page. Not being able to change it when I change any property of the state, the modal.
ExpertFeedback.js
import ModalExpertFeedback from './ModalExpertFeedback';
export default class ExpertFeedback extends Component {
constructor(props) {
super(props);
this.state = {
modalVisible: false,
projects: [{name:'project0', name:'project1'}],
feedback: {title: '', content: '', project_id: ''}
};
}
proveProjectIsntNull() {
if (this.state.feedback.project_id != null){
this.setModalVisible(true);
} else {
this.setModalVisible(false);
Alert.alert('Select a project please');
}
}
setModalVisible(visible) {
this.setState({modalVisible: visible});
}
render() {
return (
<View>
<View>
<TextInput
placeholder="Write title"
onChangeText={(feedback_title) => this.setState( prevState => ({
feedback: {
...prevState.feedback,
title: feedback_title
}}))
}
value={this.state.feedback.title}
/>
<Picker
selectedValue={this.state.feedback.project_id}
onValueChange={(itemValue, itemIndex) => this.setState( prevState => ({
feedback: {
...prevState.feedback,
project_id: itemValue
}}))
}>
<Picker.Item label="Select a project" value={null} />
{typeof this.state.projects === 'object' && this.state.projects.length && (this.state.projects.map((project, index) => {
return (<Picker.Item label={project.name} value={project.id} />)
}))}
</Picker>
</View>
<ModalExpertFeedback visible={this.state.modalVisible} navigation={this.props.navigation} feedback={this.state.feedback} />
<TouchableOpacity
onPress={() => {
this.proveProjectIsntNull();
}}>
<View>
<Text>SEND NOW</Text>
</View>
</TouchableOpacity>
</View>
)
}
}
ModalExpertFeedback.js
export default class ExpertFeedback extends Component {
feedback = {
title: "",
content: "",
project_id: "",
};
state = {
modalVisible: false
};
setModalVisible(visible) {
this.setState({modalVisible: visible});
}
componentWillReceiveProps(props) {
this.setState({ modalVisible: props.visible});
this.setState({ feedback: props.feedback });
}
render() {
return (
<View>
<Modal
animationType="slide"
transparent={true}
visible={this.state.modalVisible}
onRequestClose={() => { console.log('close') }} >
<View>
<TouchableOpacity
onPress={() => {
this.setModalVisible(false);
}}
>
<View>
<Text>Close</Text>
</View>
</TouchableOpacity>
</View>
</Modal>
</View>
)
}
}
When I change feedback.title with TextInput in ExpertFeedback, the Modal opens
If you want to 'connect' the Parent and the Child, you'll need to pass a handler, essentially a function as a prop from your Parent to your child.
Example below:
ExpertFeedback.js
parentHandler(result){
//do your update here
this.setState({result});
}
<ModalExpertFeedback
visible={this.state.modalVisible}
navigation={this.props.navigation}
feedback={this.state.feedback}
handler={this.parentHandler.bind(this)} />
ModalExpertFeedback.js
<Modal
animationType="slide"
transparent={true}
visible={this.state.modalVisible}
onRequestClose={() => { this.props.handler(someValue) }} >
I have used FlatList in multiple places in my app previously without any issues, but now when I created a new one it doesn't seem to register touches/swipes correctly. Only like 1/6 touches seem to register.
See the video here: https://photos.app.goo.gl/NZCtVYX6GLVCQN392
This is how I use the FlatList:
render() {
return (
<Container>
...
<FlatList
data={this.state.exercises}
renderItem={({item}) =>
<SetsRepsAndWeightItem exercise={item}/>
}
keyExtractor={item => item.name}
style={style.list}
/>
</Container>
);
}
And the SetsRepsAndWeightItem:
render() {
return (
<View style={style.container}>
<View style={style.header}>
<Text style={style.headerText}>{this.props.exercise.name}</Text>
</View>
<View style={style.about}>
<TouchableWithoutFeedback onPress={this.handleSetsPressed}>
<StatisticNumber metric="Sets" value={7}/>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress={this.handleRepsPressed}>
<StatisticNumber metric="Reps" value={5}/>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress={this.handleWeightPressed}>
<StatisticNumber metric="kg" value={35}/>
</TouchableWithoutFeedback>
</View>
</View>
);
}
handleSetsPressed = () => {
console.log("sets pressed");
}
handleRepsPressed = () => {
console.log("reps pressed");
}
handleWeightPressed = () => {
console.log("weight pressed");
}
Also: the TouchableWithoutFeedback elements are not calling their onPress functions when they are touched.
The Container is as simple as this:
export default class Container extends Component {
static propTypes = {
children: Proptypes.any,
backgroundColor: Proptypes.string
};
render() {
const containerStyles = StyleSheet.flatten([
style.container,
this.props.backgroundColor ? { backgroundColor: this.props.backgroundColor } : null,
]);
return (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<View style={containerStyles}>
{this.props.children}
</View>
</TouchableWithoutFeedback>
);
}
}
The following two fixes solved the issues for me:
Remove the onPress={() => Keyboard.dismiss()} from the Container component
Move the TouchableWithoutFeedback into the StatisticNumber component, and pass onPress as a prop from SetsRepsAndWeightItem