How do I make react handler working for multiple values? - javascript

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) {
// ...
}

Related

Having an issue getting vaues to onClickHandler Due to MUI

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

React: Updating shared state from one child component has unwanted side effects on other

I'm trying to make a piece of code to allow dynamic rule-making for Trading strategies. When it comes to selecting Indicators to use, I've bumped into a bit of a problem. You don't need to understand anything about Trading to help me out with this. (But to be honest, I know nothing either)
I have the following component, IndicatorSelector, which is the parent component.
import { Grid, Button, Stack, Typography } from '#mui/material';
import IndicatorBox from './indicatorBox';
export default function IndicatorSelector(props) {
const [chosenIndicators, setChosenIndicators] = useState([]);
const availableIndicators = // LOAD THESE FROM LOCAL FILE, EXAMPLE GIVEN BELOW.
const addIndicator = (val) => {
const temp = [...chosenIndicators];
temp.push(val);
setChosenIndicators(temp);
}
const updateOptions = (i, key, value) => {
const newArray = [...chosenIndicators];
newArray[i].arguments[key] = value;
setChosenIndicators(newArray);
}
const chooseIndicator = (i, name) => {
availableIndicators.forEach((val, index) => {
if (val.name === name) {
const temp = [...chosenIndicators];
temp[i] = val;
console.log("Choosing " + val.name);
console.log(temp);
setChosenIndicators(temp);
}
})
}
const deleteIndicator = (i) => {
const temp = [...chosenIndicators];
temp.splice(i, 1);
setChosenIndicators(temp);
}
const handleSubmit = () => {
if (chosenIndicators.length < 1) {
alert("Please select an indicator!");
} else {
props.onComplete(chosenIndicators);
}
}
return (
<Stack>
<Typography variant="h3" sx={{paddingBottom: 5}}>Step 1: Choose Your Indicators</Typography>
<Grid item xs={12}>
<Stack>
{chosenIndicators.map(
(indicator, i) => {
return <IndicatorBox key={i} indicator={indicator} chooseIndicator={chooseIndicator} updateOptions={updateOptions} onDelete={deleteIndicator} index={i} available={availableIndicators}></IndicatorBox>
}
)}
</Stack>
</Grid>
<Grid item xs={12} sx={{display: 'flex', justifyContent: 'space-evenly'}}>
<Button variant="contained" color="secondary" onClick={() => {addIndicator(availableIndicators[0])}}>Add Another</Button>
<Button variant="contained" color="success" onClick={handleSubmit}>Submit</Button>
</Grid>
</Stack>
)
}
This component displays some buttons to allow you to add more indicators and to submit your choices. Then, the options for selecting indicators are displayed as multiple IndicatorBox components. This component is shown below.
import { Grid, Select, FormControl, InputLabel, MenuItem, TextField, Box, IconButton, Typography } from '#mui/material';
import ClearIcon from '#mui/icons-material/Clear';
export default function IndicatorBox(props) {
const choose = (name) => {
props.chooseIndicator(props.index, name);
}
return (
<Box sx={{bgcolor: '#212121', padding: 1, borderRadius: 2, width: '90vw', diplay: 'flex', justifyContent: 'space-evenly'}}>
<Grid container>
<Typography variant="body2">{props.index}</Typography>
<Grid item xs={11}>
<FormControl sx={{width: '95%'}}>
<InputLabel id="indicator-selector">Indicator</InputLabel>
<Select labelId="indicator-selector"
value={props.indicator.name}
label="Indicator"
onChange={(e) => {choose(e.target.value)}}
>
{props.available.map((object, i) => {
return <MenuItem key={object.name} value={object.name}>{object.properName}</MenuItem>
})}
</Select>
</FormControl>
</Grid>
<Grid item sx={{padding: 1}}>
<IconButton onClick={() => props.onDelete(props.index)}>
<ClearIcon />
</IconButton>
</Grid>
{ (Object.keys(props.indicator).length > 0) ?
<Grid item xs={12} sx={{paddingTop: 1.5}}>
{Object.keys(props.indicator.arguments).map((key, i) => {
return <TextField
key={key}
label={key.toUpperCase()}
value={props.indicator.arguments[key] ? props.indicator.arguments[key] : 0}
variant="outlined"
onChange={(e) => props.updateOptions(props.index, key, parseInt(e.target.value))}
/>
})}
</Grid>
: null}
</Grid>
</Box>
)
}
To add further detail, an example of the JSON structure of an 'indicator' is shown below.
[
{
"name": "rsi",
"properName": "Relative Strength Index",
"functionName": "talib.RSI",
"plot": null,
"data": ["close"],
"arguments": {
"timeperiod": 14
},
"output": [
"rsi"
]
}
]
When running the code, one is presented with this screen.
When a user clicks "add another", then selects an indicator, the arguments for said indicator pop up underneath it, like so.
Now, here's where the problem arises. If the user selects a second indicator of the same type, the options values become linked, with both of them changing when the user types in either text field. This occurs no matter how many indicators are selected, as long as two or more are of the same type.
The desired outcome: I want the IndicatorBoxes to have their values be independent of one another, and for the array in IndicatorSelector: 'chosenIndicators', to reflect their true, independent values. I want the user to be able to add as many indicators as they want to the array, with each of them having their own independently customisable options.
EDIT: To clarify, 'availableIndicators' is not a list that is meant to change during execution of the program. Think of it as a 'menu' of indicators that the user can select from. When they choose one, the indicator is found by name from 'availableIndicators', and copied into the array 'chosenIndicators'. I don't think this should affect availableIndicators, but for some reason, chosen indicators seem to be linked when two or more of the same are selected.
This issue has likely been caused by my lack of experience and knowledge with React, so you're more than welcome to rag on my appalling code. In fact, any advice on how to approach this problem better would be appreciated.
This is not really a fulll answer but it way to long for a comment. If I am following your code correctly, it looks like you are filtering the inidicators here:
const chooseIndicator = (i, name) => {
availableIndicators.forEach((val, index) => {
if (val.name === name) {
const temp = [...chosenIndicators];
temp[i] = val;
console.log("Choosing " + val.name);
console.log(temp);
setChosenIndicators(temp);
}
})
}
So if 2 or more indicators have the same name (you say type I assume you mean name?), Your filter condition is going to match both of them.
Instead of filtering by name you should inject a unique id onto the nested object using a library like uuid that way you can filter by something that you know will be unique .
So when you an indicator is created you would do something like:
indicatorData.id = uuid()
before setting to state
now you can filter through them by something you know is unique.
Also, there is array.find and array.filter methods that are specifically meant for iterating over an array and returning elements based on a condition, no need to do array.forEach followed by an if-else

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

