Having an issue getting vaues to onClickHandler Due to MUI - javascript

So I want to get a category navigation which contains icons and labels.
I've tried with Chips:
{categories.map((category) => (
<Chip
key={category._id}
avatar={<Avatar alt={category.name} src={category.icon} />}
label={category.name}
variant="outlined"
onClick={(e) => handleClick(e)}
value={category._id}
/>
))}
Ive tried using Tabs -> Tab. But they don't produce a "value" when i
const handleClick = (e) => {
const valueTargeting= e.target.value;
console.log(valueTargeting);
};
Is there a way to use any of these, or do I have to resort to designing a button?
Also notice they do output a "value" when clicked at a certain area(which is small surface area). Is that a bug on my part?

Chip is not returning the expected value is because the Chip does not explicitly maintain a value. In order for it to return a value to your event handler, you'll need to wrap the value that you want it to return in the onClick handler itself. For example:
{categories.map((category) => {
return (
<Chip
label={category.name}
// Notice 'category._id' being sent from the handler, not `e`
onClick={handleClick(category._id})}
/>
);
})}
Working MUI 4 Code Sandbox: https://codesandbox.io/s/chip-click-mui4-ggl0z?file=/demo.js:940-1325
Working MUI 5 Code Sandbox: https://codesandbox.io/s/chip-click-mui5-y5xkk?file=/demo.js

Related

React-hook-form working with multiple array data and conditional fields within a map array

