Material-UI Disabled attribute not working - javascript

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
})
}

Related

Switching classes between items from the list in React

im doing this simple ecommerce site, and on the product page you have different attributes you can choose, like sizes, colors - represented by clickable divs, with data fetched from GraphQl and then generated to the DOM through map function.
return (
<div className="attribute-container">
<div className="attribute">{attribute.id.toUpperCase()}:</div>
<div className="attribute-buttons">
{attribute.items.map((item) => {
if (type === "Color") {
return (
<AttributeButton
key={item.id}
className="color-button"
style={{ backgroundColor: item.value }}
onClick={() => addAtribute({type: type, value: item.value })}/>
);
}
return (
<AttributeButton
key={item.id}
className="size-button"
size={item.value}
onClick={() => addAtribute({ type: type, value: item.value })}
/>
);
})}
</div>
</div>
);
Im importing external component, then I check if an attribute's type is Color (type color has different styling), then render depending on that type.
What I want to implement is that when i click on one attribute button, its style changes, BUT of course when i click on another one, choose different size or color for the item i want to buy, new button's style changes AND previously selected button goes back to it's default style. Doing the first step where buttons style changes onClick is simple, but i cant wrap my head around switching between them when choosing different attributes, so only one button at the time can appear clicked.
Here is code for AttributeButton:
class Button extends PureComponent {
constructor(props){
super(props);
this.state = {
selected: false,
}
}
render() {
return (
<div
className={ !this.state.selected ? this.props.className : "size-selected "+this.props.className}
style={this.props.style}
onClick={() => {this.props.onClick(); this.setState({selected: !this.state.selected}) }}
>
{this.props.size}
</div>
);
}
}
export default Button;
PS - i have to use class components for this one, it was not my choice.
You need to have the state selected outside of your <Button> component and use it as a prop instead. Something like:
handleSelect = (button) => {
const isSelected = this.state.selected === button;
this.setState({ selected: isSelected ? null : button });
};
render() {
return (
<>
<Button
isSelected={this.state.selected === "ColorButton"}
onClick={() => this.handleSelect("ColorButton")}
/>
<Button
isSelected={this.state.selected === "SizeButton"}
onClick={() => this.handleSelect("SizeButton")}
/>
</>
);
}

React not rendering correctly after state change

I have a simple form. You click an "Add Item" button and a textbox appears. On blur, the text entered in the textbox gets added to a state variable array. Click the "Add Item" button again, another textbox appears and so on.
For each textbox, there is also a "Remove Item" button. When this button is clicked, the current item is removed from the array and the current textbox is removed from the page.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
}
}
addItem() {
this.setState({
items: [...this.state.items, []]
}
)
}
removeItem(index) {
//var items = this.state.items;
var items = [...this.state.items];
items.splice(index, 1);
this.setState({
items: items
})
}
changeItem(e, index) {
var items = this.state.items;
items[index] = e.target.value;
this.setState({
items: items
})
}
render() {
return (
<div>
{
this.state.items.map((item, index) => {
return (
<React.Fragment key={index}>
<hr />
<Row>
<Col column sm="8">
<Form.Control
type="text"
name="item"
onBlur={(e) => this.changeItem(e, index)}
/>
</Col>
</Row>
<Row>
<Col column sm="8">
<Button
onClick={() => this.removeItem(index)}
variant="link"
size="sm">
Remove Item
</Button>
</Col>
</Row>
</React.Fragment>
)
})
}
<br />
<Button
onClick={(e) => this.addItem(e)}
variant="outline-info">Add item
</Button>
</div>
)
}
}
The problem I have is, although the array is successfully modified in removeItem(index), the textbox that gets removed from the page is always the last one added, not the one that should be removed. For example:
Click "Add Item", type: aaa items: ['aaa']
Click "Add Item", type: bbb items: ['aaa', 'bbb']
Click "Add Item", type: ccc items: ['aaa', 'bbb', 'ccc']
Click "Remove Item" under aaa. Items gets successfully updated: items: ['bbb', 'ccc']
The page should show a textbox with bbb and one with ccc. But it shows:
How can I remove the correct textbox from the page?
There are a few problems with your code:
Firstly, you are directly changing the this.state without using this.setState() in the changeItem function, I have changed it to var items = [...this.state.items];
You are using index as key for a list item, that you are rendering using the this.state.items.map((item, index) => {...}) in <React.Fragment key={index}>. This key should be a unique identifier for the list item, usually, it is the unique id from the database. Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity. However, in your case, since you don't have unique ids for the items, I am creating those using uuid module. learn more about keys: https://reactjs.org/docs/lists-and-keys.html and https://robinpokorny.medium.com/index-as-a-key-is-an-anti-pattern-e0349aece318
I have removed column attribute from the Col component, because it was giving some warning
Since, now I am adding a unique id to each list item, I have changed the structure of the this.state.items from array of strings to array of objects, where each object has a id and data, where data is text
you were using Form.Control component without using value prop. Suppose you added one list item, wrote something in the input, clicked on the add item button again. at this point, since you changed focus, the onBlur event would trigger, and your this.state.items would change accordingly, so far, so good. BUT now when it re-renders the whole thing again, it is going to re-render the Form.Control component, but without the value prop, this component will not know what data to show, hence it will render as empty field. Hence, I added value prop to this component
Since I added value prop in Form.Control component, react now demands that I add onChange event to the component, otherwise it will render as read-only input, hence I changed onBlur to onChange event. There is no need for onBlur to change the state value, when onChange is already there.
Here is the finished code:
import React from "react";
import { v4 } from 'uuid';
import { Button, Row, Col, Form } from "react-bootstrap";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
};
}
addItem() {
this.setState({
items: [...this.state.items, {data: "", id: v4()}]
});
}
removeItem(index) {
console.log("dbg1", index);
//var items = this.state.items;
var items = [...this.state.items];
items.splice(index, 1);
this.setState({
items: items
});
}
changeItem(e, index) {
console.log("dbg2", this.state.items);
var items = [...this.state.items];
items[index].data = e.target.value;
this.setState({
items: items
});
}
render() {
console.log("dbg3", this.state.items);
return (
<div>
{this.state.items.map((item, index) => {
return (
<React.Fragment key={item.id}>
<hr />
<Row>
<Col sm="8">
<Form.Control
type="text"
name="item"
value={item.data}
onChange={(e) => this.changeItem(e, index)}
// onBlur={(e) => this.changeItem(e, index)}
/>
</Col>
</Row>
<Row>
<Col sm="8">
<Button
onClick={() => this.removeItem(index)}
variant="link"
size="sm"
>
Remove Item
</Button>
</Col>
</Row>
</React.Fragment>
);
})}
<br />
<Button onClick={(e) => this.addItem(e)} variant="outline-info">
Add item
</Button>
</div>
);
}
}
export default App;

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}
/>

