Decoupling delete modal from button? - javascript

I've put together a sample I am working on where I want to decouple my dialogue Modal from the delete button. I'm running into trouble trying to think of how I can restructure the components while still keeping functionality. Ideally the modal should be rendered once and be modular. Data passed into it should change during the request to show the modal.
https://stackblitz.com/edit/react-l8hbdr
Right now I believe a delete modal is created for every single button which is horribly inefficient. I've heard using references are bad and using Redux for this seems overkill. Maybe I should use a React portal?
Where should the modal be rendered from? I'd like a reusable modal and I found this guide, however I don't know where I should be storing the Modal component and how data should pass into it.

There is a problem with props drilling in your code
For example, expenses and updateExpenses are passed from <Main /> to DisplayExpenses and then passed to <DeleteExpense />, if the app is getting bigger, this will make the code hard to maintain. I think you can put const [expenses, updateExpenses] = React.useState(...) from <Main /> to <DisplayExpenses />.
import React from 'react'
import { Col, Card, Row, Button } from "antd";
import uuid from 'react-uuid'
import 'antd/dist/antd.css';
import addExpense from '../utilities/AddExpense';
import DeleteExpense from '../utilities/DeleteExpense';
const Sentencer = require('sentencer');
export default function DisplayExpenses(props) {
// move expenses from <Main /> to <DisplayExpenses />
const [expenses, updateExpenses] = React.useState([
{
"id": uuid(),
"name": Sentencer.make("{{ noun }}"),
"amount": 53.22,
},
{
"id": uuid(),
"name": Sentencer.make("{{ noun }}"),
"amount": 76.16,
},
{
"id": uuid(),
"name": Sentencer.make("{{ noun }}"),
"amount": 716.16,
},
]);
const ListOfExpenses = () => {
return expenses.map((index) => {
return (
<Col key={index.id}>
<Card
key={index.id}
title={index.name}
bordered={true}
style={{ width: "100%", border: "1px solid black" }}
actions={[
<DeleteExpense
key={index.id}
id={index.id}
name={index.name}
amount={index.amount}
updateExpensesFn={updateExpenses}
expenses={expenses} />
]}
>
<p key={index.id}>Amount: {index.amount}</p>
</Card>
</Col>
)
})
}
return (
<>
<Row gutter={[16, 8]}>
<ListOfExpenses />
<Button
type="primary"
style={{ margin: "5% 5%" }}
onClick={() => addExpense({ expenses, updateExpensesFn: updateExpenses})}
>
Add Expense
</Button>
</Row>
</>
);
}
if you want to render <DeleteModal /> only once instead of every single button, you can adjust <DeleteExpense /> slightly, make <DeleteModal /> rendered only when modalVisibleBool is true
{modalVisibleBool && (
<DeleteModal
showModal={modalVisibleBool}
onOkay={() => onConfirmDelete({
updateExpensesFn: props.updateExpensesFn,
expenses: props.expenses,
expenseToDelete
})}
cancelFn={() => onCancelDeleteModal()}
expenseToDelete={expenseToDelete}
/>
)}

Related

How can I access the values of the props in my component?

I have this jsx component ArticleListItem which receives data as props and displays it. When I run my web app, I get this error: Objects are not valid as a React child. How should I handle the props to access them the way I'm trying.
Here's the ArticleListItem component which throws the error:
import { Link } from 'react-router-dom';
import { Button, Icon, Item, Segment, SegmentGroup } from 'semantic-ui-react';
export default function ArticleListItem(props) {
return(
<SegmentGroup>
<Segment>
<Item>
<Item.Header>
{props.article.title}
</Item.Header>
<Item.Content>
{props.article.description}
</Item.Content>
</Item>
</Segment>
<Segment>
<span>
<Icon name='clock' /> {props.article.publishedAt}
<Icon name='newspaper' /> {props.article.author}
</span>
</Segment>
<Segment>
<span>
<Icon name='globe' /> <Button>
as={Link}
to={props.article.url}
color='teal'
floated='right'
content='View' </Button>
</span>
</Segment>
</SegmentGroup>
)
}
Here's an example of the props
{
"source": {
"id": "business-insider",
"name": "Business Insider"
},
"author": "Diamond Naga Siu",
"title": "See how much Apple pays engineers, analysts and thousands of others",
"description": "Insider analyzed thousands of Apple's H-1B visa applications to get a sense of how much it pays employees.",
"url": "http://www.businessinsider.com/see-how-much-apple-pays-engineers-analysts-and-thousands-others-2022-9",
"urlToImage": "https://i.insider.com/633222528345c90018de0060?width=1200&format=jpeg",
"publishedAt": "2022-09-28T12:00:00Z",
"content": null
}
And here's the code block where I'm passing the props to this component:
return (
<>
{props.articles && props.articles.map(article => (<ArticleListItem key={article.title} article={article} />
))}
</>
)
The idea of my web app is to fetch news from my api and to show them. I get an array of news, map over them and create an ArticleListItem for each of them.
Any help appreciated!
Your props are not inside the button tag. The ">" tag was misplaced
<Button
as={Link}
to={props.article.url}
color='teal'
floated='right'
content='View'>
Test
</Button>

