ReactJS Patternfly reduce duplicated code for dropdowns - reactjs

Hi I'm having 2 Dropdowns but for that I'm managing 2 states with it. Please help me to reduce the duplicated code. Suppose, if i want to have 10 dropdowns then my number of states and same methods gets repeated the same. If there a way to refractor the code to reduce the number of states and methods would be better.
Note : I have a class based component
this.state = {
isOpen: false,
selected: null,
isDisabled: false,
isOpen1: false,
selected1: null,
isDisabled1: false,
isOpen2: false,
selected2: null,
isDisabled2: false, }
Inside Constructor
this.options = [
<SelectOption key={0} value={hourTxt} isPlaceholder />,
<SelectOption key={1} value={weekTxt} />,
<SelectOption key={2} value={dayTxt} />,
<SelectOption key={3} value={neverTxt} />,
];
this.options1 = [
<SelectOption key={0} value={hourTxt} />,
<SelectOption key={1} value={weekTxt} />,
<SelectOption key={2} value={dayTxt} isPlaceholder />,
<SelectOption key={3} value={neverTxt} />,
];
this.onToggle = (isOpen) => {
this.setState({
isOpen
});
};
this.onToggle1 = isOpen1 => {
this.setState({
isOpen1
});
};
this.onSelect = (event, selection, isPlaceholder) => {
if (isPlaceholder){
this.clearSelection();
}
else {
this.setState({
selected: selection,
isOpen: false
},() => { this.postSelectData()
});
}
};
this.onSelect1 = (event, selection, isPlaceholder) => {
if (isPlaceholder) this.clearSelection1();
else {
this.setState({
selected1: selection,
isOpen1: false
},() => { this.postSelectData()
});
}
};
this.clearSelection = () => {
this.setState({
selected: null,
isOpen: false
},() => { this.postSelectData()
});
};
this.clearSelection1 = () => {
this.setState({
selected1: null,
isOpen1: false
},() => { this.postSelectData()
});
};
Under render()
const {isOpen, selected,isOpen1, selected1} = this.state
Under return()
<Select
variant={SelectVariant.single}
onToggle={this.onToggle}
onSelect={this.onSelect}
selections={selected}
isOpen={isOpen}
>
{this.options}
</Select>
<Select
variant={SelectVariant.single}
onToggle={this.onToggle1}
onSelect={this.onSelect1}
selections={selected1}
isOpen={isOpen1}
>
{this.options1}
</Select>

