Pass a handler from class to component in loop - javascript

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

Opening Modal from different component

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>

Event Handler Not Setting State Before Component Renders

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 inside function's property type?

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
}

Add a function into a onClick

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

React + MaterialUi handling actions in IconMenu and ListItem

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

Categories

Resources