How to use mapping in React JS for Image

Hi guys so I'm trying to make some web page about hotel room information using React JS and I wanted to change the name, description, image of the page depending on the room type the user choose. But I don't know how to map the image, can somebody help me to do the mapping ?
I haven't make the button tag to change the room type yet.
Here's my room.js code:
import React from 'react'
import {Row, Col, Container} from "react-bootstrap"
const RoomInfo = [
{
MainPhoto:"",
RoomType:"Superior Twin",
RoomDescription:"",
LittlePhoto:'Photo1.jpg'
},
{
MainPhoto:"",
RoomType:"Double Room Twin",
RoomDescription:"",
LittlePhoto:'Photo1.jpg'
},
]
const Room = () => {
return (
<>
<Container fluid={true} className="p-0">
<Row>
<Col>
<h1 className="text-center"> Check out our room</h1>
</Col>
</Row>
</Container>
</>
)
}
export default Room
I hope I understand your question correctly.
you can do <img src={variable} /> and than assign the variable the link to the picture.
You can using map array like this to render element from an array:
...
<h1 className="text-center"> Check out our room</h1>
{
RoomInfo.map((item, i) => {
return (
<div key={i}>
<img src={item.LittlePhoto} />
</div>
);
});
}
...

Is it possible to handle several forms by one submit button?