To avoid code duplication you can abstract the logic that would be duplicated into a component and then create multiple instances of that component. How much can or cannot be reused depends on the specific requirements at hand.
In your case, you could create a Dropdown component that has all the static parts, generic logic, and the related handler functions.
Everything that is specific for each instance, in turn, must be managed by the parent component and passed on to the dropdown component (typically as props).
The snippet below might help you achieve what you want. It's a basic dropdown component that offers a enabling/disabling toggle (general logic) and contains all the static parts (e.g., <select>, default value). The parent component (App) renders multiple instances of the Dropdown component by passing it the specifics (label, options, onChange action) as props. Through the onChange action, the state in the parent component will be updated with the value selected last in either of the dropdowns.
const { useState } = React;
const Dropdown = (props) => {
const [disabled, setDisabled] = useState(false);
return (
<div>
{props.label}
<button onClick={() => setDisabled(!disabled)}>{disabled ? "enable" : "disable"}</button>
<br />
<select onChange={(e) => props.action(e.target.value)} disabled={disabled}>
<option value="default">I am a default value</option>
{props.options.map((o) => (
<option value={o.value}>{o.label}</option>
))}
</select>
</div>
);
};
const App = () => {
const dropdown1 = {
label: "My first Dropdown",
options: [
{ value: "value1", label: "entry1" },
{ value: "value2", label: "entry2" },
{ value: "value3", label: "entry3" },
],
action: (val) => setLastSelectedValue(val),
};
const dropdown2 = {
label: "My second Dropdown",
options: [
{ value: "value4", label: "entry4" },
{ value: "value5", label: "entry5" },
{ value: "value6", label: "entry6" },
],
action: (val) => setLastSelectedValue(val),
};
const [lastSelectedValue, setLastSelectedValue] = useState();
return (
<div>
<Dropdown {...dropdown1} />
<Dropdown {...dropdown2} />
{lastSelectedValue && <p>Last selected value: {lastSelectedValue}</p>}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

Component not render the newest value?

I'm getting used to with redux. My problem is the itemList correctly render the latest value but the value of Checkbox which is from hook state not get the latest value. It should be checked for all item list but it is not. Although I console.log the values in the map func, it still get the latest values and the find func is correct.
export default function Component(props) {
const dispatch = useDispatch();
const { itemList } = useSelector((state) => state.AllCourses);
const [values, setValues] = useState({
all: true,
items: []
});
useEffect(() => {
dispatch(
someActions.getItemList(payload)
); //this will get latest itemList
}, []);
useEffect(() => {
if (itemList.length) {
const newValues = {
all: true,
items: itemList.map((item) => ({
select: true,
id: item.id,
})),
};
setValues(newValues);
}
}, [itemList]);
return (
<Box ml={4}>
{ itemList?.map((item) => {
return (
<Box key={item.id}>
<Checkbox
name={item.name}
value={values?.items?.find((itemVal) => item.id === itemVal.id)?.select}
/>
</Box>
);
})}
</Box>
);
}
`
Tried several solutions but still not correctly
It seems like you are using Material UI. For checkbox component you need to set the checked prop.
import Checkbox from '#mui/material/Checkbox';
const label = { inputProps: { 'aria-label': 'Checkbox demo' } };
export default function Checkboxes() {
return (
<div>
<Checkbox {...label} defaultChecked />
<Checkbox {...label} />
<Checkbox {...label} disabled />
<Checkbox {...label} disabled checked />
</div>
);
}
If you use html input tag with type checkbox, then again you have to set the checked attribute accordingly see below.
<label for="vehicle2"> I have a car</label><br>
<input type="checkbox" name="vehicle3" value="Boat" checked>
And lastly, you don't need a local state in your example and you can remove
const [values, setValues] = useState({
all: true,
items: []
});
and
useEffect(() => {
if (itemList.length) {
const newValues = {
all: true,
items: itemList.map((item) => ({
select: true,
id: item.id,
})),
};
setValues(newValues);
}
}, [itemList]);
and replace with
const values = {
all: true,
items: itemList && itemList.length ? itemList.map((item) => ({
select: true,
id: item.id,
})) : [],
};

Cannot set defaultValue in React-Select

I am using React-Select library in my react project, i am stuck on a point where i want to set default value on the first select option rendered in a loop.
Here is the code below for your understanding
export default function FormSection() {
const options = [
{ value: "Titular", label: "Titular", isDisabled: true },
{ value: "Conjuge", label: "Conjuge" },
{ value: "Filho(a)", label: "Filho(a)" },
];
const formik = useFormik({
initialValues: {
relation: null,
},
onSubmit: (values) => {
console.log(values);
},
});
const calcFormikValuesSelect = (index) => {
if (formik.values.relation == null) {
return null;
} else if (formik.values.relation) {
let allrelation = formik.values.relation;
return allrelation[index];
}
};
const handleChangeRelation = (selectedOption, index) => {
console.log(selectedOption, index);
formik.setFieldValue(`relation[${index}]`, selectedOption);
// formik.setFieldValue('firstSmoker', selectedOption)
};
return (
<div className="secondSection">
<form onSubmit={formik.handleSubmit}>
{Array.apply(null, { length: 3 }).map((item, index) => (
<React.Fragment>
<div className="wrapper-person-2">
<p className="tab-text">Profissão</p>
<Select
value={calcFormikValuesSelect(index)}
name={`relation[${index}]`}
onChange={(selectedOption) =>
handleChangeRelation(selectedOption, index)
}
options={options}
className="select-smoker"
/>
</div>
</React.Fragment>
))}
<button type="submit" className="button-enabled">
CONTINUE
</button>
</form>
</div>
);
}
So for index=0, i want to set default value
{value:'Titular,label:'Titular}
and disabled that Select so that dropdown does not show on click and then for index above 0 I want to show the options as options array has
I tried passing prop to React-Select like
defaultValue:{label:'Titular',value:'Titular}
but that doesn't work
"react-select": "^4.3.1",
Hope someone helps me out, thanks !
To set the default value in formik, you need to provide it in initialValues like this:
const formik = useFormik({
initialValues: {
relation: {0: options[0] } OR [options[0]], // depends on how you put it
},
onSubmit: (values) => {
console.log(values);
},
});

Adding new options to form on click in ReactJs

I am doing a React search where user can add multiple filters. The idea is that at first there is only one filter (select and input field) and if user wishes to add more, he can add one more row of (select and input) and it will also take that into account.
I cannot figure out the part on how to add more rows of (select, input) and furthermore, how to read their data as the list size and everything can change.
So I have multiple options in the select array:
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
Now if user selects the first value from the Select box and then types a text in the input box I will get their values and I could do a search based on that.
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends React.Component {
state = {
selectedOption: null,
textValue: null
};
handleOptionChange = selectedOption => {
this.setState({ selectedOption: selectedOption.value });
};
handleTextChange = event => {
this.setState({ textValue: event.target.value });
};
handleSubmit = () => {
console.log(
"SelectedOption: " +
this.state.selectedOption +
", textValue: " +
this.state.textValue
);
};
addNewRow = () => {
console.log("adding new row of filters");
};
render() {
const { selectedOption } = this.state;
return (
<div>
<div style={{ display: "flex" }}>
<Select
value={selectedOption}
onChange={this.handleOptionChange}
options={options}
/>
<input
type="text"
value={this.state.textValue}
onChange={this.handleTextChange}
/>
</div>
<button onClick={this.addNewRow}>AddNewRow</button>
<button onClick={this.handleSubmit}>Submit</button>
</div>
);
}
}
export default App;
I have also created a CodeSandBox for this.
If user clicks on the addNewRow a new row should appear and the previous (search, input) should be selectable without the row that was previously selected.
I don't even really know how I should approach this.
To add new row of inputs on click of button you need to add new input item into the list of inputs, like I have mention below::
import React, { Component } from 'react'
import Select from "react-select";
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends Component {
constructor(props) {
super(props);
this.state = { inputGroups: ['input-0'] };
}
handleSubmit = () => {
console.log("form submitted");
};
AddNewRow() {
var newInput = `input-${this.state.inputGroups.length}`;
this.setState(prevState => ({ inputGroups: prevState.inputGroups.concat([newInput]) }));
}
render() {
return (
<div>
<div>
<div>
{this.state.inputGroups.map(input =>
<div key={input} style={{ display: "flex" }}>
<Select
options={options}
/>
<input
type="text"
// value={this.state.textValue}
// onChange={this.handleTextChange}
/>
</div>
)}
</div>
</div>
<button onClick={() => this.AddNewRow()}>AddNewRow</button>
<button onClick={this.handleSubmit()}>Submit</button>
</div>
);
}
}
export default App;
After click on "AddNewRow" button it will add new input group for you. Now you need to wrap this inputGroup inside "Form" to get data of each inputGroup on click of submit.
I hope it will resolve your issue.

Updating the Values of Multiple Instances of a React Component Returns .map is not a function

I am developing a webpage that is made up of a component containing a dropdown menu of restrictions, and textbox for integer entry. However, since the component can be cloned I am using the map function to make copies. When I go to update the value of either the textbox or the dropdown, the console returns TypeError: this.state.selectedIntervals.map is not a function.
My code is separated into a parent(App.js) and child component(Intervals.js). Intervals.js contains event handlers to check for values being updated and App.js makes copies of the Intervals component with the map function.
From previous posts, I have tried to check whether updating the value removes elements from my array and modifying the method through how events are handled in the child component with no luck.
The following is a reduced version of the code. Ignore the countries and selectedCountry parts of the state, they are for another purpose.
App.js
import React, { Component } from "react";
///import NavigationMenu from "../NavigationMenu";
import Intervals from "../Components/Intervals";
class Application extends Component {
state = {
countries: [
{ id: 1, country: "USA" },
{ id: 2, country: "Brazil" },
{ id: 3, country: "Russia" },
],
selectedCountry: [{ value: 0 }],
selectedIntervals: [{ id: 1, duration: 0, restriction: "None" }],
};
handleInterval = (newValue) => {
this.setState({ selectedIntervals: { duration: newValue } });
};
handleNewInterval = () => {
const selectedIntervals = [...this.state.selectedIntervals];
selectedIntervals.push({
id: this.state.selectedIntervals.length + 1,
duration: 0,
restriction: "None",
});
this.setState({ selectedIntervals });
};
handleDeleteInterval = () => {
const selectedIntervals = [...this.state.selectedIntervals];
selectedIntervals.pop();
this.setState({ selectedIntervals });
};
handleRestriction = (newValue) => {
this.setState({ selectedIntervals: { restriction: newValue } });
};
render() {
return (
<div>
<br />
<button onClick={this.handleNewInterval}>+</button>
<button onClick={this.handleDeleteInterval}>-</button>
<br />
<div>
{this.state.selectedIntervals.map((interval) => (
<Intervals
key={interval.id}
duration={interval.duration}
restriction={interval.restriction}
onIntervalUpdate={this.handleInterval}
onRestrictionUpdate={this.handleRestriction}
/>
))}
</div>
<br />
</div>
);
}
}
export default Application;
Intervals.js
import React, { Component } from "react";
class Intervals extends Component {
handleRestrictionChange = (event) => {
this.props.onRestrictionUpdate(event.target.value);
};
handleDurationChange = (event) => {
this.props.onIntervalUpdate(event.target.value);
};
render() {
return (
<div>
<label>
Enter the length of the interval:
<input type="text" onChange={this.handleDurationChange} />
<select onChange={this.handleRestrictionChange} defaultValue="None">
<option key="1" value="Lockdown">
Lockdown
</option>
<option key="2" value="Vacation">
Vacation
</option>
<option key="3" value="School closure">
School closure
</option>
<option key="4" value="None">
None
</option>
</select>
</label>
</div>
);
}
}
export default Intervals;
handleInterval = (newValue) => {
this.setState({ selectedIntervals: { duration: newValue } });
};
You forgot to wrap it in an array:
handleInterval = (newValue) => {
this.setState({ selectedIntervals: [{ duration: newValue }] });
};
EDIT: looks like you're trying to do a deep merge. setState won't merge only on the top level. Deep objects need to be merged manually.

How to update state in map function in reactjs

I am having 4 buttons each button have name id and selected boolean flag.
What I am trying to achieve is, on click of button, boolean button flag should be changed of that particular button. For this, I need to setState in map function for that particular button Id.
My issue is I am unable to setState in map function for that particular clicked button, its btnSelected should be changed
My aim is to create a multi-select deselect button.Its kind of interest selection for the user and based on that reflect the UI as well my array. Here is my code.
Thanks in anticipation.
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
export default class Test extends Component {
constructor(props, context) {
super(props, context);
this.handleChange = this.handleChange.bind(this);
this.state = {
value: "",
numbers: [1, 2, 3, 4, 5],
posts: [
{
id: 1,
topic: "Animal",
btnSelected: false
},
{
id: 2,
topic: "Food",
btnSelected: false
},
{
id: 3,
topic: "Planet",
btnSelected: false
},
{ id: 4, topic: "Nature", btnSelected: false }
],
allInterest: []
};
}
handleChange(e) {
//console.log(e.target.value);
const name = e.target.name;
const value = e.target.value;
this.setState({ [name]: value });
}
getInterest(id) {
this.state.posts.map(post => {
if (id === post.id) {
//How to setState of post only btnSelected should change
}
});
console.log(this.state.allInterest);
if (this.state.allInterest.length > 0) {
console.log("Yes we exits");
} else {
console.log(id);
this.setState(
{
allInterest: this.state.allInterest.concat(id)
},
function() {
console.log(this.state);
}
);
}
}
render() {
return (
<div>
{this.state.posts.map((posts, index) => (
<li
key={"tab" + index}
class="btn btn-default"
onClick={() => this.getInterest(posts.id)}
>
{posts.topic}
<Glyphicon
glyph={posts.btnSelected === true ? "ok-sign" : "remove-circle"}
/>
</li>
))}
</div>
);
}
}
Here's how you do something like this:
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (e) => {
const { posts } = this.state;
const { id } = e.target;
posts[id].selected = !this.state.posts[id].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="radio" id={i} key={i} checked={p.selected} onClick={this.handleClick} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here.
You can do this by passing the index from the map into each button's handleClick function, which would then return another function that can be triggered by an onClick event.
In contrast to Colin Ricardo's answer, this approach avoids adding an id prop onto each child of the map function that is only used for determining the index in the handleClick. I've modified Colin's example here to show the comparison. Notice the event parameter is no longer necessary.
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (index) => () => {
const { posts } = this.state;
posts[index].selected = !this.state.posts[index].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="checkbox" key={i} checked={p.selected} onClick={this.handleClick(i)} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here

Resources