How to dispatch an action that changes the state of a component from its parent component? - react-redux

I'm trying to implement a modal dialog that asks if I'm sure if I want to delete or not an item in application. I've this components:
const Options = item => (
<OptionsMenu>
<MenuItem onClick={_ => {
console.log(`Deleting item ${JSON.stringify(item)}`)
}}>
<IconButton aria-label="Delete" color="accent">
<DeleteIcon />
</IconButton>
<Typography>
Eliminar
</Typography>
</MenuItem>
<DeleteDialog
item={item}
/>
</OptionsMenu>
)
And my dialog component is:
const DeleteDialog = props => (
<div>
<Button onClick={() => {
this.props.openDeleteDialog(this.props.item)
}}>Delete</Button>
<Dialog open={this.props.open} onRequestClose={this.props.cancelDeleteData}>
<DialogTitle>{"DELETE"}</DialogTitle>
<DialogContent>
<DialogContentText>
Are you sure you want to delete the item: {this.props.item.name}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.props.cancelDeleteData} color="primary">
Cancel
</Button>
<Button onClick={this.props.deleteData(this.props.item)} color="primary">
Delete
</Button>
</DialogActions>
</Dialog>
</div>
);
const mapStateToProps = state => ({
open: state.item.delete.open,
})
const mapDispatchToProps = dispatch => ({
...deleteDispatchesForScope(scopes.ITEM, dispatch)
})
What I want is to dispatch the openDeleteDialog action, that sets the open state to true, from the Options component in a way that I will allow me to reuse the modal Dialog in other components.
I'm using react-redux and material-ui v1 for this.

In order to have more reusable components, I would decouple the DeleteDialog from the OptionsMenu, and rely on a ParentComponent to pass down the props required for each child:
<ParentComponent>
<OptionsMenu>
<MenuItem onClick={this.props.openDeleteDialog}>Eliminar</MenuItem>
</OptionsMenu>
<DeleteDialog
open={this.props.isDeleteDialogOpen}
item={this.props.item}
onDelete={this.props.deleteData}
/>
</ParentComponent>

Related

Can't get Formik form to work with Material UI

I'm struggeling with making my Formik form work. Here a part of my code:
import { Formik, Form as FormikForm, Field } from "formik";
import {TextField} from "#mui/material";
<Formik
initialValues={{
name: "test",
}}
onSubmit={this.onSubmit}
>
{({ values,handleChange }) => (
<FormikForm>
<Field
component={TextField}
name="name"
label="Name"
fullWidth
></Field>
<Button
color="success"
variant="contained"
type="submit"
onClick={() => {
console.log(values);
}}
>
Erstellen
</Button>
</FormikForm>
)}
</Formik>
It seems like its having trouble connecting the values state to the Field value, since the initialValue isn't displayed in the names field. When i submit, it logs {name: 'test'} to console, no matter what i enter in the input field.
Edit 1:
I also have a very similar form that works. The only difference between the two that i can think of is: the working one is a .jsx while this one is .tsx. Dont know if that has anything to do with it.
According to the Field documentation you need to spread the field and pass it to the component like this:
import { Formik, Form as FormikForm, Field } from "formik";
import { Button, TextField } from "#mui/material";
const MuiTextField = ({ field, form, ...props }) => {
return <TextField {...field} {...props} />;
};
const MyComponent = () => {
return (
<Formik
initialValues={{
name: "test"
}}
onSubmit={(values) => {
console.log(values);
}}
>
{({ values, handleChange }) => (
<FormikForm>
<Field
component={MuiTextField}
name="name"
label="Name"
fullWidth
></Field>
<Button
color="success"
variant="contained"
type="submit"
onClick={() => {
console.log(values);
}}
>
Erstellen
</Button>
</FormikForm>
)}
</Formik>
);
};
export default MyComponent;
You can take a look at this sandbox for a live working example of this solution.
Like Ahmet pointed out, the problem seems to be that the field prop needs to be spread (see his answer). However i found, that using as instead of component works too and doesnt need spreading.
<Field
as={TextField}
name="name"
label="Name"
fullWidth
></Field>

React formik form onsubmit event callings many times?

import { Formik, Form, Field } from "formik";
import { Button } from "antd";
const AddUser = () => {
const initialValues = {
name: "",
};
return (
<>
<Formik
initialValues={initialValues}
onSubmit=(values) => {
alert("hi");//calling mamy times
Here added api call (post method)
}}
>
{({ isValid, submitForm, isSubmitting, values }) => {
return (
<Form>
<Field
name="name"
label="Name"
placeholder="Dataset Name"
/>
<Button
type="primar"
htmltype="submit"
loading=(props.addingdata) // this is my reducer state intial was false after post call request became true and success state value false
>
Add Dataset
</Button>
</Form>
);
}}
</Formik>
</div>
</>
);
};
export default AddUser;
I have simple formik form antd button I have used when click submit button post api calling twice and thrice even If I added loading property in button why its happening like this?

How to use react state hook in dynamic modal

I'm trying to use a react modal to allow someone to change a value and have the modal buttons be dynamic such that initially there are Cancel / Submit buttons, but after Submit is pressed and the value is changed, the buttons are replaced with a Close button.
The problem I am having is that using "{modalButtonGroup}" in the modal results in "tmpName" being undefined when "handleSetNameSubmit" is called. If I instead comment out the "{modalButtonGroup}" line and just use hard coded buttons (which are currently commented out in the below code), then "tmpName" is set correctly when "handleSetNameSubmit" is called.
Is there some aspect of state context that causes "tmpName" to not be known when "{modalButtonGroup}" is used?
import { useState, useEffect } from 'react';
import { Row, Table, Form, Button, Modal, Alert } from 'react-bootstrap';
const System = () => {
const [tmpName, setTmpName] = useState();
const [showName, setShowName] = useState(false);
const handleClose = () => {
setShowName(false);
}
const handleCancel = () => {
setShowName(false);
};
const handleSetNameSubmit = () => {
console.log('tmpName: ', tmpName);
//code to change the name to tmpName
setModalButtonGroup(modalButtonsPostSubmit);
}
const modalButtonsPreSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleCancel}>
Cancel
</Button>
<Button variant="primary" onClick={handleSetNameSubmit}>
Submit
</Button>
</>
)
};
const modalButtonsPostSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
</>
)
};
const [modalButtonGroup, setModalButtonGroup] = useState(modalButtonsPreSubmit);
return (
<>
<div className="card">
<h5>System</h5>
<Table variant="dark" responsive>
<tr>
<td>Name:</td>
<td>Name <Button onClick={() => setShowName(true)}>Edit</Button></td>
</tr>
</Table>
</div>
{/* Set Name */}
<Modal show={showName} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Set Name</Modal.Title>
</Modal.Header>
<Modal.Body>
<span>
<Form.Control
type="text"
defaultValue=name
onChange={(event) => setTmpName(event.target.value)}
/>
</span>
</Modal.Body>
<Modal.Footer>
{modalButtonGroup}
{/*<Button variant="secondary" onClick={handleCancel}>*/}
{/* Cancel*/}
{/*</Button>*/}
{/*<Button variant="primary" onClick={handleSetNameSubmit}>*/}
{/* Submit*/}
{/*</Button>*/}
</Modal.Footer>
</Modal>
}
export default System;
UPDATE, I tried updating the code per suggestion as follows but now no buttons are appearing at all.
import { useState, useEffect } from 'react';
import { Row, Table, Form, Button, Modal, Alert } from 'react-bootstrap';
const System = () => {
const [tmpName, setTmpName] = useState();
const [showName, setShowName] = useState(false);
const [submitted, setSubmitted] = useState(false);
const handleClose = () => {
setShowName(false);
}
const handleCancel = () => {
setShowName(false);
};
const handleSetNameSubmit = () => {
console.log('tmpName: ', tmpName);
//code to change the name to tmpName
setSubmitted(modalButtonsPostSubmit);
}
const modalButtonsPreSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleCancel}>
Cancel
</Button>
<Button variant="primary" onClick={handleSetNameSubmit}>
Submit
</Button>
</>
)
};
const modalButtonsPostSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
</>
)
};
const buttons = submitted ? modalButtonsPostSubmit : modalButtonsPreSubmit;
return (
<>
<div className="card">
<h5>System</h5>
<Table variant="dark" responsive>
<tr>
<td>Name:</td>
<td>Name <Button onClick={() => setShowName(true)}>Edit</Button></td>
</tr>
</Table>
</div>
{/* Set Name */}
<Modal show={showName} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Set Name</Modal.Title>
</Modal.Header>
<Modal.Body>
<span>
<Form.Control
type="text"
defaultValue=name
onChange={(event) => setTmpName(event.target.value)}
/>
</span>
</Modal.Body>
<Modal.Footer>
{buttons}
{/*<Button variant="secondary" onClick={handleCancel}>*/}
{/* Cancel*/}
{/*</Button>*/}
{/*<Button variant="primary" onClick={handleSetNameSubmit}>*/}
{/* Submit*/}
{/*</Button>*/}
</Modal.Footer>
</Modal>
}
export default System;
Here's what's happening. It's kind of complicated because of the unusual way in which you have written your component. I'll suggest a simpler way to do it below, but it might be educational to unpack what's going on.
Your <System> component renders for the first time:
tmpName is undefined
the handleNameSubmit function is generated and it "closes over" the current value of tmpName. This means every time this particular function value is called, it will always console.log 'tmpName: undefined'. See background on JavaScript closures
the modalButtonsPreSubmit function is generated and it closes over the current value of handleNameSubmit and binds this value to the submit button click event.
Then, you pass the modalButtonsPreSubmit function as the initial value of a useState hook. The way that useState works, this initial value is only used in the first render (see docs). The modalButtonsGroup value returned by this useState call will be frozen to this particular value (with all the closures) through subsequent re-renders, until you change it by calling setModalButtonsPreSubmit with a new function.
The user types some text in the textbox. For each character your onChange handler calls setTempName, which triggers the <System> component to re-render with a new value in the tmpName state. However, modalButtonsPreSubmit is still frozen to what it was in the first render.
The user clicks "Submit", which triggers the version handleNameSubmit that was generated on the first render, when tmpName was undefined.
The way to simplify things so that it works as expected is to not store functions in state. That way they'll get re-generated on each re-render with fresh values for any other state that they reference.
So instead of..
const modalButtonsPreSubmit = () => (
<> {/* Markdown for "Submit" and "Cancel" buttons */} </>
);
const modalButtonsPostSubmit = () => (
<> {/* Markdown for "Close" button */} </>
);
const [modalButtonGroup, setModalButtonGroup] = useState(modalButtonsPreSubmit);
return (
<div>
{/* The rest of the app */}
{modalButtonGroup}
</div>
);
You'd do something like this...
const [submitted, setSubmitted] = useState(false);
const buttons = submitted ?
<> {/* Markdown for "Close" button */} </> :
<> {/* Markdown for "Submit" and "Cancel" buttons */} </>;
return (
<div>
{/* The rest of the app */}
{buttons}
</div>
);
See this codesandbox for a working solution.

react hook, validate Form with reduxForm

I Have external component managing my input Field and throws an error if no input is made.
On submit of form previously with class component along with reduxForm effect, this would throw an error of missing input, am wondering how to achieve this with hooks since submission passes whether i have input or Not.
import ConstructField from '../components.render';
const ActivitiesForm = () => {
const handleSubmit_ = () => {
console.log({ activityName });
};
const [activityName, setActivityName] = useState(null);
const handleInputName = (e) => setActivityName(e.target.value);
const { items } = useSelector((state) => ({
items: state.items,
}));
const { register, handleSubmit, errors, control } = useForm();
return (
<div>
<Form onSubmit={handleSubmit(handleSubmit_)} className='ui form'>
<Form.Group widths='equal'>
<Field
component={ConstructField('input')}
onChange={handleInputName}
label='Activity Name'
name='activityName'
placeholder='Activity Name'
validate={required}
/>
</Form.Group>
<br />
<Form.Group inline>
<Button.Group>
<Button primary>Save</Button>
<Button.Or />
<Button positive onClick={goBackButton}>
Go Back
</Button>
</Button.Group>
</Form.Group>
</Form>
</div>
);
};
const required = (value) => (value ? undefined : 'this field is required');
const activityform = reduxForm({
form: 'activityform',
enableReinitialize: true,
})(ActivitiesForm);
export default activityform;

Admin on Rest onClick being called on render and not on click

The issue I am having is I have a button:
const LogActions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter, refresh, record }) => (
<CardActions style={cardActionStyle}>
<RaisedButton primary label="Cancel" onClick={HandleClick(record["id"])} />/>
</CardActions>
);
This button is used in a List like so:
export const LogList = (props) => (
<List {...props} perPage={100} title="Logs and Reports" filters={< FileFilter/>}>
<Datagrid>
<TextField source="inputfile" label="Input File" />
<TextField source="cycle" label="Cycle" />
<TextField source="job" label="Job" />
<TextField source="name" label="File Name" />
<ShowButton/>
<LogActions/>
</Datagrid>
</List>
);
This is the HandleClick:
const HandleClick = (id) => {
fetch(`controller_service/archivedfiles/${id}`, { method: 'GET', body:{} })
.then(() => {
// do stuff
})
.catch((e) => {
console.error(e);
//error
});
}
Okay so my problem is that whenever I go to this page, it will create the datagrid, but while the button is being rendered it calls HandleClick which then fetches that data before I even click on the button, and when I click on the button nothing happens. Can someone explain what I am doing wrong?
As wesley6j said, the function gets called when you assign it with parameters.
A way to avoid this is to use .bind(this,params) or wrap your HandleClick in another function like this:
onClick={() => HandleClick(record["id"])}

Resources