I want to pass a click handle (handleDeleteUser) to other component, from user.js to DropDownUserTool.js actually:
User.js
handleDeleteUser = (id) => {
alert(id);
};
...
// in render
User.data.map(function (User, i) {
return (
<DropDownUserTool
id={User.id}
click={(e) => {
this.handleDeleteUser(e);
}}
/>
);
});
DropDownUserTool.js
const DropDownUserTool = (props) => {
return (
<ButtonDropdown isOpen={dropdownOpen} toggle={toggle}>
<DropdownToggle color="secondary" caret>
tools
</DropdownToggle>
<DropdownMenu>
<DropdownItem>
<Link to={"/User/Edit/" + props.id}>Edit</Link>
</DropdownItem>
<DropdownItem onClick={() => props.click(props.id)}>
Delete
</DropdownItem>
</DropdownMenu>
</ButtonDropdown>
);
};
But after click it return an error:
TypeError: Cannot read properties of undefined (reading 'handleDeleteUser')
On this line:
<DropDownUserTool id={User.id} click={(e) => {this.handleDeleteUser(e)}}/>
You are trying to reach this inside map, just define this ! inside render before loop:
let realthis = this;
Then call your handler like this:
<DropDownUserTool id={User.id} click={(e) => {realthis.handleDeleteUser(e)}}/>
When you're doing your map you're using a standard function call and losing your context. Use an arrow function instead.
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [{ id: 1 }, { id: 2 }, { id: 3 }]
};
}
handleDeleteUser = (id) => alert(id);
render() {
return (
<div>
// this needs to be an arrow function
// that way you won't change the context of `this`
{this.state.data.map((item, i) => {
return (
<DropDownUserTool
id={item.id}
key={item.id}
handleDelete={this.handleDeleteUser }
/>
);
})}
</div>
);
}
}
const DropDownUserTool = ({ handleDelete, id }) => (
<button onClick={() => handleDelete(id)}>Delete</button>
);
Related
CardComponent:
export class Card extends Component<Prop, State> {
state = {
isCancelModalOpen: false,
};
marketService = new MarketService();
deleteMarket = () => {
this.marketService
.deleteMar()
.then((response) => {
})
.catch((error) => {
console.log(error);
});
};
handleModalToggle = () => {
this.setState(({ isCancelModalOpen }) => ({
isCancelModalOpen: !isCancelModalOpen,
}));
};
render() {
const {isCancelModalOpen, isDropdownOpen } = this.state;
const dropdownItems = [
<DropdownItem
key="action"
component="button"
onClick={this.handleModalToggle}
>
Delete
</DropdownItem>
];
return (
{this.CancelModal.map((listing) => (
<DeleteModal handleModal={this.handleModalToggle}
deleteProd = {this.deleteProduct}
description = {listing.description} ></DeleteModal>
))}
);
}
}
DeleteModal Component:
interface Prop {
description: string;
handleModal: Function;
deleteProd: Function;
}
class DeleteModal extends Component<Prop, State> {
state = {
deleteConfirmModel: false
};
render() {
const { deleteConfirmModel } = this.state;
const {description} = this.props;
return (
<Modal
isSmall
title="Confirmation"
isOpen={deleteConfirmModel}
onClose={() => this.props.handleModal}
showClose = {false}
actions={[
<Button
key="confirm"
variant="primary"
onClick={() => this.props.deleteProd}
>
Delete
</Button>,
<Button key="cancel" variant="link" onClick={() => this.props.handleModal}>
Cancel
</Button>
]}
>
{description}
</Modal>
);
}
}
export default DeleteModal;
I want DeleteModal component to get open on click of Dropdown Delete button available in Card component. There is no error in the code, still I am not able to trigger modal on click of Dropdown from Card component. Can anyone help me with what's wrong with the code?
So I found the solution to call the modal from different component:
In the card component: to call deleteModal component:
<DeleteModal
displayModal={deleteModalOpen}
handleModal={this.handleModalToggle}
description="Are you sure you want to delete"
/>
DisplayModal, handleModal,decription will be props in DeleteModal component:
DeleteModal:
interface Prop {
displayModal: boolean;
handleModal: Function;
description: string;
}
<Modal
isSmall
title="Confirmation"
isOpen={this.props.displayModal}
onClose={() => this.props.handleModal()}
showClose={false}
actions={[
<Button
key="cancel"
variant="link"
onClick={() => this.props.handleModal()}
>
Cancel
</Button>,
]}
>
{this.props.description}
</Modal>
Component renders with initial state before state is updated.
The initial state is null and onHandlePrint method updates the state when the button is clicked.
class App extends React.Component {
state = {
pdf: null,
};
updatePDF = (data) => {
}
onHandlePrint = (pdf) => {
this.setState({pdf}, () => {
this.updatePDF(this.state.pdf)
})
}
render() {
return (
<div className="container">
<Router>
<ActivityDetail results={this.state.results} clickPrint={this.onHandlePrint} />
<Switch>
<Route
path="/pdf"
render={() => (
<PDFDocument data={this.state.pdf} />
)}
/>
</Switch>
</Router>
</div>
);
}
}
The button is a Link using to open a new tab that will render a PDF document with the data passed into the event as the "obj"
const ActivityDetail = ({ results, clickPrint }) => {
const renderedList = results.map((obj, index) => {
return (
<li key={index}>
<div className="service-container">
<Link to="/pdf" target="_blank" className="print-button-container">
<button
className="print-button"
onClick={() => clickPrint(obj)}
>Print</button>
</Link>
</div>
</li>
);
});
return (
<div>
<ul>
{renderedList}
</ul>
</div>
);
};
export default ActivityDetail;
This is the PDF document that should get the data when the Print button is clicked but props is undefined.
const styles = StyleSheet.create({
page: {
flexDirection: 'row',
},
section: {
margin: 10,
padding: 10,
flexGrow: 1
}
})
const PDFDocument = (props) => {
const { NameOfService } = props
console.log('props:', props)
return(
<PDFViewer className="pdf-viewer">
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>
{NameOfService}
</Text>
</View>
</Page>
</Document>
</PDFViewer>
)
}
export default PDFDocument
EDIT
So what I know have is a callback to a method that handles the newly set state.
onHandlePrint = (pdf) => {
this.setState({pdf}, () => {
this.updatePDF(this.state.pdf)
})
}
My new question is how do I send that data from the updatePDF method to the component ?
You should not setState within a setState callback function. Instead it should return the new state. This:
onHandlePrint = (pdf) => {
this.setState({pdf}, () => {
this.setState({pdfStatus: true})
});
};
should be:
onHandlePrint = (pdf) => {
this.setState(() => {pdf, pdfStatus: true});
};
But really if you don't need to use previous state you don't need to use a callback. Just do:
onHandlePrint = (pdf) => {
this.setState({pdf, pdfStatus: true});
};
Use async & await
onHandlePrint = async (pdf) => {
await this.setState({pdf}, () => {
this.setState({pdfStatus: true})
});
};
What is the difference between functional component and class component to define function's property type inside?
For example,
In class component, there is no problems.
class ListView extends React.Component {
...
renderItem: SectionListRenderItem<Circle> = ({ item }) => { // no lint warning
const { circleStore } = this.props
return (
<CircleList
hideButton
title={item.title}
desc={item.desc}
iconSource={{ uri: item.logoImageUrl }}
onPress={() => circleStore && circleStore.setCurrentCircle(item)}
></CircleList>
)
}
render() {
return (
<SectionListView ... />
)
}
}
...
In functional component, it makes 'item' is missing in props validation - eslint(react/prop-types) ploblem as below.
...
const ListView = (props: Props) => {
const renderItem: SectionListRenderItem<Circle> = ({ item }) => ( // lint warning
<CircleList
hideButton
title={item.title}
desc={item.desc}
iconSource={{ uri: item.logoImageUrl }}
onPress={() => {}}
></CircleList>
)
...
return (
<SectionListView ... />
)
}
...
It can be fixed as below code
const renderItem: SectionListRenderItem<Circle> = ({ item }) => (
change to
const renderItem: SectionListRenderItem<Circle> = ({ item }: { item: Circle }) => (
Circle is the interface I declared.
I wonder why this happens and what difference there is.
Only renderItem in stateless component makes a lint error in comparison to a class component.
Full source
interface Props {
popularCircles: Circle[]
recentCircles: Circle[]
onPressSection?: (section: Circle) => void
onPressCircle?: (circle: Circle) => void
}
export const Main = ({
popularCircles,
recentCircles,
onPressCircle,
}: Props) => {
const renderSectionHeader = ({ section: { title } }: SectionHeader) => {
return (
<SectionHeaderCell
title={title}
buttonTitle={''}
disabled
></SectionHeaderCell>
)
}
const renderItem: SectionListRenderItem<Circle> = ({
item,
}) => ( // This line makes a eslint warning only in functional component.
<CircleList
hideButton
title={item.title}
desc={item.desc}
iconSource={{ uri: item.logoImageUrl }}
onPress={() => onPressCircle && onPressCircle(item)}
></CircleList>
)
return (
<Container>
<SectionList
sections={[
{
title: 'Title1',
data: popularCircles,
},
{
title: 'Title2',
data: recentCircles,
},
]}
renderSectionHeader={renderSectionHeader}
renderItem={renderItem}
keyExtractor={(item, index) => item + index}
contentContainerStyle={{ flex: 1 }}
/>
</Container>
)
}
export default Main
Circle.d.ts
export interface Circle {
circleId: number
title: string
desc: string
logoImageUrl?: string
}
I want to use React.js to build a single page application and I want to create a list in a material-ui drawer. I want to add an element into an array every time I press a button but I don't how to write this function.
Here is my buttom:
<RaisedButton
label="Next"
primary={true}
onClick={this.onNext}
/>
Here is onNext function:
onNext = (event) => {
const current = this.state.controlledDate;
const date = current.add(1, 'days');
this.setState({
controlledDate: date
});
this.getImage(moment(date));
}
And this is the code I want to add into onNext function:
menuItems.push(<MenuItem onClick={this.handleClose}>{this.state.image.date}</MenuItem>);
This is a sample code that adds drawer menu items using state
const { RaisedButton, MuiThemeProvider, Drawer, getMuiTheme, MenuItem } = MaterialUI;
class Sample extends React.Component {
state = {
open: false,
items: [],
}
handleClose = () => {
this.setState({ open: false });
}
handleOpen = () => {
this.setState({ open: true })
}
onNext = () => {
this.setState(state => {
return Object.assign({}, state, {
items: state.items.concat([
{
// add any other button props here (date, image, etc.)
text: `Item ${state.items.length + 1}`
}
]),
});
})
}
render() {
return (
<div>
<Drawer
openSecondary={true}
width={200}
open={this.state.open}
>
{this.state.items.map(item => (
<MenuItem onClick={this.handleClose}>{item.text}</MenuItem>
))}
</Drawer>
<RaisedButton
label="Next"
primary={true}
style={{ margin: 12 }}
onClick={this.onNext} />
<RaisedButton
label="Open Drawer"
primary={true}
style={{ margin: 12 }}
onClick={this.handleOpen} />
</div>
);
}
}
const App = () => (
<MuiThemeProvider muiTheme={getMuiTheme()}>
<Sample />
</MuiThemeProvider>
);
ReactDOM.render(
<App />,
document.getElementById('container')
);
Try it here: https://jsfiddle.net/jprogd/eq533rzL/
Hope it should give you an idea how to go further
I'm learning react and I try to create simple TODO based on material-ui, I have problem with handling IconMenu menu actions, menu is displayed in listItem element. At this moment I have no idea how trigger deleteItem function with item name as a parameter when delete action is clicked in menu.
const iconButtonElement = (
<IconButton touch={true} tooltip="More" tooltipPosition="bottom-left">
<MoreVertIcon color="black"/>
</IconButton>
);
const rightIconMenu = (
<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem value="done" leftIcon={<Done />}>Mark as done</MenuItem>
<MenuItem value="delete" leftIcon={<Delete />}>Delete</MenuItem>
</IconMenu>
);
class TodoElements extends Component {
deleteItem(nameProp)
{
this.props.delete(nameProp);
}
render() {
var listItemRender = function(item) {
return <ListItem key={item.name} primaryText={item.name} style={listItemStyle} rightIconButton={rightIconMenu}/>
};
listItemRender = listItemRender.bind(this);
return (
<List>
{this.props.items.map(listItemRender)}
</List>
)
}
}
As far as I can see, you should be able to add an onChange handler to your IconMenu. So your rightIconMenu can look like this:
const RightIconMenu = ({onChange}) => (
<IconMenu iconButtonElement={iconButtonElement} onChange={onChange}>
<MenuItem value="done" leftIcon={<Done />}>Mark as done</MenuItem>
<MenuItem value="delete" leftIcon={<Delete />}>Delete</MenuItem>
</IconMenu>
);
Then you can use it in your TodoElements like this:
class TodoElements extends Component {
constructor(props){
super(props);
this.state = {
items: props.items
};
}
createChangeHandler = (nameProp) => {
return (event, value) => {
if(value==="delete"){
this.deleteItem(nameProp);
}
};
}
deleteItem = (nameProp) =>
{
this.setState({
items: this.state.items.filter((item) => {
return item.name !== nameProp);
})
});
}
render() {
return (
<List>
{this.state.items.map((item) => {
<ListItem key={item.name} primaryText={item.name} style={listItemStyle}
rightIconButton={<RightIconMenu onChange={this.createChangeHandler(item.name)} />}/>
})}
</List>
)
}
}
Alternative
As an alternative solution you could bind an onClick handler to your delete MenuItem instead. I would probably implement it like this:
const RightIconMenu = ({onDelete}) => (
<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem value="done" leftIcon={<Done />}>Mark as done</MenuItem>
<MenuItem value="delete" leftIcon={<Delete />} onClick={onDelete}>Delete</MenuItem>
</IconMenu>
);
And then replace the appropriate functions in the TodoElements:
createChangeHandler = (nameProp) => {
return (event, value) => {
this.deleteItem(nameProp);
};
}
render() {
return (
<List>
{this.state.items.map((item) => {
<ListItem key={item.name} primaryText={item.name} style={listItemStyle}
rightIconButton={<RightIconMenu onDelete={this.createDeleteHandler(item.name)} />}/>
})}
</List>
)
}
As for handling the state of your list of items, you should probably take a look at global state management such as Redux.
I think that a nicer approach would be using the onTouchTap every MenuItem has, So the onChange function won't have a switch or many if statements.
I'm actually using it when I iterate over all menu items,
To me it looks like this:
_.map(menuItems, (currItem, index) => {
return (<MenuItem primaryText={currItem.primaryText}
rightIcon={currItem.rightIcon}
leftIcon={currItem.leftIcon}
key={`menu-item-${index}`}
value={currItem.value}}
onTouchTap={currItem.onTouchTap}/>)
})