Dynamically change styled component based on state AND index

So I have a list that was returned from an API request (not important)
lets call it list = [1,2,3,4,5,6,7];
Now, inside render(), I have something like the following
render(){
<Wrapper>
{list.map((i) => {
return (<Button id = {i} onClick = {this.customFunc.bind(this, i)} />)
}
</Wrapper>
}
Now, I have another list, lets call it list_check = [false...] (for all 7 elements listed above)
Assume that customFunc changes the respective button id in list_check from false to true.
e.g. if I clicked button 1 (id = 1) then list_check becomes [false, true, false...]
** Now my problem is: I have 2 styled components, Button and Button_Selected,
how can i dynamically change between the 2 styled components so that if that unique button is clicked (change list_check[index] = true), the element becomes Button_Selected instead of Button (The entire list is initalized as Button since all elements are false)
Just to make it clear:
Both arrays are located in this.state and by 2 styled components I mean there exists
const Button = styled.div`
//styling here
`;
and
const Button_Selected = Button.extend`
//Small styling change to differentiate between selected and not selected
`;
edit: all answers are great! too bad I can only select one :(
You could just save the component to another variable.
this.state.list_check.map((item, i) => {
const NecessaryButton = item ? SelectedButton : Button;
return <NecessaryButton onClick={() => this.makeSelected(i)}>Normal Button</NecessaryButton>
})
You can see a live example here.
Although you can return 2 buttons based on conditional rendering .You can also pass props to your styled Button so that based on props you can change your styles.
render(){
<Wrapper>
{list.map((i) => {
return (<Button id = {i} isSelected={this.state.list_check[i]} onClick = {this.customFunc.bind(this, i)} />)
}
</Wrapper>
}
And in your styled Button:
const Button = styled.div`
styleName: ${props => props.isSelected ? 'styling_if_Selected' : 'styling_if_not_selected'};
`;
The easiest approach would be to have a single Button component and handle in the state if it was selected. Depending on this state you could switch classes.
Example:
class Button extends React.Component {
state = {
isSelected: false
};
handleClick() {
//Here we will set the state change and call the onClick function passed by props
this.setState({isSelected: !this.state.isSelected}, () => { this.props.onClick(); });
}
render() {
return (
<button
class={this.state.isButtonSelected ? "classBtnSelected" : "classBtnDefault"}
onClick={this.handleClick}
/>
);
}
}
Still, if you want to switch components you can use state to control if it was selected and do a conditional rendering. Example:
render(){
<Wrapper>
{list.map((i) => {
return (this.state.isSelected
? <Button id={i} onClick = {this.customFunc.bind(this, i)} />
: <Button_Selected id={i} onClick = {this.customFunc.bind(this, i)} />)
}
</Wrapper>
}

Categories

Resources