├── ExcelVideo.gif ├── README.md ├── package.json ├── public └── index.html └── src ├── components └── excelPage.js ├── index.js ├── styles.css └── utils └── editable.js /ExcelVideo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abiodunsulaiman694/excel-app/7b97ac4006a2a2dfd18b67042b32c3af1b64ec3e/ExcelVideo.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## An excel import app with React that allows for editing, deleting and adding rows to the imported data 2 | 3 | ![Excel Import Demo](ExcelVideo.gif) 4 | 5 | To run: `npm run start` 6 | 7 | To build: `npm run build` 8 | 9 | Working sample on [Codesandbox](https://codesandbox.io/s/github/abiodunsulaiman694/excel-app/tree/master/) 10 | 11 | [Article to explain the code on abiodun.dev](https://abiodun.dev/import-spreadsheets-or-excel-in-your-react-component/) 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "excel-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "antd": "3.20.1", 9 | "react": "16.8.6", 10 | "react-dom": "16.8.6", 11 | "react-excel-renderer": "1.1.0", 12 | "react-scripts": "3.0.1" 13 | }, 14 | "devDependencies": { 15 | "typescript": "3.3.3" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | React App 24 | 25 | 26 | 27 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/components/excelPage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Table, Button, Popconfirm, Row, Col, Icon, Upload } from "antd"; 3 | import { ExcelRenderer } from "react-excel-renderer"; 4 | import { EditableFormRow, EditableCell } from "../utils/editable"; 5 | 6 | export default class ExcelPage extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | cols: [], 11 | rows: [], 12 | errorMessage: null, 13 | columns: [ 14 | { 15 | title: "NAME", 16 | dataIndex: "name", 17 | editable: true 18 | }, 19 | { 20 | title: "AGE", 21 | dataIndex: "age", 22 | editable: true 23 | }, 24 | { 25 | title: "GENDER", 26 | dataIndex: "gender", 27 | editable: true 28 | }, 29 | { 30 | title: "Action", 31 | dataIndex: "action", 32 | render: (text, record) => 33 | this.state.rows.length >= 1 ? ( 34 | this.handleDelete(record.key)} 37 | > 38 | 43 | 44 | ) : null 45 | } 46 | ] 47 | }; 48 | } 49 | 50 | handleSave = row => { 51 | const newData = [...this.state.rows]; 52 | const index = newData.findIndex(item => row.key === item.key); 53 | const item = newData[index]; 54 | newData.splice(index, 1, { 55 | ...item, 56 | ...row 57 | }); 58 | this.setState({ rows: newData }); 59 | }; 60 | 61 | checkFile(file) { 62 | let errorMessage = ""; 63 | if (!file || !file[0]) { 64 | return; 65 | } 66 | const isExcel = 67 | file[0].type === "application/vnd.ms-excel" || 68 | file[0].type === 69 | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; 70 | if (!isExcel) { 71 | errorMessage = "You can only upload Excel file!"; 72 | } 73 | console.log("file", file[0].type); 74 | const isLt2M = file[0].size / 1024 / 1024 < 2; 75 | if (!isLt2M) { 76 | errorMessage = "File must be smaller than 2MB!"; 77 | } 78 | console.log("errorMessage", errorMessage); 79 | return errorMessage; 80 | } 81 | 82 | fileHandler = fileList => { 83 | console.log("fileList", fileList); 84 | let fileObj = fileList; 85 | if (!fileObj) { 86 | this.setState({ 87 | errorMessage: "No file uploaded!" 88 | }); 89 | return false; 90 | } 91 | console.log("fileObj.type:", fileObj.type); 92 | if ( 93 | !( 94 | fileObj.type === "application/vnd.ms-excel" || 95 | fileObj.type === 96 | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 97 | ) 98 | ) { 99 | this.setState({ 100 | errorMessage: "Unknown file format. Only Excel files are uploaded!" 101 | }); 102 | return false; 103 | } 104 | //just pass the fileObj as parameter 105 | ExcelRenderer(fileObj, (err, resp) => { 106 | if (err) { 107 | console.log(err); 108 | } else { 109 | let newRows = []; 110 | resp.rows.slice(1).map((row, index) => { 111 | if (row && row !== "undefined") { 112 | newRows.push({ 113 | key: index, 114 | name: row[0], 115 | age: row[1], 116 | gender: row[2] 117 | }); 118 | } 119 | }); 120 | if (newRows.length === 0) { 121 | this.setState({ 122 | errorMessage: "No data found in file!" 123 | }); 124 | return false; 125 | } else { 126 | this.setState({ 127 | cols: resp.cols, 128 | rows: newRows, 129 | errorMessage: null 130 | }); 131 | } 132 | } 133 | }); 134 | return false; 135 | }; 136 | 137 | handleSubmit = async () => { 138 | console.log("submitting: ", this.state.rows); 139 | //submit to API 140 | //if successful, banigate and clear the data 141 | //this.setState({ rows: [] }) 142 | }; 143 | 144 | handleDelete = key => { 145 | const rows = [...this.state.rows]; 146 | this.setState({ rows: rows.filter(item => item.key !== key) }); 147 | }; 148 | handleAdd = () => { 149 | const { count, rows } = this.state; 150 | const newData = { 151 | key: count, 152 | name: "User's name", 153 | age: "22", 154 | gender: "Female" 155 | }; 156 | this.setState({ 157 | rows: [newData, ...rows], 158 | count: count + 1 159 | }); 160 | }; 161 | 162 | render() { 163 | const components = { 164 | body: { 165 | row: EditableFormRow, 166 | cell: EditableCell 167 | } 168 | }; 169 | const columns = this.state.columns.map(col => { 170 | if (!col.editable) { 171 | return col; 172 | } 173 | return { 174 | ...col, 175 | onCell: record => ({ 176 | record, 177 | editable: col.editable, 178 | dataIndex: col.dataIndex, 179 | title: col.title, 180 | handleSave: this.handleSave 181 | }) 182 | }; 183 | }); 184 | return ( 185 | <> 186 |