I have a project on ReactJS which you can find here (see develop-branch) or check it out on our web site.
As you can see, I use formik to handle forms.
Now I have only one submit button which handles all forms however it does not link with forms by form attribute. It was OK.
Unfortunately, I've faced a problem when having a go to implement form validation. I still prefer using formik validation, but the thing is that it demands a direct connection between form and submit button like this:
export function GenerateButton(props) {
return (
<Button id="genButton"
form="form1"
type="submit"
onClick={props.onClick}>
Generate
</Button>
);
}
Any ideas how I can link all forms with submit button?
Or I have to just use fictitious buttons in every form (position: absolute; left: -9999px;) and imitate their click after pushing generate button?
P.S. now there is id="forms" in html form tag, it is just stupid mistake, must be class attribute. I can generate unique id this way: id={"form"+(props.index + 1)}.
P.S.S. I am so sorry for my English.
I think you can handle this with fieldarray of Formik very easily.
you will have an array and a list of forms in it then you can simply add and remove forms.
you won't have any problem with using validation of Formik too.
here is an example that exactly does what you want:
import React from "react";
import { Formik, Form, Field, FieldArray } from "formik";
const formInitialValues = { name: "", lastName: "" };
const FormList = () => (
<Formik
initialValues={{ forms: [formInitialValues] }}
onSubmit={values =>
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
}, 500)
}
render={({ values }) => (
<Form>
<FieldArray
name="forms"
render={arrayHelpers => (
<div>
{values.forms.map((formItem, index) => (
<div key={index}>
<Field name={`forms.${index}.name`} />
<Field name={`forms.${index}.lastName`} />
<button
type="button"
onClick={() => arrayHelpers.remove(index)} // remove a form from the list of forms
>
-
</button>
<button
type="button"
onClick={() =>
arrayHelpers.insert(index, formInitialValues)
} // insert an empty string at a position
>
+
</button>
</div>
))}
<div>
<button type="submit">Submit</button>
</div>
</div>
)}
/>
</Form>
)}
/>
);
export default FormList;
I have provided a code sandbox version for you too
please let me know if you still have any problem
more reading:
https://jaredpalmer.com/formik/docs/api/fieldarray#fieldarray-array-of-objects
The fieldArray solution looks quite complex and I think useFormik is a better option here.
For example:
import React from 'react'
import { Button, Grid, TextField } from '#mui/material'
import clsx from 'clsx'
import { useFormik } from 'formik'
export function MyFormComponent(props: any) {
const formA = useFormik({
initialValues: {
age: 23
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const formB = useFormik({
initialValues: {
name: 'bar'
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const formC = useFormik({
initialValues: {
description: 'A flat object'
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const submitForm = () => {
//get your values here
const values1 = formA.values
const values2 = formB.values
const values3 = formC.values
//data access for storing the values
}
return (
<div>
<form onSubmit={formA.handleSubmit}>
<TextField
style={{ marginBottom: 20 }}
fullWidth
type='number'
label='Age'
id='age'
name='age'
InputProps={{
inputProps: {
min: 0
}
}}
onChange={formA.handleChange}
value={formA.values.age}
/>
</form>
<form onSubmit={formB.handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
onChange={formB.handleChange}
value={formB.values.name}
style={{ marginRight: 20 }}
fullWidth
name='name'
type='text'
label='Name'
InputProps={{
inputProps: {
min: 0
}
}}
/>
</Grid>
</Grid>
</form>
<form onSubmit={formC.handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
onChange={formC.handleChange}
value={formC.values.description}
style={{ marginRight: 20 }}
fullWidth
name='description'
type='text'
label='Description'
InputProps={{
inputProps: {
min: 0
}
}}
/>
</Grid>
</Grid>
</form>
<Button color='secondary' variant='contained' onClick={submitForm}>
Submit
</Button>
</div>
)
}
I'm created a special utility library to work with multiple forms (including repeatable forms) from one place. I see that you are using Formik component (instead of useFormik hook), but maybe it still be useful.
Check this example
https://stackblitz.com/edit/multi-formik-hook-basic-example?file=App.tsx
You can find the library here

filter table by value with react redux and firebase

I am trying to filter a table in a react component by a value inside a document in firebase.
The important code looks like this. Claims is being parsed into another component for the table rows.
class Claims extends Component {
componentDidMount() {
this.props.getClaims();
}
render() {
const { Claims, loading } = this.props.data;
let recentClaimsMarkup = !loading ? (
Claims.map(Claim => <ClaimsTable Claim={Claim} />)
) : (
<p>Loading...</p>
);
return (
<Grid>
<Paper className = {mergeClasses.root}>
<Typography> {this.props.match.params.Project} Claim's</Typography>{" "}
</Paper>
<Paper className={mergeClasses.root}>
<Table className={mergeClasses.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Claim ID</TableCell>
<TableCell align="right">Date received</TableCell>
<TableCell align="right">Progress</TableCell>
<TableCell>Edit Claim</TableCell>
</TableRow>
</TableHead>
<TableBody>{recentClaimsMarkup}</TableBody>
</Table>
</Paper>
I am wanting to filter it by the url Project, shown below
{this.props.match.params.Project}
This is due to the claims being tied to the project name.
If this was in Python I would just use a for loop or something similar, but a little bit confused how to filter what Claims are able to be mapped.
Basically want it to be, for claims with Project == {this.props.match.params.Project}: map claims.
Appreciate any help :)
Thanks
So basically filter also returns a new copy of array and you can read more here, but the problem is you can't create the jsx element, if you want to know more
you can read this question
so in your case you need to first filter and then you need to render the jsx element using map like shown below
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";
import "./styles.css";
const User = props => {
const claimsUserHasAccess = props.data.claims.filter(
claim => claim.project === props.match.params.access
);
return (
<>
{claimsUserHasAccess.map(claim => (
<p key={claim.name}>{claim.name}</p>
))}
</>
);
};
const userData = {
claims: [
{ project: "enabled", name: "job" },
{ project: "enabled", name: "nick" },
{ project: "disabled", name: "jef" }
]
};
function App() {
return (
<div className="App">
<BrowserRouter>
<Route
path="/user/:access"
render={props => <User data={userData} {...props} />}
/>
</BrowserRouter>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
in the url you need to enter enabled or disabled to see the users. I hope this will give a better view of your problem.
working codesandbox

How to create this react modal the right way?

I have been working on my first Meteor application and am a bit stuck. I want to create my code following the latest guidelines (ES6 and React 15) but I am confused with all the recent changes in Javascript.
I want to add a Bootstrap Modal in my current comments list but can't seem to figure out how to add my content to the modal using the right up to date syntax.
Here is my current code:
In comment.js:
import React from 'react';
import { Row, Col, ListGroupItem, FormControl, Button } from 'react-bootstrap';
import { Bert } from 'meteor/themeteorchef:bert';
import { CommentsModal } from './comments-modal'
export const Comment = ({ comment }) => (
<ListGroupItem key={ comment._id }>
<Row>
<Col xs={ 8 } sm={ 10 }>
<FormControl
type="text"
defaultValue={ comment.title }
/>
</Col>
<Col xs={ 4 } sm={ 2 }>
<Button
bsStyle="danger"
className="btn-block">
Remove Comment
</Button>
</Col>
</Row>
<CommentsModal/>
</ListGroupItem>
);
In Comments-modal.js:
import React, { Component } from 'react';
import { Modal, Button, Tooltip } from 'react-bootstrap';
export class CommentsModal extends Component {
constructor(props) {
super(props);
this.state = {
showModal: false,
};
this.close = this.close.bind(this);
this.open = this.open.bind(this);
}
close() {
this.setState({ showModal: false });
}
open() {
this.setState({ showModal: true });
}
render() {
return (
<div>
<Button
bsStyle="primary"
bsSize="large"
onClick={this.open}
>
</Button>
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title >Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>
<h4>Text in a modal</h4>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.close}>Close</Button>
</Modal.Footer>
</Modal>
</div>
);
}
}
And last comments-list.js:
import React from 'react';
import { ListGroup, Alert } from 'react-bootstrap';
import { Comment } from './comment';
export const CommentsList = ({ comments }) => (
comments.length > 0 ? <ListGroup className="comments-list">
{comments.map((com) => (
<Comment key={ com._id } comment={ com } />
))}
</ListGroup> :
<Alert bsStyle="warning">No comments yet. Please add some!</Alert>
);
CommentsList.propTypes = {
comments: React.PropTypes.array,
};
I manage to get the Modal to show up and work but when I want to display data in it, I can't get it to work. What is the best way to combine both these into one?
Pass the data in props to the CommentsModal and render it as you would normally do.
I try to keep local state out of component when using redux if possible, so to answer your question on making it stateless, I would take the following steps:
Remove the button that opens the modal from the modal.js itself
Remove the actual modal from the modal.js, just put the modal content inside of there.
Change the open modal button to hook into an action creator that sets a prop to open the modal and passes it's content (also set one to close it)
So that looks something like this
<ListGroupItem key={ comment._id }>
<Row>
<Col xs={ 8 } sm={ 10 }>
<FormControl
type="text"
defaultValue={ comment.title }
/>
</Col>
<Col xs={ 4 } sm={ 2 }>
<Button
bsStyle="danger"
className="btn-block">
Remove Comment
</Button>
</Col>
</Row>
<!-- Here is where it changes, -->
<Button
bsStyle="primary"
bsSize="large"
onClick={this.props.openModal(comment)}
>
</Button>
<Modal show={this.props.commentModal} onHide={this.props.closeModal}>
<CommentsModal content={this.props.commentModal} />
</Modal>
Keep in mind, these naming conventions are just for examples sake : use whatever works best for you.
So what happens here is when you click that button you fire this.props.openModal (an action) which does something like this in the reducers -
case actions.OPEN_COMMENT_MODAL:
return state.set('commentModal', action.content);
the close buttons fire the onHide which is linked to the this.props.closeModal action which just does:
case actions.OPEN_COMMENT_MODAL:
return state.set('commentModal', undefined);
So what this allows you to do is have just 1 modal instance and you pass the current comment to it with that button click and open it. The show just checks the truthy value, so you set it back to undefined and it will hide itself.
Then I am passing the prop of content to the modal, so you can then use it inside the modal itself. Again, change the names to whatever works best for you.

Categories