├── 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 | 
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 |
79 | {editable ? (
80 | {this.renderCell}
81 | ) : (
82 | children
83 | )}
84 | |
85 | );
86 | }
87 | }
--------------------------------------------------------------------------------