Refactoring from React Class to Pure Function
- Comment out import statements to
material-ui
andreact-bootstrap
.
import { CardText, Checkbox } from 'material-ui';
import { Table } from 'react-bootstrap';
- Remove React Mixin.
import { ReactMeteorData } from 'meteor/react-meteor-data';
import ReactMixin from 'react-mixin';
- Add best practice imports.
// react goodies
import React, { useState } from 'react';
// utilities
import moment from 'moment-es6'
import _ from 'lodash';
let get = _.get;
let set = _.set;
// user interface
import {
Button,
Card,
Checkbox,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TableFooter,
TablePagination,
IconButton,
FirstPageIcon,
KeyboardArrowLeft,
KeyboardArrowRight,
LastPageIcon
} from '@material-ui/core';
// icons
// https://react-icons.netlify.com/#/
import { FaTags, FaCode, FaPuzzlePiece, FaLock } from 'react-icons/fa';
- Add new styling pattern.
// We're using the new theme provider!
import { ThemeProvider, makeStyles } from '@material-ui/styles';
const useStyles = makeStyles(theme => ({
button: {
background: theme.background,
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: theme.buttonText,
height: 48,
padding: '0 30px',
}
}));
- Comment out old styling method.
// DONT USE PLAIN JSON OBJECTS
let styles = {
button: {
background: theme.background,
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: theme.buttonText,
height: 48,
padding: '0 30px',
},
widget: {
width: '100%'
}
}
- Create pure component.
function MyFunctionalWidget(props){
const classes = useStyles();
return(<div id="myWidget" className={classes.widget}>
Hello World!
</div>);
}
- Add pagination and internal state.
//---------------------------------------------------------------------
// Pagination
let rows = [];
let rowsPerPageToRender = 5;
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
if(props.rowsPerPage){
// if we receive an override as a prop, render that many rows
// best to use rowsPerPage with disablePagination
rowsPerPageToRender = props.rowsPerPage;
} else {
// otherwise default to the user selection
rowsPerPageToRender = rowsPerPage;
}
let paginationCount = 101;
if(props.count){
paginationCount = props.count;
} else {
paginationCount = rows.length;
}
- Copy over functions.
function rowClick(id){ ... }
function renderActionIconsHeader(){ ... }
function renderActionIcons(procedure ){ ... }
function onMetaClick(_id){ ... }
function removeRecord(_id){ ... }
function onActionButtonClick(id){ ... }
function renderBarcode(id){ ... }
function renderBarcodeHeader(){ ... }
function renderSubject(name, type){ ... }
function renderSubjectHeader(){ ... }
- Add
function
. Replace<tr>
and<th>
with<TableCell>
. Replacethis.props
withprops
// OLD
renderBarcode(id){
if (!this.props.hideBarcodes)
return (
<td><span className="barcode">{id}</span></td>
);
}
}
// NEW
function renderBarcode(id){
if (!props.hideBarcodes) {
return (
<TableCell><span className="barcode" >{id}</span></TableCell>
);
}
}
- Create mapping file.
flattenCondition = function(procedure, dateFormat){
let result = {
_id: '',
id: '',
identifier: '',
status: '',
code: '',
codeDisplay: '',
subject: '',
subjectReference: '',
performedStart: '',
performedEnd: ''
};
if(!dateFormat){
dateFormat = get(Meteor, "settings.public.defaults.dateFormat", "YYYY-MM-DD");
}
result._id = get(procedure, 'id') ? get(procedure, 'id') : get(procedure, '_id');
result.id = get(procedure, 'id', '');
result.status = get(procedure, 'status', '');
result.identifier = get(procedure, 'identifier[0].value');
result.code = get(procedure, 'code.coding[0].code');
if(get(procedure, 'subject')){
result.subject = get(procedure, 'subject.display', '');
result.subjectReference = get(procedure, 'subject.reference', '');
} else if(get(procedure, 'patient')){
result.subject = get(procedure, 'patient.display', '');
result.subjectReference = get(procedure, 'patient.reference', '');
}
if(get(procedure, 'performedPeriod')){
result.performedStart = moment(get(procedure, 'performedPeriod.start')).format(dateFormat);
result.performedEnd = moment(get(procedure, 'performedPeriod.end')).format(dateFormat);
}
return result;
}
- Copy over HTML render block.
// OLD
export class MyFunctionalWidget extends React.Component {
getMeteorData() {}
render(){
return(
<Table id='conditionsTable' hover >
<thead>
<tr>
{ this.renderCheckboxHeader() }
{ this.renderActionIconsHeader() }
{ this.renderIdentifierHeader() }
{ this.renderPatientNameHeader() }
{ this.renderAsserterNameHeader() }
{ this.renderStatusHeader() }
<th className='snomedCode'>Code</th>
<th className='snomedDisplay'>Condition</th>
{ this.renderVerificationHeader() }
{ this.renderSeverityHeader() }
{ this.renderEvidenceHeader() }
{ this.renderDateHeader('Start') }
{ this.renderDateHeader('End') }
</tr>
</thead>
<tbody>
{ tableRows }
</tbody>
</Table>
);
}
}
// NEW
function MyFunctionalWidget(props){
return(
<div>
<Table id='conditionsTable'>
<TableHead>
<TableRow>
{ renderCheckboxHeader() }
{ renderActionIconsHeader() }
{ renderIdentifierHeader() }
{ renderPatientNameHeader() }
{ renderAsserterNameHeader() }
{ renderStatusHeader() }
{ renderSnomedCodeHeader() }
{ renderSnomedDisplayHeader() }
{ renderVerificationHeader() }
{ renderSeverityHeader() }
{ renderEvidenceHeader() }
{ renderDateHeader('Start') }
{ renderDateHeader('End') }
</TableRow>
</TableHead>
<TableBody>
{ tableRows }
</TableBody>
</Table>
{ paginationFooter }
</div>
);
}
-
Table columns and headers should have their own functions, with prop override to enable/disable.
-
Add pagination.
let tableRows = [];
let proceduresToRender = [];
let dateFormat = "YYYY-MM-DD";
if(props.showMinutes){
dateFormat = "YYYY-MM-DD hh:mm";
}
if(props.dateFormat){
dateFormat = props.dateFormat;
}
if(props.procedures){
if(props.procedures.length > 0){
let count = 0;
props.procedures.forEach(function(procedure){
if((count >= (page * rowsPerPageToRender)) && (count < (page + 1) * rowsPerPageToRender)){
proceduresToRender.push(flattenProcedure(procedure, dateFormat));
}
count++;
});
}
}
if(proceduresToRender.length === 0){
console.log('No procedures to render');
// footer = <TableNoData noDataPadding={ props.noDataMessagePadding } />
} else {
for (var i = 0; i < proceduresToRender.length; i++) {
tableRows.push(
<TableRow className="procedureRow" key={i} onClick={ rowClick.bind(this, proceduresToRender[i]._id)} style={{cursor: 'pointer'}} hover="true" >
{ renderToggle() }
{ renderActionIcons(proceduresToRender[i]) }
{ renderIdentifier(proceduresToRender.identifier ) }
{ renderStatus(proceduresToRender[i].status)}
{ renderCategory(proceduresToRender[i].categoryDisplay)}
{ renderCode(proceduresToRender[i].code)}
{ renderCodeDisplay(proceduresToRender[i].codeDisplay)}
{ renderSubject(proceduresToRender[i].subject)}
{ renderSubjectReference(proceduresToRender[i].subjectReference)}
{ renderPerformer(proceduresToRender[i].performerDisplay)}
{ renderBodySite()}
{ renderPerformedStart(proceduresToRender[i].performedStart)}
{ renderPerformedEnd(proceduresToRender[i].performedEnd)}
{ renderNotes(proceduresToRender[i].notesCount)}
{ renderBarcode(proceduresToRender[i]._id)}
{ renderActionButton(proceduresToRender[i]) }
</TableRow>
);
}
}
- Audit all props referenced in the function, and make sure they are registered as PropTypes.
ConditionsTable.propTypes = {
data: PropTypes.array,
conditions: PropTypes.array,
query: PropTypes.object,
paginationLimit: PropTypes.number,
disablePagination: PropTypes.bool,
renderCheckboxes: PropTypes.bool,
renderActionIcons: PropTypes.bool,
renderIdentifier: PropTypes.bool,
renderPatientName: PropTypes.bool,
renderDates: PropTypes.bool,
onCellClick: PropTypes.func,
onRowClick: PropTypes.func,
onMetaClick: PropTypes.func,
onRemoveRecord: PropTypes.func,
onActionButtonClick: PropTypes.func,
showActionButton: PropTypes.bool,
actionButtonLabel: PropTypes.string,
rowsPerPage: PropTypes.number,
dateFormat: PropTypes.string,
showMinutes: PropTypes.bool,
renderEnteredInError: PropTypes.bool
};
ConditionsTable.defaultProps = {
renderCheckboxes: true,
renderActionIcons: true,
renderIdentifier: true,
renderPatientName: true,
renderDates: true,
rowsPerPage: 10,
dateFormat: "YYYY-MM-DD",
showMinutes: false
}
- Make sure the export function is correct.
export default MyFunctionalWidget;
- Remove any commented code that has been confirmed to be working in refactor location.
- Compile and debug.
General Pattern Cleanups
- Make sure to replace any
.bind('this')
statements with.bind(this)