Dropdown with multiple selection limit - javascript

I relatively new to React and Semantic UI as well.
There is a component called Dropdown with a props multiple and selection, which allows to select multiple items.
On the output my state looks like this:
const selectedItems = [
{key: 1, value: 1, text: 'Item 1'},
{key: 2, value: 2, text: 'Item 2'},
{key: 3, value: 3, text: 'Item 3'},
];
How can I do setup limit of N amount of elements?
Many thanks

The Semantic UI React Dropdown option provides a function called as onAddItem. You will have to use the value data key and do something like this:
const onAddItem = (event, data) => {
1.Fetch the state of the selected values, stored in the value key
2. Check if the limit is greater than 2
3. If the condition is met, then add
4. Else, show an error
}
Documentation Link

Well according to https://react.semantic-ui.com/modules/dropdown#dropdown-example-multiple-selection you need to create controlled component, which means you will bind value={this.state.selectedItems} then you will bind onChange={(e,data) => { this.handleChange(e,data )} and in your code
onChange (e, data) {
const currentItems = this.state.selectedItems
if (currentItems.length <= MAX_SELECTION ) {
currentItems.push(data)
this.setState({
selectedItems: currentItems
})
}
}
this will crate controlled component which will allows you to control state by yourself, and you will limit changing state, probably you will need to also handle removing items from state inside this onChange event.

