Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#34 #37 #40 datatable row cell components #51

Merged
merged 15 commits into from
Jul 30, 2019
13 changes: 3 additions & 10 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
{
"extends": "airbnb",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd just ignore the root project stuff. Though you might be interested in a discussion around whether to use airbnb linter rules or not. You might even note that I've gone sour on semi colons.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure what to contribute right now, but I'd say it's pretty appropriate to create a @openmsupply/configs repo to export linter and prettier rules

I guess I prefer semi-colons, but I don't really care as long as it's consistent. I mean, especially if a linter is doing it for me

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also agree with exporting linter and prettier rules (BE are doing this). Re configs, if they're consistent, I don't mind (although I prefer semi-colons). I think the best way to go is just use defaults across the board, just because it stops any disagreements.

"parser": "babel-eslint",
"root": true,
"extends": "@react-native-community",
"rules": {
"no-param-reassign": "off",
"jsx-boolean-value": "off",
"no-use-before-define": "off",
"object-shorthand": "off",
"strict": 0,
"no-confusing-arrow": ["error", {"allowParens": true}],
"global-require": "off",
"react/jsx-boolean-value": "off",
"semi": 0
}
}
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"singleQuote": true,
"semi": false
}
3 changes: 3 additions & 0 deletions examples/demoTablesApp/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
module.exports = {
root: true,
extends: '@react-native-community',
rules: {
semi: 0,
}
};
6 changes: 6 additions & 0 deletions examples/demoTablesApp/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"singleQuote": true,
"semi": false
}
97 changes: 0 additions & 97 deletions examples/demoTablesApp/App.js

This file was deleted.

8 changes: 4 additions & 4 deletions examples/demoTablesApp/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* @format
*/

import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import { AppRegistry } from 'react-native'
import App from './src/App'
import { name as appName } from './app.json'

AppRegistry.registerComponent(appName, () => App);
AppRegistry.registerComponent(appName, () => App)
2 changes: 2 additions & 0 deletions examples/demoTablesApp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"private": true,
"scripts": {
"start": "react-native start",
"runa": "react-native run-android",
"log": "react-native log-android",
"test": "jest",
"lint": "eslint ."
},
Expand Down
127 changes: 127 additions & 0 deletions examples/demoTablesApp/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* Table experiment hole
* https://github.com/facebook/react-native
*
* @format
* @flow
*/

import React, { Fragment, useState, useCallback, useReducer } from 'react'
import { Button, StatusBar } from 'react-native'
import { DataTable } from './components/DataTable'
import { Row } from './components/Row'
import { Cell } from './components/Cell'

// Configurable constants for demo data volume
const rowCount = 10
const columnCount = 4

const baseData = []
const baseColumns = []

// Make columns definition used in demo app
for (let index = 0; index < columnCount; index++) {
baseColumns.push({ key: `col${index}`, editable: index === columnCount - 1 }) // index === columnCount - 1
}

// Generate data for use in demo app
for (let index = 0; index < rowCount; index++) {
const rowValues = {}
baseColumns.forEach((column, columnIndex) => {
rowValues[column.key] = `row${index}Col${columnIndex}`
})

baseData.push({ id: `r${index}`, ...rowValues })
}

const keyExtractor = item => item.id
const dataReducer = (data, action) => {
switch (action.type) {
case 'editCell':
const { value, rowKey, columnKey } = action
const rowIndex = data.findIndex(row => keyExtractor(row) === rowKey)

// Immutable array editing so only the row/cell edited are re-rendered.
// If you don't do this, every row will re-render as well as the cell
// edited.
return data.map((row, index) => {
if (index !== rowIndex) {
return row
}
const rowEdited = { ...row }
rowEdited[columnKey] = value
return rowEdited
})
case 'reverseData':
return [...data.reverse()]
default:
return data
}
}

// Action yay
const editCell = (value, rowKey, columnKey) => ({
type: 'editCell',
value,
rowKey,
columnKey,
})

