Is there possible to determine parameter is one of the given collection types? - reactjs

Imagine I have a Select component like this one. For simplicity, we ignore other props.
Now we Select component just receive two props:
set a set of string , Select component will render this set into a bunch of Option component.
defaultValue is initial selected option, it is one of set props's value.
I assume two case. The case one should work well without warning
<Select set={["peter", "tom", "jos"]} defaultValue={"tom"} />
Case two should be warning in editor, because 'ben' is not in set.
<Select set={["peter", "tom", "jos"]} defaultValue={'ben'} />
Can we do static check in case two instead of runtime check ?

You can define a type for such a component. It must be a generic component, that will infer the types of the string literals passed in as the set property:
function Select<T extends V[], V extends string>(p: { set: T, defaultValue: T[number] }) {
return <div></div>
}
let s = () => <Select set={["peter", "tom", "jos"]} defaultValue="tom" />
let s2 = () => <Select set={["peter", "tom", "jos"]} defaultValue="ben" /> // err
Without changing the definition of the original Select your only option would be to use a helper function to do the checking:
function Select(p: { set: string[], defaultValue: string }) {
return <div></div>
}
function SelectData<T extends V[], V extends string>(p: { set: T, defaultValue: T[number] }) : { set: string[], defaultValue: string } {
return p
}
let s = () => <Select {... SelectData({ set: ["peter", "tom", "jos"], defaultValue:"tom" })} />
let s2 = () => <Select {... SelectData({ set: ["peter", "tom", "jos"], defaultValue:"ben" })} /> // err

Related

How to pass a generic to a React component and use that generic within the component