react: why does the onclick get executed even without having clicked?

Warning: Severe React beginner.
I have a class:
export default class ItemsView extends React.Component {
//...
render() {
return (
<div>
<Container>
<Grid container>
<Grid item xs={4}>
<ul style={{listStyleType:"none"}}>
{
this.state.items.map((item) => {
return <li key={item.number} style={{cursor: "pointer"}}><Item onClick={this.handleSelected(item)} value={item.timestamp}/></li>
})
}
</ul>
</Grid>
<Grid item xs={4}>
<ItemDetail item={this.selected} />
</Grid>
</Grid>
</Container>
</div>
)
}
}
handleSelected(item) {
console.log("handle");
this.selected = item;
}
What I want is that when I click on the Item div, which is rendered dynamically as a list of elements, the details of the item would appear in the ItemDetails component.
Apart from the fact that probably my design isn't really "React"y, why does the handleSelected get called when iterating, and not when I click on it?
You are invoking the function rather than passing a function reference to be executed on click. You should either define the handler to return a function or use a lambda / fat arrow function in the click handler
onClick={() => this.handleSelected(item)}
remember, this.handleSelected is the functions reference. this.handleSelected() is the return value of an already invoked function.
Aside from the answer Vincenzo posted, you need to also use component state here. A handler that updates a property on the class will not result in a new render cycle. I can see that you are using this.selected in the render as a prop to ItemDetail
<ItemDetail item={this.selected} />
This is problematic as changing the value of this.selected will not trigger a new render. This is a great use case to use component state
export default class ItemsView extends React.Component {
state = { selected: null, items: [] }
handleSelected = (selected) => (event) => {
// ----------------------^------------^----
// return a function in the handler to pass a callback to the click handler
console.log("handle");
this.setState({ selected });
// -----------^------------^----
// set the selected item on component state when clicked
}
//...
render() {
return (
<div>
<Container>
<Grid container>
<Grid item xs={4}>
<ul style={{listStyleType:"none"}}>
{ this.state.items.map((item) => <li key={item.number} style={{cursor: "pointer"}}>
<Item onClick={this.handleSelected(item)} value={item.timestamp}/>
// --------------------------------------------^---------------
// no change necessary here since the handler returns a function.
</li>
)}
</ul>
</Grid>
<Grid item xs={4}>
<ItemDetail item={this.state.selected} />
// --------------------------------------^---------------
// reference state here
</Grid>
</Grid>
</Container>
</div>
)
}
}
You are mapping over a list of items and want to pass the item to a handler callback (handleSelected in this case). You usually would pass that through by invoking the function with parenthesis (). However, the side effect of this is the function is immediately executed. To fix that you can place it inside an arrow function, allowing it to execute on the click instead.
Hence: onClick={() => this.handleSelected(item)}

