Creating a map function inside a map in React - reactjs

I have a JSON array like below. I want to generate dynamic components using the array. I have an array inside the array and need to generate a checkbox dynamically inside a card.
export const DemoProductSpec =
{
"productSpecCharacteristic": [
{
"configurable": false,
"name": "testing",
"productSpecCharacteristicValue": [
{
"value": 2000
},
{
"value": 2002
},
{
"value": 2003
}
],
},
{
"configurable": false,
"name": "testing2",
"productSpecCharacteristicValue": [
{
"value": 20003
},
{
"value": 200233
},
{
"value": 200333
},
{
"value": 20034
}
],
},
]
}
My react code is as below. but this code is not working. first map function is working. but the second map is not working.
{DemoProductSpec.productSpecCharacteristic.map((Characteristic) => (
<Card
title={Characteristic.name}
bordered={true}
size="small"
extra={
<Radio.Group onChange={onChange} value={value}>
<Radio value={1}>Deafult</Radio>
<Radio value={2}>Overriden</Radio>
</Radio.Group>
}
>
{" "}
<span>Values: </span>
{Characteristic.productSpecCharacteristicValue.map((Values) => (<Checkbox>{Values.value}</Checkbox>))}
</Card>
))
}

Related

How do I iterate, dynamically load my form input elements and then retrieve the input values on Form Submit in React?