const App = () => {
const [isButtonOof, toggleButton] = useState(false)
const [data, dataDispatch] = useReducer(dataReducer, baseData)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where we start getting a hint on how to use/form the new API, but there are decisions to be made on how prescribed we make this thing, mostly relating to how we pass around dataDispatch.

const columns = baseColumns

const renderCells = useCallback(
(rowData, rowKey) => {
return columns.map(col => (
<Cell
key={col.key}
value={rowData[col.key]}
rowKey={rowKey}
columnKey={col.key}
editable={col.editable}
editAction={editCell}
dataDispatch={dataDispatch}
/>
))
},
[columns, dataDispatch]
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current design has Cell onChange event call dataDispatch with an action like { type: 'editRowCell', newValue, rowKey, columnKey }. This ends up just being a bit rigid I think...

Ok I experimented with giving a callBack to the cell that calls the dispatch with what ever you want. But then you have another function reference to manage (boom all cells on the row re-render on an edit). useCallback isn't allowed inside a useCallback hook haha. If only there was some kind of "actionCreator" pattern prescribed by some very common library 🤔


const renderItem = useCallback(
({ item, index }) => {
const rowKey = keyExtractor(item)
return (
<Row
rowData={data[index]}
rowKey={rowKey}
renderCells={renderCells}
dataDispatch={dataDispatch}
/>
)
},
[data, renderCells, dataDispatch]
)

return (
<Fragment>
<StatusBar hidden />
<Button
title={'sort data'}
onPress={() => dataDispatch({ type: 'reverseData' })}
/>
<Button
title={isButtonOof ? 'oof' : 'Press me'}
onPress={() => toggleButton(!isButtonOof)}
/>
<DataTable
data={data}
renderRow={renderItem}
keyExtractor={keyExtractor}
/>
</Fragment>
)
}

export default App
39 changes: 39 additions & 0 deletions examples/demoTablesApp/src/components/Cell.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react'
import PropTypes from 'prop-types'
import { View, Text, TextInput, StyleSheet, ViewPropTypes } from 'react-native'

export const Cell = React.memo(
({ value, rowKey, columnKey, editable, editAction, dataDispatch }) => {
const _onEdit = newValue =>
dataDispatch(editAction(newValue, rowKey, columnKey))

console.log(`cell: ${value}`)
return (
<View style={defaultStyles.cell}>
{editable ? (
<TextInput value={value} onChangeText={_onEdit} />
) : (
<Text>{value}</Text>
)}
</View>
)
}
)

Cell.propTypes = {
...ViewPropTypes,
style: ViewPropTypes.style,
textStyle: Text.propTypes.style,
width: PropTypes.number,
}

Cell.defaultProps = {
width: 1,
}

const defaultStyles = StyleSheet.create({
cell: {
flex: 1,
justifyContent: 'center',
},
})
38 changes: 38 additions & 0 deletions examples/demoTablesApp/src/components/DataTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* @flow weak */

/**
* mSupply Mobile
* Sustainable Solutions (NZ) Ltd. 2016
*/

import PropTypes from 'prop-types'
import React from 'react'
import {
StyleSheet,
VirtualizedList,
VirtualizedListPropTypes,
} from 'react-native'

export const DataTable = React.memo(({ renderRow, ...otherProps }) => (
<VirtualizedList
style={defaultStyles.virtualizedList}
renderItem={renderRow}
{...otherProps}
/>
))

DataTable.propTypes = {
...VirtualizedListPropTypes,
renderHeader: PropTypes.func,
renderRow: PropTypes.func.isRequired,
}
DataTable.defaultProps = {
getItem: (items, index) => items[index],
getItemCount: items => items.length,
}

const defaultStyles = StyleSheet.create({
virtualizedList: {
flex: 1,
},
})
25 changes: 25 additions & 0 deletions examples/demoTablesApp/src/components/Row.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import PropTypes from 'prop-types'
import React from 'react'
import { View, StyleSheet, ViewPropTypes } from 'react-native'

export const Row = React.memo(
({ rowData, rowKey, renderCells, dataDispatch }) => {
console.log('====================================')
console.log(`Row: ${rowKey}`)
console.log('====================================')

return <View style={defaultStyles.row}>{renderCells(rowData, rowKey)}</View>
}
)

Row.propTypes = {
style: ViewPropTypes.style,
onPress: PropTypes.func,
}

const defaultStyles = StyleSheet.create({
row: {
flex: 1,
flexDirection: 'row',
},
})
Loading