Can't update the state in my react class component - javascript

I have 2 components:
Form
EmailForm (which extends Form)
I don't understand why my state "errors" is not updated whenI submit the form button?
When I check in the component react tool in chrome dev tools nothing happens.
Do you know what I didn't do correctly in the handleSubmit and validate function?
import React from 'react';
import Form from './form';
class EmailForm extends Form {
state = {
data: { email: '' },
errors: {}
};
render() {
return (
<div>
<p>
<i>Renseignez votre email pour être averti dès que l'application sera disponible :</i>
</p>
<form onSubmit={this.handleSubmit}>
{this.renderInput('email', 'Email')}
{this.renderButton('Envoyer')}
</form>
</div>
);
}
}
export default EmailForm;
import React, { Component } from 'react';
import { ButtonPrimary } from './buttonPrimary';
import { ButtonTransparent } from './buttonTransparent';
import Input from './input2';
interface IFormState {
data: any;
errors: any;
}
class Form extends React.PureComponent<{}, IFormState> {
state = {
data: {},
errors: {}
};
validate = (): any => {
return { email: 'Email is required' };
};
validateProperty = (input: any) => {
console.log('validated Property');
};
handleSubmit = (e: any) => {
e.preventDefault();
const errors: any = this.validate();
this.setState({ errors });
if (errors) return;
this.doSubmit();
};
doSubmit = () => {
console.log('Submitted');
};
handleChange = ({ currentTarget: input }: any) => {
const errors: any = { ...this.state.errors };
const errorMessage: any = this.validateProperty(input);
if (errorMessage) errors[input.name] = errorMessage;
else delete errors[input.name];
const data: any = { ...this.state.data };
data[input.name] = input.value;
this.setState({ data, errors });
};
renderButton(label: string) {
return <ButtonPrimary disabled={!this.validate()}>{label}</ButtonPrimary>;
}
renderInput(name: string, label: string, type = 'email') {
const { data, errors }: any = this.state;
return (
<Input
autoFocus
type={type}
name={name}
value={data[name]}
label={label}
onChange={this.handleChange}
error={errors[name]}
/>
);
}
}
export default Form;

Related

Sending multipart Form in ReactJS vs Postman

