Array to excel React - reactjs

I am a little new to this, I am trying to pass my data array to an excel, but nothing works for me so far
I have an example
import {ExcelFile, ExcelSheet} from "react-export-excel";
const [listSelectNewTable, setListSelectNewTable] = useState([])
const DataExcel = [
{
columns: [
{ value: "Clave Cliente", widthPx: 50 },
{ value: "Nobre Cliente", widthCh: 20, widthCh: 20 },
{ value: "Clave Articulo", widthPx: 60},
{ value: "Nombre Cliente", widthPx: 60},
{ value: "Clave Unidad", widthPx: 60},
{ value: "Precio", widthPx: 60},
],
data: [listSelectNewTable],
}
];
class Download extends React.Component {
render() {
return (
<ExcelFile>
<ExcelSheet dataSet={DataExcel} name="Price"/>
</ExcelFile>
);
}
Can someone tell me what I'm doing wrong?

Before we begin:
Well obviously you're not going to have any data, if you're not passing any to the dataSet:
const [listSelectNewTable, setListSelectNewTable] = useState([])
// listSelectNewTable = []
Also you are attempting to call useState outside of a reactjs component and then proceed to use a class component later down the line. Without meaning to sound condescending, you really should study up on when to use life-cycle methods and when to use hooks (hint: it's either one or the other), because it's outside of the scope of this question / answer, but your code will never work if you don't put the time in to at least understand the basics.
The remainder of my answer assumes you at least fixed that.
The Problem:
Furthermore, even if it had any data, it would result in an error, because your data is incorrectly typed:
The dataSet prop type is defined as:
dataSet: Array<ExcelSheetData>
considering the following package type definitions:
interface ExcelSheetData {
xSteps?: number;
ySteps?: number;
columns: Array<string>;
data: Array<ExcelCellData>;
}
type ExcelCellData = ExcelValue | ExcelCell;
type ExcelValue = string | number | Date | boolean;
interface ExcelCell {
value: ExcelCell;
// [Note]: this is actually incorrectly typed in the package itself.
// Should be value: ExcelCellValue instead
style: ExcelStyle;
}
Essentially, if you had typescript enabled (which by the way I'd advise you to do), you'd see you're attempting to pass incorrect data-type to the dataSet property.
What you should be passing to dataSet prop is
Array<ExcellCellData>
// expecting: Array<ExcelValue | ExcelCell> (eg. ['A', 22])
What you are actually passing to to dataSet prop is
Array<Array<never>>
// received: [[]]
// [] = Array<never>
As you can see, the Array<Array<>> wraps it twice, meaning you are passing an unnecessarily wrapped array there.
Solution:
Now that we know the cause, let's fix it:
const DataExcel = [
{
columns: [
{ value: "Clave Cliente", widthPx: 50 },
{ value: "Nobre Cliente", widthCh: 20, widthCh: 20 },
{ value: "Clave Articulo", widthPx: 60},
{ value: "Nombre Cliente", widthPx: 60},
{ value: "Clave Unidad", widthPx: 60},
{ value: "Precio", widthPx: 60},
],
data: listSelectNewTable, // previously was [listSelectNewTable]
// Note we removed the extra wrapping nested array [] ˆ
},
];
Now you are passing your data correctly - but obviously you're not going to have anything inside the excel sheet barring the columns unless you pass any actual data instead of the empty array.
Final note (not necessary for answer):
Also in general I'd recommend using packages that have higher star ratings on their github page (this one has 65) and is only a fork of a an already existing package, especially for more complex stuff like excel exporting. While there sometimes truly are hidden diamonds in the rough, most of the time the stars are a good indicator of the quality of the package.
The fact that I found a typing error inside my own answer after a random glance at the package for the first time makes me fear how many other errors are actually in it, unless you know and trust the author or it's a package for a very niche thing.
Because the recursive
interface ExcelCell {
value: ExcelCell; // should be ExcelCellValue
style: ExcelStyle;
}
makes positively no sense and would just require you to recursively state the object interface definition ad infinitum

There are a couple of things that you need to change:
You cannot use the useState hook with a class component. To use this hook, change your component to a functional one.
You are not using the library correctly. Here is a good example of how to use it: https://github.com/rdcalle/react-export-excel/blob/master/examples/simple_excel_export_01.md
Specifically, you did not add any column to the sheet.
I'm guessing you want to provide dynamic data for the component (it should not always display the same stuff). If you want to do this, you should inject the data at the parent's component level.
So for your class:
import React, { useState } from 'react';
import ReactExport from "react-export-excel";
const ExcelFile = ReactExport.ExcelFile;
const ExcelSheet = ReactExport.ExcelFile.ExcelSheet;
const ExcelColumn = ReactExport.ExcelFile.ExcelColumn;
const Download = ({ data }) => {
const [listSelectNewTable, setListSelectNewTable] = useState(data);
return (
<ExcelFile>
<ExcelSheet data={listSelectNewTable} name="Price">
{
listSelectNewTable[0].columns.map(d =>
<ExcelColumn label={d.value} value={0} />)
}
</ExcelSheet>
</ExcelFile>
); }
export default Download;
And for the parent:
<Download data={DataExcel} />
With the data you had originally in your class in the parent, so that you can inject it into your component.

Related

Formatting data from a database in TypeScript

I am having trouble with writing the following method on an Angular class. I don't know how to add values from arrayId to the data array in the series object.
getChartOptions() {
const arrayId=[];
const arrayTimestamp=[];
const arrayData=[];
const arrayData2=[];
var i=0;
this.httpClient.get<any>('http://prod.kaisens.fr:811/api/sleep/?deviceid=93debd97-6564-454b-be33-35bd377a2563&startdate=1612310400000&enddate=1614729600000').subscribe(
reponse => {
this.sleeps = reponse;
this.sleeps.forEach(element => { arrayId.push(this.sleeps[i]._id),arrayTimestamp.push(this.sleeps[i].timestamp),arrayData.push(this.sleeps[i].data[18]),arrayData2.push(this.sleeps[i].data[39])
i++;
});
console.log(arrayId);
console.log(arrayTimestamp);
console.log(arrayData);
console.log(arrayData2);
}
)
return {
series: [{
name: 'Id',
data: [35, 65, 75, 55, 45, 60, 55]
}]
}
}
I have two main pieces of advice for you:
Know the types of that data that you are dealing with.
Get familiar with all of the various array methods.
get<any>() is not a helpful type. If you understand what the response is then Typescript can help ensure that you are handling it correctly.
I checked out the URL and it looks like you get an array of objects like this:
{
"_id": 4,
"device_id": "93debd97-6564-454b-be33-35bd377a2563",
"timestamp": 1612310400000.0,
"data": "{'sleep_quality': 1, 'sleep_duration': 9}"
},
That data property is not properly encoded as an object or as a parseable JSON string. If you control this backend then you will want to fix that.
At first I thought that the data[18] and data[39] in your code were mistakes. Now I see that it as attempt to extract values from this malformed data. Accessing by index won't work if these numbers can be 10 or more.
The type that you have now is:
interface DataPoint {
_id: number;
device_id: string;
timestamp: number;
data: string;
}
The type that you want is:
interface DataPoint {
_id: number;
device_id: string;
timestamp: number;
data: {
sleep_quality: number;
sleep_duration: number;
}
}
You can type the request as this.httpClient.get<DataPoint[]>( and now you'll get autocomplete on the data.
It looks like what you are trying to do is basically to convert this from one array of rows to a separate array for each column.
You do not need the variable i because the .forEach loop handles the iteration. The element variable in the callback is the row that you want.
this.sleeps.forEach(element => {
arrayId.push(element._id);
arrayTimestamp.push(element.timestamp);
arrayData.push(element.data[18]);
arrayData2.push(element.data[39]);
});
The .forEach loop that you have now is efficient because it only loops through the array once. A .map for each column is technically less efficient because we have to loop through separately for each column, but I think it might make the code easier to read and understand. It also allows Typescript to infer the types of the arrays. Whereas with an empty array you would need to annotate it like const arrayId: number[] = [];.
const mapData = (response: DataPoint[]) => {
return [{
name: 'Id',
data: response.map(element => element._id)
}, {
name: 'Timestamp',
data: response.map(element => element.timestamp)
}, {
name: 'Sleep Quality',
data: response.map(element => parseInt(element.data[18])) // fix this
}, {
name: 'Sleep Duration',
data: response.map(element => parseInt(element.data[39])) // fix this
}]
}
The HTTP request is asynchronous. If you access your array outside of the subscribe callback then they are still empty. I'm not an angular person so this part I'm unsure of, but I think that you want to be updating a property on your class instead of returning the value?
Just follow this piece of code:
series: [{
name: 'Id',
data: arrayId
}]

Use (nested) prop value to reference another prop

I'm attempting to consume a nested prop value, then use that value to dynamically fetch another prop. This works for shallow (first level) props, but it fails when the prop is nested.
function DynamicContent(props) {
const content = props.data[props.children]
return <span>{content}</span>
}
Works (returns "My Post Title):
{
children: ["postTitle"],
data: {
postTitle: "My Post Title",
category: {
title: "The Category Title",
}
}
}
Does NOT work (returns undefined, expect "The Category Title"):
{
children: ["category.title"],
data: {
postTitle: "My Post Title",
category: {
title: "The Category Title",
}
}
}
You are doing great, you have done right. You need just a simple tweak.
Using eval you can evaluate as much nested as you want.
const path = 'props.data' + props.children;
const content = eval(path);
You cannot do this, but a simple solution is to make something like this :
children: ["category", "title"]
And inside your function :
const content = props.data[props.children[0]][props.children[1]]
I assume, is not better solution but you have an idea for achieve what you want, maybe you can split into two functions one for access property inside object, and the second for access to sub child into object

How to correctly update redux state in ReactJS reducer?

I have the following structure in my Redux data store:
{
filterData: {
22421: {
filterId: 22421,
selectedFilters: [
{
filterName: 'gender',
text: 'Male',
value: 'male'
},
{
filterName: 'gender',
text: 'female',
value: 'female'
}
]
}
22422: {
filterId: 22422,
selectedFilters: [
{
filterName: 'colour',
text: 'Blue',
value: 'blue'
},
{
filterName: 'animal',
text: 'sheep',
value: 'Sheep'
}
]
}
Can someone point me towards using the correct way to update the selectedFilters array without mutating the state directly? i.e. How can I add/remove elements in the selectedFilters array for a given filterId?
Generally it's done by using non mutating (ie. returning a new object, rather than modifying the existing one) operators and function:
spread operator (...) for objects and arrays (for additions and edits),
filtering, mapping and reduction for arrays (for edits and removals),
assigning for object (for edits and additions).
You have to do this on each level leading to the final one—where your change happens. In your case, if you want to change the selectedFilters on one of those objects you'll have to do something like that:
// Assuming you're inside a reducer function.
case SOME_ACTION:
// Returning the new state object, since there's a change inside.
return {
// Prepend old values of the state to this new object.
...state,
// Create a new value for the filters property,
// since—again—there's a change inside.
filterData: {
// Once again, copy all the old values of the filters property…
...state.filters,
// … and create a new value for the filter you want to edit.
// This one will be about removal of the filter.
22421: {
// Here we go again with the copy of the previous value.
...state.filters[22421],
// Since it's an array and we want to remove a value,
// the filter method will work the best.
selectedFilters:
state.filters[22421].selectedFilters.filter(
// Let's say you're removing a filter by its name and the name
// that needs to be removed comes from the action's payload.
selectedFilter => selectedFilter.name !== action.payload
)
},
// This one could be about addition of a new filter.
22422: {
...state.filters[22422],
// Spread works best for additions. It returns a new array
// with the old values being placed inside a new one.
selectedFilters: [
// You know the drill.
...state.filters[22422].selectedFilters,
// Add this new filter object to the new array of filters.
{
filterName: 'SomeName',
text: 'some text',
value: action.value // Let's say the value comes form the action.
}
]
},
}
}
This constant "copy old values" is required to make sure the values from nested objects are preserved, since the spread operator copies properties in a shallow manner.
const someObj = {a: {b: 10}, c: 20}
const modifiedObj = {...someObj, a: {d: 30}}
// modifiedObj is {a: {d: 30}, c: 20}, instead of
// {a: {b: 10, d: 30}, c: 20} if spread created a deep copy.
As you can see, this is a bit mundane to do. One solution to that problem would be to create some kind of nested reducers functions that will work on separate trees of the state. However, sometimes it's better not to reinvent the wheal and use tools that are already available that were made to solve those kind of problems. Like Immutable.js.
If you want to use a dedicated library for managing the immutable state (like suggested in another answer) take a look at Immer.
I find that this library is simpler to be used than Immutable.js (and the bundle size will be smaller too)

React - shape method

I am going through some React code while I am learning it. I have come across the shape method used in PropTypes, as I understood we use the shape method to validate if the given value is of certain shape, that we pass it as an argument. But, not sure what is it's purpose if we don't pass it any value that we want to validate, like in this example:
Field.propTypes = {
fields: PropTypes.shape().isRequired,
};
Can't we just have it like this:
Field.propTypes = {
fields: PropTypes.isRequired,
};
PropTypes is not for production usage it's used in the development environment and it's not the replacement of validation as some newbie people get it wrong.
For example, I had this dropdown component.
Dropdown.propTypes = {
// Notice this I define the shape of object here
item: PropTypes.shape({
value: PropTypes.string,
text: PropTypes.string,
}),
};
Developers who are supposed to use this component they are required to pass item object but the problem is this component will only work when the object has the particular shape like this.
{
value: "value1",
text: "text1",
},
So, what I did I defined the shape of the object that how item's object should be and if a someone pass wrong shaped object the component will throw the warning in console.

TypeScript Objects as Dictionary types as in C#

I have some JavaScript code that uses objects as dictionaries; for example a 'person' object will hold a some personal details keyed off the email address.
var people = {<email> : <'some personal data'>};
adding > "people[<email>] = <data>;"
getting > "var data = people[<email>];"
deleting > "delete people[<email>];"
Is it possible to describe this in Typescript? or do I have to use an Array?
In newer versions of typescript you can use:
type Customers = Record<string, Customer>
In older versions you can use:
var map: { [email: string]: Customer; } = { };
map['foo#gmail.com'] = new Customer(); // OK
map[14] = new Customer(); // Not OK, 14 is not a string
map['bar#hotmail.com'] = 'x'; // Not OK, 'x' is not a customer
You can also make an interface if you don't want to type that whole type annotation out every time:
interface StringToCustomerMap {
[email: string]: Customer;
}
var map: StringToCustomerMap = { };
// Equivalent to first line of above
In addition to using an map-like object, there has been an actual Map object for some time now, which is available in TypeScript when compiling to ES6, or when using a polyfill with the ES6 type-definitions:
let people = new Map<string, Person>();
It supports the same functionality as Object, and more, with a slightly different syntax:
// Adding an item (a key-value pair):
people.set("John", { firstName: "John", lastName: "Doe" });
// Checking for the presence of a key:
people.has("John"); // true
// Retrieving a value by a key:
people.get("John").lastName; // "Doe"
// Deleting an item by a key:
people.delete("John");
This alone has several advantages over using a map-like object, such as:
Support for non-string based keys, e.g. numbers or objects, neither of which are supported by Object (no, Object does not support numbers, it converts them to strings)
Less room for errors when not using --noImplicitAny, as a Map always has a key type and a value type, whereas an object might not have an index-signature
The functionality of adding/removing items (key-value pairs) is optimized for the task, unlike creating properties on an Object
Additionally, a Map object provides a more powerful and elegant API for common tasks, most of which are not available through simple Objects without hacking together helper functions (although some of these require a full ES6 iterator/iterable polyfill for ES5 targets or below):
// Iterate over Map entries:
people.forEach((person, key) => ...);
// Clear the Map:
people.clear();
// Get Map size:
people.size;
// Extract keys into array (in insertion order):
let keys = Array.from(people.keys());
// Extract values into array (in insertion order):
let values = Array.from(people.values());
You can use templated interfaces like this:
interface Map<T> {
[K: string]: T;
}
let dict: Map<number> = {};
dict["one"] = 1;
You can use Record for this:
https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkt
Example (A mapping between AppointmentStatus enum and some meta data):
const iconMapping: Record<AppointmentStatus, Icon> = {
[AppointmentStatus.Failed]: { Name: 'calendar times', Color: 'red' },
[AppointmentStatus.Canceled]: { Name: 'calendar times outline', Color: 'red' },
[AppointmentStatus.Confirmed]: { Name: 'calendar check outline', Color: 'green' },
[AppointmentStatus.Requested]: { Name: 'calendar alternate outline', Color: 'orange' },
[AppointmentStatus.None]: { Name: 'calendar outline', Color: 'blue' }
}
Now with interface as value:
interface Icon {
Name: string
Color: string
}
Usage:
const icon: SemanticIcon = iconMapping[appointment.Status]
You can also use the Record type in typescript :
export interface nameInterface {
propName : Record<string, otherComplexInterface>
}
Lodash has a simple Dictionary implementation and has good TypeScript support
Install Lodash:
npm install lodash #types/lodash --save
Import and usage:
import { Dictionary } from "lodash";
let properties : Dictionary<string> = {
"key": "value"
}
console.log(properties["key"])

Resources