I'm building an input component that takes a displayName that is a string and a value that should be a generic. It also takes a function as a prop that needs to fire and handle that generic value when the input is changed.
So, let's say MyInterfaceXYZ holds the displayName and value like so:
interface MyInterfaceXYZ<T> {
displayName: string
value: T
}
We then want this generic value to be used in our main props interface that wraps our first interface... Like the one below (but my syntax is wrong I think):
interface MyComponentProps<T> {
myFunctionThatTakesMyGeneric: (value: T) => void
options: MyInterfaceXYZ<T>[]
}
We then want to pass use this as our props in our react component - where we will receive a generic from an onChange listener.
// The syntax on the line below is totally invalid
const InputSelect: React.FC<MyComponentProps<T>> = ({
myFunctionThatTakesMyGeneric,
options,
}) => {
// `<T extends MyInterfaceXYZ>` requires 1 argument
// that I don't know how to pass here
function onChangeHandler<T extends MyInterfaceXYZ>(option: T) {
myFunctionThatTakesMyGeneric(option.value)
}
return (
<>
<myElement onChange={onChangeHandler}>
Blah blah!
</myElement>
</>
)
How can I modify the syntax so the above code is valid?
You don't actually need to make the onChangeHandler generic.
import React from 'react'
interface MyInterfaceXYZ<T> {
displayName: string
value: T
}
interface MyComponentProps<T> {
myFunctionThatTakesMyGeneric: (value: T) => void
options: MyInterfaceXYZ<T>[]
}
// The syntax on the line below is totally invalid
function InputSelect<T>({
myFunctionThatTakesMyGeneric,
options,
} : MyComponentProps<T>) {
// `<T extends MyInterfaceXYZ>` requires 1 argument
// that I don't know how to pass here
const onChangeHandler = (option: MyInterfaceXYZ<T>) => {
myFunctionThatTakesMyGeneric(option.value)
}
return (
<>
<div onChange={() => onChangeHandler(options[0])}>
Blah blah!
</div>
</>
)
}
<>
<InputSelect myFunctionThatTakesMyGeneric={(a:string) => {console.log(a);}} options={[{displayName: 'string', value:'1'}]} />
<InputSelect myFunctionThatTakesMyGeneric={(a:number) => {console.log(a);}} options={[{displayName: 'number', value:1}]} />
</>
Playground link
Also I changed the onChange to onChange={() => onChangeHandler(options[0])}, because you need to pass an option to the function. options[0] is probably not what you want.

react-select component options are not applied

I'm trying to give options props programmingly, but options do not appear.
I'm getting data from graphql server by using react-apollo-hooks, and try to give some data to options of react-select.
I checked data and they were fine. However, using data doesn't work (Select component shows "No Options"), while declaring array works.
I checked that I properly passed variables between files, and received right data.
Here's my code:
Conatiner.js
const generated_list = [];
const listGenerator = () => {
data.sample.map( single => {
const temp = { label: single.A, value: single.B };
generated_list.push(temp);
}
}
where data is
data.sample = [ { A: '1', B: '1', C: '1' }, { A: '2', B: '2', C: '2' } ]
Presenter.js
<Button onClick={() => listGenerator()} text={'Get Lists'} />
<Select options={generated_list} />
But following code worked as I expect.
const declared_list = { [label: '1', value: '1'],
[label: '2', value: '2'] };
...
<Button onClick={() => listGenerator()} text={'Get Lists'} />
<Select options={declared_list} />
I thought generated list differs from declared list, so I compared them by console log.
console.log(
typeof generated_list,
generated_list`,
JSON.stringify(generated_list),
typeof declared_list,
declared_list`,
JSON.stringify(declared_list),
JSON.stringify(generated_list) === JSON.stringify(declared_list),
);
All things were same, and === operator returned true.
I don't know what to check next, so any helps would be much appreciated.
As suggested by Sathish Kumar in the comments, changes on the array do not affect React to trigger a rerender of the component.
You should move the array into the state and update the array using this.setState (or if you are using a functional component, use Reacts new Hooks).
class MySelect extends Component {
state = {
options: [],
}
populateArray = () => {
var newOptions = [];
/* Populate the `newOptions` array */
this.setState({ options: newOptions });
}
render() {
return (<>
<Button onClick={this.populateArray}>Click me</Button>
<Select { ... } options={this.state.options} />
<>);
}
}

failed to get some values from dynamic multiple input fields using reactjs

i have implemented dynamic input fields that are created whenever u click add Product button but i dont why i can only pick itemAmount values but not input values for productId and itemQuantity.
iam using react v16.0.2
productItems: Array(2)
0: {"": "450000"} //productId is empty
1: {"": "670000"} //why does it only pick itemAmount but not the quantity and productId
i happen to use react-bootstarp for styling.
constructor(){
this.state = {
invoice_notes: '',
itemValues: [] //suppose to handle the productItems that are being added.
}
//for the product items
handleItemChange(i,evt){
const {name,value} = evt.target;
const items = [...this.state.itemValues]
items[i] = {[name]: value}
//this.setState({items}) //it just has empty values
this.setState({itemValues: items}) //works but empty for productId n amount
}
addProductItem(){
const item = {
productId: '',
itemQty:'',
itemAmount: ''
}
this.setState({itemValues: [...this.state.itemValues, item]})
}
createItemsUI(){
//Use #array.map to create ui (input element) for each array values.
//while creating the fields, use a remove button with each field,
return this.state.itemValues.map((elem,i)=> //recall arrow functions do not need return statements like {}
<div key = {i}>
<FormGroup controlId="formControlsSelect">
<ControlLabel>Product</ControlLabel>
<FormControl
componentClass="select"
name = "productId"
bsClass="form-control"
value = {this.state.itemValues[i].productId}
onChange = {this.handleItemChange.bind(this,i)}>
<option value = "" disabled>{'select the product'}</option>
{productOptions.map((item,i)=>{
return(
<option
key = {i} label= {item} value = {item}
>
{item}
</option>
)
})}
</FormControl>
</FormGroup>
<FormInputs
ncols={["col-md-4","col-md-4"]}
proprieties={[
{
label: "Quantity",
type: "number",
bsClass: "form-control",
defaultValue:this.state.itemValues[i].itemQty,
onChange: this.handleItemChange.bind(this,i)
},
{
label: "Amount",
type: "number",
bsClass: "form-control",
defaultValue: this.state.itemValues[i].itemAmount,
onChange: this.handleItemChange.bind(this,i)
}
]}
/>
<Button onClick = {this.removeProductItem.bind(this,i)}>Remove</Button>
</div>
)
}
}
please i have read many similar questions so i think iam on the right track.. I prefer to be inquisitive
So after #steve bruces comment, i made some changes to the handleItemChange function but i happen to get almost the same behaviour of not picking the productId and itemQty input values.
handleItemChange(i,evt){
const name = evt.target.getAttribute('name');
console.log('let see name', name) //i getting the name of the atrribute --> productId, itemQty, ItemAmount
const items = [...this.state.itemValues]
items[i] = {[name]: value}
this.setState({itemValues: items})
}
This is what happens if i try using setState({items}) as suggested by similar questions
items[i] = {[name]: value}
this.setState({items})
RESULT of the productItems when u click the submit button
productItems: Array(2)
0: {productId: "", itemQty: "", itemAmount: ""}
1: {productId: "", itemQty: "", itemAmount: ""}
but when i use this.setState({itemValues: items}) atleast i can get the last value of the itemAmount
productItems: Array(2)
0: {itemAmount: "25000"}
1: {itemAmount: "45000"}
length: 2
Any help is highly appreciated
I believe your error is coming up in your handleItemChange(evt,i) function.
It looks like you want to return "placeholder" by destructuring evt.target. This does not work because evt.target doesn't understand "name". I think you want to try evt.target.attribute('name') to return the name. evt.target.value will return the value of that input and evt.target.name does not exist so it returns undefined.
A small tip. You no longer have to bind functions in your constructor. You can can get rid of your constructor and update your functions to be arrow functions.
For example this:
handleItemChange(i,evt){
const {name,value} = evt.target;
const items = [...this.state.itemValues]
items[i] = {[name]: value}
//this.setState({items}) //it just has empty values
this.setState({itemValues: items}) //works but empty for productId n amount
becomes this:
handleItemChange = (i,evt) => {
// const {name,value} = evt.target;
//updated that to this here
const { value } = evt.target;
const name = evt.target.attribute("name");
...
And the call to in your onChange would look like this:
onChange = {() => { this.handleItemChange(evt,i)}
you must add the () => in your onChange so the handleItemChange is not invoked on every render before it's actually changed. It's invoked the moment you pass the params evt, and i. So the () => function is invoked onChange and then handleItemChange(evt,i) is invoked.
Hope this helps.

React select to render values from array

Using https://github.com/JedWatson/react-select. I have an method that will loop through an array that I would like to input into the value for react-select picker. Any ideas why const value is invalid?
renderList (thelist, i) {
const { selectedOption } = this.state;
console.log(thelist);
// this throws me error
const value = {
value: {thelist.name},
label: {thelist.name}
}
return (
<div>
<Select
name="form-field-name"
onChange={this.handleChange}
value={value}
/>
</div>
);
You don't need the curly braces for the object values. Just do:
const value = {
value: thelist.name,
label: thelist.name
}
You only use curly braces when declaring an object or when you need to tell React to interpret something inside render() as plain JavaScript instead of a string.
However, you probably want to define an options prop too to your select component. value only gives the dropdown a selected value, but options is what actually defines... well, the options. The options can be multiple, so we define them in an array of objects.
So do:
options={[
{
value: thelist.name1,
label: thelist.name1
},
{
value: thelist.name2,
label: thelist.name2
}
]}

Angular 1.X ng-options equivalent using React

Angular 1.X has ng-options for the choices in a select dropdown, each item being an object. In plain HTML, the value of an option can only be a string. When you select one option in Angular, you can see the actual selected object while in plain html, you can only get that string value.
How do you do the equivalent of that in React (+Redux)?
I came up with a solution that does not use JSON.stringify / parse for the value of the select React element nor does it use the index of the array of choice objects as the value.
The example is a simple select dropdown for a person's gender -- either male or female. Each of those choices is an actual object with id, text, and value properties. Here is the code:
MySelect component
import React, { Component } from 'react';
class MySelect extends Component {
onGenderChange = (event) => {
// Add the second argument -- the data -- and pass it along
// to parent component's onChange function
const data = { options: this.props.options };
this.props.onGenderChange(event, data);
}
render() {
const { options, selectedOption } = this.props;
// Goes through the array of option objects and create an <option> element for each
const selectOptions = options.map(
option => <option key={option.id} value={option.value}>{option.text}</option>
);
// Note that if the selectedOption is not given (i.e. is null),
// we assign a default value being the first option provided
return (
<select
value={(selectedOption && selectedOption.value) || options[0].value}
onChange={this.onGenderChange}
>
{selectOptions}
</select>
);
}
}
App component that uses MySelect
import _ from 'lodash';
import React, { Component } from 'react';
class App extends Component {
state = {
selected: null
}
onGenderChange = (event, data) => {
// The value of the selected option
console.log(event.target.value);
// The object for the selected option
const selectedOption = _.find(data.options, { value: parseInt(event.target.value, 10) });
console.log(selectedOption);
this.setState({
selected: selectedOption
});
}
render() {
const options = [
{
id: 1,
text: 'male',
value: 123456
},
{
id: 2,
text: 'female',
value: 654321
}
];
return (
<div>
<label>Select a Gender:</label>
<MySelect
options={options}
selectedOption={this.state.selected}
onGenderChange={this.onGenderChange}
/>
</div>
);
}
}
Lodash is used to look up the choice object in the array of choice objects inside the onGenderChange function in the App component. Note that the onChange passed to the MySelect component requires two arguments -- an extra data argument is added in order to be able to access the choice objects ("options"). With that, you can just set the state (or call an action creator if using Redux) with the choice object for the selected option.
I run into this same situation while migrating a angular 1 app to react. And I felt it is a much needed feature that I couldn't find so here I leave my implementation of NgOption in react using bootstrap (you can can change that to whatever you are using) for anybody that's missing the goo'old angular 1:
import React from 'react';
import { Form, InputGroup } from 'react-bootstrap';
interface props<T, U>{
options: Array<T>,
selected?: U,
onSelect: (value: T) => any,
as: (value: T) => string,
trackBy: (value: T) => U,
disabled?: boolean
}
type state = {}
export default class NgOptions extends React.Component<props<any, any>, state> {
componentDidMount() {
if (this.props.selected) {
this.props.onSelect(this.props.options.find((o) => this.props.trackBy(o)===this.props.selected))
}
}
public render() {
return ( <InputGroup>
<Form.Control as="select"
disabled={this.props.disabled}
onChange={(e) => this.props.onSelect(this.props.options.find((o) => this.props.trackBy(o)===e.target.value))}>
{this.props.options.map( option =>
<option key={this.props.trackBy(option)}
selected={this.props.selected===this.props.trackBy(option)}
value={this.props.trackBy(option)}>{this.props.as(option)}</option>
)}
</Form.Control>
</InputGroup>
)
}
}

Resources