I am creating a sample dynamic form and I want to load my input elements based on a JSON which contains different input elements like "textbox", "text-area", "dropdown", "radio-input" and so on..
I have a JSON file created to get this as shown below:
[
{
"id": "1",
"type": "textbox",
"text": "",
"required": true,
"label": "lbl"
},
{
"id": "2",
"type": "textbox",
"text": "",
"required": true,
"label": "Specification Name"
},
{
"id": "3",
"type": "dropdown",
"text": "",
"required": true,
"label": "Specification Reviewed",
"options":["a","2"]
},
{
"id": "4",
"type": "dropdown",
"text": "",
"required": true,
"label": "Action Required",
"options":["1","2","3"]
},
{
"id": "5",
"type": "textbox",
"text": "",
"required": true,
"label": "lbl"
}
]
I have an App base component which calls another component called "Input" which has my layout and I retrieve the elements through that component. I am able to pull the text box and dropdown here but I am not able to iterate through the dropdown select. I'm not sure how to do it.
Here's my App Base solution: Here I use the map concept to fetch the data from the JSON local file and assign it to inputvalues which I then use in the return within the form tag.
I'm able to list all my input elements dynamically
But I'm not able to get the dropdown values from my JSON file
function App() {
const [inputObject, setInputObject] = React.useState(inputData)
const inputvalues = inputObject.map( input => {
return (
<Input
key={input.id}
input={input}
/>
)
})
const handleSubmit = (event) => {
event.preventDefault();
}
return (
<div className="App">
<header className="App-header">
<form>
<div>
{inputvalues}
</div>
<input type="submit" value="submit" onClick={handleSubmit} />
</form>
</header>
</div>
);
}
export default App;
And, here's my input.js component file: This basically lays out the input elements and I fetch the data using Props but I am unable to fetch the dropdown selection values because I would need to somehow iterate within each of those dropdown elements.
export default function Input(props) {
const [state, setState] = React.useState({
textBoxValue: ""
})
function handleChange(evt) {
setState({ [props.input.id] : evt.target.value });
}
if (props.onChange) {
props.onChange(state);
}
return (
<div>
<label>{props.input.type}: </label>
{props.input.type === "textbox" && <input name={props.input.type} placeholder={props.input.type} id={props.input.id} value={state.firstName} onChange={handleChange}/>}
{props.input.type === "dropdown" && <select name={props.input.type} id={props.input.id}>
<option value={props.input.options}></option></select>
}</div>)}
Please help me or guide me because I'm still learning React.
In addition to this, how would i later get all the input values upon FORM SUBMIT ? For this I tried adding a handleChange event to see if data comes through but it does not work.
Thank you so much in advance!
You may find Yup and Formik useful.
With Yup, you can include types to fields as well as things such as if the field is required.
The example linked should get you in the right direction.
Edit - (after OP comment)
So without using any external library, you could do something like this:
// Get a hook function
const {useState} = React;
const INPUTS = [
{
"id": "1",
"type": "textbox",
"value": "",
"required": true,
"label": "lbl"
},
{
"id": "2",
"type": "textbox",
"value": "",
"required": true,
"label": "Specification Name"
},
{
"id": "3",
"type": "dropdown",
"value": "",
"required": true,
"label": "Specification Reviewed",
"options":["a","2"]
},
{
"id": "4",
"type": "dropdown",
"value": "",
"required": true,
"label": "Action Required",
"options":["1","2","3"]
},
{
"id": "5",
"type": "textbox",
"value": "",
"required": true,
"label": "lbl"
}
];
const convertArrayToObject = (array, key, targetKey) => {
const initialValue = {};
return array.reduce((obj, item) => {
return {
...obj,
[item[key]]: item[targetKey],
};
}, initialValue);
};
const Form = () => {
const [formState, setFormState] = useState(
convertArrayToObject(INPUTS, "id", "value")
);
const handleChanges = (keyName) => (e) => {
const newValue = e.target.value
setFormState(prev => ({
...prev,
[keyName]: newValue
}));
}
console.log(formState);
return (
<form>
{INPUTS.map((input, inputIndex) => (
<div key={inputIndex}>
<label htmlFor={input.id}>{input.label}</label>
{input.type === "dropdown" && input.options ? (
<select onChange={handleChanges(input.id)}>
{input.options.map((option, optionIndex) => (
<option
key={optionIndex}
value={option}>{option}
</option>
))}
</select>
) : (
<input
id={input.id}
name={input.id}
required={input.required}
type={input.type}
onChange={handleChanges(input.id)}
value={formState.value}
/>
)}
</div>
))}
</form>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<Form />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
A little reasoning behind some of the code written:
React wants a key prop to be passed when mapping over objects (hence I've added it for each wrapper div and option element.
I've mapped over the INPUTS object to build the initial state, and then created an onChange handler that is curried, that way it is generic enough to be used everywhere
I'm just console.loging the formState to demonstrate the changes as you update the form.
Beyond
Consider adding Auto Complete if applicable or worthwhile
You will of course need some kind of some kind of submit button if you plan to submit the data to an API.
<button type="submit">Submit</button
But I will leave that as an exercise for you...
Hope this helps!

MUI Datatables Column Header Not Tidy

I was using MUI-Datatables in 2020, and I really satisfied with it so I want to use it again with my upcoming project. Lately, Material UI was upgrading and I think MUI-Datatables have styling issue regarding the placement of column headers.
I am using MUI Datatables with this simple ReactJS code:
import Checkbox from '#mui/material/Checkbox';
import FormControlLabel from '#mui/material/FormControlLabel';
import MUIDataTable from "mui-datatables";
function UserList(props){
const {title} = props;
const columns = [
{
"name": "name",
"label": "Name",
"options": {
"filter": true,
"sort": true,
}
},
{
"name": "username",
"label": "Username",
"options": {
"filter": true,
"sort": true,
}
},
{
"name": "active",
"label": "Active",
"options": {
"customBodyRender": (value) => {
return <FormControlLabel control={<Checkbox checked={value} color="success" />} disabled label="" />;
},
"filter": true,
"sort": false,
}
}
];
const dummyData = [
{
"name": "Administrator",
"username": "admin#gmail.com",
"active": true
},
{
"name": "Jalaluddin AF",
"username": "jalaluddin#gmail.com",
"active": true
},
{
"name": "Alex",
"username": "alexandro#gmail.com",
"active": false
}
];
const options = {
selectableRows: "none",
viewColumns: false
};
return(
<MUIDataTable
columns={columns}
data={dummyData}
options={options}
title={title}
/>
);
}
export default UserList;
Basically, it was just showing a table with Name, Username, and Active/Inactive checkbox. But the thing is, when I start npm, the column header aren't in place with its value.
Anyone could help me?
My environment:
#mui/icons-material: 5.2.0
#mui/material: 5.2.0
#mui/styles: ^5.2.0
mui-datatables: ^4.0.0
react: 17.0.2
I think there is some clash with the libraries. Can you check if you are not using multiple version. You can also override the existing css using inspect element.

React Autocomplete matching highlighted word

I have autocompleted features. when I search for something we will get the data accordingly. But I want to add one more feature like when we search data will come as well as whatever I typed in the search box that character should be highlighted in yellow color.
Here is the piece of code I have written.
import logo from './logo.svg';
import './App.css';
import React, { useState } from "react";
function App() {
const [names, setnames] = useState([{
"name": "Barbara-anne"
}, {
"name": "Debi"
}, {
"name": "Cara"
}, {
"name": "Cristin"
}, {
"name": "Jocelyne"
}, {
"name": "Joellyn"
}, {
"name": "Elmo"
}, {
"name": "Ivette"
}, {
"name": "Lea"
}, {
"name": "Michel"
}, {
"name": "Leigha"
}, {
"name": "Titus"
}, {
"name": "Nollie"
}, {
"name": "Celle"
}, {
"name": "Thea"
}, {
"name": "Brynn"
}, {
"name": "Sloane"
}, {
"name": "Margalo"
}, {
"name": "Genevieve"
}, {
"name": "Niel"
}, {
"name": "Heddi"
}, {
"name": "Gregg"
}, {
"name": "Eduard"
}, {
"name": "Kizzee"
}, {
"name": "Truman"
}, {
"name": "Merill"
}, {
"name": "Lindie"
}, {
"name": "Vasily"
}, {
"name": "Averil"
}, {
"name": "Golda"
}, {
"name": "Zorine"
}, {
"name": "Odele"
}, {
"name": "Amalie"
}, {
"name": "Ilsa"
}, {
"name": "Pepillo"
}, {
"name": "Hewe"
}, {
"name": "Byrann"
}, {
"name": "Alford"
}, {
"name": "Lanny"
}, {
"name": "Kristina"
}, {
"name": "Mar"
}, {
"name": "Vittoria"
}, {
"name": "Winslow"
}, {
"name": "Ashlan"
}, {
"name": "Gayelord"
}])
const [searchTerm, setSearchTerm] = useState('')
const filteredName=names.filter((val)=>{
if(searchTerm ===""){
return val;
}else if(val.name.toLowerCase().includes(searchTerm.toLowerCase())){
return val;
}
});
const renderStatementResult = searchTerm && searchTerm.length > 0;
return (
<>
<div className="srchField">
<label for="statement">Statement Name</label>
<div className="valueField">
<input type="text" name="fileName" id="statement" data-validate="true" placeholder="Type Name" onChange={event => {setSearchTerm(event.target.value)}}/>
{
renderStatementResult ? <ul className="lookup-results">
{filteredName.map((value)=>(<li key={value.name}>{value.name}</li>))}
</ul> : null
}
</div>
</div>
</>
);
}
export default App;
refer this image
Can anyone have an idea of how to match the highlighted text. I want to highlight all s in yellow color
Replace your map function with
filteredName.map((value) => {
let string = value.name.substr(
0,
value.name.toLowerCase().indexOf(searchTerm.toLowerCase())
);
let endString = value.name.substr(
value.name.toLowerCase().indexOf(searchTerm.toLowerCase()) +
searchTerm.length
);
let highlightedText = value.name.substr(
value.name.toLowerCase().indexOf(searchTerm.toLowerCase()),
searchTerm.length
);
return (
<li key={value.name}>
{string}
<span style={{ "background-color": "#FFFF00" }}>
{highlightedText}
</span>
{endString}
</li>
);
})
The first line( let string =...) extracts the part of string which comes before the part that should be highlighted, and the next line extracts the part after the highlight. The search term itself is kept in a span tag which is styled to highlight.
The above snippet only highlights the first occurrence of the search term, so if the search term is 's' and one of the names is 'Samson', only the first 's' would be highlighted. If you want to highlight all occurrences, then you can use regular expressions to find out the indices of all occurrences and loop across the indices, constructing your li tag along the way.
A working sandbox can be found at https://codesandbox.io/embed/elated-chandrasekhar-ggoi1?fontsize=14&hidenavigation=1&theme=dark
Try this
{filteredName.map((value)=>(<li key={value.name}>{
value.name.split('').map((char) => {
if (searchTerm.toLowerCase().split('').includes(char.toLowerCase())) {
return <span style={{ color: 'yellow' }}>{char}</span>;
} else {
return <span>{char}</span>;
}
})
}</li>))}

react-select add all items from group by click on group title

react-select
let options = [
{
"label": "Group 1",
"options": [
{
"label": "Item1", "value": "1|1"
},
{
"label": "Item2", "value": "1|2"
},
{
"label": "Item3", "value": "1|3"
}
]
},
{
"label": "Group n",
"options": [
{
"label": "Item1", "value": "2|1"
},
{
"label": "Item2", "value": "2|2"
}
]
}
];
<Select
onChange={this.onChange}
closeMenuOnSelect={false}
isMulti
menuIsOpen={true}
options={options}
/>
onChange is fired only for subitems of Group, in documentation I don't find property to set group title clickable or other options that would accomplish this, any ideas?
I also tried to add value for groups like this
[
{
"label": "Group 1",
"value": [
"1|1",
"1|2",
"1|3"
],
"options": [
{
"label": "item1",
"value": "1|1"
},
{
"label": "item2",
"value": "1|2"
},
{
"label": "item3",
"value": "1|3"
}
]
}
]
But group is not clickable
Yes, that's how react-select works, you can't directly be able to click/select on the group.
However, In order to make the group clickable, provide a label and value in an object inside options array.
Like this
let options = [
{
label: "---GROUP 1---",
value: "1"
},
{
options: [
{
label: "Item1",
value: "1|1"
...
This will make the group clickable and selectable.
Working copy of your code is in the codesandbox
Another info which might be useful to know is:
You can write custom component for the group. This way you can attach onClick or do or other kinds of stuff.
<Select
components={{GroupHeading: () => <div onClick={() => console.log('i am a group and i am clickable .. yay!')}>My Group Heading</div>}}
onChange={onChange}
====EDIT: Based on request in the comment:====
In order to select all group items upon clicking on group label, do the following:
maintain a state value for example
write a little custom component for the group label and attach an onClick which will concatenate the value(state)
use value prop of react-select and provide the value from state
write custom onChange and update the state i.e. value upon item select
I have updated the codesandbox working copy of code (same link as above)
Code Snippet:
const group1Options = [
{
label: "Item1",
value: "1|1"
},
{
label: "Item2",
value: "1|2"
},
{
label: "Item3",
value: "1|3"
}
];
const group2Options = [
{
label: "Item1",
value: "2|1"
},
{
label: "Item2",
value: "2|2"
}
];
const createGroup = (groupName, options, setValue) => {
return {
label: (() => {
return (
<div
onClick={() =>
setValue(value =>
value.concat(options.filter(grpOpt => !value.includes(grpOpt)))
)
}
>
{groupName}
</div>
);
})(),
options: options
};
};
export default function App() {
const [value, setValue] = useState([]);
let options = [
createGroup("---GROUP 1---", group1Options, setValue),
createGroup("---GROUP 2---", group2Options, setValue)
];
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Select
// components={{GroupHeading: () => <div onClick={() => console.log('i am a group and i am clickable .. yay!')}>My Group Heading</div>}}
onChange={option => {
console.log(option);
return setValue(option);
}}
closeMenuOnSelect={false}
isMulti
menuIsOpen={true}
options={options}
value={value}
/>
</div>
);
}

react 16, render with array, with first and last element conditiona with divl in complex render

I have a render that returns array and it's working well, it's iterate over an array to show fields in a form. But now i Want to enclose in a divs to group some fields conditionnaly accord to a propery. to get somethi like
<div class='section>
<Field>
<Field>
</div>
<div class='section>
<Field>
</div>
actually i get just:
<div class='section>
<Field>
<Field>
</div>
One example of a branch in my object:
(it's when the field have "formNewSection" property to separate the fields grouped by div)
"identitydocs": {
"type": "String",
"dbtype": "Json",
"labelChildsGlobal": true,
"labelChildsShow": true,
"subfields": {
"id": {
"type": "ID",
"typeInput": "hidden"
},
"type": {
"type": "String",
"label": "id_doctype"
},
"country": {
"type": "String",
"validators": [
"required"
],
"typeInput": "selectBox",
"listSource": "countries"
},
"number": {
"type": "String",
"label": "id_docnumber"
},
"idnameisother": {
"type": "Boolean",
"typeInput": "checkbox",
"formNewSection": true
},
"lastname": {
"type": "String",
"validators": [
"required",
"alphai18n",
"minLength:3"
],
"normalize": "UpperCase"
},
"firstname": {
"type": "String",
"validators": [
"required",
"alphai18n",
"minLength:3"
]
},
"idexpiration": {
"type": "String",
"dbtype": "Date"
},
"idiauthority": {
"type": "String"
},
"ididate": {
"type": "String",
"dbtype": "Date"
},
"idaddressisother": {
"type": "Boolean",
"typeInput": "checkbox",
"formNewSection": true
},
"addressline1": {
"type": "String",
"validators": [
"required"
]
},
"addressline2": {
"type": "String",
"validators": [
"required"
]
},
"cp": {
"type": "String",
"inputSize": 7
},
"city": {
"type": "String"
},
"cityid": {
"type": "ID",
"typeInput": "hidden"
}
}
},
My code working:
return [
<Field
key={index+'-'+subindex+'-'+fieldKey}
name={`${rowValues}.${fieldKey}`}
type={subfield.typeInput ? subfield.typeInput : 'text'}
typeInput={subfield.typeInput ? subfield.typeInput : 'text'}
component={FormField}
label={field.labelChildsShow ? t(labelKey ):''}
placeHolder={!field.labelChildsShow || subfield.placeHolder ? t(labelKey) : ''}
listSource={subfield.listSource ? aSources[subfield.listSource] : ''}
index={subindex + 1}
width="270px"
icon={subfield.icon}
/>,
fields.length === 1 && subindex + 1 === Object.keys(Tables[tableCrud].fields[fieldParentKey].subfields).length ?
<div key={index+'-'+subindex+'-b'} style={ { marginTop: "10px", marginRight: "5px" } }><a href="#" onClick={() => fields.remove(index)}>
<ShowIcon size="25" color="gray" icon="removecircleblack"/>
</a></div>
: null,
];
My new codigo does not work, adding and on the top and botomm of array but conditionnaly:
return [
(subfield.formNewSection && <div className="formSubSection" >),
<Field
key={index+'-'+subindex+'-'+fieldKey}
name={`${rowValues}.${fieldKey}`}
type={subfield.typeInput ? subfield.typeInput : 'text'}
typeInput={subfield.typeInput ? subfield.typeInput : 'text'}
component={FormField}
label={field.labelChildsShow ? t(labelKey ):''}
placeHolder={!field.labelChildsShow || subfield.placeHolder ? t(labelKey) : ''}
listSource={subfield.listSource ? aSources[subfield.listSource] : ''}
index={subindex + 1}
width="270px"
icon={subfield.icon}
/>,
(fields.length === 1 && subindex + 1 === Object.keys(Tables[tableCrud].fields[fieldParentKey].subfields).length ?
<div key={index+'-'+subindex+'-b'} style={ { marginTop: "10px", marginRight: "5px" } }><a href="#" onClick={() => fields.remove(index)}>
<ShowIcon size="25" color="gray" icon="removecircleblack"/>
</a></div>
: null)
(subfield.formNewSection && </div>),
];
with this modificaiotn i get print "fields.length === 1 && subindex + 1 === ..." on the screen.
is possible to do what i'm looking for with react? I can't do it in a simple way, becase this render is inside another render do it with .map, some fields have a mark to be grouped by divs and anothers not, so i can't see for the simple solutions
Could you provide the array structure or the real data for the data I'll take a look and see If I can help you with that.
One idea is suppose you had a a data structure housing the the items with index or a length that is equivalent to the fields something like this:
{
0: [ ... , ]
... to whatever data length
} <-- this can be an array instead of object
iterate over this container/w.e and have a div surrounding it
pending if you used array or object if object you can use Object.keys(w.e)
like poopArrayContainer.map( (item,ind) => { item.map( poop => ... ) } ) ... pretty sure you get it from here hope that helps, not sure of that's the best implementation but that's a thot LuL...
Why not just add the div tag before the field one and at the end like :
<div className="formSubSection" > <Field.......
..
</a></div></div>
: null)

Resources