I've created a registration api that works fine using postman, I added some values there plus image an everything stores correctly, data an image, Later I started my React form and only text works, image is not currently sent to the api I guess.
Doing console.log() api in Node:
console.log(req.files);
with React Form: []
with Postman: an array of object that perfectly works
req.files output using postman
{
fieldname: 'images',
originalname: 'Screen Shot 2021-02-22 at 17.18.41.png',
encoding: '7bit',
mimetype: 'image/png',
destination: 'uploads/',
filename: '091f77f82fb805b1ede9f23205cc578e',
path: 'uploads/091f77f82fb805b1ede9f23205cc578e',
size: 37052
}
Here's my react classes:
httpService.js
import axios from "axios";
import { toast } from "react-toastify";
axios.interceptors.response.use(null, (error) => {
const expectedError =
error.response &&
error.response.status >= 400 &&
error.response.status < 500;
if (!expectedError) {
console.log("Loggind the error: ", error);
toast("An unexpected error ocurred.");
}
return Promise.reject(error);
});
export default {
post: axios.post
};
itemService.js
export function saveItem(item) {
const headers = {
'Content-Type': 'multipart/form-data',
};
return http.post(MY_ENDPOINT, item, headers);
}
itemForm.js
import React from "react";
import Joi from "joi-browser";
import Form from "./common/form";
import { saveItem } from "../services/itemService";
class ItemForm extends Form {
state = {
data: { title: "", description: "", category: "", images: "" },
categories: [],
errors: {},
};
schema = {
_id: Joi.string(),
title: Joi.string().required().label("Title"),
description: Joi.string().required().label("Description"),
category: Joi.string().required().label("Category"),
images: Joi.required().label("Image"),
};
doSubmit = async () => {
console.log('form data> ', this.state.data); // This shows the correct object.
let formData = new FormData();
formData.append('title', this.state.data.title);
formData.append('description', this.state.data.description);
formData.append('category', this.state.data.category);
formData.append('images', this.state.data.images);
try {
await saveItem(formData);
} catch (ex) {
}
};
render() {
return (
<div>
<h1>New item</h1>
<form onSubmit={this.handleSubmit}>
{this.renderInput("title", "Title")}
{this.renderInput("description", "Description")}
{this.renderSelect(
"category",
"title",
"Category",
this.state.categories
)}
{this.renderInputFile("images", "images", "file", false)}
{this.renderButton("Register")}
</form>
</div>
);
}
}
export default ItemForm;
form.jsx (The extended class)
import React, { Component } from "react";
import Joi from "joi-browser";
import Input from "./input";
import Select from "./select";
class Form extends Component {
state = {
data: {},
errors: {},
};
validate = () => {
const options = { abortEarly: false };
const { error } = Joi.validate(this.state.data, this.schema, options);
if (!error) return null;
const errors = {};
for (let item of error.details) errors[item.path[0]] = item.message;
return errors;
};
validateProperty = ({ name, value }) => {
const obj = { [name]: value };
const schema = { [name]: this.schema[name] };
const { error } = Joi.validate(obj, schema);
return error ? error.details[0].message : null;
};
handleSubmit = (e) => {
e.preventDefault();
const errors = this.validate();
this.setState({ errors: errors || {} });
if (errors) return;
this.doSubmit();
};
handleChange = ({ currentTarget: input }) => {
const errors = { ...this.state.errors };
const errorMessage = this.validateProperty(input);
if (errorMessage) errors[input.name] = errorMessage;
else delete errors[input.name];
const data = { ...this.state.data };
data[input.name] = input.value;
this.setState({ data, errors });
};
handleInputFileChange = ({ currentTarget: input }) => {
const errors = { ...this.state.errors };
const errorMessage = this.validateProperty(input);
if (errorMessage) errors[input.name] = errorMessage;
else delete errors[input.name];
const data = { ...this.state.data };
data[input.name] = input.value;
this.setState({ data, errors });
};
renderButton(label) {
return (
<button className="btn btn-primary" disabled={this.validate()}>
{label}
</button>
);
}
renderInput(name, label, type = "text", multiple = false) {
const { data, errors } = this.state;
return (
<Input
type={type}
name={name}
value={data[name]}
label={label}
onChange={this.handleChange}
error={errors[name]}
multiple={multiple}
/>
);
}
renderSelect(name, contentField, label, options) {
const { data, errors } = this.state;
return (
<Select
name={name}
value={data[name]}
label={label}
contentField={contentField}
options={options}
onChange={this.handleChange}
error={errors[name]}
/>
);
}
renderInputFile(name, label, type = "text", multiple = false) {
const { data, errors } = this.state;
return (
<Input
type={type}
name={name}
value={data[name]}
label={label}
onChange={this.handleInputFileChange}
error={errors[name]}
multiple={multiple}
accept="image/*"
/>
);
}
}
export default Form;

Redirect after posting to another page

