I am new to redux-sagas and struggling to make the code below work.
This is the saga i am working with, it is supposed to read an xlsx file and dispatch an action when done. The action should be dispatched AFTER the file has been completely read, but it is not and i have no idea what i am doing wrong.
import { put } from 'redux-saga/effects';
import * as XLSX from 'xlsx';
import * as actionTypes from '../actions/actionTypes';
export function* importFileSaga(action) {
console.log('[importFileSaga]', action.fileToUpload);
const response = yield importFile(action.fileToUpload);
console.log('[importFileSaga]', response);
yield put ({type: actionTypes.SET_DATA, payload: response});
}
const importFile = file2upload => {
console.log('[importFile]', file2upload);
const reader = new FileReader();
reader.onload = (evt) => {
/* Parse data */
const bstr = evt.target.result;
const wb = XLSX.read(bstr, { type: 'binary' });
/* Get first worksheet */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = XLSX.utils.sheet_to_csv(ws, { header: 1 });
const processedData = processData(data);
console.log('[importFile]',processedData);
return processedData;
};
reader.readAsBinaryString(file2upload);
}
// process CSV data
const processData = dataString => {
const dataStringLines = dataString.split(/\r\n|\n/);
const headers = dataStringLines[0].split(/,(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/);
const list = [];
for (let i = 1; i < dataStringLines.length; i++) {
const row = dataStringLines[i].split(/,(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/);
if (headers && row.length === headers.length) {
const obj = {};
for (let j = 0; j < headers.length; j++) {
let d = row[j];
if (d.length > 0) {
if (d[0] === '"')
d = d.substring(1, d.length - 1);
if (d[d.length - 1] === '"')
d = d.substring(d.length - 2, 1);
}
if (headers[j]) {
obj[headers[j]] = d;
}
}
// remove the blank rows
if (Object.values(obj).filter(x => x).length > 0) {
list.push(obj);
}
}
}
// prepare columns list from headers
const columns = headers.map(c => ({
name: c,
selector: c,
}));
const processedData = {header: columns, data: list};
console.log('[processData]', processedData);
return processedData;
}
When a new file is uploaded, this is the sequence of output i receive in the console
[importFileSaga]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:7
[importFile]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:14
[importFileSaga] undefined importFile.js:9
SET_DATA reducers.js:31
undefined
TypeError: action.payload is undefined
The above error occurred in task importFileSaga
created by takeEvery(UPLOAD_FILE_START, importFileSaga)
created by watchImport
Tasks cancelled due to error:
takeEvery(UPLOAD_FILE_START, importFileSaga)
takeEvery(TEST_SAGA_INIT, importDeviceSaga) index.js:1
[processData]
Object { header: (50) […], data: (2) […] }
importFile.js:71
[importFile]
Object { header: (50) […], data: (2) […] }
This is unexpected...but i assume i might still be a bit confused. I'd expect simething like this
[importFileSaga]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:7
[importFile]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:14
[processData]
Object { header: (50) […], data: (2) […] }
importFile.js:71
[importFile]
Object { header: (50) […], data: (2) […] }
[importFileSaga] some data here importFile.js:9
SET_DATA reducers.js:31
I understand that this line of code
const response = yield importFile(action.fileToUpload);
means that the line below that is not executed until importFile is finished. Is that correct? How can i fix it?
-- EDIT --
Thanks to markerikson i modified the importFile function
const importFile = file2upload => {
return new Promise((resolve, reject) => {
console.log('[importFile]', file2upload);
const reader = new FileReader();
reader.onload = (evt) => {
/* Parse data */
const bstr = evt.target.result;
const wb = XLSX.read(bstr, { type: 'binary' });
/* Get first worksheet */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = XLSX.utils.sheet_to_csv(ws, { header: 1 });
const processedData = processData(data);
console.log('[importFile]',processedData);
//return processedData;
resolve(processedData);
};
reader.readAsBinaryString(file2upload);
});
}
and now it works like a charm!!
There's two problems here conceptually:
yield importFile()
is going to pass undefined
to the saga middleware, which will presumably keep right on goingYou'll need to restructure the code so that it waits until the reader logic is done.
One option might to be to manually create a Promise
inside of importFile
and return it, and resolve the promise at the end of the .onload
callback.