I would like to suggest another approach.
set useState directly to the dropdown value.
import React, { useState } from 'react';
import { Dropdown } from 'semantic-ui-react';
const MAX_FRUITS_SELECTION = 3;
const FruitsSelect = () => {
const [fruitId, setFruitId] = useState([]);
const optionsFRUITSFake = [
{ key: 1, value: 1, text: 'Orange' },
{ key: 2, value: 2, text: 'Lemon' },
{ key: 3, value: 3, text: 'Apple' },
{ key: 4, value: 4, text: 'Banana' },
{ key: 5, value: 5, text: 'Melon' },
{ key: 6, value: 6, text: 'Pineapple' }
];
const handleDropFilterFruit = (e: any, data?: any) => {
if (data.value.length <= MAX_FRUITS_SELECTION) {
setFruitId(data.value);
}
};
return (
<Dropdown
placeholder="Select Fruits"
onChange={handleDropFilterFruit}
value={fruitId}
fluid
multiple
selectOnNavigation={false}
search
selection
options={optionsFRUITSFake}
/>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<FruitsSelect />
</React.StrictMode>,
rootElement
);
<!DOCTYPE html>
<html lang="en">
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
</body>
</html>

I'm posting my workaround here. It's probably more short and simple.
At first, save the values in the state (preferably redux state) after every onChange. React state also would do fine. Then, make it disabled when a certain array length of the value is reached.
const districtData = ['Dhaka', 'Bagerhat', 'Bandarban',
'Barguna', 'Barishal', 'Bhola']
const [districtValue, setDistrictValue] = useState();
<Dropdown
onChange={async (e, { name, value }) => {setDistrictValue(value)}}
options={districtData.map((currentValue, index) => {
return {
key: `${currentValue}`,
value: `${currentValue}`,
text: `${currentValue}`,
disabled: districtValue.length > 2 ? true : false
}
// Do other things here
// Max 3 values here, then options will be disabled.
// Will be live again if any selected options are less than 3 here
/>

Related

Creating a createSelector in Redux that does not rerender unrelated components

I have a simple document maker that allows users the create multiple fields of two types (input field and label field). They can also rearrange the field via drag and drop. My data structure is based on the Redux guide on data normalization.
LIST field is input type (e.g. children id 21 and 22) - onChange will dispatch action and allows the input text to modified without re-rendering other fields.
With LABEL field we allow users to select from a dropdown list of only 3 labels (Danger, Slippery, Flammable). Selecting one with remove it from the dropdown list to prevent duplicate label. For example, if there are "Danger" and "Slippery" in the field, the dropdown field will only show one option "Flammable". To achieve this, I use createSelector to get all children and filter them base on their parent (fieldId). This gives me an array of existing labels ["Danger","Slippery"] in that fieldId. This array will then be used to filter from a fixed dropdown array of three options with useEffect.
Now whenever I update the input text, it will also re-render the LABEL field (based on the Chrome React profiler).
It does not affect performance but I feel like I am missing something with my createSelector.
Example:
export const documentSlice = createSlice({
name: "document",
initialState: {
fields: {
1: { id: 1, children: [11, 12] },
2: { id: 2, children: [21, 22] },
},
children: {
11: { id: 11, type: "LABEL", text: "Danger", fieldId: 1 },
12: { id: 11, type: "LABEL", text: "Slippery", fieldId: 1 },
21: { id: 21, type: "LIST", text: "", fieldId: 2 },
22: { id: 22, type: "LIST", text: "", fieldId: 2 },
},
fieldOrder:[1,2]
},
});
createSelector
export const selectAllChildren = (state) => state.document.children;
export const selectFieldId = (state, fieldId) => fieldId;
export const getChildrenByFieldId = createSelector(
[selectAllChildren, selectFieldId],
(children, fieldId) => {
const filterObject = (obj, filter, filterValue) =>
Object.keys(obj).reduce(
(acc, val) =>
obj[val][filter] !== filterValue ? acc : [...acc, obj[val].text],
[]
);
const existingChildren = filterObject(children, "fieldId", filterId);
return existingChildren;
}
);
After more reading up, this is what finally works for me. Hopefully someone will find it useful.
Given the normalized data, the object reduce function can be simplified.
slice.js
export const getChildrenByFieldId = createSelector(
[selectAllChildren, selectFieldId],
(children, fieldId) => {
// get the children from fields entry => array [11,12]
const childrenIds = state.document.fields[fieldId].children
let existingChildrenText = [];
// loop through the created array [11,12]
childrenIds.forEach((childId) => {
// pull data from the individual children entry
const childText = children[childId].text;
existingChildrenText.push(childText);
});
return existingChildrenText;
}
);
To prevent re-render, we can use shallowEqual comparison on the output array to compare only the values of the array.
//app.js
import { shallowEqual, useSelector } from "react-redux";
const childrenTextData = useSelector((state) => {
return getChildrenByFieldId(state, blockId);
}, shallowEqual);

React-select is going blank when the options array changes (codesandbox included)

I am trying to use react-select in combination with match-sorter as described in this stackoverflow answer (their working version). I have an initial array of objects that get mapped to an array of objects with the value and label properties required by react-select, which is stored in state. That array is passed directly to react-select, and when you first click the search box everything looks good, all the options are there. The onInputChange prop is given a call to matchSorter, which in turn is given the array, the new input value, and the key the objects should be sorted on. In my project, and reproduced in the sandbox, as soon as you type anything into the input field, all the options disappear and are replaced by the no options message. If you click out of the box and back into it, the sorted options show up the way they should. See my sandbox for the issue, and here's the sandbox code:
import "./styles.css";
import { matchSorter } from "match-sorter";
import { useState } from "react";
import Select from "react-select";
const objs = [
{ name: "hello", id: 1 },
{ name: "world", id: 2 },
{ name: "stack", id: 3 },
{ name: "other", id: 4 },
{ name: "name", id: 5 }
];
const myMapper = (obj) => {
return {
value: obj.id,
label: <div>{obj.name}</div>,
name: obj.name
};
};
export default function App() {
const [options, setOptions] = useState(objs.map((obj) => myMapper(obj)));
return (
<Select
options={options}
onInputChange={(val) => {
setOptions(matchSorter(options, val, { keys: ["name", "value"] }));
}}
/>
);
}
I am sure that the array in state is not getting removed or anything, I've console logged each step of the way and the array is definitely getting properly sorted by match-sorter. It's just that as soon as you type anything, react-select stops rendering any options until you click out and back in again. Does it have something to do with using JSX as the label value? I'm doing that in my project in order to display an image along with the options.
I had to do two things to make your code work:
Replaced label: <div>{obj.name}</div> with label: obj.name in your mapper function.
I am not sure if react-select allows html nodes as labels. Their documentation just defines it as type OptionType = { [string]: any } which is way too generic for anything.
The list supplied to matchSorter for matching must be the full list (with all options). You were supplying the filtered list of previous match (from component's state).
const objs = [
{ name: "hello", id: 1 },
{ name: "world", id: 2 },
{ name: "stack", id: 3 },
{ name: "other", id: 4 },
{ name: "name", id: 5 }
];
const myMapper = (obj) => {
return {
value: obj.id,
label: obj.name, // -------------------- (1)
name: obj.name
};
};
const allOptions = objs.map((obj) => myMapper(obj));
export default function App() {
const [options, setOptions] = useState(allOptions);
return (
<Select
options={options}
onInputChange={(val) => {
setOptions(
matchSorter(
allOptions, // ----------------> (2)
val,
{ keys: ["name", "value"]
}
));
}}
/>
);
}

Material-UI Rating returns string instead of number

I am running into a problem with the Material UI rating component.
I want it to return a number when I change it, but it returns a string. The initial value I provide (this.props.data.feelings_rating) is a number, but whenever I change it in my app it becomes a string. Here is the relevant code:
Rating component:
<Rating
value={this.props.data.feelings_rating}
name="feelings_rating"
onChange={handleChange}
size="large"
getLabelText={(value) => customIcons[value].label}
IconContainerComponent={IconContainer}
/>
handleChange:
handleChange = (e) => {
var diary = this.state.diary
diary[e.name] = e.value
this.setState({diary: diary})
}
other stuff to do with the icons:
const customIcons = {
1: {
icon: <SentimentVeryDissatisfiedIcon />,
label: 'Very Dissatisfied',
},
2: {
icon: <SentimentDissatisfiedIcon />,
label: 'Dissatisfied',
},
3: {
icon: <SentimentSatisfiedIcon />,
label: 'Neutral',
},
4: {
icon: <SentimentSatisfiedAltIcon />,
label: 'Satisfied',
},
5: {
icon: <SentimentVerySatisfiedIcon />,
label: 'Very Satisfied',
},
};
function IconContainer(props) {
const { value, ...other } = props;
return <span {...other}>{customIcons[value].icon}</span>;
}
IconContainer.propTypes = {
value: PropTypes.number.isRequired,
};
I cannot find anyone else on the internet with this problem, so can anyone spot what I am doing wrong? Any answers would be appreciated.
The onChange of the Rating component gives you 2 arguments one is the event object and the other is actual value. You can retrive the selected value from the second argument newValue
handleChange = (e, newValue) => {
// create a copy of the state
const clonedDiary = {...this.state.diary}
// now mutate the clonedDiary directly
clonedDiary[e.name] = newValue;
this.setState({diary: clonedDiary })
}
Refer
Rating API

Passing value to state using react-select

I'm new to react and trying to learn on my own. I started using react-select to create a dropdown on a form and now I'm trying to pass the value of the option selected. My state looks like this.
this.state = {
part_id: "",
failure: ""
};
Then in my render
const {
part_id,
failure
} = this.state;
My form looks has 2 fields
<FormGroup>
<Label for="failure">Failure</Label>
<Input
type="text"
name="failure"
placeholder="Failure"
value={failure}
onChange={this.changeHandler}
required
/>
</FormGroup>
<FormGroup>
<Label for="part_id">Part</Label>
<Select
name="part_id"
value={part_id}
onChange={this.changeHandler}
options={option}
/>
</FormGroup>
the changeHandler looks like this
changeHandler = e => {
this.setState({ [e.target.name]: e.target.value });
};
The change handler works fine for the input but the Select throws error saying cannot read property name. I went through the API docs and came up with something like this for the Select onChange
onChange={part_id => this.setState({ part_id })}
which sets the part_id as a label, value pair. Is there a way to get just the value? and also how would I implement the same with multiselect?
The return of react-select onChange event and the value props both have the type as below
event / value:
null | {value: string, label: string} | Array<{value: string, label: string}>
So what the error means is that you can't find an attribute of null (not selected), or any attributes naming as name (you need value or label)
For multiple selections, it returns the sub-list of options.
You can find the related info in their document
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
Update
For your situation (single selection)
option having type as above
const option = [
{value: '1', label: 'name1'},
{value: '2', label: 'name2'}
]
state save selected value as id
changeHandler = e => {
this.setState({ part_id: e ? e.value : '' });
};
pick selected option item via saved id
<Select
name="part_id"
value={option.find(item => item.value === part_id)}
onChange={this.changeHandler}
options={option}
/>
For multiple selections
state save as id array
changeHandler = e => {
this.setState({ part_id: e ? e.map(x => x.value) : [] });
};
pick via filter
<Select
isMulti // Add this props with value true
name="part_id"
value={option.filter(item => part_id.includes(item.value))}
onChange={this.changeHandler}
options={option}
/>
onChange function is a bit different in react-select
It passes array of selected values, you may get first one like
onChange={([selected]) => {
// React Select return object instead of value for selection
// return { value: selected };
setValue(selected)
}}
I have tried the above solutions but some of these solutions does update the state but it doesn't gets rendered on the Select value instantly.
Herewith a demo example:
this.state = {
part_id: null,
};
handleUpdate = (part_id) => {
this.setState({ part_id: part_id.value }, () =>
console.log(`Option selected:`, this.state.part_id)
);
};
const priceOptions = [
{ value: '999', label: 'Item One' },
{ value: '32.5', label: 'Item Two' },
{ value: '478', label: 'Item Three' }
]
<Select
onChange={this.handleUpdate}
value={priceOptions.find(item => item.value === part_id)}
options={priceOptions}
placeholder={<div>Select option</div>}
/>

How can you access the constant variables of one Component in another Component - React

I am working on React Table. I am basically a beginner in React. I have a dashboard page where I display a React Table of 8 columns. I have a customize button which will open a popup page, this popup page has 8 check boxes allows me to show/hide those React columns. Initially all the check boxes in this popup page is set to true. When I uncheck a column that particular column get disabled.
There are images in the end to see what I am trying to do.
I will be using this logic for show hide columns (this question was asked by me two days back) - You can see how this is done using a React Table attribute/property called show. When show is true that column/sub column is shown and When show is false that column/sub column is hidden.
The React Table data is like this
const columns = [
{
Header: 'Column 1',
accessor: 'firstName',
// show: true // shows the particular column (true is default)
// show: false // hides the particular column
},
{
Header: 'Column 2',
accessor: 'firstName',
},
{
Header: 'Column 3',
accessor: 'firstName',
},
{
Header: 'Column 4',
accessor: 'firstName',
},
{
Header: 'Column 5',
accessor: 'firstName',
},
{
Header: 'Column 6',
accessor: 'firstName',
},
{
Header: 'Column 7',
accessor: 'firstName'
},
{
Header: 'Column 8',
accessor: 'firstName',
}
];
Now have a look at this image. The image shows my dashboard page along with the checkbox popup page which can turn my column on/off (or show/hide)
This is the code for the popup page for checkbox
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { ActionCreators } from '../../../actions';
import ButtonComponent from '../../common/button/ButtonComponent';
import { CheckBox } from '../../common/chkbox/CheckBox';
class CustomizedView extends Component {
constructor(props) {
super(props);
this.handleCheckChildElement = this.handleCheckChildElement.bind(this);
this.state = {
items: [
{ id: 1, value: 'Column 1', isChecked: true },
{ id: 2, value: 'Column 2', isChecked: true },
{ id: 3, value: 'Column 3', isChecked: true },
{ id: 4, value: 'Column 4', isChecked: true },
{ id: 5, value: 'Column 5', isChecked: true },
{ id: 6, value: 'Column 6', isChecked: true },
{ id: 7, value: 'Column 7', isChecked: true },
{ id: 8, value: 'Column 8', isChecked: true },
]
};
}
handleClick() {
this.setState({ isChecked: !this.state.isChecked });
}
handleCheckChildElement(event) {
const { items } = this.state; //extract state values like this to a const variable
const newItems = items.map((item) => { //do map on items because map returns a new array. It’s good practice to use .map than forEach in your case
if(item.value === event.target.value) {
item.isChecked = event.target.checked;
return item; //return updated item object so that it will be pushed to the newItems array
}
return item; // return item because you need this item object as well
});
this.setState({ items: newItems }); //finally set newItems array into items
const column1checked = items[0].isChecked;
console.log('column1checked ' + column1checked);
const column2checked = items[1].isChecked;
console.log('column2checked ' + column2checked);
const column3checked = items[2].isChecked;
console.log('column3checked ' + column3checked);
const column4checked = items[3].isChecked;
console.log('column4checked ' + column4checked);
const column5checked = items[4].isChecked;
console.log('column5checked ' + column5checked);
const column6checked = items[5].isChecked;
console.log('column6checked ' + column6checked);
const column7checked = items[6].isChecked;
console.log('column7checked ' + column7checked);
const column8checked = items[7].isChecked;
console.log('column8checked ' + column8checked);
}
render() {
return (
<div className='div-container-custom' >
<div className='bottomBar'>
<ButtonComponent
text='Apply'
className='activeButton filterMargin-custom'
width='100'
display='inline-block'
onClick={() => { this.props.applyFilter(this.state, false); }}
/>
<ButtonComponent
text='Clear Filter'
className='greyedButton clear-custom-filter'
width='100'
display='block'
marginTop='60'
onClick={() => { this.props.applyFilter(this.state, true); }}
/>
</div>
<div>
<div className='data-points-text'>
<span> Columns </span>
</div>
<div className="App">
<ul>
{
this.state.items.map((item, i) => {
return (<div key={i} ><CheckBox handleCheckChildElement={this.handleCheckChildElement} {...item} /></div>);
})
};
</ul>
</div>
</div>
</div>
);
}
}
CustomizedView.propTypes = {
applyFilter: PropTypes.func.isRequired
};
CustomizedView.defaultProps = {
};
function mapStateToProps(state) {
return {
auth: state.auth
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(ActionCreators, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(CustomizedView);
Now my point is when I only uncheck 4th column in the checkbox popup page I get the desired values (the above code)
column1checked true
column2checked true
column3checked true
column4checked false
column5checked true
column6checked true
column7checked true
column8checked true
And ultimately this is my checkbox page
import React from 'react';
import PropTypes from 'prop-types';
export const CheckBox = (props) => {
// super(props);
return (
<li>
<input key={props.id} onClick={props.handleCheckChildElement} type="checkbox" checked={props.isChecked} value={props.value} /> {props.value}
</li>
);
};
CheckBox.propTypes = {
id: PropTypes.string,
handleCheckChildElement: PropTypes.func,
isChecked: PropTypes.bool,
value: PropTypes.string,
};
export default CheckBox;
Now my question is I have the constants in CustomizedView Component. I need those const variables available in ReactTable Component (path is like this - '../../common/chkbox/CheckBox')
In the main dashboard page I import the React Table
import ReactTableComponent from '../common/react_table/ReactTableComponent';
and just use it in the render function of the main dashboard page in this way.
<ReactTableComponent />
So I need to use all those const variables of Customized variables in ReactTable Component to show/hide tables.
Also here I have to use show: some_const (I cannot use true or false)
I am a complete beginner in React and I need help from this community. Kindly help to implement this idea.
All I want to know the best/easiest (may be great way or dumb way) way to transfer those 8 const variables from components\abcdashboard\customized_view\Customizedview to components\common\react_table\ReactTableComponent
Generally, when you want to share data across components like this, what you want is to consolidate the data in a higher-up component, and use functions to modify it.
Here's a contrived example (CodeSandbox here):
import React from "react";
import ReactDOM from "react-dom";
class Parent extends React.Component {
state = {
name: "Bill"
};
changeName = name => {
this.setState({ name });
};
render() {
return (
<React.Fragment>
<h1>name is: {this.state.name}</h1>
<ChildA changeName={this.changeName} />
<ChildB changeName={this.changeName} />
</React.Fragment>
);
}
}
class ChildA extends React.Component {
state = {
nameA: "Steve"
};
render() {
return (
<button onClick={() => this.props.changeName(this.state.nameA)}>
change to my name
</button>
);
}
}
class ChildB extends React.Component {
state = {
nameB: "Elon"
};
render() {
return (
<button onClick={() => this.props.changeName(this.state.nameB)}>
change to my name
</button>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
Here, the Parent can access the value of the names of the children through the changeName function which is passed down.
If you just want to share variables and not data per se, you could just define a whole file and export various variables like:
export const PI = 3.14
and so on, then do like:
import { PI } from './variables'
in other files, for example.

Categories