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}
/>
Related
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
I have a React component which displays a modal. Displays fine the first time. But when I close and reopen it "holds" the input value: The code for the render method is:
render() {
return (
<>
<Button
type="primary"
onClick={() => {
this.setState({
topup: 0,
modalVisible: true
});
}}
>
Replace
</Button>
<Modal
visible={this.state.modalVisible}
onOk={this.onOk}
onCancel={() => {
this.setState({
topup: 0,
modalVisible: false
});
}}
>
<Form>
<Row gutter={24}>
<Col span={24}>
<Descriptions>
<Descriptions.Item label="Topup">
<Input
type="number"
defaultValue={this.state.topup}
onChange={value => {
console.log('value.target.value is ', value.target.value);
if (Number(value.target.value)) {
this.setState({
...this.state,
topup: Number(value.target.value)
});
}
}}
/>
</Descriptions.Item>
</Descriptions>
</Col>
</Row>
</Form>
</Modal>
</>
);
}
When it first displays, the value in the topup Input is 0, which is correct. If I change to 10, then close the modal, I reset the state so the topup is 0. When I click the button, somehow, the Input still has 10. I can see in the state the topup property is 0. What could possible be going on?
It might be because that Input is uncontrolled, as in you only provide default value, so maybe try using value instead of defaultValue if you want value to always come from state.
This is irrelevant to your problem, but if you're using this.setState method of class component, you don't need to spread this.state, because whatever you pass into this.setState will be merged with current state. Also using old state to create new state like that can lead to bugs, so if you ever need to use old state to generate new one (for example, you want to increment state variable), you should pass a function into this.setState:
this.setState((prevState)=>{
return {
value: prevState.value + 1
};
});
That way React will handle your setState properly and guarantee prevState is actual current state at the moment setState is running.
I have a grid of boxes that fetched people's info from a JSON, each box have a button function that is from component 'CZButton', this button is included in "personlist" and it shows a pop up, I want to show the person's email inside the pop up , i am not sure how can i show a unique json item on each click, whatever i add in the pop up its shown on all the buttons, and what i want is to show specific details about the person once the button is clicked. I'm new to react and would appreciate the help. here is a sandbox snippet.
https://codesandbox.io/s/r5kz3jx3z4?fontsize=14&moduleview=1
A simple solution to this would be to extend the CZButton component so that it accepts a person property, by which the person data can then be rendered within the pop up dialog:
/* Adapted from your codesandbox sample */
class CZButton extends React.Component {
constructor(props) {
super(props);
this.state = { open: false };
}
toggle = () => {
let { toggle } = this.state;
this.setState({ open: !this.state.open });
};
render() {
const { open } = this.state;
return (
<div>
{" "}
<button onClick={this.toggle}>Show</button>
<Drawer
open={this.state.open}
onRequestClose={this.toggle}
onDrag={() => {}}
onOpen={() => {}}
allowClose={true}
modalElementClass="modal"
containerElementClass="my-shade"
parentElement={document.body}
direction="bottom" >
{/* This render the contents of the `person` prop's `email` field in dialog */}
<div>{this.props.person.email}</div>
{/* This renders the contents of `person` prop in dialog */}
<div>{JSON.stringify(this.props.person)}</div>
</Drawer>
</div>
);
}
}
Seeing that your CZButton now rendering the contents of the person prop, the change above would also require that you supply this data when rendering the CZButton in the PersonList component's render() method like so:
<div className="row">
{console.log(items)}
{items.map(item => (
<Person
className="person"
Key={item.id.name + item.name.first}
imgSrc={item.picture.large}
Title={item.name.title}
FName={item.name.first} >
{/* Pass the "person item" into our new person prop when rendering each CZButton */ }
<CZButton person={item} />
</Person>
))}
</div>
Here is a forked copy of your original code with the updates mentioned above for you to try out. Hope this helps!
In your PersonList component, as you map each of your items, you want to send the item's email as a prop to the CZButton, like this:
{items.map(item => (
<Person
className="person"
Key={item.id.name + item.name.first}
imgSrc={item.picture.large}
Title={item.name.title}
FName={item.name.first}
>
{" "}
<CZButton email={item.email} />
</Person>
))}
Now, each CZButton gets a prop called email. In, the render method of your CZButton, you will want the content of Drawer to look like this:
<Drawer ...>
<div>{this.props.email || "No email address for this person."}</div>
</Drawer>
You can try this out to see if it works for you.
So I am mapping an array to a list group to display a list of items. I want to be able to sort that list by any property of the items. I am able to resort the array easy enough, and I have a Reactstrap dropdown that triggers the sort, and the arrays sorts properly and even gets updated in the component properly. However, the component does not re-render. HOWEVER, if I click the button to open the dropdown list again, the component THEN re-renders. I'm baffled. Any thoughts?
Function in the container component file that does the sorting (uses another external utility function, again this works fine):
select(event) {
this.setState({
sortBy: event.target.innerText
}, function () {
const propName = this.state.sortBy.toLowerCase();
this.state.dives.sort(Utilities.sortByParam(propName));
});
}
This is the presentational component:
const DiveList = (props) => {
const {
dives,
toggle,
select,
isOpen,
} = props;
console.log('dives: ', dives);
return (
<div>
<h3>My Dives
<Dropdown size="sm" className="float-right" isOpen={isOpen} toggle={toggle}>
<DropdownToggle caret>
Sort By
</DropdownToggle>
<DropdownMenu right>
<DropdownItem onClick={select}>Number</DropdownItem>
<DropdownItem onClick={select}>Location</DropdownItem>
<DropdownItem onClick={select}>Date</DropdownItem>
</DropdownMenu>
</Dropdown>
</h3>
{dives.length > 0 &&
<div>
<ListGroup>
{dives.map((dive) => (
<ListGroupItem key={`dive-${dive.number}`}>
<Link to={`/divedetails/${dive.number}`}>
<div>
<span>Dive #{dive.number}</span>
<span className="float-right">{dive.date}</span>
</div>
<div className="float-left">{dive.location}</div>
</Link>
</ListGroupItem>
))}
</ListGroup>
</div>
}
{dives.length <= 0 &&
<h5>You don't have any dives logged yet. Time to get wet!!!</h5>
}
</div>
);
};
export default DiveList;
Again, the console log "dives" updates the array immediately when the button is selected (clicked) in the dropdown, but the ListGroup component does not re-render.
If I click the "Sort by" button again (NOT one of the menu buttons, but the toggle button) then the components refreshes.
Help...
The problem is that you're a) sorting in the setState callback and b) your sort is mutating dives instead of setting state.dives to a new, sorted array via setState. This is because render isn't called if state mutates directly - only if setState is called. Instead you can perform the sort in the select function and set sortBy and dives in a single setState call.
select(event) {
const sortBy = event.target.innerText;
const sortByParam = sortBy.toLowerCase();
this.setState({
sortBy,
dives: [...this.state.dives.sort(Utilities.sortByParam(sortByParam))];
});
}
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
})
}