I am working on a react coding template where after clicking on submit, it shows me a successful message and stays on the same page. I would like to add a redirect to the submit button if successful without changing the design. My ./AskPage component uses interfaces from ./Form. How do I add a redirect to the ./AskPage once I click on submit? Below is my code
./Form code is:
import { FC, useState, createContext, FormEvent } from 'react';
import { PrimaryButton, gray5, gray6 } from './Styles';
/** #jsx jsx */
import { css, jsx } from '#emotion/core';
export interface Values {
[key: string]: any;
}
export interface Errors {
[key: string]: string[];
}
export interface Touched {
[key: string]: boolean;
}
/* export interface onSubmit {
[key: string]: any
} */
interface FormContextProps {
values: Values;
setValue?: (fieldName: string, value: any) => void;
errors: Errors;
validate?: (fieldName: string) => void;
touched: Touched;
setTouched?: (fieldName: string) => void;
}
export const FormContext = createContext<FormContextProps>({
values: {},
errors: {},
touched: {},
});
type Validator = (value: any, args?: any) => string;
export const required: Validator = (value: any): string =>
value === undefined || value === null || value === ''
? 'This must be populated'
: '';
export const minLength: Validator = (value: any, length: number): string =>
value && value.length < length
? `This must be at least ${length} characters`
: '';
interface Validation {
validator: Validator;
arg?: any;
}
interface ValidationProp {
[key: string]: Validation | Validation[];
}
export interface SubmitResult {
success: boolean;
errors?: Errors;
}
interface Props {
submitCaption?: string;
validationRules?: ValidationProp;
onSubmit: (values: Values) => Promise<SubmitResult>;
successMessage?: string;
failureMessage?: string;
}
export const Form: FC<Props> = ({
submitCaption,
children,
validationRules,
onSubmit,
successMessage = 'Success!',
failureMessage = 'Something went wrong',
}) => {
const [values, setValues] = useState<Values>({});
const [errors, setErrors] = useState<Errors>({});
const [touched, setTouched] = useState<Touched>({});
const [submitting, setSubmitting] = useState(false);
const [submitted, setSubmitted] = useState(false);
const [submitError, setSubmitError] = useState(false);
const validate = (fieldName: string): string[] => {
if (!validationRules) {
return [];
}
if (!validationRules[fieldName]) {
return [];
}
const rules = Array.isArray(validationRules[fieldName])
? (validationRules[fieldName] as Validation[])
: ([validationRules[fieldName]] as Validation[]);
const fieldErrors: string[] = [];
rules.forEach(rule => {
const error = rule.validator(values[fieldName], rule.arg);
if (error) {
fieldErrors.push(error);
}
});
const newErrors = { ...errors, [fieldName]: fieldErrors };
setErrors(newErrors);
return fieldErrors;
};
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (validateForm()) {
setSubmitting(true);
setSubmitError(false);
const result = await onSubmit(values);
setErrors(result.errors || {});
setSubmitError(!result.success);
setSubmitting(false);
setSubmitted(true);
}
};
const validateForm = () => {
const newErrors: Errors = {};
let haveError: boolean = false;
if (validationRules) {
Object.keys(validationRules).forEach(fieldName => {
newErrors[fieldName] = validate(fieldName);
if (newErrors[fieldName].length > 0) {
haveError = true;
}
});
}
setErrors(newErrors);
return !haveError;
};
return (
<FormContext.Provider
value={{
values,
setValue: (fieldName: string, value: any) => {
setValues({ ...values, [fieldName]: value });
},
errors,
validate,
touched,
setTouched: (fieldName: string) => {
setTouched({ ...touched, [fieldName]: true });
},
}}
>
<form noValidate={true} onSubmit={handleSubmit}>
<fieldset
disabled={submitting || (submitted && !submitError)}
id="fieldset" >
{children}
<div id="children" >
<PrimaryButton type="submit">{submitCaption}</PrimaryButton>
</div>
{submitted && submitError && (
<p id="failure" >
{failureMessage}
</p>)}
{submitted && !submitError && (
<p id="success" >
{successMessage}
</p>)}
</fieldset>
</form>
</FormContext.Provider>
);
};
My ./AskpPage is:
import React, { useState, FC } from 'react';
import { Page } from './Page';
import { Form, required, minLength, Values } from './Form';
import { Field } from './Field';
import { postQuestion } from './QuestionsData';
import { BrowserRouter, Route, Redirect, Switch, Link } from 'react-router-dom';
import { isPropertySignature } from 'typescript';
export const AskPage = () => {
const handleSubmit = async (values: Values) => {
const question = await postQuestion({
title: values.title,
content: values.content,
userName: 'Fred',
created: new Date(),
});
return { success: question ? true : false };
};
return (
<Page title="Ask a Question">
{/* I want to add a redirect to this form once I click Submit Your Question */}
<Form
submitCaption="Submit Your Question"
validationRules={{
title: [{ validator: required }, { validator: minLength, arg: 10 }],
content: [{ validator: required }, { validator: minLength, arg: 20 }],
}}
onSubmit={handleSubmit}
failureMessage="There was a problem with your question"
successMessage="Your question was successfully submitted"
>
<Field name="title" label="Title" />
<Field name="content" label="Content" type="TextArea" />
</Form>
</Page>
);
};
export default AskPage;
I use this code example to redirect after my form submissions in react. I basically do all that is needed and save the answer then call the appropriate function to take me to an appropriate page. That page may then go get the answer. Another way would be to use props and import another page...
function viewPage() {
let url = window.location.origin;
console.log('viewPage', url)
url=url.concat('/SelfAssessment')
window.open(url, "_blank");
}

