How do I pass the data to the child component? - javascript

When I select a product, the other AutoComplete for colors should filter based on the available variations of colors where the quantity is not equal to 0. For example, if I'll select the product Tumbler, the colors should be Black, pink, and green. And if I'll select the product Notebook, the color should be Green, Red, and Black.
The list of the products shows in the AutoComplete as options (working)
According to the what product was selected, it shows the available colors whose quantity is not equal to 0 for that selected product. For example, I chose Tumbler, this will show the list of colors the tumbler has which are Black, Pink, and Green. And if I’ll choose the product Shirt, the list of colors that will display are Blue and Black. (Not working)
Pass the data that contains the available colors of the selected product to the NestedArray component and show up as options of the AutoComplete. (Not working)
How can I do this? Thank you.
I have recreated the problem in codesandbox:
FieldArray Component
const FieldArray = ({ products }) => {
const options = products.map(
(object) =>
object.prodName +
" - " +
object.size +
);
console.log(fields, "f");
const selectedProduct = fields.map((object) => object.product);
console.log(selectedProduct, "selectedProduct");
return (
<div>
<ul>
{fields.map((item, index) => {
return (
<li key={item.id} style={{ listStyleType: "none" }}>
<Controller
control={control}
name={`order.${index}.product`}
render={({ field: { onChange, value = "", ...rest } }) => (
<Autocomplete
{...rest}
onInputChange={(e, newValue) => {
onChange(newValue);
}}
inputValue={value}
options={options}
isOptionEqualToValue={(option, value) =>
option?.value === value?.value
}
renderInput={(params) => (
<TextField
{...params}
label="Product"
variant="outlined"
fullWidth
/>
)}
/>
)}
/>
<NestedArray
nestIndex={index}
{...{ control, register, products }}
/> //pass the corresponding colors here of the selected product (AutoComplete) in the above component
</li>
);
})}
</ul>
<section>
//button to add more product
</section>
</div>
);
};
export default FieldArray;
NestedArray Component:
To show the corresponding colors here according to what Product (AutoComplete) was selected in the above component
const NestedArray = ({ nestIndex, control, register, products }) => {
const { fields, remove, append } = useFieldArray({
control,
name: `order[${nestIndex}].variation`,
});
return (
<div>
{fields.map((item, k) => {
return (
<div key={item.id} style={{ marginLeft: 20 }}>
<label>{k + 1}</label>
//the quantity input here
<Controller
control={control}
name={`order[${nestIndex}].variation[${k}].color`}
render={({ field: { onChange, value = "", ...rest } }) => (
<Autocomplete
{...rest}
onInputChange={(e, newValue) => {
onChange(newValue);
}}
inputValue={value}
options={options}
isOptionEqualToValue={(option, value) =>
option?.value === value?.value
}
renderInput={(params) => (
<TextField
{...params}
label="Color"
variant="outlined"
fullWidth
/>
)}
/>
)}
/>
</div>
);
};
export default NestedArray;

I did not dig in to see how react-hook-form works, so you can probably come up with a nicer solution.
Here is my solution in codesandbox: https://codesandbox.io/s/react-hook-form-wizard-form-from-reddit-with-data-setting-up-a-setproduct-forked-wyo8i2?file=/src/index.js
To describe the changes I made:
I added a state variable in FieldArray to hold the currently selected product. (you probably incorporate this better using your form library). The value is then passed to NestedArray.
Edited the rendered <Autocomplete> props in FieldArray to include onChange instead of onInputChange (used onChange instead of onInputChange to get the product object instead of the generated label as an argument to the function).
I aslo had to map the colors of the product object to an array similar to the one you had created.
const options = Object.keys(product.colorMap).map((color) => ({
label: color,
value: color
}))

Ok Pennie, for building a dynamic options (if I understood what you are tring to develop here).
I think you should pass the options as props into the
nestedFieldsArray.js,
fieldsArray.js->
<NestedArraynestIndex={index}
{...{ control, register, options }}
/>
and in the nestedFieldsArray.js->
const NestedArray = ({ nestIndex, control, register, options }) => {
const { fields, remove, append } = useFieldArray({
control,
name: `order[${nestIndex}].variation`
});
and remove the hard coded options-
const options = [ //should be delete
{ label: "red", value: "red" },
{ label: "green", value: "green" },
{ label: "blue", value: "blue" }
];
https://codesandbox.io/s/react-hook-form-wizard-form-from-reddit-with-data-forked-ledb12?file=/src/nestedFieldArray.js

Related

How to get the dynamic image inside 'startAdornment' in MUI's Autocomplete?

I am using MUI's autocomplete component to display some of my objects as suggestions.
Everything is working fine, but I am placing an avatar as a start adornment inside of the textfield (inside of renderInput), where I wish to put the value of corresponding image prop of what is selected.
What I want : I want that whatever value is selected in autocomplete, (there is an image property inside my object, whose array i mapped to options prop of autocomplete). So, I want that corresponding image, together with the option label (inside of textfield), the corresponding image should also be shown inside startAdornment.
Well, I want it similar to LinkedIn's company field which we fills while posting a new job.
Here's my MUI Autocomplete, I am new to 'typescript'.
<Autocomplete
open = {open}
onOpen = {() => setOpen(true)}
onClose = {() => setClose(true)}
options = {items.sort((a, b) => -b.name.localeCompare(a.name))}
isOptionEqualToValue = {(option, value) => option.name === value.name}
getOptionLabel = {(option : any) => option.name}
renderInput = {(params : any ) => (
<TextField
{...params}
className = {'inputField'}
placeholder = {'Enter item name'}
label = {'items'}
InputProps = {{
...params.InputProps,
startAdornment : ( <Avatar src = {params.image} /> )
}} />
)}
renderOption = {(props, option) => {
return (
<li
key = {option.id}
{option.name}
</li> )}} />
so I want that whatever name is selected from autocomplete, its corrosponding image should be displayed into the Avatar(inside of startadornment).
How to achieve this?
And, yes, here is my Object array be like -
[{ id : number, name : string, image : string }, {...}, {...}, and so on...];
image here is url of the image, ( that I wish to show into the avatar inside startAdornment.)
Do i need to provide a new state for this?
All suggestions would be appretiated, eagerly looking for answers that fits.
My approach would be to create a state to store the select value and whenever this state changes it would update the avatar component:
const [value, setValue] = useState("");
const AvatarAdornment = useMemo(()=>
<Avatar src={items.find(item =>item.name === value).image} />,
[value, items])
And in your autocomplete component use it like:
<Autocomplete
open = {open}
onOpen = {() => setOpen(true)}
onClose = {() => setClose(true)}
options = {items.sort((a, b) => -b.name.localeCompare(a.name))}
isOptionEqualToValue = {(option, value) => option.name === value.name}
getOptionLabel = {(option : any) => option.name}
renderInput = {(params : any ) => (
<TextField
{...params}
className = {'inputField'}
placeholder = {'Enter item name'}
label = {'items'}
InputProps = {{
...params.InputProps,
startAdornment : ( <AvatarAdornment/> )
}} />
)}
renderOption = {(props, option) => {
return (
<li
key = {option.id}
{option.name}
</li> )}} />

How to add rows dynamically in ReactJS?

I'm still beginner to ReactJS and need to build a dynamic table for my work.
In that table, the user can add new lines and can also remove any existing lines.
The problem is, I don't know how to save the values that are typed into the new fields. My onChange function isn't working, I've done several tests, but I'm not able to save the entered values.
Here's my code I put into codesandbox.
Could you tell me what I'm doing wrong to save the entered values? Thank you in advance.
import React from "react";
import "./styles.css";
import List from "./List/List";
const App = () => {
const [data, setData] = React.useState([
[
{
label: "Property Name",
field: "propertyName",
value: ""
},
{
label: "Value",
field: "value",
value: ""
}
]
]);
const handleOnChange = (field) => (e) => {
setData((prev) => ({
...prev,
[field]: e.target.value
}));
};
const addRow = () => {
setData([
...data,
[
{
label: "Property Name",
field: "propertyName",
value: ""
},
{
label: "Value",
field: "value",
value: ""
}
]
]);
};
const removeRow = (index) => {
const _data = [...data];
_data.splice(index, 1);
setData(_data);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<List
data={data}
addRow={addRow}
removeRow={removeRow}
handleOnChange={handleOnChange}
/>
</div>
);
};
export default App;
import React from "react";
import AddCircleIcon from "#material-ui/icons/AddCircle";
import RemoveCircleIcon from "#material-ui/icons/RemoveCircle";
import TextField from "#material-ui/core/TextField";
import "./styles.scss";
const List = ({ data, handleOnChange, addRow, removeRow }) => {
return (
<div className="container">
{data.map((items, index) => (
<div key={index} className="content">
<div className="content-row">
{items.map((item, index) => (
<TextField
key={index}
label={item.label}
value={item.value}
onChange={handleOnChange(index)}
variant="outlined"
/>
))}
</div>
<div>
<AddCircleIcon onClick={addRow} />
{data.length > 1 && (
<RemoveCircleIcon onClick={() => removeRow(index)} />
)}
</div>
</div>
))}
</div>
);
};
export default List;
Good that you shared the code. There are several issues with your code. I have an updated code placed under this URL,
https://codesandbox.io/s/reverent-mclean-hfyzs
Below are the problems,
Your data structure is an array of arrays and your onchange event doesn't respect that.
You have no property available [name/id] to identify a textbox when you change the value.
I had to add a name property to each textbox and design it like a 2D array so any textbox will have a unique name.
I had to map through the data array and find the node where I have to update the value and set the new value as the new state when any textbox changes.
I have added a console.log while adding a row so you can see the current state.

I want to change variant or background of the clicked button (onClick) only in reactjs. How can i achieve it?

What i am trying to do it, when a button is clicked, at the onclick it's variant(material ui button should change from outlined to contained) or simply its background should change. (Please do not suggest for the onFocus property because these is another button in another component, which when clicked focus is lost. So onFocus is not a choice for me here). I am atatching my method here, you can change it (because mine is not working anyhow, it's changing state to true indefinitely)
const [clicked, setClicked] = useState(false);
const categoryChangedHandler = (e) => {
setCategory(e);
};
{categories.map((category, index) => {
console.log("catogoried.map called and categories= " + category);
return <Button className="CategoryButton"
variant={clicked ? "contained" : "outlined"}
color="primary"
value={category}
onClick={() => {
categoryChangedHandler(category);
setClicked(true);
}}
style={{ textAlign: 'center' }}
>
{category}
</Button>
})
}
If you want to show a different color base on it's category, you probably want to change the variant base on the state ( whether it's selected ).
Example
const categories = ['apple', 'banana', 'mango']
const App = () => {
const [ selected, setSelected ] = useState([])
const onClick = (value) => {
//this is a toggle to add/remove from selected array
if (selected.indexOf(value) > -1) {
//if exist, remove
setSelected( prev => prev.filter( item => item !== value )
} else {
//add to the selected array
setSelected( prev => [ ...prev, value ] )
}
}
return <div>
{categories.map((category, index) => {
return <Button className="CategoryButton"
/* if the category had been selected, show contained */
variant={ selected.indexOf(category) > -1 ? "contained" : "outlined"}
color="primary"
value={category}
onClick={() => {
onClick(category);
}}
style={{ textAlign: 'center' }}
>
{category}
</Button>
})
}</div>
}
The above example keeps an array of categories selected. OF course, if you only want to allow ONE to be selected at each click, then instead of an array, you can use setSelected(value) (where value is the category name), then in your button component use
variant={ selected === category ? 'contained' : 'outlined' }
Remember to change your use state to use string instead of array
const [ selected, setSelected ] = useState('') //enter a category name if you want it to be selected by default

MaterialUI Select set value is always out of range

i've a MaterialUI Select code, and i'm handling the value parameter dynamically. My problem is, when i set any value, it says always it's out of range, even showing the value in the valid values.
SelectInput.js:291 Material-UI: you have provided an out-of-range value `100001,250000` for the select (name="followers") component.
Consider providing a value that matches one of the available options or ''.
The available values are `0,50000`, `50001,100000`, `100001,250000`, `250001,500000`, `500001,750000`, `750001,9007199254740991`.
(anonymous) # SelectInput.js:291
And this is my code simplified:
const followers = [
{ '0-50k': [0, 50000] },
{ '50k-100k': [50001, 100000] },
{ '100k-250k': [100001, 250000] },
{ '250k-500k': [250001, 500000] },
{ '500k-750k': [500001, 750000] },
{ '+1M': [750001, Number.MAX_SAFE_INTEGER] },
];
<div className={classes.formGroup}>
<InputLabel id="followersL">Followers</InputLabel>
<Select
className={classes.field}
fullWidth
id="followers"
labelId="followersL"
margin="dense"
displayEmpty
name="followers"
onChange={(event) => setValue(event.target.value)} //I've updated the sate with the new value
value={
filters.basicInfo.followers
? value
: ''
}
variant="outlined"
>
{followers.map((element) => (
<MenuItem
value={element[Object.keys(element)]}
key={Object.keys(element)[0]}
>
{Object.keys(element)[0]}
</MenuItem>
))}
</Select>
</div>
As you can see in the message, the value selected 100001,250000 it's inside the range examples 100001,250000
Where is the problem?
add this defaultValue = "" like this
<Select
...
defaultValue=""
>
This advice may be useful for others:
If the value for Select element is object, it should be the exact instance of the object from the list of Options.
For example:
const [test, setTest] = useState("");
//list of options for Material UI select
const testOptions = [
{name: "123"},
{name: "456"},
{name: "769"},
];
//let's try to change value to {name: "123"} using JS
setTest(testOptions[0]); // everything is OK
setTest({name: "123"}); // Error! You provided out of range value!
Stringifying your value will get this to work.
element[Object.keys(element)] + ""}
If you needed it to be in its original array format before sending the result to your server you could use a function like this to do this
const strToArr = str => str.split(',').map(item => Number(item))
In my code here I have used your provided example and been able to replicate your error. But Stringifying the value removes the error and gets it to work as expected.
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import InputLabel from "#material-ui/core/InputLabel";
import MenuItem from "#material-ui/core/MenuItem";
import Select from "#material-ui/core/Select";
const useStyles = makeStyles(theme => ({
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
}
}));
export default function SimpleSelect() {
const classes = useStyles();
const followers = [
{ "0-50k": [0, 50000] },
{ "50k-100k": [50001, 100000] },
{ "100k-250k": [100001, 250000] },
{ "250k-500k": [250001, 500000] },
{ "500k-750k": [500001, 750000] },
{ "+1M": [750001, Number.MAX_SAFE_INTEGER] }
];
const [value, setValue] = React.useState("");
const handleChange = event => setValue(event.target.value);
return (
<div>
<p>value - {value}</p>
<div className={classes.formGroup}>
<InputLabel id="followersL">Followers</InputLabel>
<Select
className={classes.field}
fullWidth
id="followers"
labelId="followersL"
margin="dense"
displayEmpty
name="followers"
onChange={handleChange}
value={value}
variant="outlined"
>
{followers.map(element => (
<MenuItem
value={element[Object.keys(element)] + ""}
key={Object.keys(element)[0]}
>
{Object.keys(element)[0]}
</MenuItem>
))}
</Select>
</div>
</div>
);
}
I ran into the same problem (you have provided an out-of-range value) when using a number state with a default value of -1:
const [selectedAccountId, setSelectedAccountId] = useState<number>(-1);
The solution to this problem was to assign an empty string for the value property in Material's UI Select component instead of using the default value of -1:
value={selectedAccountId === -1 ? '' : selectedAccountId}
Full component example:
<FormControl fullWidth>
<InputLabel>Account</InputLabel>
<Select
id="account"
value={selectedAccountId === -1 ? '' : selectedAccountId}
onChange={event => {
setSelectedAccountId(Number(event.target.value));
}}>
{allAccounts.map((account, index) => (
<MenuItem key={index} value={account.id}>
{account.exchange} ({account.id})
</MenuItem>
))}
</Select>
</FormControl>
From some research, what I've come to understand to be the reason for this warning, in my case, is the MUI Select was trying to map over a list of options that were not available on the first render as the list was coming from Axios response.
I made a component named Dropdown that renders the MUI Select component. I was providing it four props:
options: the list of options,
initialValue: the default value as I
had different default values for different instances of the Dropdown component that were not the first item of the
options list always
... and 2 other props that are not scoped for this discussion.
So, for each instance of the Dropdown component, I had to check whether the options list has any data, and only then render it. And this is what removed the warning from the console. To give a glimpse of what I did:
{viewsData.length > 0 && (
<Dropdown
options={viewsData}
initialValue={7}
{...otherProps}
/>
)}
This was bugging me for a long time. Hopefully this will help someone.
I got the same error and I solved it by making sure that the default value and the other select values thereafter are the same, for example if the default value is a string like '' then the other values are objects it will show the warning so to avoid such a problem make the default value to be either a [] or {} or most preferably null
To add to #Ait Friha Zaid response.
I also added the defaultValue attribute but also added an additional option:
const values = ['option 1', 'option 2', 'option 3'];
<FormControl fullWidth>
<InputLabel>{title}</InputLabel>
<Select
defaultValue="choose"
label={title}
onChange={e => func({...state, [keyName]: e.target.value}) }
>
<MenuItem disabled value="choose">Choose Option</MenuItem>
{values.map((value) => (
<MenuItem value={value} key={value}>{value}</MenuItem>
))}
</Select>
</FormControl>
That way you always have a disabled option that works as a placeholder which is the default option, and in case you want to do form validation, until the user changes the option, the state wont be changed.

ReactJS: How to dynamically render Material-UI's <MenuItem/> inside <DropDownMenu/>?

Using ReactJS + Material-UI, I have an array called colors and contains strings of different colors. Say for example the array colors has 3 color strings: "white", "blue", "green. Then I would like to render each color string has a <MenuItem/> inside a <DropDownMenu/> (http://www.material-ui.com/#/components/dropdown-menu). And once a <MenuItem/> is selected, I'd like to console log that particular color like, say chose "white": console.log("white").
So I used .forEach yet the does not show any strings and it is empty. What could I be doing wrong?
Here is the code:
constructor() {
super()
this.state = {
value: 1,
}
}
dropDownColorChange(event, index, value) {
this.setState({value: value})
//Not sure how to implement here dynamically based on array size. Would like to console.log the color string of the selected
}
render() {
var colors = ["white", "blue", "green"] //would be able to handle any array size
return (
<div>
<DropDownMenu
value={this.state.valueTwo}
onChange={this.dropDownColorChange}
>
{
<MenuItem value={1} primaryText="Select" />
colors.forEach(color => {
<MenuItem primaryText={color}/>
})
}
</DropDownMenu>
</div>
)
}
Thank you
You've almost got it right. You have to map over available colors and return a MenuItem for each color:
const colors = ['white', 'blue', 'green'];
class ColorChanger extends Component {
constructor() {
super();
this.state = {
selectedColorValue: 1,
};
}
handleColorChange(event, index, value) {
console.log(`You have selected ${colors[value]} color`);
this.setState({
selectedColorValue: value
});
}
render() {
return (
<div>
<DropDownMenu value={this.state.selectedColorValue} onChange={this.handleColorChange}>
{colors.map((color, index) =>
<MenuItem key={index} value={index} primaryText={color} />
)}
</DropDownMenu>
</div>
);
}
}
map (contrary to forEach) returns an array where each element is the return value of predicate function. In your case it returns a <MenuItem />.
I used the react hook to set the menu items on clicking my menu icon and I also set the value I want to pass to my action method.
const [menuItems, setMenuItems] = React.useState<IMenuItem[]>();
const [menuValue, setMenuValue] = React.useState<IMenuValue>();
const handleClickMenu = (
event: React.MouseEvent<HTMLElement>,
value: IMenuValue,
) => {
setMenuItems(value.menuItems);
setMenuTransaction(value);
setMenuAnchorEl(event.currentTarget);
};
return (
// ... code ...
<PositionedVertMenu
data-testid={`menu`}
open={Boolean(menuAnchorEl)}
anchorEl={menuAnchorEl}
onClick={(event: React.MouseEvent<HTMLElement>) => handleClickMenu(event, value)}
onClose={handleCloseMenu}
>
{menuValue &&
menuItems?.map((option, menuIndex) => (
<MenuItem
data-testid={`menu-item-${menuIndex}`}
onClick={() => option.action(menuValue, handleCloseMenu)}
>
<Typography>{translate(option.text)}</Typography>
</MenuItem>
))}
</PositionedVertMenu>
)

Categories