React - shape method - reactjs

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.

Related

Array to excel React

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.

How to use interface to type function argument in Flow

I'm trying to implement a React component that contains a list of options and shows their id and name. I want this component to be reusable so I define an interface Option to ensure the required fields are always provided.
And here comes the issue: if I pass any type with more fields than those 2 { id, name, /* anything */}, Flow complains. Is it not possible to use interfaces in Flow like this?
Here's the minimal relevant code:
interface Option {
id: string,
name: string
}
const List = (options: Option[]) => {
options.forEach(o => null)
}
type ImplementsOption = {
id: string,
name: string,
description: string
}
const plans: ImplementsOption[] = []
List(plans)
Error:
Cannot call List with plans bound to options because property description is missing in Option 1 but exists in ImplementsOption [2] in array element.
Trying with casting:
List((plans: Option[]))
And also with classes:
class ComplexOption implements Option {
id: string
name: string
}
const complexOptions: ComplexOption[] = []
List(complexOptions)
Nothing seems to work!
There is a playground with all these snippets already.
Imagine we had a list of ImplementsOption: [{ id: 'id', name: 'name', description: 'description' }, ...]. Now we pass it into the List function, which has the signature Option[] => void. This is totally valid from the point of view of List since ImplementOption is a supertype of Option. However, there is no guarantee in the List function that it won't modify the list that is passed in. Thus, the function could add an element of type Option to the list, which would be valid for a Option[] but invalid for a ImplementsOption[].
To fix this, you can type plans as a $ReadOnlyArray<Option>, which tells Flow that the List function will not modify the elements (try flow).
Reference issues #4425, #4483, or #5206 for more information.

Is there a way I can use uri instead of require for React Native Prop types?

When I try to use uri, the image does not show and I want to avoid local images because I can't dynamically use require.
static propTypes = {
...ViewPropTypes,
initialPage: PropTypes.number,
pager: PropTypes.instanceOf(IndicatorViewPager),
tabs: PropTypes.arrayOf(PropTypes.shape({
text: PropTypes.string,
iconSource: Image.propTypes.source, <-- Here is the problem
selectedIconSource: Image.propTypes.source
})).isRequired,
}
Is there a way I can make this accept uri sources? Also here is the rest of my code:
let tabs = [{
text: `${this.state.tags.toLowerCase()}`,
iconSource: 'https://www.exmaple.com/img/cookies.png',
selectedIconSource: require("../img/bluep.png")
}];
I think you are mixing a couple of things. An Image source can be a require() or a uri, but the uri is not just a string you can pass to the source prop of the Image. It needs to be an object that has a uri string prop, like this:
<Image source={{uri: 'http://myimageurl.com'}}/>
Second, with prop types you are just defining which props validation you want to use at Debug time. It has nothing to do with your Image not showing up. If you send a wrong prop, it will show you a Yellow warning screen.
Lastly, your problem is that you are just sending the prop as a string, and you need to send the object.
So, without seeing much of the rest of the code, you should be fine changing this part:
let tabs = [{
text: `${this.state.tags.toLowerCase()}`,
iconSource: {uri: 'https://www.exmaple.com/img/cookies.png'},
selectedIconSource: require("../img/bluep.png")
}];

React PropTypes - How to make a shape optional with its fields required?

I have a component that receives a badge prop (see example down below). The badge is optional, but once it is used I want it to have some required fields inside it. I've tried the following:
Component.propTypes = {
badge: PropTypes.shape({
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
}),
}
It works, but I get this warning on Chrome when trying to use it without the badge:
Warning: Failed prop type: The prop badge.src is marked as required
in Component, but its value is null.
What is the proper way of doing this?
Component usage example:
badge={{ src: 'usa.png', alt: 'United States' }}
badge not supplied
This is the correct way to do this. I was very intrigued by how this wouldn't work, so I pasted it in a CodePen (https://codepen.io/Oblosys/pen/xLvxrd) and it works just as expected. (Open a console, and uncomment the failing one to see a prop-type error)
Something else must be going wrong in your code, and at some point you render a Component with a badge property object that has src: null. Maybe before data from an api request has arrived? With some console logging in Component and its parent, you should be able to find the culprit.

