I am having trouble rendering data from firebase.
Here is my current data structure on firebase:
{name: "sleep"}
ie. that's the only object the database contains atm.
Using a ListView, it renders a row (shows the background and border of the row according to the number of items in the database), however, there is no text inside the row..
ListView code:
<ListView
enableEmptySections={true}
dataSource={this.state.dataSource}
renderRow={this._renderItem.bind(this)}
/>
_renderItem(task) {
return (
<ListItem task={task} />
);
}
Code for ListItem:
class ListItem extends Component {
render() {
return (
<View style={styles.listItem}>
<Text style={styles.listItemTitle}>{this.props.task.name}</Text>
</View>
);
}
}
Code used to listen to tasks
listenForTasks(tasksRef) {
tasksRef.on('value', (dataSnapshot) => {
var tasks = [];
dataSnapshot.forEach((child) => {
tasks.push({
name: child.val().name,
_key: child.key
});
console.log(dataSnapshot.val()); // returns JSON object of {name: 'sleep'}
console.log(tasks); // returns an array containing the object of {name: 'sleep'}
console.log(dataSnapshot.val().name); // returns 'sleep'
});
this.setState({
dataSource: this.state.dataSource.cloneWithRows(tasks)
});
});
}
You need to setup a constructor for your ListItem Component:
class ListItem extends Component {
//Add this....
constructor(props){
super(props)
}
//--------------
render() {
return ( <View style={styles.listItem}>
<Text style={styles.listItemTitle>
{this.props.task.name}
</Text>
</View> );
}
}
Related
I am wonder if in screenA I have an object data = {} that will be changed dynamically, can I receive changes in screenB by just sending this props from screenA through this.props.navigation.navigate('screenB', {data})?
And in screenB to have a componentWillReceiveProps(nextProps) to get this changes through something like nextProps.navigation.state.param.data
Or there is a way to achieve this?
You can use onWillFocus of NavigationEvents, which fires whenever the screen is navigated to.
_willFocus = () => {
const { navigation } = this.props
const data = navigation.getParam('data', null)
if (data !== null) {
/* do something */
}
}
/* ... */
render () {
return (
<View>
<NavigationEvents onWillFocus={_willFocus()}
</View>
)
}
It is easy, just as you said: send some data navigation.navigate('screenB', { data }) and receive it in the screenB as navigation.state.params.data.
I agree with #FurkanO you probably show use Redux instead to control all the state of your app, but for simple stuff I think isn't necessary!
I made a simple snack demo to show you: snack.expo.io/#abranhe/stackoverflow-56671202
Here some code to follow up:
Home Screen
class HomeScreen extends Component {
state = {
colors: ['red', 'blue', 'green'],
};
render() {
return (
<View>
{this.state.colors.map(color => {
return <Text>{color}</Text>;
})}
<View>
<Text>Details Screen</Text>
<Button
title="Go to Details"
onPress={() => this.props.navigation.navigate('Details', { colors: this.state.colors })}
/>
</View>
</View>
);
}
}
Details Screen
class DetailsScreen extends Component {
state = {
colors: [],
};
componentWillMount() {
this.setState({ colors: this.props.navigation.state.params.colors });
}
render() {
return (
<View>
{this.state.colors.map(color => {
return <Text>{color}</Text>;
})}
<Text>Details Screen</Text>
</View>
);
}
}
Update
The question's author requested an update to add a setTimeout() to see the exact moment when the data is on the other screen, so it will look like this:
componentWillMount() {
setTimeout(() => {
this.setState({ colors: this.props.navigation.state.params.colors });
}, 3000);
}
I am working on a react-native and meteor js project.
My problem is that the props received from withTracker() function are only received in componentDidUpdate(prevProps) I don't get them in constructor or componentWillMount.
Another issue is when i pass props directly from parent to child. it receives them late due to which my component does not update
iconGroups prop comes from withTracker() method
and openSection props which i am using in this showGroupIcons()
is passed directly from parent to this component.
I want to open Accordian section that is passed to it via parent. but problem is in componentDidUpdate(prevProps) I am changing state due to which component re-renders.
openSection variable by default value is Zero. when props arrvies it value changes which i required But Accordian does not update.
Below is my code
import React, { Component } from 'react';
import Meteor, { withTracker } from 'react-native-meteor';
import {
View, Image, ScrollView, TouchableOpacity,
} from 'react-native';
import PopupDialog from 'react-native-popup-dialog';
import {Text, Icon, Input, Item, List,} from 'native-base';
import Accordion from 'react-native-collapsible/Accordion';
import { Col, Row, Grid } from 'react-native-easy-grid';
import styles from './styles';
import CONFIG from '../../config/constant';
import {MO} from "../../index";
const staticUrl = '../../assets/img/icons/';
class IconPickerComponent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
itemName: 'apple1',
activeSections: 0,
showAccordian: true,
accordianData: []
};
}
componentDidUpdate(prevProps) {
if(prevProps.iconGroups !== this.props.iconGroups) {
let images = this.props.iconGroups.map(icon => icon.images);
let flatten = [].concat.apply([], images).map(img => { return {name: img, icon: CONFIG.ICON_URL+img+'.png'} })
this.setState({ filteredItems: flatten, dataSource: flatten, accordianData: this.props.iconGroups });
}
}
componentDidMount() {
this.props.onRef(this);
}
componentWillUnmount() {
this.props.onRef(null);
}
method() {
// this.setState(...this.state,{
// searchText: ''
// })
this.iconPicker.show(); // show icon picker
}
onSearchChange(text) {
this.setState({
showAccordian: !(text.length > 0)
});
const searchText = text.toLowerCase();
const filteredItems = this.state.dataSource.filter((item) => {
const itemText = item.name.toLowerCase();
return itemText.indexOf(searchText) !== -1;
});
this.setState({ filteredItems });
}
onIconSelect(item) {
this.setState({
itemName: item,
});
this.iconPicker.dismiss();
if (this.props.onIconChanged) {
this.props.onIconChanged(item);
}
}
_renderSectionTitle = section => {
return (
<View style={styles.content}>
<Text></Text>
</View>
);
};
_renderHeader = section => {
return (
<View style={styles.accordHeader}>
<Text style={{color: 'white'}}>{this.state.showAccordian} - {section.group}</Text>
<Text>
<Icon style={styles.downArrow} name="ios-arrow-down" />
</Text>
</View>
);
};
_renderContent = section => {
return (
<View style={styles.accordContent}>
{
section.images.map((img, key) => (
<TouchableOpacity onPress={() => this.onIconSelect(img)} key={key}>
<View style={styles.iconsGrid}>
<Image style={styles.image} source={{uri: CONFIG.ICON_URL+ img + '.png'}}/>
</View>
</TouchableOpacity>
))
}
</View>
);
};
_updateSections = activeSections => {
this.setState({ activeSections });
};
hasGroupIcons() {
return this.props.iconGroups.length > 0;
};
showGroupIcons() {
if(this.state.showAccordian){
let openSection;
if(!!this.props.openSection) {
let groupIndex = this.state.accordianData.findIndex(icon => icon.group === this.props.openSection);
if(groupIndex !== -1) {
openSection = groupIndex;
} else {
openSection = 0;
}
} else {
openSection = 0;
}
return(<Accordion
sections={this.state.accordianData}
activeSections={this.state.activeSections}
renderSectionTitle={this._renderSectionTitle}
renderHeader={this._renderHeader}
renderContent={this._renderContent}
onChange={this._updateSections}
initiallyActiveSection={openSection} />);
} else {
return(<View style={{flexWrap: 'wrap', flexDirection: 'row'}}>
{
this.state.filteredItems.map((item, key) => (
<TouchableOpacity onPress={() => this.onIconSelect(item.name)} key={key}>
<View style={styles.iconsGrid}>
<Image style={styles.image} source={{uri: item.icon}}/>
</View>
</TouchableOpacity>
))
}
</View>)
}
};
render() {
return (
<PopupDialog
overlayOpacity={0.8}
overlayBackgroundColor="#414141"
dialogStyle={styles.dialogBox}
containerStyle={styles.dialogContainer}
ref={(popupDialog) => { this.iconPicker = popupDialog; }}
>
<ScrollView>
<View style={styles.dialogInner}>
<Item searchBar rounded style={styles.searchbar}>
<Icon style={styles.searchIcon} name="search" />
<Input onChangeText={this.onSearchChange.bind(this)} style={styles.inputSearch} placeholder="Search" />
</Item>
{
this.hasGroupIcons() && this.showGroupIcons()
}
</View>
</ScrollView>
</PopupDialog>
);
}
}
export default withTracker(params => {
MO.subscribe('ipSubsId3', 'IconGroups');
return {
iconGroups: MO.collection('IconGroups', 'ipSubsId3').find({}),
};
})(IconPickerComponent);
I am new to react. I am assuming when props change component re-renders.
Use this life cycle method
static getDerivedStateFromProps(prevProps, prevState) {
if(prevProps.iconGroups !== this.props.iconGroups) {
let images = this.props.iconGroups.map(icon => icon.images);
let flatten = [].concat.apply([], images).map(img => { return {name: img, icon: CONFIG.ICON_URL+img+'.png'} })
this.setState({ filteredItems: flatten, dataSource: flatten, accordianData: this.props.iconGroups });
}
}
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
Read more about this lifecycle method here
I have fixed this issue. Actually my concepts were not right. I thought props are first received in constructor and componentWillMount. But I get all props in render() and everything works fine i dont have to use any lifecycle method to use props now
I'm trying to make a search filter with flatlist.
It works, but my problem is that after the flatlist update the constructor of the items are not called any more and I need to set a state in the constructor. What is the best way to handle this ?
flatlist
<FlatList
data={this.state.searchedArray}
keyExtractor={this._keyExtractor}
removeClippedSubviews={true}
extraData={this.state}
renderItem={this._renderItem} />
Handling searchfilter
_handleChange(message) {
let text = message.toLowerCase();
let searchedArray = [];
console.log(searchedArray);
if (text.length < 2) {
return false;
}
// search trough json array
searchedArray = this.state.data.filter(row => {
if (row.symbol.toLowerCase().indexOf(text) !== -1 || row.name.toLowerCase().indexOf(text) !== -1 || row.id.toLowerCase().indexOf(text) !== -1) {
return true;
}
return false;
}).map((row) => {
return row;
});
this.setState({
searchedArray: searchedArray
});
}
child component
export default class SearchItem extends PureComponent {
constructor(props) {
super(props);
const item = this.props.item;
this.state = {
image: item.symbol
}
}
render() {
const item = this.props.item;
return (
<View style={globalStyles.row}>
<View style={[globalStyles.cell, globalStyles.primaryCell]}>
<Image
source={{ uri: this.state.image}}
style={styles.image}
blurRadius={0}
onError={(a) => {
this.setState({ image: 'question'})
}}
/>
</View>
</View>
)
}
}
Thanks for your answers, but I already found a way to fix my "default image" problem.
export default class SearchItem extends PureComponent {
constructor(props) {
super(props);
const item = this.props.item;
this.state = {
error: false
}
}
render() {
const item = this.props.item;
let symbol = (this.state.error) ? 'question' : item.symbol;
return (
<View style={globalStyles.row}>
<View style={[globalStyles.cell, globalStyles.primaryCell]}>
<Image
source={{ uri: symbol}}
style={styles.image}
blurRadius={0}
onError={(a) => {
this.setState({ error: true})
}}
/>
</View>
</View>
)
}
}
Add to SearchItem:
componentWillReceiveProps(newProps){
if (this.state.image !== newProps.item.symbol) {
this.setState({image: newProps.item.symbol})
}
}
Try to use componentWillReceiveProps as well as extending your child component from react Component instead of PureComponent.
I am trying to create a SectionList from a json file named notes.json. Basically one object in the notes json array will correspond to one SectionList entry. I already load the json array in notesData. However, when i try to use notesData as a source for my SectionList i get the error: TypeError: undefined is not an object (evaluating 'props.sections.reduce')
Here is my code:
import React from 'react';
import { Text, View, SectionList, ListItem, H1 } from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { styles } from '~/containers/Notes/styles';
import { notes } from './Notes.json';
const notesData = [];
Object.keys(notes).forEach((key) => {
notesData.push(notes[key]);
});
class NotesContainer extends React.Component {
render() {
return (
<View style={styles.container}>
<SectionList
renderItem={({ item }) => <ListItem title={item.RELEASE_NOTE} />}
renderSectionHeader={({ section }) => <Text title={section.RELEASE_VERSION} />}
sections={this.notesData}
/>
</View>
);
}
}
export { NotesContainer };
export default connect(null, null)(NotesContainer);
Here is my Notes.json
{
"notes": [
{
"RELEASE_VERSION": "0.1.1",
"RELEASE_DATE": "01 Mar 2018",
"RELEASE_NOTE": [
"General bug fixes"
]
},
{
"RELEASE_VERSION": "0.1.0",
"RELEASE_DATE": "01 Feb 2018",
"RELEASE_NOTE": [
"Initial Launch"
]
}
]
}
Your data structure for SectionList is not correct, it should have a data prop with the array of data you want to render in that section. Below there is an example for the data you have.
section
An object that identifies the data to be rendered for a given section.
Properties:
data array
The data for rendering items in this section. Array of
objects, much like FlatList's data prop.
Example
export default class App extends Component {
constructor() {
super();
this.notesData = Object.keys(notes).map((key) => {
return { data: notes[key].RELEASE_NOTE, version: notes[key].RELEASE_VERSION }
});
}
render() {
return (
<View style={styles.container}>
<SectionList
renderItem={({ item }) => <Text>{item}</Text>}
renderSectionHeader={({ section }) => <Text>{section.version}</Text>}
sections={this.notesData}
/>
</View>
);
}
}
You can do lists which based on your json data with ListView component;
constructor(props) {
super(props);
this.ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
this.listNotes = this.listNotes.bind(this);
this.state = {
notesData: this.ds.cloneWithRows({}),
};
}
componentWillMount() {
this.setState({ notesData: this.ds.cloneWithRows([notes]) });
}
renderState() {
return (
<View>
<ListView
dataSource={this.state.notesData}
enableEmptySections
renderRow={this.listNotes}
/>
</View>
);
}
listNotes(rowData) {
return (
<View>
{rowData.map((item, key) => (
<View key={key}>
...
</View>
))}
</View>
);
}
render() {
<ScrollView>
{this.renderState()}
</ScrollView>
}
I'm building react-native app, but my problem is linked with React itself.
It's an app that connects to external JSON, fetches data, and creates react component for each of item in that JSON data, so it's like 70 child components inside 1 wrapper. App is also using Navigator and phone storage but that's a part of the question.
To visualize:
Parent component (People) has methods to operate on a DB, it fetches data, creates component for each of item in array and exposes methods to child components (Person). Each person has a "add to favourites" button, this is a method updating empty star to full one (conditional rendering) if clicked, updates state of component and whenever local state has been changed it fires parents component to update it's state - and from there parent's component saves all data to DB. So I've made a link to synchronize Child's State -> Parent's State -> Local DB (phone's memory).
Problem is, it's quite slow when updating parent's state. It freezes for 1-1.5 sec. but it's all cool if I remove method to update parent's state (I've marked that in example attached).
Question 1: How to refactor this code to fix performance issue when updating parent's (People's state)?
Question 2: I'm open to any other suggestions and lessons how to improve quality of my code.
Here's a link to visualize my code, I've just removed few non-relevant methods and styles.
https://jsfiddle.net/xvgfx90q/
class People extends React.Component {
constructor() {
super();
this.state = {
peopleData: [],
database: {}
}
}
componentDidMount() {
this.fetchApi();
this.syncDatabase();
}
// function that connects to external JSON file and parses it
fetchApi() {... it sets peopleData state after promise has been resolved}
// function called from PersonSection to pass it's state and update main state of People
syncStates(data) {
const newState = this.state;
newState.database[data.id] = data;
this.setState(newState); // <-- !! PERFORMANCE DROP HERE !!
this.saveDatabase();
}
// connects to phone's DB and updates state with result of promise
async syncDatabase() {
AsyncStorage.getItem(this.state.DBKey).then((data) => {
let newState = {};
newState.database = JSON.parse(data);
this.setState(newState);
}).catch((error) => {
return error;
})
}
// saves current state to DB
async saveDatabase() {
AsyncStorage.setItem(this.state.DBKey, JSON.stringify(this.state.database));
}
renderTeams() {
return Object.keys(this.state.peopleData).map((team) => {
return (
<TeamSection key={team} teamName={team} membersList={this.state.peopleData[team]}>
{this.renderPeople(team)}
</TeamSection>
)
})
}
renderPeople(team) {
return this.state.peopleData[team].map((people) => {
return (
<PersonSection
key={people.id}
data={people}
database={_.has(this.state.database, people.id) ? this.state.database[people.id] : false}
navigator={this.props.navigator}
syncStates={this.syncStates.bind(this)}
/>
)
})
}
render() {
return (
<ScrollView style={styles.wrapper}>
<Options filterPeople={this.filterPeople.bind(this)} />
{this.renderTeams()}
</ScrollView>
)
}
}
class PersonSection extends Component {
constructor(props) {
super(props);
this.state = {
database: {
id: this.props.data.id,
name: this.props.data.name,
favourites: this.props.database.favourites
}
}
}
// updates components state and sends it to parent component
toggleFavourites() {
const newState = this.state.database;
newState.favourites = !newState.favourites;
this.setState(newState);
this.props.syncStates(this.state.database);
}
render () {
return (
<View>
<View>
<View>
<Text>{this.props.data.name}</Text>
<Text>{this.props.data.position}</Text>
<Text>{this.props.data.ext}</Text>
</View>
<View>
<TouchableOpacity onPress={() => this.toggleFavourites()}>
{ this.state.database.favourites
? <Icon name="ios-star" size={36} color="#DAA520" />
: <Icon name="ios-star-outline" size={36} color="#DAA520" />}
</TouchableOpacity>
</View>
</View>
</View>
)
}
};
export default PersonSection;
React.render(<People />, document.getElementById('app'));`
This is not a recommended way to do it, but basically you can just update the child state instead of the parent and passing it back down.
class People extends React.Component {
constructor() {
super();
this.state = {
peopleData: [],
database: {}
}
}
componentDidMount() {
this.fetchApi();
this.syncDatabase();
}
// function that connects to external JSON file and parses it
fetchApi() {... it sets peopleData state after promise has been resolved}
// function called from PersonSection to pass it's state and update main state of People
syncStates(data) {
this.state.database[data.id] = data;
this.saveDatabase();
}
// connects to phone's DB and updates state with result of promise
async syncDatabase() {
AsyncStorage.getItem(this.state.DBKey).then((data) => {
let newState = {};
newState.database = JSON.parse(data);
this.setState(newState);
}).catch((error) => {
return error;
})
}
// saves current state to DB
async saveDatabase() {
AsyncStorage.setItem(this.state.DBKey, JSON.stringify(this.state.database));
}
renderTeams() {
return Object.keys(this.state.peopleData).map((team) => {
return (
<TeamSection key={team} teamName={team} membersList={this.state.peopleData[team]}>
{this.renderPeople(team)}
</TeamSection>
)
})
}
renderPeople(team) {
return this.state.peopleData[team].map((people) => {
return (
<PersonSection
key={people.id}
data={people}
database={_.has(this.state.database, people.id) ? this.state.database[people.id] : false}
navigator={this.props.navigator}
syncStates={this.syncStates.bind(this)}
/>
)
})
}
render() {
return (
<ScrollView style={styles.wrapper}>
<Options filterPeople={this.filterPeople.bind(this)} />
{this.renderTeams()}
</ScrollView>
)
}
}
class PersonSection extends Component {
constructor(props) {
super(props);
this.state = {
database: {
id: this.props.data.id,
name: this.props.data.name,
favourites: this.props.database.favourites
}
}
}
// updates components state and sends it to parent component
toggleFavourites() {
const newState = this.state.database;
newState.favourites = !newState.favourites;
this.setState(newState);
this.props.syncStates(this.state.database);
}
render () {
return (
<View>
<View>
<View>
<Text>{this.props.data.name}</Text>
<Text>{this.props.data.position}</Text>
<Text>{this.props.data.ext}</Text>
</View>
<View>
<TouchableOpacity onPress={() => this.toggleFavourites()}>
{ this.state.database.favourites
? <Icon name="ios-star" size={36} color="#DAA520" />
: <Icon name="ios-star-outline" size={36} color="#DAA520" />}
</TouchableOpacity>
</View>
</View>
</View>
)
}
};
export default PersonSection;
React.render(<People />, document.getElementById('app'));