How to make multiple radio groups work in React.js - javascript

I'm using React with Chakra UI for this.
I'm having an issue with my radio buttons. I have two groups of radio buttons, second group is showed dynamically based on the first group selection. However, first group works fine, but when selecting an option from the second group it doesn't get marked, but looking at the state of the component it does pass the value.
Below is the code of my component.
const SuperAttack = ({ handleChange }: Props) => {
const [selectedMutiplier, setSelectedMultiplier] = useState<SAMultiplier>();
const handleMultiplierSelect = (name: string) => {
const selected = SAMultipliers.filter((multiplier) => {
return multiplier.name === name;
});
setSelectedMultiplier(selected[0]);
};
return (
<Box
border="1px"
borderColor="gray.200"
borderRadius={10}
p="20px"
mb="10px"
>
<FormControl>
<FormLabel>Super attack multiplier</FormLabel>
<RadioGroup name="attack" onChange={handleMultiplierSelect}>
<Stack direction="row">
{SAMultipliers.map((multiplier) => {
return <Radio value={multiplier.name}>{multiplier.name}</Radio>;
})}
</Stack>
</RadioGroup>
</FormControl>
{selectedMutiplier ? (
<FormControl>
<FormLabel>Select SA level</FormLabel>
<RadioGroup name="multiplier" onChange={handleChange}>
<Stack direction="row">
{selectedMutiplier.SA.map((multiplier) => {
return (
<Radio value={multiplier.value}>{multiplier.name}</Radio>
);
})}
</Stack>
</RadioGroup>
</FormControl>
) : null}
</Box>
);
};
export default SuperAttack;
Thanks in advance

Related

Problems using a Collapse MaterialUI componente inside an iterableble component

Im having troubles to expand and contract a Collapse Component from MaterialUI since Im mapping and array and iterating the same component, when i press the collapse button, all components expands/contracts at the same time ( I suppose that Im not providing an identifier to point where the collapse function should be used),Im currently Using an State to control the collapse action:
const [expanded, setExpanded] = useState(false);
This is the return where I iterate the component using map on RecetasAll object,
return (
<React.Fragment key={RecetasAll.id}>
<Card className="searchItem" sx={{ maxWidth: 345 }}>
<CardHeader
action={<IconButton aria-label="settings"></IconButton>}
title={RecetasAll.titulo}
/>
<h4
className="Dieta"
style={{
backgroundColor: color(RecetasAll.Tiporeceta.tipoReceta),
}}
>
{RecetasAll.Tiporeceta.tipoReceta}
</h4>
<span className="Calorias">{RecetasAll.informacionNutricional}</span>
<CardMedia
component="img"
height="194"
image={RecetasAll.imagen}
alt="Paella dish"
/>
<CardContent>
{RecetasAll.Productos.map((Productos) => {
return (
<React.Fragment key={Productos.id}>
<Typography variant="body2" color="text.secondary">
{Productos.producto}
</Typography>
</React.Fragment>
);
})}
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteIcon />
</IconButton>
<ExpandMore
expand={expanded}
onClick={() => setExpanded(!expanded)}
aria-expanded={expanded}
>
<ExpandMoreIcon />
</ExpandMore>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent id={RecetasAll.id}>
<Typography paragraph>Preparacion:</Typography>
<Typography paragraph>{RecetasAll.pasos}</Typography>
<Button
href="#contained-buttons"
variant="contained"
onClick={handleSearch}
>
Ver mas
</Button>
</CardContent>
</Collapse>
</Card>
</React.Fragment>
);
});
return <>{itemRecetas}</>;
}
Im triying to set an id property to the CardContent since its the child of the Collapse component
id={RecetasAll.id}
this is the function Im using to expand or collapse but I dont know how to get the id properly to compare its value with expanded state:
const handleExpandClick = (e) => {
let clickedItemId = e.currentTarget.id;
if (expanded === clickedItemId) {
setExpanded(!expanded);
} else {
setExpanded(clickedItemId);
}
};
You could refactor every card into a new component and that way you can have a state to open/close the individual card. When iterating you can pass in the RecetasAll.
const MyCard = ({ RecetasAll }) => {
const [isExpanded, setIsExpanded] = useState(false);
const toggleExpanded = () => {
setIsExpanded(prevIsExpanded => !prevIsExpanded);
};
return (
...
<ExpandMore
expand={isExpanded}
onClick={toggleExpanded}
aria-expanded={isExpanded}
>
...
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
...
);
};
If you don't want to use a new component you could store all the ids of the expanded cards in a state. Based on if the id is in the array the card will be expanded or collapsed.
const [expandedIds, setExpandedIds] = useState([]);
const toggleExpanded = (id) => {
setExpandedIds((prevExpandedIds) => {
// if id is already in array remove
if (prevExpandedIds.includes(id))
return prevExpandedIds.filter((i) => i !== id);
// else add to array
return [...prevExpandedIds, id];
});
};
return (
...
<ExpandMore
expand={expandedIds.includes(RecetasAll.id)}
onClick={() => toggleExpanded(RecetasAll.id)}
aria-expanded={expandedIds.includes(RecetasAll.id)}
>
...
<Collapse in={expandedIds.includes(RecetasAll.id)} timeout="auto" unmountOnExit>
...
)