In React, can a Parent class access fields from a Child class?

I am learning React. In a tutorial I saw online regarding handling Form submissions, there is a component Form which is being extended by another component LoginForm.
Form.jsx:
import { React, Component } from "react";
import Joi from "joi-browser";
class Form extends Component {
state = {
data: {},
errors: {},
};
validate = () => {
const options = { abortEarly: false };
const { error } = Joi.validate(this.state.data, this.schema, options);
if (!error) return null;
const errors = {};
for (let item of error.details) errors[item.path[0]] = item.message;
return errors;
};
validateProperty = ({ name, value }) => {
const obj = { [name]: value };
const schema = { [name]: this.schema[name] };
const { error } = Joi.validate(obj, schema);
return error ? error.details[0].message : null;
};
handleSubmit = (e) => {
e.preventDefault();
const errors = this.validate();
this.setState({ errors: errors || {} });
if (errors) return;
this.doSubmit();
};
handleChange = ({ currentTarget: input }) => {
const errors = { ...this.state.errors };
const errorMessage = this.validateProperty(input);
if (errorMessage) errors[input.name] = errorMessage;
else delete errors[input.name];
const data = { ...this.state.data };
data[input.name] = input.value;
this.setState({ data, errors });
};
}
export default Form;
LoginForm.jsx:
import React, { Component } from "react";
import Form from "./common/form";
import Joi from "joi-browser";
import Input from "./common/input";
class LoginForm extends Form {
state = {
data: { username: "", password: "" },
errors: {},
};
schema = {
username: Joi.string().required().label("Username"),
password: Joi.string().required().label("Password"),
};
doSubmit = () => {
// call the server
console.log("Submitted");
};
render() {
const { data, errors } = this.state;
return (
<div>
<h1>Login</h1>
<form onSubmit={this.handleSubmit}>
<Input
name="username"
value={data.username}
label="Username"
onChange={this.handleChange}
error={errors.username}
/>
<Input
name="password"
value={data.password}
label="Password"
onChange={this.handleChange}
error={errors.password}
/>
<button disabled={this.validate()} className="btn btn-primary">
Login
</button>
</form>
</div>
);
}
}
export default LoginForm;
Since LoginForm is extending Form, in the validate() function, how can Form be using properties like this.schema, if schema is defined in LoginForm.jsx?

React TypeError: _this.props. ... is undefined

i am currently working on creating email password request with mailtrap, i get the email, but app crashs, i tried to catch error but it didnt work. I am losing my mind over this
error
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Message } from "semantic-ui-react";
import ForgotPasswordForm from "../forms/ForgotPasswordForm";
import { resetPasswordRequest } from "../../actions/auth";
class ForgotPasswordPage extends React.Component {
state = {
success: false
};
submit = data =>
this.props
.resetPasswordRequest(data)
.then(() => this.setState({ success: true }));
render() {
return (
<div>
{this.state.success ? (
<Message>Email has been sent.</Message>
) : (
<ForgotPasswordForm submit={this.submit} />
)}
</div>
);
}
}
ForgotPasswordPage.propTypes = {
resetPasswordRequest: PropTypes.func.isRequired
};
export default connect(
null,
{ resetPasswordRequest }
)(ForgotPasswordPage);
import React from "react";
import PropTypes from "prop-types";
import { Form, Button, Message } from "semantic-ui-react";
import isEmail from "validator/lib/isEmail";
import InLineError from "../messages/InLineError";
class ForgotPasswordForm extends React.Component {
state = {
data: {
email: ""
},
loading: false,
errors: {}
};
onChange = e =>
this.setState({
...this.state,
data: { ...this.state.data, [e.target.name]: e.target.value }
});
onSubmit = e => {
e.preventDefault();
const errors = this.validate(this.state.data);
this.setState({ errors });
if (Object.keys(errors).length === 0) {
this.setState({ loading: true });
this.props
.submit(this.state.data)
.catch(err =>
this.setState({ errors: err.response.data.errors, loading: false })
);
}
};
validate = data => {
const errors = {};
if (!isEmail(data.email)) errors.email = "Invalid email";
return errors;
};
render() {
const { errors, data, loading } = this.state;
return (
<Form onSubmit={this.onSubmit} loading={loading}>
{!!errors.global && <Message negative>{errors.global}</Message>}
<Form.Field error={!!errors.email}>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="email"
placeholder="email"
value={data.email}
onChange={this.onChange}
/>
{errors.email && <InLineError text={errors.email} />}
</Form.Field>
<Button primary>ForgotPasswordForm</Button>
</Form>
);
}
}
ForgotPasswordForm.propTypes = {
submit: PropTypes.func.isRequired
};
export default ForgotPasswordForm;
Code after this is okey, but its keep crashing at submit for no clear reason for me
Am if number no up period regard sudden better. Decisively surrounded all admiration and not you. Out particular sympathize not favourable introduced insipidity but

