I'm having a tricky problem. I've created this ecommerce platform with react and I'm using the commerce JS API. I connected my sandbox stripe API key with the sandbox commerce JS key so I could do a test transaction, but each time I get an error and the transaction doesn't go through, leaving my cart quantity unchanged.
In console I get the following error from the commerce JS API:
According to the commerce JS developer setting logs this is the request I'm sending:
{
"line_items": [
{
"id": "item_7RyWOwmK5nEa2V",
"product_id": "prod_LvJjoPJGale0nO",
"name": "Ferrari",
"product_name": "Ferrari",
"media": {
"type": "image",
"source": "https://cdn.chec.io/merchants/28537/assets/o0wPb0EDWAlJnEV1|1.jpg"
},
"sku": null,
"permalink": "kKwz9a",
"quantity": 1,
"price": {
"raw": 1.5,
"formatted": "1.50",
"formatted_with_symbol": "£1.50",
"formatted_with_code": "1.50 GBP"
},
"line_total": {
"raw": 1.5,
"formatted": "1.50",
"formatted_with_symbol": "£1.50",
"formatted_with_code": "1.50 GBP"
},
"is_valid": true,
"product_meta": [],
"tax": {
"is_taxable": false,
"taxable_amount": null,
"amount": null,
"breakdown": null
},
"selected_options": [],
"variant": []
}
],
"customer": [],
"shipping": {
"name": "Primary",
"county_state": "WSM",
"country": "GB"
},
"fulfillment": {
"shipping_method": "ship_RqEv5xvDqwZz4j"
},
"payment": {
"gateway": "stripe",
"stripe": {
"payment_method_id": "pm_1J1FitHjVwDyRBqGNRnw8Ryt"
}
}
}
I don't understand how the email field, shipping address and shipping town/city are missing, as this is what my submission form looks like:
I filled in everything accurately.
Here is my AddressForm component:
import React, { useState, useEffect } from "react";
import {
InputLabel,
Select,
MenuItem,
Button,
Grid,
Typography,
} from "#material-ui/core";
import { useForm, FormProvider } from "react-hook-form";
import { Link } from "react-router-dom";
import { commerce } from "../../lib/commerce";
import FormInput from "./CustomTextField";
const AddressForm = ({ checkoutToken, next }) => {
const [shippingCountries, setShippingCountries] = useState([]);
const [shippingCountry, setShippingCountry] = useState("");
const [shippingSubdivisions, setShippingSubdivisions] = useState([]);
const [shippingSubdivision, setShippingSubdivision] = useState("");
const [shippingOptions, setShippingOptions] = useState([]);
const [shippingOption, setShippingOption] = useState("");
const methods = useForm();
const fetchShippingCountries = async (checkoutTokenId) => {
const { countries } = await commerce.services.localeListShippingCountries(
checkoutTokenId
);
setShippingCountries(countries);
setShippingCountry(Object.keys(countries)[0]);
};
const fetchSubdivisions = async (countryCode) => {
const { subdivisions } = await commerce.services.localeListSubdivisions(
countryCode
);
setShippingSubdivisions(subdivisions);
setShippingSubdivision(Object.keys(subdivisions)[0]);
};
const fetchShippingOptions = async (
checkoutTokenId,
country,
region = null
) => {
const options = await commerce.checkout.getShippingOptions(
checkoutTokenId,
{ country, region }
);
setShippingOptions(options);
setShippingOption(options[0].id);
};
useEffect(() => {
fetchShippingCountries(checkoutToken.id);
}, []);
useEffect(() => {
if (shippingCountry) fetchSubdivisions(shippingCountry);
}, [shippingCountry]);
useEffect(() => {
if (shippingSubdivision)
fetchShippingOptions(
checkoutToken.id,
shippingCountry,
shippingSubdivision
);
}, [shippingSubdivision]);
return (
<>
<Typography variant="h6" gutterBottom>
Shipping address
</Typography>
<FormProvider {...methods}>
<form
onSubmit={methods.handleSubmit((data) =>
next({
...data,
shippingCountry,
shippingSubdivision,
shippingOption,
})
)}
>
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<FormInput required name="firstName" label="First name" />{" "}
</Grid>
<Grid item xs={12} sm={6}>
{" "}
<FormInput required name="lastName" label="Last name" />
</Grid>
<Grid item xs={12} sm={6}>
{" "}
<FormInput required name="address1" label="Address line 1" />
</Grid>
<Grid item xs={12} sm={6}>
{" "}
<FormInput required name="email" label="Email" />
</Grid>
<Grid item xs={12} sm={6}>
{" "}
<FormInput required name="city" label="City" />
</Grid>
<Grid item xs={12} sm={6}>
{" "}
<FormInput required name="zip" label="Zip / Postal code" />
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Country</InputLabel>
<Select
value={shippingCountry}
fullWidth
onChange={(e) => setShippingCountry(e.target.value)}
>
{Object.entries(shippingCountries)
.map(([code, name]) => ({ id: code, label: name }))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Subdivision</InputLabel>
<Select
value={shippingSubdivision}
fullWidth
onChange={(e) => setShippingSubdivision(e.target.value)}
>
{Object.entries(shippingSubdivisions)
.map(([code, name]) => ({ id: code, label: name }))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Options</InputLabel>
<Select
value={shippingOption}
fullWidth
onChange={(e) => setShippingOption(e.target.value)}
>
{shippingOptions
.map((sO) => ({
id: sO.id,
label: `${sO.description} - (${sO.price.formatted_with_symbol})`,
}))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
</Grid>
<br />
<div style={{ display: "flex", justifyContent: "space-between" }}>
<Button component={Link} variant="outlined" to="/cart">
Back to Cart
</Button>
<Button type="submit" variant="contained" color="primary">
Next
</Button>
</div>
</form>
</FormProvider>
</>
);
};
export default AddressForm;
My payment form component:
import React from "react";
import { Typography, Button, Divider } from "#material-ui/core";
import {
Elements,
CardElement,
ElementsConsumer,
} from "#stripe/react-stripe-js";
import { loadStripe } from "#stripe/stripe-js";
import Review from "./Review";
const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLIC_KEY);
const PaymentForm = ({
checkoutToken,
nextStep,
backStep,
shippingData,
onCaptureCheckout,
}) => {
const handleSubmit = async (event, elements, stripe) => {
event.preventDefault();
if (!stripe || !elements) return;
const cardElement = elements.getElement(CardElement);
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: "card",
card: cardElement,
});
if (error) {
console.log("[error]", error);
} else {
const orderData = {
line_items: checkoutToken.live.line_items,
customer: {
firstname: shippingData.firstName,
lastname: shippingData.lastName,
email: shippingData.email,
},
shipping: {
name: "Primary",
street: shippingData.address1,
town_city: shippingData.city,
county_state: shippingData.shippingSubdivision,
postal_zip_code: shippingData.zip,
country: shippingData.shippingCountry,
},
fulfillment: { shipping_method: shippingData.shippingOption },
payment: {
gateway: "stripe",
stripe: {
payment_method_id: paymentMethod.id,
},
},
};
onCaptureCheckout(checkoutToken.id, orderData);
nextStep();
}
};
return (
<>
<Review checkoutToken={checkoutToken} />
<Divider />
<Typography variant="h6" gutterBottom style={{ margin: "20px 0" }}>
Payment method
</Typography>
<Elements stripe={stripePromise}>
<ElementsConsumer>
{({ elements, stripe }) => (
<form onSubmit={(e) => handleSubmit(e, elements, stripe)}>
<CardElement />
<br /> <br />
<div style={{ display: "flex", justifyContent: "space-between" }}>
<Button variant="outlined" onClick={backStep}>
Back
</Button>
<Button
type="submit"
variant="contained"
disabled={!stripe}
color="primary"
>
Pay {checkoutToken.live.subtotal.formatted_with_symbol}
</Button>
</div>
</form>
)}
</ElementsConsumer>
</Elements>
</>
);
};
export default PaymentForm;
And also my APP JS:
import React, { useState, useEffect } from "react";
import { Products, Navbar, Cart, Checkout } from "./components";
import { commerce } from "./lib/commerce";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
const App = () => {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState({});
const [order, setOrder] = useState({});
const [errorMessage, setErrorMessage] = useState("");
const fetchProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data);
};
const fetchCart = async () => {
setCart(await commerce.cart.retrieve());
};
const handleAddToCart = async (productId, quantity) => {
const { cart } = await commerce.cart.add(productId, quantity);
setCart(cart);
};
const handleUpdateCartQty = async (productId, quantity) => {
const { cart } = await commerce.cart.update(productId, { quantity });
setCart(cart);
};
const handleRemoveFromCart = async (productId) => {
const { cart } = await commerce.cart.remove(productId);
setCart(cart);
};
const handleEmptyCart = async () => {
const { cart } = await commerce.cart.empty();
setCart(cart);
};
const refreshCart = async () => {
const newCart = await commerce.cart.refresh();
console.log(newCart);
setCart(newCart);
};
const handleCaptureCheckout = async (checkoutTokenId, newOrder) => {
try {
const incomingOrder = await commerce.checkout.capture(
checkoutTokenId,
newOrder
);
setOrder(incomingOrder);
refreshCart();
} catch (error) {
setErrorMessage(error.data.error.message);
}
};
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
return (
<Router>
<div>
<Navbar totalItems={cart.total_items} />
<Switch>
<Route exact path="/">
<Products products={products} onAddToCart={handleAddToCart} />
</Route>
<Route exact path="/cart">
<Cart
cart={cart}
handleUpdateCartQty={handleUpdateCartQty}
handleRemoveFromCart={handleRemoveFromCart}
handleEmptyCart={handleEmptyCart}
/>
</Route>
<Route exact path="/checkout">
<Checkout
cart={cart}
order={order}
onCaptureCheckout={handleCaptureCheckout}
error={errorMessage}
/>
</Route>
</Switch>
</div>
</Router>
);
};
export default App;
I spoke to some people on the Commerce JS slack and they pointed out that these fields were 'missing' but I don't understand what they mean by that. I've read that others have had this issue too (specifically people following this tutorial: https://www.youtube.com/watch?v=377AQ0y6LPA) so I'm not sure if it's a problem with the code or the API.
I'm at a loss here so I'd really appreciate any help or suggestions.
Thanks
Console log ur shipping data and check if the names of the objects are the same
for example if : shippingData has the email stored in Email instead of email.So Payment Form cannot recognize the information
in your FormInput component check that you use render instead of as props and try to spread param of callback inside ex:
<Controller control={control}
name={name}
render={({field})=>(<TextField
label={label}
{...field}
required={required}
fullWidth
value={field.value}
/>)}
/>
Related
I Working with hook-form and MaterialUI, I want to make the validation of the form, the validation working well in adding, but on update (input are ready full) in shows me erros (inputs are empty):
my code:
import Box from "#mui/material/Box";
import Button from "#mui/material/Button";
import Grid from "#mui/material/Grid";
import TextField from "#mui/material/TextField";
import axios from "axios";
import "bootstrap/dist/css/bootstrap.min.css";
import React from "react";
import { useForm } from "react-hook-form";
import { useNavigate, useParams } from "react-router-dom";
function UpdateEmployer(props) {
const { id } = useParams();
const { register, handleSubmit, formState, control } = useForm({
mode: "onChange",
});
// declare states
const [FullName, setFullName] = React.useState("");
const [cin, setCin] = React.useState("");
const [profile, setProfile] = React.useState("");
const [years, setYears] = React.useState("");
const { isSubmitting, errors, isValid } = formState;
const navigate = useNavigate();
// get data from form and post to api
const onSubmit = async (data) => {
// set state from data
const { FullName, cin, profile, years } = data;
console.log(data);
await axios.put(`http://localhost:5000/employers/${id}`, {
fullname: FullName,
cin: cin,
profile: profile,
experience: years,
});
navigate("/");
};
React.useEffect(() => {
axios.get(`http://localhost:5000/employers/${id}`).then((res) => {
setFullName(res.data.fullname);
setCin(res.data.cin);
setProfile(res.data.profile);
setYears(res.data.experience);
});
}, []);
// -------------------- render componenet --------------------
return (
<div className="container mt-4">
<div className="text-center my-3 font-weight-bold">Update Employer</div>
<form onSubmit={handleSubmit(onSubmit)} style={{ width: "100%" }}>
<Grid container spacing={2}>
<Grid item xs={8}>
<TextField
id="FullName"
name="FullName"
label="Full Name"
variant="outlined"
fullWidth
value={FullName}
{...register("FullName", { required: "Please Entre your name" })}
onChange={(e) => setFullName(e.target.value)}
/>
<span className="text-danger">
{errors.FullName && errors.FullName.message}
</span>
</Grid>
<Grid item xs={4}>
<TextField
id="cin"
name="cin"
label="CIN"
variant="outlined"
value={cin}
fullWidth
{...register(
"cin",
{ required: "Please Entre your cin" },
{
minLength: {
value: 6,
message: "Oops! the cin have to ve at least 6 characters",
},
}
)}
onChange={(e) => {
console.log(e.target.value);
setCin(e.target.value);
}}
/>
<span className="text-danger">
{errors.cin && errors.cin.message}
</span>
</Grid>
<Grid item xs={8}>
<TextField
id="profile"
name="profile"
label="Profile"
variant="outlined"
fullWidth
value={profile}
{...register("profile", {
required: "Please Entre your profile",
})}
onChange={(e) => setProfile(e.target.value)}
/>
</Grid>
<Grid item xs={4}>
<TextField
id="years"
name="years"
label="Years of Experience"
variant="outlined"
value={years}
fullWidth
{...register("years", { required: "Please Entre your years" })}
onChange={(e) => setYears(e.target.value)}
/>
</Grid>
<Grid item xs={12}>
<Box textAlign="center">
<Button
id="Update_employer"
variant="contained"
color="primary"
type="submit"
// disabled={!isValid}
>
Update Employee
</Button>
</Box>
</Grid>
</Grid>
</form>
</div>
);
}
export default UpdateEmployer;
The fill he inputs in with useEffect() and it shows me that the input are full with the right information but when I want to submit it shows me that error that the input are empty.
I'm brand new to Material UI. I am currently trying to implement it into my project. Unfortunately, it's not cooperating. I think I am putting in everything right, but because I have never used MUI before, I am not sure. Can someone please help me figure out where the error is? I have tried multiple times, putting in the code I had before I tried to implement MUI. Thank you!
I will include the code before I tried to implement MUI, and after. The first is before MUI implementation. The last block of code is my Auth.jsx file.
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
export default function Login({ updateToken, toggleView }) {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleUsername = (e) => setUsername(e.target.value);
const handlePassword = (e) => setPassword(e.target.value);
const history = useHistory();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const fetchResults = await fetch(`http://localhost:3001/user/`, {
method: "POST",
body: JSON.stringify({ username, password }),
headers: new Headers({
"Content-Type": "application/json",
}),
});
const json = await fetchResults.json();
if (!json.user || !json.sessionToken) {
alert(json.message);
return;
}
updateToken(json.sessionToken);
history.push("/home");
} catch (err) {
console.error(err);
}
};
return (
<div>
<div className='signin'>
<form onSubmit={handleSubmit}>
<input
type='username'
onChange={handleUsername}
name='username'
placeholder='Username'
required
value={username}
className='signin__input'
/>
<input
type='password'
onChange={handlePassword}
name='password'
placeholder='Password'
type='password'
required
value={password}
className='signin__input'
/>
<button className='login__button' type='submit'>
Login
</button>
</form>{" "}
<p className='signin__toggle' onClick={() => history.push("./signup")}>
Don't have an account? Sign up here.{" "}
</p>
</div>
</div>
);
}
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import Avatar from "#mui/material/Avatar";
import Button from "#mui/material/Button";
import CssBaseline from "#mui/material/CssBaseline";
import TextField from "#mui/material/TextField";
import FormControlLabel from "#mui/material/FormControlLabel";
import Checkbox from "#mui/material/Checkbox";
import Link from "#mui/material/Link";
import Grid from "#mui/material/Grid";
import Box from "#mui/material/Box";
import LockOutlinedIcon from "#mui/icons-material/LockOutlined";
import Typography from "#mui/material/Typography";
import Container from "#mui/material/Container";
import { createTheme, ThemeProvider } from "#mui/material/styles";
function Copyright(props) {
return (
<Typography
variant='body2'
color='text.secondary'
align='center'
{...props}
>
{"Copyright © "}
<Link color='inherit' href='https://courtney-downs.web.app/'>
Courtney Downs Portfolio
</Link>{" "}
{new Date().getFullYear()}
{"."}
</Typography>
);
}
const theme = createTheme();
export default function Login2({ updateToken, toggleView }) {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleUsername = (e) => setUsername(e.target.value);
const handlePassword = (e) => setPassword(e.target.value);
const history = useHistory();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const fetchResults = await fetch(`http://localhost:3001/user/`, {
method: "POST",
body: JSON.stringify({ username, password }),
headers: new Headers({
"Content-Type": "application/json",
}),
});
const json = await fetchResults.json();
if (!json.user || !json.sessionToken) {
alert(json.message);
return;
}
updateToken(json.sessionToken);
history.push("/home");
} catch (err) {
console.error(err);
}
};
return (
<ThemeProvider theme={theme}>
<Container component='main' maxWidth='xs'>
<CssBaseline />
<Box
sx={{
marginTop: 8,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Avatar sx={{ m: 1, bgcolor: "secondary.main" }}>
<LockOutlinedIcon />
</Avatar>
<Typography component='h1' variant='h5'>
Sign in
</Typography>
<Box
component='form'
onSubmit={handleSubmit}
noValidate
sx={{ mt: 1 }}
>
<TextField
margin='normal'
required
fullWidth
id='username'
label='Username'
name='username'
autoComplete='username'
value={username}
onChange={handleUsername}
autoFocus
/>
<TextField
margin='normal'
required
fullWidth
name='password'
label='Password'
type='password'
id='password'
value={password}
onChange={handlePassword}
autoComplete='current-password'
/>
<FormControlLabel
control={<Checkbox value='remember' color='primary' />}
label='Remember me'
/>
<Button
type='submit'
fullWidth
variant='contained'
sx={{ mt: 3, mb: 2 }}
>
Sign In
</Button>
<Grid container>
<Grid item xs>
<Link href='#' variant='body2'>
Forgot password?
</Link>
</Grid>
<Grid item>
<Link onClick={toggleView} variant='body2'>
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</Box>
</Box>
<Copyright sx={{ mt: 8, mb: 4 }} />
</Container>
</ThemeProvider>
);
}
import React, { useState } from "react";
import Login2 from "./Login2";
import Signup from "./Signup";
export default function Auth({ sessionToken, updateToken }) {
const [loginShowing, setLoginShowing] = useState(true);
const toggleView = () => setLoginShowing(!loginShowing);
return (
<div>
{loginShowing ? (
<Login2
token={sessionToken}
updateToken={updateToken}
toggleView={toggleView}
/>
) : (
<Signup
updateToken={updateToken}
token={sessionToken}
toggleView={toggleView}
/>
)}
</div>
);
}
Hello I am currently fetching through an API call to search by name in my search bar. I'm having an error of whenever I type a string in my search bar.
This is my postman URL /product/list/search?search=assorted where I can fetch names with "assorted"
I'm fetching it in frontend like this
export const searchProducts = async (query) => {
return await get(`product/list/search=?${query})
}
and this is how I implement it on my search page
const SearchPage = () => {
const [products, setProducts] = useState([])
const [query, setQuery] = useState("")
useEffect(() => {
getAllProducts().then((products) => {
setProducts(products)
})
}, [])
useEffect(() => {
const loadProducts = async () => {
const query = await searchProducts(query)
setQuery(query)
}
loadProducts()
}, [query])
return (
<>
<Grid container spacing={3}>
<Grid item xs={1}>
<Box pt={1.5}>
<Link to="#">
<ArrowBackIcon className={classes.backSize} />
</Link>
</Box>
</Grid>
<Grid item xs={11}>
<Paper className={classes.root}>
<IconButton aria-label="menu"></IconButton>
<InputBase
className={classes.input}
placeholder="Search for foods"
onChange={(e) => setQuery(e.target.value)}
value={query}
/>
<IconButton aria-label="search">
<SearchIcon />
</IconButton>
</Paper>
</Grid>
</Grid>
<Box pt={1}>
<CategoryList categories={categories} />
</Box>
<Box pt={1}>
<ProductList products={products} />
</Box>
</>
)
}
Did you try with / at the beginning?
export const searchProducts = async (query) => {
return await get(`/product/list/search?search=${query}`)
}
From what i can see your postman api call and the one in the await doesn't seems to be matching - it should be
await get(`product/list/search?search=${query}`);
I wasn't able to get the values from my nested forms using final form.
here is my index.tsx
import React, { useState, ChangeEvent } from 'react';
import { Form } from 'react-final-form';
import {
Box, Typography, Button, IconButton, Grid, FormControl, InputLabel, Select,
} from '#material-ui/core';
import AddCircleIcon from '#material-ui/icons/AddCircle';
import FieldInput from 'forms/shared/fields/input';
import FieldFileUploader from 'forms/shared/fields/file-uploader';
import FieldCheckbox from 'forms/shared/fields/checkbox';
import FormRadioQuestions from './partials/form-radio';
import FormExperience from './partials/form-experience';
import FormEducation from './partials/form-education';
import FormPersonalInfo from './partials/form-personal-info';
import FormGovernmentIds from './partials/form-government-id';
import ItemExperience from './partials/experience-item';
import ItemEducation from './partials/education-item';
import useStyles from './styles';
const PublicApplicationForm = () => {
const [state, setState] = useState<{ client: string | number; name: string }>({ client: '', name: 'hai' });
const [showExp, setOpenExp] = useState(false);
const [showEdu, setOpenEdu] = useState(false);
const [data, setData] = useState({
experience: [{}],
});
const [educations, setEducations] = useState([]);
const [experiences, setExperiences] = useState([]);
const classes = useStyles();
const radioValues = [{ value: 'yes', label: 'Yes' }, { value: 'no', label: 'No' }];
const variables = { title: 'How did you hear about us?*', typeOfService: 'Type of Service*' };
const relationOptions = ['Walk-In', 'Employee Referral', 'Job Boards', 'Job Fair', 'Social Media'];
const checkBoxLabel = 'Please be informed that your application to this job offer will trigger some processing of your personal data by the company. For more information on data processing, please refer to the company’s talent acquisition privacy policy.';
const handleChange = ({ target }: ChangeEvent<{ name?: string; value: unknown }>) => {
setState({ ...state, [target.name as keyof typeof state]: target.value });
};
const handleBlur = (event: any) => {
data.experience[0] = { ...data.experience[0], [event.target.name]: event.target.value };
setData({ ...data });
};
const onAddEdu = (edu: any) => { setEducations([...educations, edu]); setOpenEdu(false); };
const onAddExp = (exp: any) => { setExperiences([...experiences, exp]); setOpenExp(false); };
return (
<Grid className={classes.pageContainer} container>
<Grid className={classes.formContainer} item xs={12}>
<Form
onSubmit={(values) => { console.log(values); }} // eslint-disable-line no-console
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Typography className={classes.formHeading} variant="h5">Personal Information</Typography>
<Box className={classes.marginedBottom}>
<FieldFileUploader
required
fileType="avatars"
accept={['image/*']}
name="resume"
/>
</Box>
<FormPersonalInfo setData={setData} data={data} />
<Box className={classes.spaced}>
<Box className={classes.fieldContainer}>
<Typography className={classes.noMarginBottom} variant="h6">Experience</Typography>
<IconButton color="primary" onClick={() => { setOpenExp(!showExp); }}><AddCircleIcon /></IconButton>
</Box>
{
showExp && (
<FormExperience
onCancel={() => setOpenExp(false)}
onSave={onAddExp}
handleBlur={handleBlur}
/>
)
}
{experiences.map((exp, index) => <ItemExperience key={index} exp={exp} />)}
</Box>
<Box className={classes.spaced}>
<Box className={classes.fieldContainer}>
<Typography className={classes.noMarginBottom} variant="h6">Education</Typography>
<IconButton color="primary" onClick={() => { setOpenEdu(!showEdu); }}><AddCircleIcon /></IconButton>
</Box>
{
showEdu && (
<FormEducation
onCancel={() => setOpenEdu(false)}
onSave={onAddEdu}
setData={setData}
data={data}
/>
)
}
{educations.map((edu, index) => <ItemEducation key={index} edu={edu} />)}
</Box>
<Typography className={classes.formText} variant="h6">On the web</Typography>
<Box className={classes.fieldContainer}>
<FieldInput className={classes.textField} type="text" required name="applicant-linkedin" label="Linkedin" onChange={(event: React.ChangeEvent<HTMLInputElement>) => setData({ ...data, [event.target.name]: event.target.value })} />
</Box>
<Box className={`${classes.fieldContainer} ${classes.marginedBottom}`}>
<FieldInput className={classes.textField} type="text" required name="applicant-facebook" label="Facebook" onChange={(event: React.ChangeEvent<HTMLInputElement>) => setData({ ...data, [event.target.name]: event.target.value })} />
</Box>
<Typography className={classes.formText} variant="h6">Resume</Typography>
<Box className={`${classes.fieldContainer} ${classes.marginedBottom}`}>
<Box className={classes.dropZone}>
<FieldFileUploader
required
fileType="resumes"
fieldLabel="Click or Drag file here to upload resume"
accept={['application/pdf']}
name="resume"
/>
</Box>
</Box>
<Typography className={classes.formText} variant="h6">Check the box of the ones you don't have.</Typography>
<Box className={classes.marginedBottom}>
<FormGovernmentIds setData={setData} data={data} />
</Box>
<Typography className={classes.formText} variant="h6">Preliminary Questions</Typography>
<Box className={`${classes.fieldContainer} ${classes.marginedBottom}`}>
<FormControl variant="outlined" className={classes.textField}>
<InputLabel htmlFor="outlined-age-native-simple">{variables.title}</InputLabel>
<Select
native
value={state.client}
onChange={handleChange}
label="How did you hear about us?"
inputProps={{ name: 'client', id: 'outlined-age-native-simple' }}
onClick={
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => setData({
...data,
[event.target.name]: event.target.value,
})
}
>
<option aria-label="None" value="" />
{relationOptions.map((item, index) => (
<option key={index} value={item.replace(' ', '-').toLowerCase()}>{item}</option>))}
</Select>
</FormControl>
</Box>
<FormRadioQuestions
fieldClassName={classes.fieldContainer}
textClassName={classes.textField}
values={radioValues}
setData={setData}
data={data}
/>
<Box className={classes.fieldContainer}>
<FieldCheckbox className={classes.textField} name="confirm-registration" label={checkBoxLabel} />
</Box>
<Box className={classes.fieldContainer}>
<Button component="button" type="submit" className={classes.spaced} variant="contained" color="primary">
Submit
</Button>
</Box>
</form>
)}
/>
</Grid>
</Grid>
);
};
export default PublicApplicationForm;
The FormPersonalInfo component consists of fields such as applicant-first-name, last name, email, location, mobile, etc.
When I click on the AddCircleIcon button to show some other fields and to add experiences/education, I got this error.
When I click the submit button I only got this log.
This is the Experience forms same with Education forms
My goal is to add experince and education array to the result set just like this.
The error is clear, there are <form> tags nested.
Quick example to reproduce error
const { useState, useEffect } = React;
const App = () => {
return <form>
<form></form>
</form>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
In your case showExp evaluates to true after click action, then FormExperience component, which apparently has form tag in it, renders and validateDOMNesting throws error.
showExp && (
<FormExperience
onCancel={() => setOpenExp(false)}
onSave={onAddExp}
handleBlur={handleBlur}
/>)
I had a similar case before I managed to solve it using Material UI Portals
import React from "react";
import Portal from "#material-ui/core/Portal";
export default NestedForms = () => {
const container = React.useRef(null);
return (
<>
<form>
{/*form content */}
<div ref={container} />
{/*form content */}
</form>
<Portal container={container.current}>
<form>
{/* otherform */}
<form/>
</Portal>
</>
)
}
Hello I am having trouble finding where I can call a route with push after authenticating the user
I did it using redux
my action:
const AuthenticateUser = (login, password) => {
return dispatch => {
dispatch (startAuth ());
// fetching data
api.post ('/ login', {login, password})
.then (response => {
localStorage.setItem ('token', response.data [0]);
dispatch (userAuthenticated (response.data [1]))
})
.catch (err => {console.error (err);});
}
}
export default AuthenticateUser;
my reducer:
const authReducer = (state = initialState, action) => {
switch (action.type) {
case USER_AUTHENTICATED:
return {
... state,
loading: false,
authenticated: true,
user: action.user,
}
case USER_FAILED_AUTH:
return {
... state,
loading: false,
message: action.error
}
default:
return state;
}
}
and my form
const SignIn = (props) => {
const classes = useStyles();
const dispatch = useDispatch();
const [login, setLogin] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault()
dispatch(auth(login, password))
}
return (
<div className={classes.root}>
<Grid container spacing={2} className={classes.gridMain}>
<Grid item lg={12} md={12} sm={12} xs={12} align="center">
<img src={require("../nodejs-icon.svg")} alt="bug" height={100} />
</Grid>
<Grid item lg={12} md={12} sm={12} xs={12} className={classes.TextField}>
<form onSubmit={handleSubmit}>
<TextField
className={classes.input2}
id="demo2"
label="Usuário"
variant="outlined"
value={login}
onChange={(e) => setLogin(e.target.value)}
InputLabelProps={{
classes: {
root: classes.label,
focused: classes.focusedLabel,
error: classes.erroredLabel
}
}}
InputProps={{
classes: {
root: classes.cssOutlinedInput,
focused: classes.cssFocused,
notchedOutline: classes.notchedOutline,
},
startAdornment: (
<InputAdornment position="start">
<PersonSharpIcon style={{ fontSize: 25, color: 'rgba(20, 176, 12,0.9)' }} />
</InputAdornment>
)
}}
/>
<TextField
className={classes.txtFd}
id="demo2"
label="Senha"
variant="outlined"
value={password}
onChange={(e) => setPassword(e.target.value)}
InputLabelProps={{
classes: {
root: classes.label,
focused: classes.focusedLabel,
error: classes.erroredLabel
}
}}
InputProps={{
classes: {
root: classes.cssOutlinedInput,
focused: classes.cssFocused,
notchedOutline: classes.notchedOutline,
},
startAdornment: (
<InputAdornment position="start">
<LockSharpIcon style={{ fontSize: 25, color: 'rgba(20, 176, 12,0.9)' }} />
</InputAdornment>
)
}}
/>
<ButtonBase variant="raised" disableFocusRipple="false" disableRipple="false" centerRipple="false">
<Typography noWrap className={classes.labelForgot} variant="subtitle2">
Esqueci minha senha
</Typography>
</ButtonBase>
<Button type="submit" className={classes.button} variant="raised" disableFocusRipple="false" disableRipple="false" centerRipple="false">
Entrar
</Button>
</form>
</Grid>
I have a route and after I authenticate this user I wanted to send it to a route or display an error msg, but I don't know where in the code to do it and how I would get it.
my route
const AppRouter = () => (
<BrowserRouter>
<Route path="/xd" component={AuthPage} exact={true} />
<Route path="/dashboard/addProduct" component={AddProduct} exact={true} />
</BrowserRouter>
);
So this is in order to give you an answer that is readable instead of using the comments.
In your SignIn component:
import { withRouter } from "react-router-dom";
const SignIn = (props) => {
const { history } = props;
const { push } = history;
// more variables
const handleSubmit = (e) => {
e.preventDefault()
// You pass the push function over here.
dispatch(auth(login, password, push))
}
// Rest of the logic
}
export default withRouter(SignIn);
The withRouter would give you all the props that are coming from react-router, this will allow you to use the history object, which contains the push function, to redirect the user to the page you want.
Then in your authenticate function which I'm guessing is the ´AuthenticateUser´ function you could do this:
const AuthenticateUser = (login, password, push) => {
return dispatch => {
dispatch (startAuth ());
// fetching data
api.post ('/ login', {login, password})
.then (response => {
localStorage.setItem ('token', response.data [0]);
dispatch (userAuthenticated (response.data [1]))
// push goes here
push("/your-awesome-route")
})
.catch (err => {console.error (err);});
}
}
export default AuthenticateUser;
You may use useEffect in SignIn component, which checks for flag authenticated: true (where this variable is passed via props) and does the redirection.
Something like that
useEffect(() => {
const { authenticated, navigation } = props;
if (authenticated) {
navigation.navigate(Routes.GoodPlace);
}
});