I'm lost in the weeds on this, but I think the issues are straightforward enough, and its just my lack of understanding as to why I cant get this working right. I have a form using react-hook-form that is part of a scheduling/ documentation feature. The initial data is pulled from 1 api endpoint which sets the initial info in the parent level of the form- the standard date/time info and the subsequent conditional goal info if the event has already been interacted with- as an 'event' prop. For the child component within the form (GoalInput), the goal titles are pulled from a separate api endpoint to ensure the available goal fields match the current report. Since the first time a user will interact with any given event, the goal fields should be un-toggled and have no associated user information, however, if they are returning to an event previously interacted with, I want the previously set information (contained in the event initial data mentioned earlier) displayed as the default.
Heres the parent form
/.../
const { register, unregister, handleSubmit, watch, control, setValue, formState: { errors } } = useForm({
defaultValues: {
visitStart: event?.visitStart,
visitEnd: event?.visitEnd,
location: event?.location,
goals: [{
title: '',
marked: false,
note: ''
}]
},
shouldUnregister: true
});
const onSubmit= async (data) => {
/.../
}
return (
<div>
<Button color='primary' variant='contained' onClick={handleClickOpen}>
Edit Visit
</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Edit Visit</DialogTitle>
<DialogContent>
<DialogContentText>
Visit for {event.client.fullName}
</DialogContentText>
<form id="editVisit"
onSubmit={(e) =>
handleSubmit(onSubmit, onError)(e).catch((e) => {
console.log("e", e);
})}>
<br></br>
<section>
/... initial fields .../
</section>
{goals && goals.map((goal, index) => (
<GoalInput
key={goal._id}
goal={goal}
index={index}
register={register}
control={control}
errors={errors}
visitGoals={event.goals}
setValue={setValue}
unregister={unregister}
/>
))}
/... end of form/ action buttons .../
And the child component:
function GoalInput({ goal, index, register, unregister, setValue, control, errors, visitGoals }) {
const [toggle, setToggle] = useState(false)
console.log("goal: ", goal)
console.log("visitGoals: ", visitGoals)
const goalData = visitGoals?.filter((value)=> {
if ( value.marked === true) {
return value
}
})
console.log("goalData: ", goalData)
useEffect(() => {
if(!toggle) {
unregister(`goals.${index}.title`)
unregister(`goals.${index}.marked`)
unregister(`goals.${index}.note`)
}
}, [unregister, toggle])
return (
<>
<FormControlLabel
{...register(`goals.${index}.title`, goal.title)}
value={goal.title}
name={`goals.${index}.title`}
control={
<Switch
key={index}
{...register(`goals.${index}.marked`)}
checked={goalData.marked || toggle}
// checked={toggle}
name={`goals.${index}.marked`}
value={goalData.marked || toggle}
onClick={console.log("marked? ", goalData.marked, "toggle ", toggle)}
// value={toggle}
onChange={
() => {
setToggle(!toggle);
setValue(`goals.${index}.title`, goal.title)
}}
/>
}
label={goal.title}
/>
<br />
{toggle ? (
<>
<Controller
control={control}
name={`goals.${index}.note`}
id={`goals.${index}.note`}
render={({field}) => (
<TextField
index={index}
error={!!errors.note}
value={goalData.note || field.value}
// value={field.value}
onChange={(e)=>field.onChange(e)}
label="Progress Note"
/>
)}
/>
<br />
</>
) : <></>}
</>
)
}
The visitGoals prop is passing down the event information if it already contains existing goals. Currently the log is showing that the component is correctly filtering out if the goals had been marked: true previously, however, the actual Switch component is not registering the goalData value. I tried setting it as state and having a useEffect set the state, but I was getting just an empty array. I'm sure theres something simple I'm missing to get the input fields to recognize the values, but I cant figure it.
As an extra question if I may, I'd also like to unregister any fields if the Switch input is not toggled, so that its false, so that I'm not storing a bunch of empty objects. Following the docs and video, I thought I've set it up correctly, even trying shouldUnregister: true in the parent form, but I can't seem to navigate that either. The submission data shows the fields are being registered fine by RHF, so I figured the unregister by the same syntax should have worked.
React Hook Form's unregister docs: https://react-hook-form.com/api/useform/unregister
Any direction or guidance would be greatly appreciated.

React: Edit and Delete from a pop-up menu on a list item

I have a list container component, which is the parent. It maps out the list rows. On the list row component, which is the child, every item has a button to toggle a pop-up menu, which has a button for edit, and a button for delete. The menu itself is a sibling to the list rows because if I include it in the list rows component, each row will render a menu and when toggled, they would all stack up on top of each other. The edit and delete buttons toggle either a form for the edit, or directly remove the item.
What I currently have is:
// Parent / Container
const [itemID, setItemID] = useState(null);
const handleMenuOpen = (id) => (e) => {
setAnchorEl(e.currentTarget); // for menu placement
setItemID(id);
};
const handleItemDelete = () => {
dispatch(deleteItem(itemID));
};
<List>
<ListRow handleMenuOpen={handleMenuOpen} />
<Menu handleItemDelete={handleItemDelete} itemID={itemID} />
</List>;
// List Row
<Button onClick={handleMenuOpen(item.id)} />;
// Menu
<MenuItem onClick={() => handleModalOpen(itemID)} />;
<MenuItem onClick={() => handleItemDelete()} />;
The edit button works fine but no matter how I try, I cannot get setItemID to work from the onClick on the list item. It always come out as the initial value of null. I console logged that the ID in the function parameter came out properly but the setState hook did not work.
I tried putting the useState on the list item and pass the ID through useContext but came out undefined when handledItemDelete was called.
I tried using ref on the child to get the ID from the parent, which also came out as undefined.
I cannot think of how to use useEffect to check for a change in the handleMenuOpen parameter.
I am out of ideas. Anyone know what the issue is and how to fix it?
You should probably just pass the handleMenuOpen function and rely on the selected element and then store it's id in itemID variable.
const handleMenuOpen = (e) => {
setAnchorEl(e.currentTarget); // for menu placement
setItemID(e.currentTarget.id);
};
<MenuItem onClick={handleMenuOpen} />;
i had the same problem before. I think you should handle the popup toggling in the child component, so something like this.
function Parent() {
function handleDelete(item) {
deleteFunction(item.id)
}
return (
<div>
{[].map((item, index) => {
return (
<ListRowItem key={index} handleDelete={handleDelete} item={item} />
)
})}
</div>
)
}
function ListRowItem({handleDelete, item}) {
const [isMenuOpen, setIsMenuOpen] = useState(false)
const [isModelVisible, setIsModalVisible] = useState(false)
return (
<div>
<Button onClick={isMenuOpen === true ? () => setIsMenuOpen(true) : () => setIsMenuOpen(false)} />
{isModelVisible === true ? <ModalItem /> :null}
{isMenuOpen === true ?
<div>
<MenuItem onClick={() => setIsModalVisible(true)} />
<MenuItem onClick={() => handleDelete(item.id)} />
</div>
: null}
</div>
)
}
I assume you are doing a certain loop to render each List Row inside the List component
let's say all items are in an items array which you loop:
{items.map(item => (
<ListRow handleMenuOpen={handleMenuOpen}/>
<Menu handleItemDelete={handleItemDelete} item={item} />
)}
now in the Menu container or component, you would have the item and pass it to the Menu item

Passing mapped button ID to component

I have react mapping that maps an array of objects into their own <cards />. Each card has its own button that opens up a <dialog />. I am trying to pass the unique ID from each object through to the relevant dialog component. At the moment I am getting all IDs through to the <dialog /> no matter which dialog is open.
Each dialog based on the ID will call unique data, currently I am getting all which I do not want.
{vehicles !== undefined ? vehicles.map(model => (
<React.Fragment>
<Card>
<CardActions className={classes.cardActions}>
<Button fullWidth color="#333" onClick={this.handleDialog}>
FIND OUT MORE
</Button>
</CardActions>
</Card>
<VehicleDialog
key={model.id}
onClose={this.handleDialog}
open={this.state.open}
id={model.id} //passes all IDs to this component
/>
</React.Fragment>
))
:
null
}
My handle is standard:
handleDialog = () => {
this.setState({ open: !this.state.open });
};
I have looked into solutions where you pass the ID with the onClick, just not sure how to then pass that one through to the component. Maybe there is a better way?
Actually what is happening here is, you are rendering multiple VehicleDialog in a loop. What you should do is- take the VehicleDialog out of the loop (out of the map, I mean). And render it after the mapping. Now when a button is clicked take note (in your state) of which model.id wants to open the VehicleDialog.
So let's first modify your handler to take an model's id as argument. It returns a function that sets the state.open and state.modelId. So whenever your dialog is open, it knows which model id wanted to open it (from state.modelId).
handleDialog = (id) => () => {
this.setState({
open: !this.state.open,
modelId: id
});
};
Now let's cut the dialog out of the loop and modify the onClick props of the buttons to reflect the new handler design change. After the loop, render a single dialog:
{vehicles !== undefined ? vehicles.map(model => (
<Card>
<CardActions className={classes.cardActions}>
<Button fullWidth color="#333" onClick={this.handleDialog(model.id)}>
FIND OUT MORE
</Button>
</CardActions>
</Card>
)):null
}
<VehicleDialog
key={model.id}
onClose={this.handleDialog}
open={this.state.open}
id={this.state.modelId}
/>

React "Maximum update depth exceeded."

I have a container which is a child container. This child container handles states and it also receives some props from the parent. I have two select statements which onChange sets state in the child container. For some reason, the setState() is causing the container to re render. The weird part is that the render() and also my setState() code is called only once. (I added debugger to check). Please find my Select combobox code below:
<Select
native
name="evidenceNode"
value={nodeType}
onChange={this.handleChange('nodeType')}
className={classes.textField}
>
<option />
{NODE_TYPE.map(function (item, i) {
return <option key={i} value={item}>{item}</option>
})}
</Select>
<Select
native
name="modelType"
value={modelType}
onChange={this.handleChange('modelType')}
className={classes.textField}
>
<option />
{MODEL_TYPE.map(function (item, i) {
return <option key={i} value={item}>{item}</option>
})}
</Select>
Here is my handleChange function:
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
I am suspecting that this is a very small fix but I don't know where am I going wrong.
Things I have tried:
Change the way I am calling handle change to a arrow function and it didnt work
I removed one of the Select statement and tried running again and it worked.
I tried to remove the handleChange call from one of the Select statement and it worked fine.
Also I should mention: I have a componentWillReceiveProps function (Not sure if it matters)
componentWillReceiveProps(nextProps, nextContext) {
if(nextProps.selectedNode !== this.state.selectedNode){
this.setState({
selectedNode: nextProps.selectedNode
});
}
}
The issue is onChange={this.handleChange('modelType')}.
You're not attaching an event, you're calling a method with that.
You're entering in an infinite loop because of that.
this.handleChange('modelType') occurs a re-render which call again this.handleChange('modelType')...etc
Attach on the onChange event an anonymous function which call handleChange
onChange={e => this.handleChange('modelType', e)}
And change handleChange params declaration :
handleChange = (name, event) => {}
The problem wasn't with the handleChange listener. Apparently [Material-UI]https://material-ui.com/ (The tool I am using for the form elements) required us to add a FormControl element above every Select I add. So the component should look something like this.
<FormControl
// className={classes.formControl}
>
<Select
native
name="nodeType"
value={nodeType}
onChange={this.handleChange('nodeType')}
className={classes.textField}
inputProps={{
name: 'nodeType',
id: 'nodeType'
}}
>
<option/>
{NODE_TYPE.map(function (item, i) {
return <option key={i} value={item}>{item}</option>
})}
</Select>
</FormControl>
The mistake I made was I had one FormControl and it had two Select elements inside it. This particular thing isn't documented properly on their website.
I think Material-UI recursively calls handleChange on every Select component inside the Form control and since I had more than one, it was going into a loop. I hope this helps.

How do I make react handler working for multiple values?

I have a handler which doesn't quite work as I wanted... I need to be able to change the 'quantity' value of many items. Right now, with my handler, if i try with multiple items, they will get updated with the last value entered. So there must be a way for me to enter multiple values and update items differently.. here is what I did so far:
this.state.purchase.selected.map(item => {
return (
<Grid item xs={4}>
<OrderItemsCard
item={item}
onChange={this.handleSelectedItemChange}
/>
</Grid>
)
})
this.handleSelectedItemChange = this.handleSelectedItemChange.bind(this)
handleSelectedItemChange(event) {
let query = Object.assign({}, this.state.purchase);
query.selected.map(item => {
item.quantity = event.target.value
})
this.setState({purchase: query})
}
If I understand correctly, you want a way to share your onChange function with multiple components, devising a way to distinguish the caller.
You can simply pass your item through to the onChange and use its values to determine the caller and perform whatever actions you wish.
This can be done as follows,
this.state.purchase.selected.map(item => {
return (
<Grid item xs={4}>
<OrderItemsCard
item={item}
onChange={(event) => this.handleSelectedItemChange(item, event)}
/>
</Grid>
);
})
handleSelectedItemChange(item, event) {
// ...
}

Categories

Resources