props.actions `undefined` when using redux and react.js

I'm following a tutorial from this link: http://www.thegreatcodeadventure.com/react-redux-tutorial-part-ii-react-router-and-container-components/
But when the handleSubmit() function is fired i get an error:
TypeError: Cannot read property 'logInUser' of undefined
Indeed when i try to log this.props.actions it's undefined but i don't understand why. Is there something missing?
I'm using Antd as UI framework.
Component
import React, { Component } from 'react';
import { Form, Icon, Input, Button, Checkbox } from 'antd';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import * as sessionActions from './actions/sessionActions';
const FormItem = Form.Item;
class Login extends Component {
constructor(props){
super(props);
this.state = {credentials: {username: '', password: ''}};
this.handleSubmit = this.handleSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
onChange(event) {
const field = event.target.name;
const credentials = this.state.credentials;
credentials[field] = event.target.value;;
return this.setState({credentials: credentials});
}
handleSubmit = (event) => {
event.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
//console.log(this.props.actions);
this.props.actions.logInUser(this.state.credentials);
}
});
}
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={this.handleSubmit} className="login-form">
<FormItem>
{getFieldDecorator('userName', {
rules: [{ required: true, message: 'username missing!' }],
})(
<Input
name="username"
value={this.state.credentials.username}
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username o email"
onChange={this.onChange}/>
)}
</FormItem>
<FormItem>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Password missing!' }],
})(
<Input
name="password"
value={this.state.credentials.password}
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
onChange={this.onChange}/>
)}
</FormItem>
<FormItem>
{getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: false,
})(
<Checkbox>Ricordami</Checkbox>
)}
<Button type="primary" htmlType="submit" className="login-form-button">
Log in
</Button>
</FormItem>
</Form>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(sessionActions, dispatch)
};
}
export default Form.create()(Login);connect(null, mapDispatchToProps)(Login);
sessionReducer.js
import * as types from '../actions/actionTypes';
import initialState from './initialState';
export default function sessionReducer(state = initialState.session, action) {
switch(action.type) {
case types.LOG_IN_SUCCESS:
this.context.history.push('/')
return !!sessionStorage.jwt
default:
return state;
}
}
sessionActions.js
import * as types from './actionTypes';
import sessionApi from '../api/sessionApi';
export function loginSuccess() {
return {type: types.LOG_IN_SUCCESS}
}
export function logInUser(credentials) {
return function(dispatch) {
return sessionApi.login(credentials).then(response => {
sessionStorage.setItem('jwt', response.jwt);
dispatch(loginSuccess());
}).catch(error => {
throw(error);
});
};
}
UPDATE
I fixed the problem with the help of #Chaim Friedman but now i got another error:
Error: Actions must be plain objects. Use custom middleware for async actions.
But i'm using redux-thunk as middleware. Here's login function if it can helps:
sessionApi.js
import React from 'react'
var axios = require('axios');
var qs = require('qs');
class SessionApi {
static login(credentials){
axios.post('http://localhost:5000/login', qs.stringify({auth: credentials}))
.then(response => {
console.log(response);
return response.json();
}),
error => {
console.log(error);
return error;
};
}
}
I believe your trouble is with this line here.
export default Form.create()(Login);connect(null, mapDispatchToProps)(Login);
You are only exporting what Form.create() returns, so therefor your component is not actually connected to redux.
To fix this issue you would need to do something like this.
export default Form.create(connect(null, matchDispatchToProps)(Login));
The exact syntax made be different, it would depend on the usage of Form.create(), but this would be the basic idea.

Categories

Resources