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

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?

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;

Set initialVariables in Formik from state if it is in edit mode

I'm using Formik for validating some data. It works fine when it should create new entity, but there are problems when I want to edit an entity.
The edit mode must be activated from the state (this.state.edit === true), also the data of the entity is stored on the state, for example this.state.name has a string value there.
I put a console log in render, the problem is that the log is printed several times, the first time with empty string on this.sate.name and the value of this.state.edit is false. The next prints it is correct, this edit on true and name containing a value.
Here is the code:
import React from 'react';
import { Redirect } from 'react-router-dom';
import { Formik, Form, Field } from 'formik';
import { Input, Button, Label, Grid } from 'semantic-ui-react';
import { connect } from 'react-redux';
import * as Yup from 'yup';
import { Creators } from '../../../actions';
class CreateCompanyForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
name: '',
redirectCreate: false,
redirectEdit: false,
edit: false,
};
}
componentDidMount() {
const {
getCompany,
getCompanies,
location: { pathname },
} = this.props;
getCompanies({
name: '',
});
if (pathname.substring(11) !== 'create') {
getCompany(pathname.substring(16));
this.setState({
edit: true,
});
this.setState({
name: this.props.company.name,
});
}
}
handleSubmitCreate = e => {
e.preventDefault();
const { createCompany, getCompanies } = this.props;
createCompany(this.state);
this.setState({ redirectCreate: true });
getCompanies(this.props.query);
};
handleSubmit = values => {
const { createCompany, getCompanies } = this.props;
createCompany(values);
this.setState({ redirectCreate: true });
getCompanies(this.props.query);
};
handleSubmitEdit = e => {
e.preventDefault();
const { name } = this.state;
const { updateCompany } = this.props;
updateCompany(this.props.company._id, {
name,
});
this.setState({ redirectEdit: true });
};
render() {
let title = 'Create company';
let buttonName = 'Create';
let submit = this.handleSubmitCreate;
const { redirectCreate, redirectEdit } = this.state;
if (redirectCreate) {
return <Redirect to="/companies" />;
}
if (redirectEdit) {
return <Redirect to={`/companies/${this.props.company._id}`} />;
}
if (this.state.edit) {
title = 'Edit company';
buttonName = 'Edit';
submit = this.handleSubmitEdit;
}
console.log('state: ', this.state); // first time it is empty, next times it has data
let initialValues = {};
if (this.state.edit) {
initialValues = {
name: this.state.name,
};
} else {
initialValues = {
name: '',
};
}
const validationSchema = Yup.object({
name: Yup.string().required('This field is required'),
});
return (
<>
<Button type="submit" form="amazing">
create company
</Button>
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ values, errors, touched, setValues, setFieldValue }) => (
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field name="name" as={Input} placeholder="write a name" />
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
<Button type="submit" floated="right" form="amazing">
{buttonName} company
</Button>
</Form>
)}
</Formik>
</>
);
}
}
const mapStateToProps = state => ({
companies: state.companies.companies,
company: state.companies.selectedCompany,
query: state.companies.query,
});
const mapDispatchToProps = {
getCompanies: Creators.getCompaniesRequest,
createCompany: Creators.createCompanyRequest,
getCompany: Creators.getCompanyRequest,
updateCompany: Creators.updateCompanyRequest,
};
export default connect(mapStateToProps, mapDispatchToProps)(CreateCompanyForm);
I put the whole file here to have more context. Is it a way to set the initialValue of name with the value from this.state.name and put it inside the input field?
By default Formik does not re-render if the initial values change. You can pass enableReinitialize prop to Formik component to allow it.
As you said in the comment, first time it renders, it has no data, hence it does initialise Formik with empty values. With that prop, it should re-render if the initial values change.
https://formik.org/docs/api/formik#enablereinitialize-boolean

Can't update the state in my react class component

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;

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