ReactJS: where to put validation logic in a form with "nested" composite components?

I'm new to ReactJS and am unsure about the best place to put validation logic that is needed both by nested child components in my form, and the overall "parent" form component itself. Here is a over-simplified example that illustrates my question...
I have a object like this that represents a pet owner:
{
name: 'Jon Arbuckle',
pets: [
{ name: 'Odie', type: 'dog' },
{ name: 'Garfield', type: 'cat' }
]
}
I'm using a composite component called <PetOwnerForm> to render a form for editing this data. <PetOwnerForm> renders something like this:
<input type="text" value={name} />
<PetList value={petOwner.pets} />
<PetList> is a composite component that renders this:
<PetListItem value={this.props.value[i]} /> // Render this for each pet...
// buttons for adding/deleting pets
<PetListItem> renders something like this:
<input type="text" value={this.props.value.name} />
<PetTypePicker value={this.props.value.type} />
Lastly, <PetTypePicker> renders a <select> with <option>s for pet types.
<PetTypePicker> needs to know how to validate the selected type so it can display an inline error message (e.g., ensure that a value is selected).
However, <PetOwnerForm> also needs to know how to validate the pet type because it needs to know how to validate the entire object (on load, each time the form is updated, and before submitting the data back to the server). If any field is invalid, the "Save" button should be disabled.
So where, for example, should the "is a valid pet type selected?" logic go? (Bear in mind that this is a trivial example; in reality I have many fields like this and nested composite components).
The options I see so far are:
A) Replicate the validation logic for pet type (or whatever field) both in <PetOwnerForm> and <PetTypePicker>. This might just be a matter of calling the same, shared validation function in both places:
//PetOwnerForm.js:
validate(petOwnerObj) {
Util.isPetTypeValid(petOwnerObj.pets[i]) // for each pet
// validate the other properties in petOwnerObj...
}
//PetTypePicker.js:
validate(petType) {
Util.isPetTypeValid(petType)
}
B) Use custom PetOwner, Pet, and PetType models that have their own validators. This way you can always ask a model to validate itself, regardless of where it is. Maybe this would look something like this:
{
name: { value: 'Jon Arbuckle', isValid: ()=>{...} },
pets: [
{
name: { value: 'Garfield', isValid: ()=>{...} },
type: { value: 'cat', isValid: ()=>{...} }
},
...
]
}
C) Modify PetOwnerForm.js go recurse the pet owner object, validating each value, and setting an 'errors' property that child components can reference, resulting in an object like this:
{
name: { value: 'Jon Arbuckle asdfasdfasdf^^', errors: ['Too many characters', 'Contains invalid character']] },
pets: [
{
name: { value: '', errors: ['Required value missing'] },
type: { value: 'tree', errors: ['Invalid pet type'] }
},
...
]
}
Which option is recommended for React apps (or is there another option)?
It's a nice elaborate question. This question is not specific to ReactJS applications. It applies to all frameworks that follow component model.
Following are my recommendations:
Differentiate between action driven validation and data format validation.
Low level components are aware of data format they accept, so they must validate for it. For example, postal-code, email, phone, SSN etc have fixed formats and their corresponding components must validate for the right input format.
Low level components are not aware of actions being performed on the overall data. For example, selection of pet-owner-type can be mandatory for "create" pet-owner action but can be optional for "save draft" action. So, low level components which are not aware of end action must not perform action driven validations.
Action driven validation must be performed by the higher level component aware of action, for example PetOwnerForm. Such validation result must be notified to low level components so that they can display appropriate errors. Every low level component must have an error state to support it.

Resources