Skip to content

Instantly share code, notes, and snippets.

@whoyawn
Created March 15, 2017 18:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save whoyawn/930a6c8a02874ded1adc9669cce65b72 to your computer and use it in GitHub Desktop.
Save whoyawn/930a6c8a02874ded1adc9669cce65b72 to your computer and use it in GitHub Desktop.
practice with nested listviews
/**
* Created by huyanh on 2017. 3. 14..
*/
import React, { Component } from 'react';
import {
AsyncStorage,
ListView,
Text,
View,
} from 'react-native';
import ViewPager from 'react-native-viewpager';
import moment from 'moment';
import Page from './page';
type Props = {}
type PageEntry = {
// key: string,
pageID: number | string,
date: string,
total: number,
// inputTitle: string,
// inputAmount: number,
items: Object[],
}
sample = [
{loading: true, total: 50, items: []},
]
class App extends Component {
state: {
pages: PageEntry[],
};
constructor(props: Props) {
super(props);
const ds = new ViewPager.DataSource({
pageHasChanged: (p1, p2) => p1 !== p2,
});
this.state = {
pages: sample,
dataSource: ds.cloneWithPages(sample),
};
this.handleUpdate = this.handleUpdate.bind(this);
this.renderPage = this.renderPage.bind(this);
}
// Every new day add a page
addPage() {
const newPages = [
...this.state.pages,
{
key: Date.now(),
date: moment(),
total: 0,
items: [],
},
];
this.setState({
pages: newPages,
});
}
setSource(pages, pagesDataSource, otherState: any = {}) {
// for top level pages
this.setState({
pages,
// allows us to keep track of different items than are rendered on the screen
dataSource: this.state.dataSource.cloneWithPages(pagesDataSource),
...otherState, // spread in any other state given
});
}
handleUpdate(pageID: number | string, updatedPage: PageEntry) {
const newPages = this.state.pages.map((page) => {
if (page.pageID !== pageID) return page;
return {
updatedPage,
};
});
// console.log('new pages', newPages);
// console.log(pageID);
this.setSource(newPages, newPages);
}
renderPage(data: PageEntry, pageID: number | string) {
// nested list
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
// page object
const dataWithDS = {
...data,
source: ds.cloneWithRows(data.items),
};
// TODO: refactor this and above to one function
// const newPages = this.state.pages.map((page) => {
// if (page.pageID !== pageID) return page;
// return {
// dataWithDS,
// };
// });
// this.setSource(newPages, newPages);
console.log('hi', pageID);
console.log('pages',this.state.pages);
console.log(this.state.pages[pageID]);
console.log(dataWithDS);
return (
<Page
date="hi"
data={dataWithDS}
onUpdate={page => this.handleUpdate(pageID, page)}
/>
);
}
render() {
return (
<ViewPager
dataSource={this.state.dataSource}
renderPage={this.renderPage}
isLoop={false}
autoPlay={false}
/>
);
}
}
export default App;
/**
* Created by huyanh on 2017. 2. 8..
*/
// @flow
import React, { Component } from 'react';
import { View, StyleSheet, ActivityIndicator, Platform, ListView, Keyboard, AsyncStorage } from 'react-native';
import Header from './header';
import FoodInput from './foodinput';
import Row from './row';
type Props = {
date: string,
data: Object,
onUpdate: (Object) => void,
// total: number,
// inputTitle: string,
// inputAmount: number,
// items: FoodEntry[],
}
type FoodEntry = {key: string, title: string, amount: string, editing: boolean}
class Page extends Component {
state: {
loading: boolean,
total: number,
title: string,
amount: string,
// items: FoodEntry[],
// dataSource: any,
};
constructor(props: Props) {
super(props);
// A rowHasChanged function is required to use ListView. Here we just say a
// row has changed if the row we are on is not the same as the previous row.
// const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
this.state = {
loading: true,
total: 0,
title: '', // will hold value for textinput
amount: '',
// items: [], // food items to input
// The clone methods suck in the new data and compute a diff for each row
// so ListView knows whether to re-render it or not.
// dataSource: ds.cloneWithRows([]),
};
this.handleUpdateText = this.handleUpdateText.bind(this);
this.handleToggleEditing = this.handleToggleEditing.bind(this);
this.handleRemoveItem = this.handleRemoveItem.bind(this);
this.setSource = this.setSource.bind(this);
this.handleAddItem = this.handleAddItem.bind(this);
}
componentWillMount() {
}
componentDidMount() {
this.props.onUpdate({ loading: false, items: [], total: 30 });
// AsyncStorage.multiGet(['items', 'total']).then((store) => {
// try {
// const items = JSON.parse(store[0][1]);
// const total = items.length > 0 ? parseInt(store[1][1], 10) : 0;
// // this.setSource(items, items, total, { loading: false });
//
// } catch (e) {
// console.log('error');
// }
// });
}
handleUpdateText(key: string, text: string) {
const newItems = this.state.items.map((item) => {
if (item.key !== key) return item;
return {
...item,
text,
};
});
this.setSource(newItems, newItems);
}
handleToggleEditing(key: string, editing: boolean) {
const newItems = this.state.items.map((item) => {
if (item.key !== key) return item;
return {
...item,
editing,
};
});
this.setSource(newItems, newItems);
}
setSource(items, itemsDataSource,
total: number = this.state.total,
otherState: any = {}) {
// this.setState({
// items,
// // allows us to keep track of different items than are rendered on the screen
// dataSource: this.state.dataSource.cloneWithRows(itemsDataSource),
// total,
// ...otherState, // spread in any other state given
// });
console.log('save', total);
const strTotal = total > 0 ? total.toString() : '0';
this.props.onUpdate({ items, total: strTotal });
// AsyncStorage.multiSet([['items', JSON.stringify(items)], ['total', strTotal]]);
}
handleRemoveItem(key: string) {
// TODO: subtract from total
// const newTotal: number = this.state.total - amount < 0 ? 0 : this.state.total - amount;
// this.setState({ total: newTotal });
let total = this.state.total;
const newItems = this.state.items.filter((item) => {
if (item.key === key) {
total = this.state.total - item.amount;
}
return item.key !== key;
});
this.setSource(newItems, newItems, total);
}
handleAddItem() {
// if i gave handleadditem a parameter bool then it does weird behavior if
// i set the handler for FoodInput passing in a bool
if (!this.state.amount) return; // don't add empty calories
const newItems = [
...this.props.data.items, // spread old items into array
{
key: Date.now(), // just unique identifier
title: this.state.title, // add text of value just entered
amount: this.state.amount,
},
];
if (this.state.amount !== '') {
// yell
}
const total = this.state.total + parseInt(this.state.amount, 10);
// now set new state and clear out text
this.props.onUpdate({ items: newItems, total });
//this.setSource(newItems, newItems, total, { title: '', amount: '' });
}
render() {
return (
<View style={styles.container}>
<Header
style={styles.header}
total={this.props.data.total}
/>
<FoodInput
title={this.state.title}
amount={this.state.amount}
onAddAmount={this.handleAddItem}
onChangeTitle={title => this.setState({ title })}
onChangeAmount={amount => this.setState({ amount })}
/>
<View style={styles.content}>
<ListView
style={styles.content}
enableEmptySections
dataSource={this.props.data.source}
onScroll={() => Keyboard.dismiss()}
// passed value of what we set in our datasource
// each value of the spread is the rest of the object
renderRow={({ key, ...value }) => (
<Row
key={key}
onUpdate={text => this.handleUpdateText(key, text)}
onToggleEdit={(editing) => {
console.log(editing.type);
this.handleToggleEditing(key, editing);
}}
onRemove={() => this.handleRemoveItem(key)}
{...value}
/>
)}
renderSeparator={(sectionId, rowId) => <View key={rowId} style={styles.separator} />}
/>
</View>
{this.props.loading && <View style={styles.loading}>
<ActivityIndicator
animating
size="large"
/>
</View>}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
...Platform.select({
ios: { paddingTop: 30 },
}),
},
header: {
flex: 5,
},
loading: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(0,0,0,.2)',
},
content: {
flex: 2,
},
list: {
backgroundColor: '#FFF',
borderColor: '#F5F5F5',
},
separator: {
borderWidth: 1,
borderColor: '#F5F5F5',
},
});
export default Page;
/**
* Created by huyanh on 2017. 2. 9..
*/
import React, {Component} from 'react';
import {View, Text, StyleSheet, TouchableOpacity, TextInput} from 'react-native';
type Props = {
onToggleEdit: (boolean) => void,
title: string,
onUpdate: () => void,
amount: number,
onRemove: () => void,
}
class Row extends Component {
props: Props;
render() { // this.props.text comes from spread operator in renderRow
const textTitleComponent = (
<TouchableOpacity onLongPress={() => this.props.onToggleEdit(true)}>
<Text style={styles.text}>{this.props.title}</Text>
</TouchableOpacity>
);
const editingTitleComponent = (
<View style={styles.textWrap}>
<TextInput
onChangeText={this.props.onUpdate}
autoFocus
value={this.props.title}
style={styles.input}
multiline
/>
</View>
);
const textAmountComponent = (
<TouchableOpacity onLongPress={() => this.props.onToggleEdit(true)}>
<Text style={styles.text}>{this.props.amount}</Text>
</TouchableOpacity>
);
const editingAmountComponent = (
<View style={styles.textWrap}>
<TextInput
onChangeText={this.props.onUpdate}
autoFocus
value={this.props.amount}
style={styles.input}
multiline
/>
</View>
);
const doneButton = (
<TouchableOpacity style={styles.done} onPress={() => this.props.onToggleEdit(false)}>
<Text style={styles.doneText}>Save</Text>
</TouchableOpacity>
);
const removeButton = (
<TouchableOpacity onPress={this.props.onRemove}>
<Text style={styles.destroy}>X</Text>
</TouchableOpacity>
);
return (
<View style={styles.container}>
{this.props.editing ? editingTitleComponent : textTitleComponent}
{this.props.editing ? editingAmountComponent : textAmountComponent}
{this.props.editing ? doneButton : removeButton }
</View>
);
}
}
const styles = StyleSheet.create({
container: {
padding: 10,
flexDirection: 'row',
alignItems: 'flex-start',
justifyContent: 'space-between',
},
input: {
height: 100,
flex: 1,
fontSize: 24,
padding: 0,
color: '#4d4d4d',
},
textWrap: {
flex: 1,
marginHorizontal: 10,
},
done: {
borderRadius: 5,
borderWidth: 1,
borderColor: '#7be290',
padding: 7,
},
doneText: {
color: '#4d4d4d',
fontSize: 20,
},
text: {
flex: 1,
fontSize: 24,
color: '#141414',
},
destroy: {
fontSize: 20,
color: '#cc9a9a',
},
});
export default Row;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment