I have a page file in my React app that has a sidebar component.
import React, { Component } from 'react';
import Navbar from './../components/layout/Navbar';
import { Table } from './../components/layout/Table';
import { Sidebar } from './../components/layout/Sidebar';
export class LandingPage extends Component {
render() {
return (
<div>
<Navbar />
<h2 >Affiliation Information</h2>
<div className="wrapper">
<Table />
<Sidebar />
</div>
</div>
)
}
}
export default LandingPage
This sidebar component consists of an input textbox and a submit button. The idea here is that the user types something into the textbox (Ex: "La") triggering an event handler that sets the state of const interest then clicks submit. The submit button then plugs the interest state value into a url string for a call to my API using Axios, which is suppose to return the query results of any items where name is like "La". (Ex: [{"contact_name": "John Latham"}]).
import React, { useState, useEffect } from 'react';
import config from '../../config/config';
import axios from "axios";
export const Sidebar = () => {
const [interest, setInterest] = useState(null);
const [response, setResponse] = useState([]);
const [loadingData, setLoadingData] = useState(true);
console.log("At top: " + config.apiURL + `/affiliations/full?name=${interest}`);
function HandleChange(event) {
setInterest(event.target.value);
};
async function callYourAPI(interest) {
await axios
.get(config.apiURL + `/affiliations/full?name=${interest}`)
.then((res) => {
setResponse(res.data);
setLoadingData(false);
})
.catch(function (error) {
console.log(error);
});
};
useEffect(() => {
if (loadingData) {
callYourAPI();
} else {
console.log(response);
}
}, [loadingData, response]); // Makes the useEffect dependent on response.
return (
<div className="sidebar" style={{
borderLeft: "1px solid gray"
}}>
<h2 style={{ textAlign: "center" }}> Filter</h2>
<form>
<label style={{}}>Name</label> <br />
<input
placeholder="Type name"
style={{ textAlign: "center", marginRight: "10px", height: "25px", width: "200px" }}
value={interest}
onChange={HandleChange}></input>
<button className="btn btn-primary" onClick={() => callYourAPI(interest)}> Search</button>
</form>
</div >
)
}
However, what happens is that when I initially type in a value in the input box, its sets the state correctly, but when I click the button, everything goes haywire! The interest variable becomes undefined (the box gets cleared) and the process seems to loop through the Sidebar() function about 3 times ( and the sync callYourAPI() function) before it finishes but never returns the console.log(response); in my useEffect() function. At this point I am confused; what am I doing wrong here?!
UPDATE: I have made the recommended changes, using e.preventDefault() in my forms onSubmit. However, it seems like this is called on load, and its stuck in some kind of infinite call loop to the API.
export const Sidebar = () => {
const [interest, setInterest] = useState("");
const [response, setResponse] = useState([]);
const [loadingData, setLoadingData] = useState(true);
function HandleChange(event) {
setInterest(event.target.value);
};
async function callYourAPI(interest) {
await axios
.get(config.apiURL + `/affiliations/full?name=${interest}`)
.then((res) => {
setResponse(res.data);
setLoadingData(false);
})
.catch(function (error) {
console.log(error);
});
};
return (
<div className="sidebar" style={{
borderLeft: "1px solid gray"
}}>
<h2 style={{ textAlign: "center" }}> Filter</h2>
<form onSubmit={(e) => { e.preventDefault(); callYourAPI(interest); }}>
<label style={{}}>Name</label> <br />
<input
placeholder="Type name"
style={{ textAlign: "center", marginRight: "10px", height: "25px", width: "200px" }}
value={interest}
onChange={HandleChange}></input>
<button className="btn btn-primary"> Search</button>
</form>
</div >
)
}
Try adding event.preventDefault() like this:
function HandleChange(event) {
event.preventDefault()
setInterest(event.target.value);
};
The default behavior when submitting a form is to reload the page, therefore resetting the state. event.preventDefault() prevents the default behavior of the page.
Related
My form was working perfectly earlier and now on after few changes the state change the input filed is loosing focus. I am using MUI and it was working earlier perfectly but suddenly it started losing focus after few minor change in state variable names. I think that is not the issue but i don't know why is this happening as all controlled forms are made like this i presume
import { useEffect, useState } from "react";
import Axios from "axios";
import { Schedule } from "./Schedule";
import { PageHeader } from "./PageHeader";
import { Button, TextField, styled, CircularProgress } from "#mui/material";
import FileUploadIcon from "#mui/icons-material/FileUpload";
import { DesktopDatePicker } from "#mui/x-date-pickers/DesktopDatePicker";
import { LocalizationProvider } from "#mui/x-date-pickers/LocalizationProvider/LocalizationProvider";
import { AdapterDayjs } from "#mui/x-date-pickers/AdapterDayjs";
import SendIcon from "#mui/icons-material/Send";
function Form() {
const [formData, setFormData] = useState({ startTime: "", endTime: "" });
const [file, setFile] = useState("");
const [data, setData] = useState("");
const [newValue, setValue] = useState(null);
const [loading, setLoading] = useState(false);
console.log(formData);
console.log("Rerendered");
function handleChange(event) {
setFormData((previousFormData) => {
return {
...previousFormData,
[event.target.name]: event.target.value,
};
});
}
function handleClick(event) {
setLoading(true);
event.preventDefault();
setTimeout(() => {
const postFormData = new FormData();
postFormData.append("startTime", formData.startTime);
postFormData.append("endTime", formData.endTime);
postFormData.append("date", newValue);
postFormData.append("file", file);
Axios.post("http://localhost:3001/aip", postFormData, {
headers: {
"Access-Control-Allow-Origin": "cors",
},
})
.then((res) => setData(res.data))
.then(setLoading(false))
.catch((err) => console.log(err));
}, 2000);
}
const Container = styled("main")({
marginTop: 50,
display: "grid",
justifyItems: "center",
});
const Div = styled("div")({
display: "flex",
gap: 50,
});
const Form = styled("form")({
border: "none",
width: "100%",
height: "100%",
margin: "auto",
display: "grid",
justifyItems: "center",
gap: 50,
});
return (
<>
<PageHeader />
<Container>
<Form onSubmit={handleClick} encType="multipart/form-data">
<Div class="timeInput">
<TextField
label="From"
name="startTime"
variant="outlined"
placeholder="9:00"
onChange={handleChange}
value={formData.startTime}
required
/>
<TextField
label="To"
name="endTime"
variant="outlined"
placeholder="9:30"
onChange={handleChange}
value={formData.endTime}
required
/>
</Div>
<div class="dateInput">
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DesktopDatePicker
value={newValue}
label="Choose Date"
onChange={(newValue) => setValue(newValue)}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
</div>
<Div>
<Button sx={{ gap: 2 }} variant="contained" component="label">
Upload Schedule
<input
hidden
accept=".xlsx"
multiple
type="file"
onChange={(event) => {
const file = event.target.files[0];
setFile(file);
}}
/>
<FileUploadIcon />
</Button>
<Button sx={{ gap: 2 }} variant="contained" component="label">
Search
<input hidden type="submit" />
<SendIcon />
</Button>
</Div>
</Form>
</Container>
{loading ? (
<CircularProgress sx={{ alignItems: "center" }} />
) : (
<Schedule data={data} />
)}
</>
);
}
export default Form;
In this example, whenever the react state changes, App will re-render. And because App re-renders, Form gets re-declared and the internal state of the underlying DOM node (form) is lost.
export default function App() {
const [value, setValue] = useState("");
const Form = styled("form")({});
return (
<Form>
<input value={value} onChange={({ target }) => setValue(target.value)} />
</Form>
);
}
Declare Form outside of the body of App and it works
export default function App() {
const [value, setValue] = useState("");
return (
<Form>
<input value={value} onChange={({ target }) => setValue(target.value)} />
</Form>
);
}
const Form = styled("form")({});
If you must declare Form in the body of App (you probably don't need to), you can memoise it with React.useMemo
export default function App() {
const [value, setValue] = useState("");
const Form = useMemo(() => styled("form")({}), []);
return (
<Form>
<input value={value} onChange={({ target }) => setValue(target.value)} />
</Form>
);
}
You should move your styled components outside of the main Form component as they are causing re renders. Just note that you will then have clashing names. Styled Form and the actual component Form. So you will have to either rename the component or the styled Form. As to why this happens, I'm not entirely sure but ran into a similar issue before. I assume that every time you type and the state changes, it's re creating the styled components and then re rendering the main component.
// Imports
import { useEffect, useState } from "react";
import Axios from "axios";
import { Schedule } from "./Schedule";
import { PageHeader } from "./PageHeader";
import { Button, TextField, styled, CircularProgress } from "#mui/material";
import FileUploadIcon from "#mui/icons-material/FileUpload";
import { DesktopDatePicker } from "#mui/x-date-pickers/DesktopDatePicker";
import { LocalizationProvider } from "#mui/x-date-pickers/LocalizationProvi/LocalizationProvider";
import { AdapterDayjs } from "#mui/x-date-pickers/AdapterDayjs";
import SendIcon from "#mui/icons-material/Send";
// Styles
const Container = styled("main")({
marginTop: 50,
display: "grid",
justifyItems: "center",
});
const Div = styled("div")({
display: "flex",
gap: 50,
});
// Component
const Form = styled("form")({
border: "none",
width: "100%",
height: "100%",
margin: "auto",
display: "grid",
justifyItems: "center",
gap: 50,
});
function Form() {
// ...
}
export default Form;
I keep getting this error: TypeError: Cannot read property 'data' of undefined, when there is no data being passed to my shopping cart page. How can I fix this error? Ideally, I would just like the page to display: "This cart is empty". I tried adding a conditional statement above the UserCardBlock, but it did not change anything. Thank you
import React, { useState } from 'react'
import { useDispatch } from 'react-redux';
import {
removeCartItem,
onSuccessBuy
} from '../../../_actions/user_actions';
import UserCardBlock from './Sections/UserCardBlock';
import { Result, Empty, Button } from 'antd';
import Paypal from '../../utils/Paypal';
function CartPage(props) {
const dispatch = useDispatch();
console.log(props)
const [Total, setTotal] = useState(props.location.state.data.price)
const [ShowTotal, setShowTotal] = useState(true)
const [ShowSuccess, setShowSuccess] = useState(false)
const removeFromCart = (productId) => {
dispatch(removeCartItem(productId))
}
const transactionSuccess = (data) => {
dispatch(onSuccessBuy({
cartDetail: props.user.cartDetail,
paymentData: data
}))
.then(response => {
setShowSuccess(true)
setShowTotal(false)
}
)
}
const transactionError = () => {
console.log('Paypal error')
}
const transactionCanceled = () => {
console.log('Transaction canceled')
}
const propductList = (data) =>{
console.log(data)
setTotal(data)
}
return (
<div style={{ width: '85%', margin: '3rem auto' }}>
<h1>My Cart</h1>
<div>
<UserCardBlock
productData={props.location.state.data}
removeItem={removeFromCart}
productList={data => propductList(data)}
/>
{ShowTotal ? (
<div style={{ marginTop: "3rem" }}>
<h2>Total amount: ${Total * 15} </h2>
</div>
) : ShowSuccess ? (
<Result status="success" title="Successfully Purchased Items" />
) : (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "center",
}}
>
<br />
<Empty description={false} />
<p>No Items In The Cart</p>
</div>
)}
</div>
{/* Paypal Button */}
{ShowTotal &&
<Paypal
toPay={Total}
onSuccess={transactionSuccess}
transactionError={transactionError}
transactionCanceled={transactionCanceled}
/>
}
</div>
)
}
export default CartPage
Seems like your component is dependent on location state.
const [Total, setTotal] = useState(props.location.state.data.price)
and
<UserCardBlock
productData={props.location.state.data}
Try using optional chaining and nullish coalescing
const [Total, setTotal] = useState(props.location?.state?.data?.price ?? 0)
<UserCardBlock
productData={props.location.state?.data ?? []}
It seems like you are using redux so i will suggest you to use redux store instead of location state.
I'm not 100% sure what's going on here. I've got a display component that displays a bunch of cards, using a map based on my database - On the card is an edit button that pops a modal up, passing props over to the edit form.. Here's kinda how it looks:
import React, { useState } from 'react'
import { useQuery, useMutation } from '#apollo/client'
import { GET_ALL_PROJECTS, REMOVE_PROJECT } from '../helpers/queries'
import { makeStyles } from '#material-ui/core/styles'
import DeleteIcon from '#material-ui/icons/Delete'
import EditIcon from '#material-ui/icons/Edit'
import AddForm from './AddForm'
import EditForm from './EditForm'
import AlertMessage from '../Alerts/AlertMessage'
import { Grid, Typography, Card, CardActionArea, CardActions, CardContent, CardMedia, Button, Modal, Backdrop, Fade } from '#material-ui/core'
const useStyles = makeStyles((theme) => ({
modal: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
paper: {
backgroundColor: theme.palette.background.paper,
border: '2px solid #000',
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
}));
const DisplayProjects = () => {
const styles = useStyles()
const [deleteItem] = useMutation(REMOVE_PROJECT)
const { loading, error, data } = useQuery(GET_ALL_PROJECTS)
const [status, setStatusBase] = useState('')
const [resultMessage, setResultMessage] = useState('')
const [addOpen, setAddOpen] = useState(false)
const [editOpen, setEditOpen] = useState(false)
const onDelete = (id, e) => {
e.preventDefault()
deleteItem({
variables: { id },
refetchQueries: [{ query: GET_ALL_PROJECTS }]
}).then(
res => handleSuccess(res),
err => handleError(err)
)
}
// Handles Result of the Delete Operation
const handleSuccess = (res) => {
console.log(res.data.deleteProject.proj_name)
// console.log('success!');
setResultMessage(res.data.deleteProject.proj_name)
setStatusBase({
msg: `Successfully Deleted ${resultMessage}`,
key: Math.random()
})
}
const handleError = (err) => {
console.log('error')
}
//Handles the Modal for Add Project
const handleAddOpen = () => {
setAddOpen(true);
};
const handleAddClose = () => {
setAddOpen(false);
};
//Handles the Modal for Edit Project
const handleEditOpen = () => {
setEditOpen(true);
};
const handleEditClose = () => {
setEditOpen(false);
};
if (loading) return '...Loading'
if (error) return `Error: ${error.message}`
return (
<div>
<div style={{ marginTop: 20, padding: 30 }}>
<Grid container spacing={8} justify='center' alignItems='center'>
{data.projects.map(p => {
return (
<Grid item key={p._id}>
<Card >
<CardActionArea>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<CardMedia
style={{ width: 400, height: 100, paddingTop: 10, }}
component='img'
alt='Project Image'
height='140'
image={require('../../images/html-css-javascript-lg.jpg')}
/>
</div>
<CardContent >
<Typography gutterBottom variant='h5' component="h2">
{p.proj_name}
</Typography>
<Typography component='p'>
{p.description}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button>
<DeleteIcon onClick={e => onDelete(p._id, e)} />
</Button>
<Button onClick={handleEditOpen}>
<Modal
open={editOpen}
onClose={handleEditClose}
closeAfterTransition
BackdropComponent={Backdrop}
className={styles.modal}
>
<Fade in={editOpen}>
<div className={styles.paper}>
<EditForm
id={p._id}
close={handleEditClose}
name={p.proj_name}
desc={p.description}
gh={p.gh_link}
live={p.live_link}
img={p.image_url}
/>
</div>
</Fade>
</Modal>
<EditIcon />
</Button>
</CardActions>
</Card>
{ status ? <AlertMessage key={status.key} message={status.msg} /> : null}
</Grid>
)
}
)}
</Grid>
<Button type='button' onClick={handleAddOpen}>Add Project</Button>
<Modal
open={addOpen}
onClose={handleAddClose}
closeAfterTransition
BackdropComponent={Backdrop}
className={styles.modal}
>
<Fade in={addOpen}>
<div className={styles.paper}>
<AddForm close={handleAddClose} />
</div>
</Fade>
</Modal>
</div>
</div >
)
}
export default DisplayProjects
And here's the form. I've destructured out the props into variables and placed them into a state object called details, so they can be overwritten and submitted to the database..
import React, { useState } from 'react'
import { useParams } from 'react-router-dom'
import { useMutation, useQuery } from '#apollo/client'
import { EDIT_PROJECT, GET_ALL_PROJECTS, GET_PROJECT_BY_ID} from '../helpers/queries'
const AddForm = (props) => {
const params = useParams()
const id = params.toString()
// console.log(id);
const [editProjectItem] = useMutation(EDIT_PROJECT)
const {loading, data, error} = useQuery(GET_PROJECT_BY_ID, {
variables: {
id
},
})
const [details, setDetails] = useState({})
if (loading) return '...Loading';
if (error) return <p>ERROR: {error.message}</p>;
if (!data) return <p>Not found</p>;
setDetails(data.projectById)
console.log(data.projectById)
const submitForm = e => {
e.preventDefault()
try {
editProjectItem({
variables: { id, proj_name, description, gh_link, live_link, image_url},
refetchQueries: [{query: GET_ALL_PROJECTS}]
})
}
catch (err) {
console.log('You Goofed')
}
// setDetails({
// proj_name: '',
// description: '',
// gh_link: '',
// live_link: '',
// image_url: ''
// })
props.close()
}
const changeDetails = (e) => {
setDetails({
...details,
[e.target.name]: e.target.value
})
}
const {_id, proj_name, description, gh_link, live_link, image_url} = details
return (
<div key = {_id}>
<h2>Edit {proj_name}</h2>
<form onSubmit = {submitForm} >
<label>
Project Name:
<input
name = 'proj_name'
value = {proj_name}
onChange = {changeDetails}
/>
</label>
<label>Description</label>
<input
name = 'description'
value = {description}
onChange = {changeDetails}
/>
<label>GitHub Link</label>
<input
name = 'gh_link'
value = {gh_link}
onChange = {changeDetails}
/>
<label>Live Link</label>
<input
name = 'live_link'
value = {live_link}
onChange = {changeDetails}
/>
<label>Preview Image</label>
<input
name = 'image_url'
value = {image_url}
onChange = {changeDetails}
/>
<button type = 'submit'>Submit</button>
</form>
</div>
)
}
export default AddForm
The problem I'm running into, is that when I access the modal, the props are sent from literally EVERY Object, instead of the one, and displays the data for the last record instead of the one I want to edit You can see what happens here (I logged props.id in order to test) https://imgur.com/a/pcEKl89
What did I miss? (Disclaimer: I am still a student, and learning the craft.. be gentle on my code please)
EDIT: I just realized that I didn't indicate that this is the final form of the EditForm component. I haven't added the logic in to make the updates yet, I just wanted to get the data showing properly first.
EDIT2: I made some changes to how the ID is passed over, I was already using React-Router, so I went ahead and made a route to /edit/:id and then using useParams(), I got the ID that way. It seems to be working, however now I'm getting a Too many re-renders message. Updated the AddForm code above to reflect the changes..
I figured out the re-render issue.. it was as simple as dropping the setDetails function into a useEffect Hook:
useEffect(()=> {
if(data){
setDetails(data.projectById)
}
},[data])
I'm trying to figure out how to use the DatePicker from antd with react-hook-form.
Currently, my attempt is:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import useForm from "react-hook-form";
import { withRouter } from "react-router-dom";
import { useStateMachine } from "little-state-machine";
import updateAction from "./updateAction";
import { Input as InputField, Form, Button, DatePicker, Divider, Layout, Typography, Skeleton, Switch, Card, Icon, Avatar } from 'antd';
import Select from "react-select";
const { Content } = Layout
const { Text, Paragraph } = Typography;
const { Meta } = Card;
const { MonthPicker, RangePicker, WeekPicker } = DatePicker;
const Team = props => {
const { register, handleSubmit, setValue, errors } = useForm();
const [ dueDate, setDate ] = useState(new Date());
const [indexes, setIndexes] = React.useState([]);
const [counter, setCounter] = React.useState(0);
const { action } = useStateMachine(updateAction);
const onSubit = data => {
action(data);
props.history.push("./ProposalBudget");
};
// const handleChange = dueDate => setDate(date);
const handleChange = (e) => {
setValue("dueDate", e.target.value);
}
const onSubmit = data => {
console.log(data);
};
const addMilestone = () => {
setIndexes(prevIndexes => [...prevIndexes, counter]);
setCounter(prevCounter => prevCounter + 1);
};
const removeMilestone = index => () => {
setIndexes(prevIndexes => [...prevIndexes.filter(item => item !== index)]);
};
const clearMilestones = () => {
setIndexes([]);
};
useEffect(() => {
register({ name: dueDate }); // custom register antd input
}, [register]);
Note: i have also tried name: {${fieldName}.dueDate - that doesn't work either.
return (
<div>
<HeaderBranding />
<Content
style={{
background: '#fff',
padding: 24,
margin: "auto",
minHeight: 280,
width: '70%'
}}
>
<form onSubmit={handleSubmit(onSubit)}>
{indexes.map(index => {
const fieldName = `milestones[${index}]`;
return (
<fieldset name={fieldName} key={fieldName}>
<label>
Title:
<input
type="text"
name={`${fieldName}.title`}
ref={register}
/>
</label>
<label>
Description:
<textarea
rows={12}
name={`${fieldName}.description`}
ref={register}
/>
</label>
<label>When do you expect to complete this milestone? <br />
<DatePicker
selected={ dueDate }
// ref={register}
InputField name={`${fieldName}.dueDate`}
onChange={handleChange(index)}
//onChange={ handleChange }
>
<input
type="date"
name={`${fieldName}.dueDate`}
inputRef={register}
/>
</DatePicker>
</label>
<Button type="danger" style={{ marginBottom: '20px', float: 'right'}} onClick={removeMilestone(index)}>
Remove this Milestone
</Button>
</fieldset>
);
})}
<Button type="primary" style={{ marginBottom: '20px'}} onClick={addMilestone}>
Add a Milestone
</Button>
<br />
<Button type="button" style={{ marginBottom: '20px'}} onClick={clearMilestones}>
Clear Milestones
</Button>
<input type="submit" value="next - budget" />
</form>
</Content>
</div>
);
};
export default withRouter(Team);
This generates an error that says: TypeError: Cannot read property 'value' of undefined
setValue is defined in handleChange.
I'm not clear on what steps are outstanding to get this datepicker functioning. Do I need a separate select function?
Has anyone figured out how to plug this datepicker in?
I have also tried:
const handleChange = (e) => {
setValue("dueDate", e.target.Date);
}
and I have tried:
const handleChange = (e) => {
setValue("dueDate", e.target.date);
}
but each of these generations the same error
I have built a wrapper component to work with external controlled component easier:
https://github.com/react-hook-form/react-hook-form-input
import React from 'react';
import useForm from 'react-hook-form';
import { RHFInput } from 'react-hook-form-input';
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
function App() {
const { handleSubmit, register, setValue, reset } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<RHFInput
as={<Select options={options} />}
rules={{ required: true }}
name="reactSelect"
register={register}
setValue={setValue}
/>
<button
type="button"
onClick={() => {
reset({
reactSelect: '',
});
}}
>
Reset Form
</button>
<button>submit</button>
</form>
);
}
try this out, let me know if it makes your life easier with AntD.
/* eslint-disable react/prop-types */
import React, { useState } from 'react';
import { DatePicker } from 'antd';
import { Controller } from 'react-hook-form';
import color from '../../assets/theme/color';
import DatePickerContainer from './DatePickerContainer';
function DatePickerAntd(props) {
const { control, rules, required, title, ...childProps } = props;
const { name } = childProps;
const [focus, setFocus] = useState(false);
const style = {
backgroundColor: color.white,
borderColor: color.primary,
borderRadius: 5,
marginBottom: '1vh',
marginTop: '1vh',
};
let styleError;
if (!focus && props.error) {
styleError = { borderColor: color.red };
}
return (
<div>
<Controller
as={
<DatePicker
style={{ ...style, ...styleError }}
size="large"
format="DD-MM-YYYY"
placeholder={props.placeholder || ''}
onBlur={() => {
setFocus(false);
}}
onFocus={() => {
setFocus(true);
}}
name={name}
/>
}
name={name}
control={control}
rules={rules}
onChange={([selected]) => ({ value: selected })}
/>
</div>
);
}
export default DatePickerAntd;
my container parent use react-hooks-form
const { handleSubmit, control, errors, reset, getValues } = useForm({
mode: 'onChange',
validationSchema: schema,
});
<DatePickerAntd
name="deadline"
title={messages.deadline}
error={errors.deadline}
control={control}
required={isFieldRequired(schema, 'deadline')}
/>
like that, its working for me ;-)
Try this:
<DatePicker
selected={ dueDate }
// ref={register}
InputField name={`${fieldName}.dueDate`}
onChange={()=>handleChange(index)}
//onChange={ handleChange }
>
<input
type="date"
name={`${fieldName}.dueDate`}
inputRef={register}
/>
It looks like if you are using onChange={handleChange(index)} it does not pass a function instead you are passing an execution result of that function.
And if you are trying to access event inside handleChange, you should manually pass if from binding scope otherwise, it will be undefined.
onChange={()=>handleChange(index, event)}
When a user submits a form, I'm checking to see if a certain field's value has changed. If it has, I'm displaying a confirmation modal. If the user clicks "Yes" in the modal, the modal component sets the "dismissedSubTypeChangeWarning" prop to true in redux.
Inside componentWillReceiveProps I listen for any changes to this prop. If it's value is true, I want to trigger the form's submission. I followed instructions at https://redux-form.com/6.5.0/examples/remotesubmit/ to configure this.
The dispatch call never fires, because in the console I see "detected dismiss" then nothing else. I expect to see "submitForm method" after "detected dismiss".
I condensed the code (below) to the most basic version to show the issue. When I run my code I see no errors in the console.
I found a solution using refs (see commented line), but faking a click doesn't seem like a good practice.
class ChangeEditForm extends React.Component {
componentWillReceiveProps (nextProps) {
if (nextProps.dismissedSubTypeChangeWarning) {
console.log('detected dismiss')
this.props.toggleDismissedSubTypeChangeWarning()
this.props.dispatch(submit('changeEdit'))
// this.refs.submit.click()
}
}
render () {
<form onSubmit={this.props.handleSubmit(this.props.submitForm)}>
<button type='submit' ref='submit'>Save</button>
</form>
}
}
const handlers = {
submitForm: props => (formValues) => {
console.log('submitForm method')
if ((formValues.subtype !== props.initialValues.subtype) && !props.dismissedSubTypeChangeWarning) {
console.log('show warning')
props.toggleSubTypeChangeConfirmModal()
} else {
console.log('submitting form')
}
}
}
export function mapStateToProps (state, props) {
return {
dismissedSubTypeChangeWarning: state.ui.dismissedSubTypeChangeWarning
}
}
export default compose(
pure,
reduxForm({
form: 'changeEdit',
onSubmit: handlers.submitForm
}),
connect(mapStateToProps, null),
withRouter,
withHandlers(handlers)
)(ChangeEditForm)
I still think you're over complicating your form submission.
As mentioned in the comments, the form submits once, the values are checked once, a popup spawns if values are different, confirming in the popup updates values, otherwise canceling closes modal and leaves form as is.
By creating a HOC we can control the modal and the form without having to resubmit the form on popup confirmation.
Working example: https://codesandbox.io/s/lxv9o1nxzl
container/ChangeNameHOC.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { reset } from "redux-form";
import { updateUser } from "../actions";
import { Values } from "redux-form-website-template";
import ChangeNameForm from "./ChangeNameForm";
import ShowModal from "../components/ShowModal";
import CurrentStore from "../containers/CurrentStore";
class ChangeNameHOC extends Component {
state = {
modalIsOpen: false,
formProps: {}
};
openModal = () => this.setState({ modalIsOpen: true });
closeModal = () => this.setState({ modalIsOpen: false });
validateFormValues = formProps => {
const { firstName, lastName } = this.props;
const nextFirstName = formProps.firstName;
const nextLastName = formProps.lastName;
if (firstName !== nextFirstName || lastName !== nextLastName) {
this.setState({ modalIsOpen: true, formProps });
}
};
handleSubmit = () => {
this.setState({ modalIsOpen: false }, () => {
this.props.updateUser(this.state.formProps);
this.props.reset("ChangeNameForm");
});
};
render = () => (
<div style={{ padding: "0px 20px" }}>
<ShowModal
{...this.state}
{...this.props}
afterOpenModal={this.afterOpenModal}
openModal={this.openModal}
closeModal={this.closeModal}
handleSubmit={this.handleSubmit}
/>
<ChangeNameForm validateFormValues={this.validateFormValues} />
<CurrentStore />
<Values form="ChangeNameForm" />
</div>
);
}
export default connect(
state => ({
firstName: state.user.firstName,
lastName: state.user.lastName
}),
{ updateUser, reset }
)(ChangeNameHOC);
containers/ChangeNameForm.js
import React from "react";
import { Field, reduxForm } from "redux-form";
import RenderField from "../components/RenderField";
const isRequired = value => (!value ? "Required" : undefined);
const ChangeNameForm = ({
handleSubmit,
reset,
submitting,
validateFormValues
}) => (
<form onSubmit={handleSubmit(validateFormValues)}>
<Field
className="uk-input"
label="First Name"
name="firstName"
component={RenderField}
type="text"
placeholder="First Name"
validate={[isRequired]}
/>
<Field
className="uk-input"
label="Last Name"
name="lastName"
component={RenderField}
type="text"
placeholder="Last Name"
validate={[isRequired]}
/>
<button
className="uk-button uk-button-primary"
type="submit"
disabled={submitting}
style={{ marginBottom: 20 }}
>
Submit
</button>
<div style={{ float: "right" }}>
<button
className="uk-button uk-button-danger"
type="button"
disabled={submitting}
onClick={reset}
style={{ marginBottom: 20 }}
>
Reset
</button>
</div>
</form>
);
export default reduxForm({
form: "ChangeNameForm"
})(ChangeNameForm);
components/ShowModal.js
import React, { PureComponent } from "react";
import Modal from "react-modal";
const customStyles = {
content: {
minWidth: "400px",
top: "50%",
left: "50%",
right: "auto",
bottom: "auto",
marginRight: "-50%",
transform: "translate(-50%, -50%)"
}
};
Modal.setAppElement("#root");
export default class ShowModal extends PureComponent {
showChange = (name, prevName, nextName) => (
<div>
{name}: <strong>{prevName}</strong> to <strong>{nextName}</strong>
</div>
);
render = () => {
const { firstName, lastName } = this.props;
const { firstName: nextFirstName } = this.props.formProps;
const { lastName: nextLastName } = this.props.formProps;
return (
<div>
<Modal
isOpen={this.props.modalIsOpen}
onAfterOpen={this.props.afterOpenModal}
onRequestClose={this.props.closeModal}
style={customStyles}
contentLabel="Are you sure?"
>
<h3>Are you sure you want to update?</h3>
<div style={{ marginBottom: 20 }}>
{firstName !== nextFirstName
? this.showChange("FirstName", firstName, nextFirstName)
: null}
{lastName !== nextLastName
? this.showChange("LastName", lastName, nextLastName)
: null}
</div>
<button
style={{ float: "left" }}
className="uk-button uk-button-primary"
onClick={this.props.handleSubmit}
>
confirm
</button>
<button
style={{ float: "right" }}
className="uk-button uk-button-danger"
onClick={this.props.closeModal}
>
cancel
</button>
</Modal>
</div>
);
};
}