Material-UI Disabled attribute not working

I'm trying to disable the edit button once i click on complete but it is not working. I have passed in the state in disabled attribute but it seems not doing anything, don't know maybe because of setState's asynchronous nature. I passed callback while calling setState method and it seems logging data randomly, Can someone suggest what should be done ?
class App extends Component {
state = {
buttons: {
id: "test"
}
};
handleCheckBox = id => {
let buttons = Object.assign({}, this.state.buttons);
buttons.id = !this.state.buttons[id]
this.setState({buttons}, ()=>console.log(this.state.buttons));
}
render() {
return (
<div>
{todos.map(todo => (
<List key={todo.id}>
<ListItem
role={undefined}
dense
button
>
<Checkbox
onClick={()=>this.handleCheckBox(todo.id)}
checked={todo.complete}
tabIndex={-1}
disableRipple
/>
<ListItemText primary={todo.text} />
<ListItemSecondaryAction>
<Button mini color="secondary" variant="fab" disabled={this.state.buttons[todo.id]}>
<Icon>edit_icon</Icon>
</Button>
ListItemSecondaryAction>
</ListItem>
</List>
))}
</div>
);
}
}
Instead of using id to change the state use index of Array to update the state
Create an array in Component state which tracks the disabled attribute of each buttons
state = {
buttons: Array(todos.length).fill(false),
};
In componentDidMount initialise the array according to todos
componentDidMount(){
const buttons=this.state.buttons.slice();
for(var i=0;i<buttons.length;i++)
buttons[i]=todos[i].complete;
this.setState({buttons:buttons})
}
Now use the value in buttons state for disabled attribute of button based on the index of the component being rendered.
<Button mini color="secondary" variant="fab"
disabled={buttons[todos.indexOf(todo)]}>
Whenever CheckBox is clicked pass the index to the handleChange function and update the value corresponding to the index value
<Checkbox
onClick={() =>this.handleCheckBox(todos.indexOf(todo))}
checked={buttons[todos.indexOf(todo)]}{...other}
/>
handleCheckBox = index => {
const buttons=this.state.buttons.slice();
buttons[index] = !buttons[index];
this.setState({
buttons:buttons
})
}

Categories

Resources