Importing Excel Component

187 | 188 | 197 |
198 |
Upload User Data
199 |
200 | 201 | 202 | 208 | Sample excel sheet 209 | 210 | 211 | 216 | {this.state.rows.length > 0 && ( 217 | <> 218 | {" "} 227 | 235 | 236 | )} 237 | 238 |
239 |
240 | this.setState({ rows: [] })} 244 | multiple={false} 245 | > 246 | 249 | 250 |
251 |
252 | "editable-row"} 255 | dataSource={this.state.rows} 256 | columns={columns} 257 | /> 258 | 259 | 260 | ); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "antd/dist/antd.css"; 4 | import ExcelPage from "./components/excelPage"; 5 | 6 | function App() { 7 | return ( 8 | <> 9 | 10 | 11 | ); 12 | } 13 | 14 | const rootElement = document.getElementById("root"); 15 | ReactDOM.render(, rootElement); 16 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/editable.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Form, Input} from 'antd' 3 | 4 | const EditableContext = React.createContext(); 5 | 6 | const EditableRow = ({ form, index, ...props }) => ( 7 | 8 | 9 | 10 | ); 11 | 12 | export const EditableFormRow = Form.create()(EditableRow); 13 | 14 | export class EditableCell extends React.Component { 15 | state = { 16 | editing: false, 17 | }; 18 | 19 | toggleEdit = () => { 20 | const editing = !this.state.editing; 21 | this.setState({ editing }, () => { 22 | if (editing) { 23 | this.input.focus(); 24 | } 25 | }); 26 | }; 27 | 28 | save = e => { 29 | const { record, handleSave } = this.props; 30 | this.form.validateFields((error, values) => { 31 | if (error && error[e.currentTarget.id]) { 32 | return; 33 | } 34 | this.toggleEdit(); 35 | handleSave({ ...record, ...values }); 36 | }); 37 | }; 38 | 39 | renderCell = form => { 40 | this.form = form; 41 | const { children, dataIndex, record, title } = this.props; 42 | const { editing } = this.state; 43 | return editing ? ( 44 | 45 | {form.getFieldDecorator(dataIndex, { 46 | rules: [ 47 | { 48 | required: true, 49 | message: `${title} is required.`, 50 | }, 51 | ], 52 | initialValue: record[dataIndex], 53 | })( (this.input = node)} onPressEnter={this.save} onBlur={this.save} />)} 54 | 55 | ) : ( 56 |
61 | {children} 62 |
63 | ); 64 | }; 65 | 66 | render() { 67 | const { 68 | editable, 69 | dataIndex, 70 | title, 71 | record, 72 | index, 73 | handleSave, 74 | children, 75 | ...restProps 76 | } = this.props; 77 | return ( 78 | 85 | ); 86 | } 87 | } --------------------------------------------------------------------------------
79 | {editable ? ( 80 | {this.renderCell} 81 | ) : ( 82 | children 83 | )} 84 |