How to display the selected value from an autocomplete textfield in React? - javascript

Newbie to react here and I am building a search page in a multi-form app where users can search for a student with first name, last name or student ID in an autocomplete textfield.
Now, options for the autocomplete textfields are successfully passed and are displayed once the cursor is in the textfield but typing or actually selecting a value doesn't seem to affect the value of the textfield.
Code below
<Autocomplete
id="auto-complete"
options={values.suggestions}
getOptionLabel={option => option.stuId+" "+ option.fName+" "+option.lName}
autoComplete
includeInputInList
fullWidth
renderInput={params => (
<TextField {...params} margin="normal" fullWidth />)}
/>
values are passed through props.
Intended outcome is for the 'searchKeyword' in state to be updated with the stuId of the student selected and for the autocomplete textfield to display it. is there an onChange or default value function I could use?

is this what you're looking for?
<Autocomplete
...
value={this.state.value}
onChange={(event, value) => this.setState({ value })}
...
/>

You can get value inside option state in hooks
/* eslint-disable no-use-before-define */
import React, { useState } from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default function ComboBox() {
const [option, setOption] = useState("");
return (
<Autocomplete
id="combo-box-demo"
options={top10Films}
getOptionLabel={(option) => option.title}
style={{ width: 300 }}
value={option}
onChange={(e, v) => {
setOption(v);
}}
renderInput={(params) => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
/>
);
}
// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
const top10Films = [
{ title: "The Shawshank Redemption", year: 1994 },
{ title: "The Godfather", year: 1972 },
{ title: "The Godfather: Part II", year: 1974 },
{ title: "The Dark Knight", year: 2008 },
{ title: "12 Angry Men", year: 1957 },
{ title: "Schindler's List", year: 1993 },
{ title: "Pulp Fiction", year: 1994 },
{ title: "The Lord of the Rings: The Return of the King", year: 2003 },
{ title: "The Good, the Bad and the Ugly", year: 1966 },
{ title: "Fight Club", year: 1999 }
];
refer to this codesandbox

Related

Get key from datalist in Form.Control

I get from the API a json, with a list of each staff member:
const MOCK_STAFF = [{
id: 1,
name: "Jhon Doe",
department: "HR"
}, {
id: 2,
name: "Jane Doe",
department: "Research"
}, etc
Then they get mapped in a datalist <option>, inside a Form.Control component:
<Form.Group className="mb-3">
<Form.Label>Name</Form.Label>
<Form.Control
name='staffName'
value={0}
list="namesList"
onChange={(e) => onChangeHandler(e)}/>
<Form.Label>Department</Form.Label>
<Form.Control disabled
name=department
value={}
/>
<datalist id="namesList">
{MOCK_DATA.map( (data) => (
<option key={data.id} value={data.department}>{data.name}</option>
))}
</datalist>
</Form.Group>
sandbox link: https://codesandbox.io/s/modal-t59e7z?file=/src/App.tsx
I would like the onChange={(e) => onChangeHandler(e)} to get the data-value of <option key={data.id} on form submit, and to make the department Form.Control to reference the value of <option value={data.department} in the datalist. The 'key' id must not show to the user, it is used as a primary key on the database.
I have tried:
function onChangeHandler(e:React.SyntheticEvent) {
console.log(e.target.key);
}
but "property key does not exist on event.target". Nor I can use document.getElementById(); with react. How can I get the values 'key', 'value' and/or 'default-value` from a Form.Control with a datalist?
Thank you
I could not achieve this with data-list, but did so with react-select:
type StaffOption = {
label: string, value: number
}
const MOCK_DATA= [{
id: 1,
name: "Jhon Doe",
department: "HR"
}, {
id: 2,
name: "Jane Doe",
department: "Research"
}, {
id: 3,
name: "Elizabeth meyer",
department: "Operations"
}]
type NameOption = {value: number, label: string, department: string}
type NameOptions = Array<NameOption>
function AddNewRowModal(props:AddNewRowProps) {
const [selectedStaffID, setSelectedStaffID] = useState(0);
function onChangeHandler(option: OnChangeValue<StaffOption, false>,
actionMeta: ActionMeta<StaffOption>) {
console.log(option); //this returns all 3 values defined on type StaffOption
if (option?.value !== undefined) {
setSelectedStaffID(option.value!);
}
}
function BuildOptions (data:any[]) {
var options:NameOptions = []
data.forEach(element => {
options.push({
value: element.id!,
label: (element.name),
department: element.department});
});
return options;
var nameOptions = BuildOptions(MOCK_DATA);
return (
<Modal
show={props.showModal}
backdrop="static"
keyboard={false}
onHide={() => props.closeModal()} >
<Modal.Header closeButton>
<Modal.Title>Add new Entry</Modal.Title>
</Modal.Header>
<Modal.Body>
<Select
options={nameOptions}
onChange={onChangeHandler} />
</Modal.Body>
<ModalFooter>
<Button variant='primary'>Create Entry</Button>
<Button variant='danger' onClick={() => props.closeModal()}>Cancel</Button>
</ModalFooter>
</Modal>
);
}
And the codesandbox

Loop through Array of object with the same data on some object and get it displayed once

I have an array of products like this
products: [
{
id: 1,
drinkName: "Chivita",
category: "Juice",
description: "The best drink ever"
},
{
id: 1,
drinkName: "5 Alive",
category: "Juice",
description: "The best drink ever"
},
{
id: 3,
drinkName: "Cocacola",
category: "Others",
description: "The best drink ever"
}
];
What I want is to loop through the array and get the category displayed only once, I know each drink can have the same category, but I want it displayed only once, and also get the drinkName display under each category.
Example:
products.map((product) => {
<AccordionItem key={productsSnapshot?.id}>
<h2>
<AccordionButton fontSize={"sm"} fontWeight={"medium"}>
<Box flex="1" textAlign="left">
{product?.category}
</Box>
<AccordionIcon />
</AccordionButton>
</h2>
</AccordionItem>
});
For example, I have a let's say two drinkName with the category "Juice", if I try to map through the products and want to get the product?.category, I will have Juice printed twice, so I want it to get printed once, if it exists twice print only once -- I hope you understand
Is that possible?
Like #Bravo says:
const products = [
{
id: 1,
drinkName: "Chivita",
category: "Juice",
description: "The best drink ever",
},
{
id: 1,
drinkName: "5 Alive",
category: "Juice",
description: "The best drink ever",
},
{
id: 3,
drinkName: "Cocacola",
category: "Others",
description: "The best drink ever",
},
];
const MyComponent = () => {
const items = products.reduce((prev, current) => {
if (!current.category in prev) {
prev[current.category] = [];
}
prev[current.category].push(current.drinkName);
return prev;
}, {});
Object.keys(items).map((key) => {
return (
<AccordionItem key={key}>
<h2>
<AccordionButton fontSize={"sm"} fontWeight={"medium"}>
<Box flex="1" textAlign="left">
{key}
</Box>
<Box flex="1" textAlign="left">
{items[key].map((drinkName) => {
return <p key={drinkName}>{drinkName}</p>;
})}
</Box>
<AccordionIcon />
</AccordionButton>
</h2>
</AccordionItem>
);
});
};

Truncate value of Material UI Autocomplete (replicate Material UI Multiple Select's truncated renderValue)

As some background
With Material UI Multiple Select, you can truncate the value shown after selection, rather than going to another line (by setting the renderValue to .join the selected options, which gives the functionality of "option A, option B, ..."). The important piece here is that it adds the "..." when the selected options are too long to fit on one line, rather than expanding to the next line.
For example, the following works with Multiple Select:
// Truncated value (I DO want this, for `Autocomplete`)
<Select
labelId="demo-mutiple-name-label"
id="demo-mutiple-name"
multiple
value={personName}
onChange={handleChange}
input={<Input />}
renderValue={selected => selected.join(", ")}
MenuProps={MenuProps}
>
{names.map(name => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
// – VERUS –
// chips that wrap to multiple lines (DON'T want this)
<Select
labelId="demo-mutiple-chip-label"
id="demo-mutiple-chip"
multiple
value={personName}
onChange={handleChange}
input={<Input id="select-multiple-chip" />}
renderValue={selected => (
<div className={classes.chips}>
{selected.map(value => (
<Chip key={value} label={value} className={classes.chip} />
))}
</div>
)}
MenuProps={MenuProps}
>
{names.map(name => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
Demo of Multiple Selects
I'm trying to replicate the functionality above with the Material UI Autocomplete
but there doesn't seem to be an obvious way to do so.
I've tried several approaches:
<Autocomplete
multiple
id="tags-standard"
options={top100Films}
getOptionLabel={option => option.title}
defaultValue={[top100Films[13]]}
renderTags={selected => {
console.log("selected = ", selected);
let renderTagsValue = selected
.map(function(elem) {
return elem.title;
})
.join(", ");
return (
<Typography noWrap={true} color="textPrimary">
{renderTagsValue}
</Typography>
);
}}
renderInput={params => (
<TextField
{...params}
variant="standard"
label="Multiple values"
placeholder="Favorites"
/>
)}
/>
renderTags – Since renderValue is not an option for Autocomplete's, I added a .join to the renderTags, but that only creates a long string that continues to wrap to the next line
disableListWrap – I hoped this would prevent anything from wrapping to the next line, but it still wraps to the next line
limitTags – This doesn't work since tags can be variable lengths. 1, 2, or 3 may fit on a line depending on which tag is selected
renderTags AND Typography – same as #1, plus returning a Typography element with noWrap set to true (this is close but still not right
My closest attempt is #4, but it's still not right. It truncates, but still wraps the placeholder text to the next line, making the textbox's height expand vertically, rather than remaining fixed (like the demo with Multiple Select).
Demo of closest attempt
Does anyone know how to replicate Material UI Multiple Select's truncated renderValue with Material UI Autocomplete?
I get reasonable behavior by just adding style={{ maxWidth: 360 }} to the Typography in your sandbox in order to leave room for the placeholder and some space to type.
Here's the full code:
/* eslint-disable no-use-before-define */
import React from "react";
import Chip from "#material-ui/core/Chip";
import Autocomplete from "#material-ui/lab/Autocomplete";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({
root: {
width: 500,
"& > * + *": {
marginTop: theme.spacing(3)
}
}
}));
export default function Tags() {
const classes = useStyles();
return (
<div className={classes.root}>
<Autocomplete
multiple
disableListWrap={true}
disableCloseOnSelect
id="tags-standard"
options={top100Films}
getOptionLabel={option => option.title}
defaultValue={[top100Films[13]]}
renderTags={selected => {
console.log("selected = ", selected);
let renderTagsValue = selected
.map(function(elem) {
return elem.title;
})
.join(", ");
return (
<Typography
style={{ maxWidth: 360 }}
noWrap={true}
color="textPrimary"
>
{renderTagsValue}
</Typography>
);
}}
renderInput={params => (
<TextField
{...params}
variant="standard"
label="Multiple values"
placeholder="Favorites"
/>
)}
/>
</div>
);
}
// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
const top100Films = [
{ title: "The Shawshank Redemption", year: 1994 },
{ title: "The Godfather", year: 1972 },
{ title: "The Godfather: Part II", year: 1974 },
{ title: "The Dark Knight", year: 2008 },
{ title: "12 Angry Men", year: 1957 },
{ title: "Schindler's List", year: 1993 },
{ title: "Pulp Fiction", year: 1994 },
{ title: "The Lord of the Rings: The Return of the King", year: 2003 },
{ title: "The Good, the Bad and the Ugly", year: 1966 },
{ title: "Fight Club", year: 1999 },
{ title: "The Lord of the Rings: The Fellowship of the Ring", year: 2001 },
{ title: "Star Wars: Episode V - The Empire Strikes Back", year: 1980 },
{ title: "Forrest Gump", year: 1994 },
{ title: "Inception", year: 2010 },
{ title: "The Lord of the Rings: The Two Towers", year: 2002 },
{ title: "One Flew Over the Cuckoo's Nest", year: 1975 },
{ title: "Goodfellas", year: 1990 },
{ title: "The Matrix", year: 1999 },
{ title: "Seven Samurai", year: 1954 },
{ title: "Star Wars: Episode IV - A New Hope", year: 1977 },
{ title: "City of God", year: 2002 },
{ title: "Se7en", year: 1995 },
{ title: "The Silence of the Lambs", year: 1991 },
{ title: "It's a Wonderful Life", year: 1946 },
{ title: "Life Is Beautiful", year: 1997 },
{ title: "The Usual Suspects", year: 1995 },
{ title: "Léon: The Professional", year: 1994 },
{ title: "Spirited Away", year: 2001 },
{ title: "Saving Private Ryan", year: 1998 },
{ title: "Once Upon a Time in the West", year: 1968 },
{ title: "American History X", year: 1998 },
{ title: "Interstellar", year: 2014 },
{ title: "Casablanca", year: 1942 },
{ title: "City Lights", year: 1931 },
{ title: "Psycho", year: 1960 },
{ title: "The Green Mile", year: 1999 },
{ title: "The Intouchables", year: 2011 },
{ title: "Modern Times", year: 1936 },
{ title: "Raiders of the Lost Ark", year: 1981 },
{ title: "Rear Window", year: 1954 },
{ title: "The Pianist", year: 2002 },
{ title: "The Departed", year: 2006 },
{ title: "Terminator 2: Judgment Day", year: 1991 },
{ title: "Back to the Future", year: 1985 },
{ title: "Whiplash", year: 2014 },
{ title: "Gladiator", year: 2000 },
{ title: "Memento", year: 2000 },
{ title: "The Prestige", year: 2006 },
{ title: "The Lion King", year: 1994 },
{ title: "Apocalypse Now", year: 1979 },
{ title: "Alien", year: 1979 },
{ title: "Sunset Boulevard", year: 1950 },
{
title:
"Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb",
year: 1964
},
{ title: "The Great Dictator", year: 1940 },
{ title: "Cinema Paradiso", year: 1988 },
{ title: "The Lives of Others", year: 2006 },
{ title: "Grave of the Fireflies", year: 1988 },
{ title: "Paths of Glory", year: 1957 },
{ title: "Django Unchained", year: 2012 },
{ title: "The Shining", year: 1980 },
{ title: "WALL·E", year: 2008 },
{ title: "American Beauty", year: 1999 },
{ title: "The Dark Knight Rises", year: 2012 },
{ title: "Princess Mononoke", year: 1997 },
{ title: "Aliens", year: 1986 },
{ title: "Oldboy", year: 2003 },
{ title: "Once Upon a Time in America", year: 1984 },
{ title: "Witness for the Prosecution", year: 1957 },
{ title: "Das Boot", year: 1981 },
{ title: "Citizen Kane", year: 1941 },
{ title: "North by Northwest", year: 1959 },
{ title: "Vertigo", year: 1958 },
{ title: "Star Wars: Episode VI - Return of the Jedi", year: 1983 },
{ title: "Reservoir Dogs", year: 1992 },
{ title: "Braveheart", year: 1995 },
{ title: "M", year: 1931 },
{ title: "Requiem for a Dream", year: 2000 },
{ title: "Amélie", year: 2001 },
{ title: "A Clockwork Orange", year: 1971 },
{ title: "Like Stars on Earth", year: 2007 },
{ title: "Taxi Driver", year: 1976 },
{ title: "Lawrence of Arabia", year: 1962 },
{ title: "Double Indemnity", year: 1944 },
{ title: "Eternal Sunshine of the Spotless Mind", year: 2004 },
{ title: "Amadeus", year: 1984 },
{ title: "To Kill a Mockingbird", year: 1962 },
{ title: "Toy Story 3", year: 2010 },
{ title: "Logan", year: 2017 },
{ title: "Full Metal Jacket", year: 1987 },
{ title: "Dangal", year: 2016 },
{ title: "The Sting", year: 1973 },
{ title: "2001: A Space Odyssey", year: 1968 },
{ title: "Singin' in the Rain", year: 1952 },
{ title: "Toy Story", year: 1995 },
{ title: "Bicycle Thieves", year: 1948 },
{ title: "The Kid", year: 1921 },
{ title: "Inglourious Basterds", year: 2009 },
{ title: "Snatch", year: 2000 },
{ title: "3 Idiots", year: 2009 },
{ title: "Monty Python and the Holy Grail", year: 1975 }
];
I used the approach based on the limitTags option, layout effect hook, and DOM measurements.
Here is my custom hook implementation. You can use it like:
const autocompleteRef = useRef(); // don't forget to set the ref
const tagsLimit = useTagsLimit(autocompleteRef, memoizedTags);
The approach has a downside in relation to the original question. It is that autocomplete turn back to multiline when focused. So, some tuning may be required
import { useLayoutEffect, useState } from 'react';
const NO_LIMIT = -1;
const KEEP_SOME_SPACE = 200;
export default function useTagsLimit(autocompleteRef, tags) {
const [tagsLimit, setTagsLimit] = useState(NO_LIMIT);
useLayoutEffect(() => {
const autocompleteDOMNode = autocompleteRef.current;
// next line code looks a bit unreliable, alternatively you can try using css class selector
const tagsContainerDOMNode =
autocompleteDOMNode?.firstElementChild?.firstElementChild;
const autocompleteWidth = autocompleteDOMNode.getBoundingClientRect().width;
const tagsWidths = getDomNodeListWidths(tagsContainerDOMNode?.children);
const fittingLength = findFittingLength(
tagsWidths,
autocompleteWidth - KEEP_SOME_SPACE
);
setTagsLimit(
fittingLength < tags.length ? fittingLength : NO_LIMIT
);
return () => {
setTagsLimit(NO_LIMIT);
};
}, [tags]);
return tagsLimit;
}
function findFittingLength(items, fitValue) {
let cumulativeTotal = 0;
for (let itemIndex = 0; itemIndex < items.length; itemIndex += 1) {
cumulativeTotal += items[itemIndex];
if (cumulativeTotal > fitValue) {
return itemIndex;
}
}
return items.length;
}
function getDomNodeListWidths(domNodeList) {
if (!domNodeList) {
return [];
}
return Array.from(domNodeList).map(
(element) => element.getBoundingClientRect().width
);
}
You can probably use something like this if you want to limit the tags all the time not only when it's not focused because in the official documentation there is an example with limitTags property but it's not limiting the tags when input is focused. I am limiting the tags to 1 in my example but you can adjust to your needs.
<Autocomplete
// ...other props here
renderTags={(value, getTagProps) => {
const numTags = value.length;
const limitTags = 1;
return (
<>
{value.slice(0, limitTags).map((option, index) => (
<Chip
{...getTagProps({ index })}
key={index}
label={option.name}
/>
))}
{numTags > limitTags && ` +${numTags - limitTags}`}
</>
);
}}
/>
Use this inside your css. This will not expand the height of Autocomplete.
.MuiAutocomplete-inputRoot {
flex-wrap: nowrap !important;
}
.Mui-focused {
flex-wrap: wrap !important;
}
You can also adjust the width of chips
.MuiAutocomplete-tag {
max-width: 100px !important;
}
Reference answer

Material UI react autocomplete set different label and different value

we can see the example here at https://material-ui.com/components/autocomplete/
I wanted to set option label and value different.
Like here is example used
const defaultProps = {
options: top5Films,
getOptionLabel: (option) => option.title,
};
<Autocomplete
{...defaultProps}
id="auto-complete"
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
autoComplete
includeInputInList
renderInput={(params) => <TextField {...params} label="clearOnEscape" margin="normal"/>}
/>
const top5Films= [
{ title: 'The Shawshank Redemption', year: 1994 },
{ title: 'The Godfather', year: 1972 },
{ title: 'The Godfather: Part II', year: 1974 },
{ title: 'The Dark Knight', year: 2008 },
{ title: '12 Angry Men', year: 1957 }
]
But I have data like:
const top5Films= [
{ id: 1, title: 'The Shawshank Redemption', year: 1994 },
{ id: 2, title: 'The Godfather', year: 1972 },
{ id: 3, title: 'The Godfather: Part II', year: 1974 },
{ id: 4, title: 'The Dark Knight', year: 2008 },
{ id: 5, title: '12 Angry Men', year: 1957 }
]
I want to set id as value and show title as label.
Looks like the object is assigned to the value.
So setting id to value crashed the options.
I used the id from the object in following way for further operation.
/* eslint-disable no-use-before-define */
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default function Playground() {
const [value, setValue] = React.useState(null);
const [id, setId] = React.useState(null);
const [title, setTitle] = React.useState(null);
return (
<div>
<div>{`value: ${value}`}</div>
<div>{`id: ${id}`}</div>
<div>{`title: ${title}`}</div>
<br />
<div style={{ width: 300 }}>
<Autocomplete
options={top5Films}
getOptionLabel={option => option.title}
id="movies"
value={value}
onChange={(event, newValue) => {
console.log(newValue);
if (newValue) {
setValue(newValue);
setId(newValue.id);
setTitle(newValue.title);
}
}}
renderInput={params => (
<TextField {...params} label="Movies" margin="normal" />
)}
/>
</div>
</div>
);
}
// Top 5 films as rated by IMDb users. http://www.imdb.com/chart/top
const top5Films = [
{ id: 1, title: "The Shawshank Redemption", year: 1994 },
{ id: 2, title: "The Godfather", year: 1972 },
{ id: 3, title: "The Godfather: Part II", year: 1974 },
{ id: 4, title: "The Dark Knight", year: 2008 },
{ id: 5, title: "12 Angry Men", year: 1957 }
];
This works for now but best answer is always welcome.
I couldn't fully understand your issue but I guess you want to have different display option for getOptionLabel and different display in the dropdown list. If that's the case, you can simply use renderOption provided by the Material UI. Check out an example here : https://codesandbox.io/s/94xlp?file=/demo.js
import React from "react";
import { TextField } from "#material-ui/core";
import { Autocomplete } from "#material-ui/lab";
return (
<Autocomplete
freeSolo
name=""
options={top5Films }
getOptionLabel={(option) => option.title} //Displays title only in input field
renderOption={(option) => (
<React.Fragment>
{option.title + "," + " " + option.year} //Displays title + year in dropdown list
</React.Fragment>
)}
renderInput={params => (
<Field
component={FormikTextField}
{...params}
label=""
name=""
/>
)}
/>
);
}
Try this, set a json value and use the name for inputValue, the input value not change why not calling onChange function
<Autocomplete
options={machineList}
inputValue={formValues.machine.name || ""} // from formik context in my case
onChange={(e, value) => {
setValues(
"machine",
value && value.id ? { id: value.id, name: value.name } : "" //use json value
);
}}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<InputField
{...params}
type="text"
name={machine.name}
label={machine.label}
fullWidth
/>
)}
/>;
okk you can try in this way -
const options = [
{
id: 212,
label: 'First Title'
},
{
id: 321,
label: 'Second Title'
},
{
id: 543,
label: 'Third Title'
}
]
<Autocomplete
placeholder="Search…"
options={options}
isOptionEqualToValue={(option, value) => option.id === value.id}
renderInput={(params) => <TextField {...params} label={'label'} />}
renderOption={(props, option) => {
return (
<li {...props}>
{option.label}
</li>
)
}}
onChange={(_event, value) => {
setValue(value.id)
}}
/>

show form in dynamic column when iterating

I have a long form which has mixed type of column. I mean some field will be of width 100% some field will be around 33% so that i can show 3 fields in a column and so on. This I could do without iterating, but how can it be done if the fields are shown with iteration to avoid code repetition? I have created a sandbox either and I am not using third party css framework like bootstrap just a flex or may be grid would work.
Here is how i have tried:
const formFields = [
{ id: 1, name: "first_name", component: TextField, label: "First Name" },
{ id: 1, name: "last_name", component: TextField, label: "Last Name" },
{ id: 1, name: "age", component: TextField, label: "Age" },
{ id: 1, name: "city", component: TextField, label: "City" },
{ id: 1, name: "state", component: TextField, label: "State" },
{ id: 1, name: "country", component: TextField, label: "Country" }
];
const FlexibleForm = () => {
return (
<React.Fragment>
<Form onSubmit={() => console.log("something")}>
<FlexRow>
<FlexColumn size={12}>
{formFields.map(({ id, name, label, component }) => {
return (
<Field
key={id}
name={name}
label={label}
component={component}
/>
);
})}
</FlexColumn>
</FlexRow>
<h2>Another without iteration</h2>
<FlexRow>
<FlexColumn size={6}>
<Field name="first_name" label="First Name" component={TextField} />
</FlexColumn>
<FlexColumn size={6}>
<Field name="last_name" label="Last Name" component={TextField} />
</FlexColumn>
</FlexRow>
<FlexRow>
<FlexColumn size={12}>
<Field name="age" label="Age" component={TextField} />
</FlexColumn>
</FlexRow>
<FlexRow>
<FlexColumn size={4}>
<Field name="city" label="City" component={TextField} />
</FlexColumn>
<FlexColumn size={4}>
<Field name="state" label="State" component={TextField} />
</FlexColumn>
<FlexColumn size={4}>
<Field name="country" label="Country" component={TextField} />
</FlexColumn>
</FlexRow>
</Form>
</React.Fragment>
);
};
export default reduxForm({
form: "form"
})(FlexibleForm);
export const FlexRow = styled.div`
display: flex;
div {
margin-right: 10px;
}
`;
export const FlexColumn = styled.div`
width: ${props => (props.size / 12) * 100}vw;
`;
Here is the codesandbox link https://codesandbox.io/s/l7lm1qp0pq
This is <FlexRow> component
const FlexRow = props => {
return (
<div style={{ display: "flex" }}>
{props.elements.map((element, index) => {
const obj = element.component;
return (
<FlexColumn key={index} size={Math.round(12 / props.elements.length)}>
<Field
name={element.name}
label={element.label}
component={obj[Object.keys(obj)[0]]}
/>
</FlexColumn>
);
})}
</div>
);
};
This is <FlexColumn> component
const FlexColumn = styled.div`
width: ${props => (props.size / 12) * 100}vw;
margin-right: 10px;
`;
Your <FlexibleForm> should look like this :
const FlexibleForm = () => {
return (
<React.Fragment>
<Form onSubmit={() => console.log("something")}>
<FlexRow
elements={[
{ name: "first_name", label: "First Name", component: {TextField} },
{ name: "last_name", label: "Last Name", component: {TextField} }
]}
/>
<FlexRow
elements={[{ name: "age", label: "Age", component: {TextField} }]}
/>
<FlexRow
elements={[
{ name: "city", label: "City", component: {TextField} },
{ name: "state", label: "State", component: {TextField} },
{ name: "country", label: "Country", component: {TextField} }
]}
/>
</Form>
</React.Fragment>
);
};
The component <FlexRow> will loop inside his props elements and will create a <FlexColumn> with the good size with the good attributes.
Litteraly, it did loop inside the props elements which is an array and return the size (12 divided by the number of rows you have), you can change this value if you want with a simple condition like :
element.size ? element.size : Math.round(12 / props.elements.length)
and add a size element in your elements array
{ name: "first_name", label: "First Name", size: 6, component: {TextField} }
Then it add the label and the name. And finally it add the component you choosed, in this example it's <TextField>
This will avoid you from repeating your code.
Here's the CodeSandBox to show you the example !
Wish I did helped you.
There is no much point of using flex if you are not using flex-wrap.
If you want to do it with simple float:
const formFields = [
{ id: 1, name: "first_name", component: TextField, label: "First Name",size: 6 },
{ id: 1, name: "last_name", component: TextField, label: "Last Name", size: 6 },
{ id: 1, name: "age", component: TextField, label: "Age", size: 12 },
{ id: 1, name: "city", component: TextField, label: "City", size: 4 },
{ id: 1, name: "state", component: TextField, label: "State", size: 4 },
{ id: 1, name: "country", component: TextField, label: "Country", size: 4 }
];
const Form = styled.form`
width: 100%;
`;
const FlexColumn = styled.div`
float: left;
width: ${props => (props.size / 12) * 100}%;
`;
const FlexibleForm = () => (
<React.Fragment>
<Form onSubmit={() => console.log("something")}>
{formFields.map(({ id, name, label, component, size }) => {
return (
<FlexColumn size={size}>
<Field key={id} name={name} label={label} component={component} />
</FlexColumn>
);
})}
</Form>
</React.Fragment>
);

Categories

Resources