ReactJs: Cannot select item from MenuItem

I have a case where the I have 3 items, and at in the case where is the first item, it should be displaying only the first item, and not allow the user to select 2nd and 3rd, but in case wher isItFirt = false then the user should be able to choose from the list. I wrote the minimal reproducible example as shown below:
import * as React from "react";
import {
Typography,
Button,
Dialog,
Box,
Select,
InputLabel,
FormControl,
MenuItem,
SelectChangeEvent
} from "#mui/material";
enum MyOptions {
FIRST = 1,
SECOND = 2,
THIRD = 3
}
export default function App() {
const [open, setOpen] = React.useState(true);
const [myOptions, setMyOptions] = React.useState(MyOptions.SECOND as number);
const handleChange = (event: SelectChangeEvent) => {
let nr = parseInt(event.target.value, 10);
setMyOptions(nr);
};
const isItFirst: boolean = false;
const handleClose = () => {
setOpen(false);
};
const somethingHappens = () => {
console.log("clicked: ", myOptions);
setOpen(false);
};
React.useEffect(() => {
if (isItFirst) {
setMyOptions(MyOptions.FIRST as number);
}
}, [isItFirst]);
return (
<div>
<Button
variant="contained"
size="small"
onClick={() => {
setOpen(true);
}}
>
Display dialog
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box>
<Typography id="modal-modal-title" variant="h6" component="h4">
Select one of the options
</Typography>
<FormControl>
<InputLabel id="1">Options</InputLabel>
<Select
labelId=""
id=""
value={myOptions}
label="Options"
onChange={(e: any) => handleChange(e)}
>
{isItFirst ? (
<MenuItem value={MyOptions.FIRST}>This is first</MenuItem>
) : (
<div>
<MenuItem value={MyOptions.SECOND} key={MyOptions.SECOND}>
This is second
</MenuItem>
<MenuItem value={MyOptions.THIRD} key={MyOptions.THIRD}>
This is third
</MenuItem>
</div>
)}
</Select>
</FormControl>
</Box>
<Button
variant="contained"
size="small"
onClick={() => {
somethingHappens();
}}
>
Select
</Button>
</Dialog>
</div>
);
}
This is the error output:
MUI: You have provided an out-of-range value `1` for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are "".
And this is the dialog box that is shown in the case when isItFirst === false, I do not understand why it is shown as blank when I set the state of myOptions with the help of useEffect.
According to this document for children prop of Select
The option elements to populate the select with. Can be some MenuItem when native is false and option when native is true.
⚠️The MenuItem elements must be direct descendants when native is false.
So technically, we cannot pass div or any other elements to wrap MenuItem.
For the fix, you can consider to use filter and map with a pre-defined option like below
const options: {value: MyOptions, label: string}[] = [
{value: MyOptions.FIRST, label: "This is first"},
{value: MyOptions.SECOND, label: "This is second"},
{value: MyOptions.THIRD, label: "This is thrid"}
]
Here is how we apply options to Select
<Select
labelId=""
id=""
value={myOptions}
label="Options"
onChange={handleChange}
key="first-select"
>
{options
.filter((option) =>
isItFirst
? option.value === MyOptions.FIRST
: option.value !== MyOptions.FIRST
)
.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>

React props updating with useState?

I have the component below where I'm trying the build functionality to allow a user to update opening times of a store.
I'm passing the original opening times as a prop and creating some state using the props opening times for initial state. I want to use the new state to submit changes but if the user selects cancel the UI updates to reflect the original times.
I have most of the functionality working but for some reason my handler to update the state with the input change also seems to update the props value so it won't go back to the original value.
How can I stop the props updating and ensure only the allOpeningHours state is changed?
VIDEO: https://www.veed.io/view/c40bf9e8-7502-408a-ba6d-fd306dbf4b6f?sharingWidget=true
const EditStudioHours: FC<{ studio: Studio }> = ({ studio }) => {
const { value: edit, toggle: toggleEdit } = useBoolean(false)
const { value: submitting, toggle: toggleSubmitting } = useBoolean(false)
const [allOpeningHours, setAllOpeningHours] = useState([
...studio.openingHours.regularDays,
])
return (
<Box>
<Typography variant='h6' mt={2} gutterBottom>
Set standard hours
</Typography>
<Typography fontWeight='light' fontSize={14}>
Configure the standard operating hours of this studio
</Typography>
<Stack mt={3} spacing={2}>
{studio.openingHours.regularDays.map((hours, i) => (
<DayOfWeek
dow={daysOfWeek[i]}
openingHours={hours}
edit={edit}
i={i}
setAllOpeningHours={setAllOpeningHours}
allOpeningHours={allOpeningHours}
/>
))}
</Stack>
<Button
variant={edit ? 'contained' : 'outlined'}
onClick={() => {
toggleEdit()
}}
fullWidth
sx={{ mt: 2 }}
disabled={!edit ? false : submitting}
>
{submitting ? (
<CircularProgress size={22} />
) : edit ? (
'Submit changes'
) : (
'Edit'
)}
</Button>
{edit && (
<Button
onClick={toggleEdit}
variant={'outlined'}
sx={{ mt: 1 }}
fullWidth
>
Cancel
</Button>
)}
</Box>
)
}
export default EditStudioHours
const DayOfWeek: FC<{
openingHours: { start: number; end: number }
dow: string
edit: boolean
i: number
setAllOpeningHours: any
allOpeningHours: any
}> = ({ openingHours, dow, edit, i, setAllOpeningHours, allOpeningHours }) => {
const [open, setOpen] = useState(openingHours.end !== openingHours.start)
const handleOpenClose = () => {
open &&
setAllOpeningHours((ps: any) => {
const newHours = [...ps]
newHours[i].start = 0
newHours[i].end = 0
return newHours
})
setOpen((ps) => !ps)
}
const handleStart = (e: any) => {
setAllOpeningHours((prevState: any) => {
const newHours = [...prevState]
newHours[i].start = e.target.value
return newHours
})
}
const handleEnd = (e: any) => {
setAllOpeningHours((ps: any) => {
const newHours = [...ps]
newHours[i].end = e.target.value
return newHours
})
}
return (
<Box display='flex' alignItems='center' justifyContent={'space-between'}>
<Box display={'flex'} alignItems='center'>
<Typography width={150}>{dow}</Typography>
<FormGroup>
<FormControlLabel
control={
<Switch
disabled={!edit}
checked={open}
onChange={handleOpenClose}
/>
}
label='Open'
/>
</FormGroup>
</Box>
{open && (
<Box display={'flex'} alignItems='center'>
<TextField
disabled={!edit}
id={`${i}open`}
select
label='Open'
value={edit ? allOpeningHours[i].start : openingHours.start}
type='number'
sx={{ minWidth: 120 }}
size='small'
onChange={handleStart}
>
{openingOptions.map((option: { value: number; label: string }) => (
<MenuItem dense key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<Typography mx={2}>TO</Typography>
<TextField
disabled={!edit}
id={`${i}close`}
select
label='Close'
value={edit ? allOpeningHours[i].end : openingHours.end}
type='number'
sx={{ minWidth: 120 }}
size='small'
onChange={handleEnd}
>
{openingOptions.map((option: { value: number; label: string }) => (
<MenuItem dense key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
</Box>
)}
</Box>
)
}
The problem is that objects within the studio.openingHours.regularDays array still share the same reference, even though you copied the array itself.
When you use something like
newHours[i].start = e.target.value
You're still updating the original objects from props.
You can use Array.prototype.splice() to remove the object at index i and replace it with a new one
const day = newHours[i];
newHours.splice(i, 1, {
...day,
start: e.target.value,
});
Do this in each of your 3 handle functions.
Alternately, break all references when creating local state from props
const [allOpeningHours, setAllOpeningHours] = useState(
studio.openingHours.regularDays.map((day) => ({ ...day }))
);

React Hook useState value being reset to initial value

The state of a value set using React useState hook gets set to the proper value and then reset to null. Critical code below. The click event that sets the startDate to the current date and time is 3 components down from where startDate is initialized. When setStartDate did not work I created an arrow function, updateStartDate. Both had the same problem where the startDate was changed after the click event (witnessed per the console.log in the top component), but was null just before the next click event (per the console.log in the click event). This is not an async problem as I see the change made before subsequent click.
If this is something that just does not work please explain. I could probably fix with useReducer but prefer to keep the useState if there is something I can do to correct this... If not correctable then I would like to at least understand why it does not work so that I can avoid this problem in the future.
export const DisplayTicTacToeContainer = (props) => {
const [startDate, setStartDate]= useState();
const updateStartDate = (newDate) => {
setStartDate(newDate);
}
useEffect (() => {
setStartDate(null);
}, []);
useEffect(() => {
console.log( "displayTicTacToeContainer useEffect for change of startDate = ", startDate)
}, [startDate]);
return (
<DisplayTicTacToeMatch arrayOfMatchingItems ={arrayOfMatchingItems}
startDate={startDate}
setStartDate={setStartDate}
updateStartDate={updateStartDate}
/>);
}
//-----------------------------------------------
export const DisplayTicTacToeMatch = (props) => {
const { startDate,
setStartDate,
updateStartDate,
} = props;
useEffect(() => {
// Performs some prep and working fine.
}, []);
return (
<TicTacToe
startDate={startDate}
setStartDate={setStartDate}
updateStartDate={updateStartDate}
/>
);
}
//-----------------------------------------------
const TicTacToeContainer = (props) => {
const { startDate,
setStartDate,
updateStartDate,
} = props;
const [board, setBoard] = useState(<Board
updateStartDate={updateStartDate}
startDate={startDate}
setStartDate={setStartDate}/>);
return (
<Board/>
)
}
export default TicTacToeContainer;
I renamed the component to BoardComponent and the state variable to boardLayout. I included the full return portion of the BoardComponent below.
As I am still experiencing the problem I would agree with you that, "DisplayTicTacToeContainer is being mounted twice". Any thoughts on how I can avoid this from happening?
Other than this inability to setStartDate, everything is working fine.
//-----------------------------------------------
const Board = (props) => {
const { updateStartDate,
startDate,
setStartDate,
} = props;
return (
<>
<Grid container maxwidth="lg" alignItems="center" spacing={1}>
<Grid item xs={9}>
<Grid container alignItems="center">
<Grid item xs={9}>
<Typography variant = "body1">
First select a square. Once the "Inquiry" word or phrase appears below, find
the correct response in the column on the right and select that buttton. A correct
response will fill the square previously selected with an "O" or "X".
</Typography>
<div style={{ width: '100%' }}>
<Box
display="flex"
flexWrap="wrap"
p={1}
m={1}
bgcolor="background.paper"
css={{ maxWidth: 900 }}
>
<Box p={1} bgcolor="grey.300">
Inquiry : {inquiry}
</Box>
</Box>
<Box
display="flex"
flexWrap="wrap"
p={1}
m={1}
bgcolor="background.paper"
css={{ maxWidth: 900 }}
>
<Box p={1} bgcolor="grey.300">
Next move by : {currentPlayer}
</Box>
<Box p={1} bgcolor="grey.300">
{showStatus}
</Box>
</Box>
</div>
</Grid>
</Grid>
<MyAux>
{boardLayout.map((row, rowId) => {
const columns = row.map((column, columnId) => (
<Grid key={columnId} item>
<ButtonBase >
<Paper
onClick={(e) => {
clickSquareHandler(e);
}}
elevation={4}
data-coord={rowId + ':' + columnId}
id={"Square" + rowId.toString() + columnId.toString()}
className={classes.Paper}>
<Icon
className={classes.Icon}
style={{fontSize: 78}}>
</Icon>
</Paper>
</ButtonBase>
</Grid>
));
return (
<Grid
key={rowId}
className={classes.Grid}
container
spacing={2}>
{columns}
</Grid>)
})}
</MyAux>
</Grid>
<Grid item xs={3} >
<Paper className={classes.paper}>
<Typography variant = "body1">
Response Options
</Typography>
<ButtonGroup
orientation="vertical"
color="secondary"
aria-label="vertical outlined secondary button group"
>
{responseChoices.map((choice) => (
<Controls.Button
key ={choice.value}
text={choice.value}
variant="contained"
color = "secondary"
onClick={() => {
chooseChecker(choice);
}}
className={
response && response.value === choice.value ? "selected" : ""
}
disabled={!!selected[choice.value]}
fullWidth = "true"
size = "small"
/>
))}
</ButtonGroup>
</Paper>
</Grid>
</Grid>
</>
)
}
BoardContainer.propTypes = {
won: PropTypes.func,
size: PropTypes.number
};
export default BoardContainer;
At least, code below doesn't make much sense.
Please don't set state value as a component.
Also, try to name state variable different from components, since it will confuse you at some ppint.
const [board, setBoard] = useState(<Board
updateStartDate={updateStartDate}
startDate={startDate}
setStartDate={setStartDate}/>);
return (
<Board/>
)
Another possibility is that the DisplayTicTacToeContainer is being mounted twice, but I can't confirm it with the code provided.

Select all and Select None buttons in Autocomplete Material UI React

I want to implement two buttons Select All and Select None inside Autocomplete React Material UI along with checkbox for each option.When Select All button is clicked all the options must be checked and when I click Select None all the options must be unchecked.
How do I implement that ?
<Autocomplete
id={id }
size={size}
multiple={multiple}
value={value}
disabled={disabled}
options={items}
onChange={handleChange}
getOptionLabel={option => option.label}
renderOption={(option, { selected }) => (
<React.Fragment >
{isCheckBox(check, selected)}
{option.label}
</React.Fragment>
)}
renderInput={params => (
<TextField id="dropdown_input"
{...params} label="controlled" variant={variant} label={label} placeholder={placeholder} />
)}
/>
export function isCheckBox(check, selected) {
if (check) {
const CheckBox = <Checkbox
id="dropdown_check"
icon={icon}
checkedIcon={checkedIcon}
checked={selected}
/>
return CheckBox;
}
return null;
}
I stumbled into the same issue earlier today.
The trick is to use local state to manage what has been selected, and change the renderOption to select * checkboxes if the local state has the 'all' key in it.
NB: At the time of writing React 16 is what I'm working with
I'm on a deadline, so I'll leave a codesandbox solution for you instead of a rushed explanation. Hope it helps :
Select All AutoComplete Sandbox
Updated
for React version 16.13.1 and later. codesandbox
const [open, setOpen] = useState(false);
const timer = useRef(-1);
const setOpenByTimer = (isOpen) => {
clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
setOpen(isOpen);
}, 200);
}
const MyPopper = function (props) {
const addAllClick = (e) => {
clearTimeout(timer.current);
console.log('Add All');
}
const clearClick = (e) => {
clearTimeout(timer.current);
console.log('Clear');
}
return (
<Popper {...props}>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button color="primary" onClick={addAllClick}>
Add All
</Button>
<Button color="primary" onClick={clearClick}>
Clear
</Button>
</ButtonGroup>
{props.children}
</Popper>
);
};
return (
<Autocomplete
PopperComponent={MyPopper}
onOpen={(e) => {
console.log('onOpen');
setOpenByTimer(true);
}}
onClose={(obj,reason) => {
console.log('onClose', reason);
setOpenByTimer(false);
}}
open={open}
.....
....
/>
);
Old Answer
Just customise PopperComponent and do whatever you want.
Autocomplete API
const addAllClick = (e: any) => {
setValue(items);
};
const clearClick = (e: any) => {
setValue([]);
};
const MyPopper = function (props: any) {
return (
<Popper {...props}>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button color="primary" onClick={addAllClick}>
Add All
</Button>
<Button color="primary" onClick={clearClick}>
Clear
</Button>
</ButtonGroup>
{props.children}
</Popper>
);
};
<Autocomplete
PopperComponent={MyPopper}
...
/>
If you want to make an autocomplete with select all option using react material ui and react hook form, you can implement to Autocomplete like so
multiple: To allow multiple selection
disableCloseOnSelect: To disable the close of the box after each selection
options: Array of items of selection
value: Selected options.
getOptionLabel: The string value of the option in our case is name
filterOptions: A function that determines the filtered options to be rendered on search, in our case we used it to add selectAll checkbox.
renderOption: Render the option, use getOptionLabel by default.
renderInput: To render the input,
onChange: Callback fired when the value changes
Now you can play with selected values using handleChange so once the select is fired check if the selected option is select all if yes then set the newest selectedOptions
<Autocomplete
multiple
disableCloseOnSelect
options={items}
value={selectedOptions}
getOptionLabel={(option) => option.name}
filterOptions={(options, params) => {
const filtered = filter(options, params)
return [{ id: 0, name: selectAllLabel }, ...filtered]
}}
renderOption={(props, option, { selected }) => {
// To control the state of 'select-all' checkbox
const selectAllProps =
option.name === 'Sélectionner Tous' ? { checked: allSelected } : {}
return (
<li {...props}>
<Checkbox checked={selected} {...selectAllProps} />
{option.name}
</li>
)
}}
renderInput={(params) => (
<TextField {...params} label={label} placeholder={label} />
)}
onChange={handleChange}
/>
you can refer to the Autocomplete API to get detailed definition of each item
You can refer to this codeSendBox to check a demo of react material Autocomplete with select all using react material ui version 5 and react hook form verion 7

Categories