I have used same form to create and update account(module). Create is working fine but in update mode I am not able to set form field value using Set State methods. I have used query component on render methods and setstate not working on rendor method.
import { Mutation } from "react-apollo";
import { Query } from "react-apollo";
import gql from "graphql-tag";
import React, { Component } from "react";
import Router from "next/router";
import Joi from "joi-browser";
const CREATE_ACCOUNT = gql`
mutation CreateAccount(
$name: String
$phone_office: String
$website: String
) {
createAccount(name: $name, phone_office: $phone_office, website:
$website) {
name
phone_office
website
}
}
`;
export const allAccountbyidQuery = gql`
query account($id: String) {
account(id: $id) {
id
name
phone_office
website
}
};
const schema = {
name: Joi.string()
.required()
.error(errors => {
return {
message: "Name is required!"
};
}),
phone_office: Joi.string()
.required()
.error(errors => {
return {
message: "Phone Number is required!"
};
}),
website: Joi.string()
.required()
.error(errors => {
return {
message: "Website is required!"
};
})
};
Main class component
class CreateAccountModule extends React.Component {
static async getInitialProps({ query }) {
const { id } = query;
return { id };
}
constructor(props) {
super();
this.state = {
isFirstRender: true,
name: "",
phone_office: "",
website: ""
};
}
handleChange = event => {
console.log("hello");
const { name, value } = event.target;
this.setState({ [name]: value });
};
validate(name, phone_office, website) {
let errors = "";
const result = Joi.validate(
{
name: name,
phone_office: phone_office,
website: website
},
schema
);
if (result.error) {
errors = result.error.details[0].message;
}
return errors;
}
setName = name => {
if (this.state.isFirstRender) {
this.setState({ name, isFirstRender: false });
}
};
render() {
let input;
const { errors } = this.state;
console.log(this.props);
const allAccountbyidQueryVars = {
id: this.props.id
};
//console.log(allAccountbyidQueryVars);
return (
<Query
query={allAccountbyidQuery}
variables={allAccountbyidQueryVars}
onCompleted={data => this.setName(data.account.name)}
>
{({ data, loading, error }) => {
<CreateAccountModule account={data.account} />;
return (
<Mutation mutation={CREATE_ACCOUNT}>
{(createAccount, { loading, error }) => (
<div>
<form
onSubmit={e => {
e.preventDefault();
const errors = this.validate(
e.target.name.value,
e.target.phone_office.value,
e.target.website.value
);
if (errors) {
this.setState({ errors });
return;
}
if (!errors) {
let accountres = createAccount({
variables: {
name: e.target.name.value,
phone_office: e.target.phone_office.value,
website: e.target.website.value
}
}).then(() => Router.push("/"));
input.value = "";
}
}}
>
{errors && <p>{errors}</p>}
<input
type="text"
name="name"
id="name"
placeholder="Name"
value={this.state.name}
onChange={this.handleChange}
/>
<input
name="phone_office"
id="phone_office"
placeholder="Phone Number"
//value={data.account.phone_office}
//value="123456"
onChange={this.handleChange}
/>
<input
name="website"
id="website"
placeholder="Website"
//value={data.account.website}
onChange={this.handleChange}
/>
<button type="submit">Add Account</button>
<button type="button">Cancel</button>
</form>
{loading && <p>Loading...</p>}
{error && <p>Error :( Please try again</p>}
</div>
)}
</Mutation>
);
}}
</Query>
);
}
}
export default CreateAccountModule;
`
I have tried with props but get props data in apollo state. anyone please suggest possible solution to fix this issue.
Related
I am working on a class project where I am creating a fullstack website using Apoll Server with express on the back end and React for the front end. I am trying to allow users to sign up and then from their dashboard page add a course. Right now the course just have courseTitle, description, and creator which is the user's id. I am able to add a course from the graphql playground but when I try it from the front end I get an error:
index.ts:58 Uncaught (in promise) Error: Response not successful: Received status code 400
I have gone over all the code and compared it to other code for signing up a user which works but I cannot get anotgher other than that error. I tried putting a console.log in resolver mutation but get nothing so somehow it isn't hitting the resolver at all.
Here is my addCourse resolver mutation:
export const CREATE_COURSE = gql`
mutation addCourse(
$courseTitle: String!
$description: String!
$creator: String!
) {
addCourse(
courseTitle: $courseTitle
description: $description
creator: $creator
) {
_id
courseTitle
description
creator {
_id
firstName
}
}
}
`;
Here is the resolver mutation:
addCourse: async (parent, args, context) => {
//if (context.user) {
const course = await Course.create(args);
return course;
//}
},
Here is the typeDef
const { gql } = require("apollo-server-express");
const typeDefs = gql`
type User {
_id: ID!
email: String
firstName: String
lastName: String
createdCourses: [Course]
enrolledCourseIds: [ID]
enrolledCourses: [Course]
}
type Course {
_id: ID
courseTitle: String!
description: String!
creator: User
}
type Mutation {
addCourse(
courseTitle: String!
description: String!
creator: String!
): Course
}
`;
// export the typeDef
module.exports = typeDefs;
This is the add course form:
import { Form, Field } from "react-final-form";
import { useMutation } from "#apollo/client";
import { useNavigate } from "react-router-dom";
import { CREATE_COURSE } from "../utils/mutations";
export const CreateCourse = () => {
const [addCourseMutation] = useMutation(CREATE_COURSE);
const navigate = useNavigate();
return (
<Form
onSubmit={async (values) => {
await addCourseMutation({
variables: {
...values,
},
onCompleted: (data) => {
console.log(data);
navigate("/dashboard");
},
});
}}
initialValues={{
courseTitle: "",
description: "",
}}
render={({ values, handleSubmit, form }) => {
return (
<div>
<br />
<br />
<h1>Course Title</h1>
<Field name="courseTitle" component="input" />
<h1>Description</h1>
<Field name="description" component="input" />
<button
disabled={
values?.password?.length === 0 || values?.email?.length === 0
}
onClick={async () => {
await handleSubmit();
form.reset();
}}
>
Submit
</button>
</div>
);
}}
/>
);
};
I made a bunch of changes trying to get the user Id for creator and took them out since I wasn't geting anything but that same error now matter what I did.
I realized that I wasn't sending the user _id from the form the form submission so I gut the user Id and set it in the initial state so it was passed to the resolver. Maybe not be the best way to do it, but I got it working.
import jwt from "jwt-decode";
import Auth from "../utils/auth";
import { Form, Field } from "react-final-form";
import { useMutation } from "#apollo/client";
import { useNavigate } from "react-router-dom";
import { CREATE_COURSE } from "../utils/mutations";
export const CreateCourse = () => {
const token = Auth.loggedIn() ? Auth.getToken() : null;
const user = jwt(token);
const [addCourseMutation] = useMutation(CREATE_COURSE);
const navigate = useNavigate();
return (
<Form
onSubmit={async (values) => {
await addCourseMutation({
variables: {
...values,
},
onCompleted: (data) => {
console.log(data);
navigate("/courses");
},
});
}}
initialValues={{
courseTitle: "",
description: "",
creator: user.data._id,
}}
render={({ values, handleSubmit, form }) => {
return (
<div>
<br />
<br />
<h1>Course Title</h1>
<Field name="courseTitle" component="input" />
<h1>Description</h1>
<Field name="description" component="input" />
<button
onClick={async () => {
await handleSubmit();
form.reset();
}}
>
Submit
</button>
</div>
);
}}
/>
);
};
How can I convert a Class Component which extends another Class component in a Functional Component in ReactJS?
input.jsx [Functional Component]
const Input = ({ name, label, error, ...rest }) => {
return (
<div className="mb-3">
<label htmlFor={name} className="form-label">
{label}
</label>
<input
autoFocus
{...rest}
id={name}
name={name}
className="form-control"
/>
{error && <div className="alert alert-danger">{error}</div>}
</div>
)
}
export default Input
form.jsx [Class Component]
import React, { Component } from "react"
import Input from "./input"
import Joi from "joi"
class Form extends Component {
state = {
data: {},
errors: {}
}
validate = () => {
const options = { abortEarly: false }
const schemaJoi = Joi.object(this.schema)
const { error } = schemaJoi.validate(this.state.data, options)
if (!error) return null
const errors = {}
error.details.map(item => (errors[item.path[0]] = item.message))
return errors
}
validateProperty = ({ name, value }) => {
const obj = { [name]: value }
const schema = {
[name]: this.schema[name]
}
const schemaJoi = Joi.object(schema)
const { error } = schemaJoi.validate(obj)
return error ? error.details[0].message : null
}
handleSubmit = e => {
e.preventDefault()
const errors = this.validate()
console.log(errors)
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 })
}
renderButton = label => {
return (
<button disabled={this.validate()} className="btn btn-primary">
{label}
</button>
)
}
renderInput = (name, label, type = "text") => {
const { data, errors } = this.state
return (
<Input
name={name}
label={label}
error={errors[name]}
type={type}
value={data[name]}
onChange={this.handleChange}
/>
)
}
}
export default Form
loginForm.jsx [Class Component which extends the other]
import Joi from "joi"
import Form from "./common/form"
class LoginForm extends Form {
state = {
data: { username: "", password: "" },
errors: {}
}
schema = {
username: Joi.string().required().label("Username"),
password: Joi.string().required().label("Password")
}
doSubmit = () => {
console.log("Submitted")
}
render() {
return (
<div>
<h1>Login</h1>
<form onSubmit={this.handleSubmit}>
{this.renderInput("username", "Username")}
{this.renderInput("password", "Password", "password")}
{this.renderButton("Login")}
</form>
</div>
)
}
}
export default LoginForm
I already know how to convert a simple Class Component to a Stateless Functional Component but what I don't know is how to convert a Class Component which extends another Class Component.
Please, may you explain me how?
I am trying to build a login form in React.js. I want to enable/disable Login button based on results return by validate method. React throws error 'Invalid value for prop disabled on tag. Either remove it from the element, or pass a string or number value to keep it in the DOM.'. Does anyone have came across same error? Help me to understand what is going wrong here?
import React, { Component } from "react";
import Input from "../common/input";
import Joi from "joi-browser";
class LoginForm extends Component {
state = {
account: {
username: "",
password: "",
},
errors: {},
};
schema = {
username: Joi.string().required().label("Username"),
password: Joi.string().required().label("Password"),
};
abortEarly = {
abortEarly: false,
};
handleSubmit = (event) => {
event.preventDefault();
const errors = this.validate();
if (errors) return;
console.log("submitted");
};
validate = () => {
const result = Joi.validate(
this.state.account,
this.schema,
this.abortEarly
);
const errors = {};
if (!result.error) return null;
result.error.details.map((detail) => {
errors[detail.path[0]] = detail.message;
return detail.path[0];
});
// console.log(errors);
this.setState({ errors });
return errors;
};
validateProperty = ({ name, value }) => {
const propertyTobeValidated = { [name]: value };
const schema = { [name]: this.schema[name] };
const { error } = Joi.validate(propertyTobeValidated, schema);
return error ? error.details[0].message : null;
};
handleChange = ({ currentTarget }) => {
const errors = { ...this.state.errors };
const error = this.validateProperty(currentTarget);
if (error) errors[currentTarget.name] = error;
else delete errors[currentTarget.name];
const account = { ...this.state.account };
account[currentTarget.name] = currentTarget.value;
this.setState({ account, errors });
};
render() {
const { account, errors } = this.state;
return (
<div>
<h1>Login</h1>
<form onSubmit={this.handleSubmit}>
<Input
label="Username"
name="username"
value={account.username}
onChange={this.handleChange}
error={errors.username}
></Input>
<Input
label="Password"
name="password"
value={account.password}
onChange={this.handleChange}
error={errors.password}
></Input>
<button disabled={this.validate} className="btn btn-primary">
Login
</button>
</form>
</div>
);
}
}
export default LoginForm;
Disabled is a boolean property, meaning it can only have a value of true or false. Instead of a boolean, your validate function is returning an object, thus React throws an "Invalid value" error. In order to fix this you could check if the result of this.validate is null:
<button
disabled={(this.validate() !== null)}
className="btn btn-primary"
>
Login
</button>
Also, you forgot to call your this.validate at all :)
Regarding the "Maximum update depth...", you should remove the this.setState from the this.validate, because you are already putting the error in state in the handleChange method.
Here you go with a solution
import React, { Component } from "react";
import Input from "../common/input";
import Joi from "joi-browser";
class LoginForm extends Component {
state = {
account: {
username: "",
password: "",
},
errors: {},
};
schema = {
username: Joi.string().required().label("Username"),
password: Joi.string().required().label("Password"),
};
abortEarly = {
abortEarly: false,
};
handleSubmit = (event) => {
event.preventDefault();
const errors = this.validate();
if (errors) return;
console.log("submitted");
};
validate = () => {
const result = Joi.validate(
this.state.account,
this.schema,
this.abortEarly
);
const errors = {};
if (!result.error) return false;
result.error.details.map((detail) => {
errors[detail.path[0]] = detail.message;
return detail.path[0];
});
// console.log(errors);
this.setState({ errors });
return true;
};
validateProperty = ({ name, value }) => {
const propertyTobeValidated = { [name]: value };
const schema = { [name]: this.schema[name] };
const { error } = Joi.validate(propertyTobeValidated, schema);
return error ? error.details[0].message : null;
};
handleChange = ({ currentTarget }) => {
const errors = { ...this.state.errors };
const error = this.validateProperty(currentTarget);
if (error) errors[currentTarget.name] = error;
else delete errors[currentTarget.name];
const account = { ...this.state.account };
account[currentTarget.name] = currentTarget.value;
this.setState({ account, errors });
};
render() {
const { account, errors } = this.state;
return (
<div>
<h1>Login</h1>
<form onSubmit={this.handleSubmit}>
<Input
label="Username"
name="username"
value={account.username}
onChange={this.handleChange}
error={errors.username}
></Input>
<Input
label="Password"
name="password"
value={account.password}
onChange={this.handleChange}
error={errors.password}
></Input>
<button disabled={this.validate} className="btn btn-primary">
Login
</button>
</form>
</div>
);
}
}
export default LoginForm;
validate method should return boolean (true or false)
I have two react components(SignupDetails.js, BasicInformation.js). SignupDetails is obviously responsible to signup the user, it will get the user information from the form and submit it to the server through axios.
On the server side(backend), if the user already exist, the server will then collect the remain information, such as first name, middle name, etc and then send it back to be collected on the client side.
Back to the client side, now the remain user information which has been returned is then stored in the central store using the redux-reducers and the page is then "redirected" to BasicInformation.js
Now on the BasicInformation.js, mapStateToPros is then executed so I will do have access to the information that has been stored on the central store but then it becomes the problem: I am struggling to show the remain information. This sounds pretty simple but I tried many things on the componentDidUpdate and on the render method but without being successful.
Please find the code below and let me know if you any ideas.
import React, {Component} from 'react';
import classes from './SignUp.module.css';
import {connect} from 'react-redux'
import * as actions from '../../store/actions/index';
import Spinner from '../../components/UI/Spinner/Spinner'
class SignupDetails extends Component {
constructor(props) {
super(props)
this.state = {
email: '',
password: ''
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
postDataHandler = () => {
const data = {
username: this.state.email,
email: this.state.email,
password: this.state.password
};
this.props.onSignupDetails(data);
}
componentDidMount() {
this.props.history.push({pathname: '/signup_details/'})
}
componentDidUpdate() {
if (this.props.signup_details_success)
this.props.history.push({pathname: '/basic_information/'})
}
render() {
let errorMessage = null;
if (this.props.error) {
errorMessage = (
<p>{this.props.signup_details_response}</p>
);
}
return (
<div>
<Spinner show={this.props.loading}/>
<div className={classes.SignUpForm}>
{errorMessage}
<h3>Take your first step.</h3>
<div>
<input
key="email"
name="email"
value={this.state.email}
onChange={this.handleInputChange}/>
</div>
<div>
<input
key="password"
name="password"
value={this.state.password}
onChange={this.handleInputChange}/>
</div>
<button className={classes.OkButton} onClick={this.postDataHandler}>Next</button>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
signup_details: state.signup.signup_details,
signup_details_success: state.signup.signup_details_success,
signup_details_response: state.signup.signup_details_response,
loading: state.signup.loading,
error: state.signup.error
};
};
const mapDispatchToProps = dispatch => {
return {
onSignupDetails: (signup_details) => dispatch(actions.signupDetails(signup_details))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SignupDetails);
-----
import React, {Component} from 'react';
import classes from './SignUp.module.css';
import {connect} from 'react-redux'
import * as actions from '../../store/actions/index';
import Spinner from '../../components/UI/Spinner/Spinner'
class BasicInformation extends Component {
constructor(props) {
super(props)
this.state = {
first_name: '',
middle_name: '',
last_name: '',
mobile_number: '',
fund_balance: ''
}
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
postDataHandler = () => {
const data = {
username: this.state.email,
email: this.state.email,
first_name: this.state.first_name,
middle_name: this.state.middle_name,
last_name: this.state.last_name,
mobile_number: this.state.mobile_number,
sfunds: [{balance: this.state.fund_balance}]
};
this.props.onSignupBasicInformation(data);
}
componentDidUpdate() {
if (this.props.signup_basic_information_success)
this.props.history.push({pathname: '/personal_information/'})
}
render() {
let errorMessage = null;
if (this.props.error) {
errorMessage = (
<p>{this.props.signup_basic_information_response}</p>
);
}
return (
<div>
<Spinner show={this.props.loading}/>
<div className={classes.SignUpForm}>
{errorMessage}
<h3>First we need some basic information</h3>
<div><input
key="first_name"
name="first_name"
value={this.state.first_name}
onChange={this.handleInputChange}/></div>
<div><input
key="middle_name"
name="middle_name"
value={this.state.middle_name}
onChange={this.handleInputChange}/></div>
<div><input
key="last_name"
name="last_name"
value={this.state.last_name}
onChange={this.handleInputChange}/></div>
<div><input
key="mobile_number"
name="mobile_number"
value={this.state.mobile_number}
onChange={this.handleInputChange}/></div>
<div><input
key="fund_balance"
name="fund_balance"
value={this.state.fund_balance}
onChange={this.handleInputChange}/></div>
<button className={classes.OkButton} onClick={this.postDataHandler}>Next</button>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
signup_details_success: state.signup.signup_details_success,
signup_details_response: state.signup.signup_details_response,
signup_basic_information: state.signup.signup_basic_information,
signup_basic_information_success: state.signup.signup_basic_information_success,
signup_basic_information_response: state.signup.signup_basic_information_response,
loading: state.signup.loading,
error: state.signup.error
};
};
const mapDispatchToProps = dispatch => {
return {
onSignupBasicInformation: (basic_information) => dispatch(actions.signupBasicInformation(basic_information))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(BasicInformation);
In case someone wonder, I managed to solve this problem through componentDidMount to set the local state.
componentDidMount(){
if(this.props.smsf_member_details) {
this.setState({
first_name: this.props.smsf_member_details.first_name,
last_name: this.props.smsf_member_details.last_name,
middle_name: this.props.smsf_member_details.middle_name,
});
}
}
In my React app, I have a component call <ComingSoonForm/> Inside that, I have a TextInputField. If the Email valid, the Notify-Button is able. If the Email is invalid, the Notify-Button is disabled.
Here is my Component File:
import React, { Component } from "react";
import { TextInputField, toaster, Button, } from "evergreen-ui";
import Box from 'ui-box';
import { validateEmail } from "../FormValidation/FormValidator";
class ComingSoonForm extends Component {
constructor(props) {
super(props);
this.state = {
emailErr: {
status: true,
value: ""
},
email: "",
isDisabled: true,
};
this.handleSubmit = this.handleSubmit.bind(this);
this.checkFormStatus = this.checkFormStatus.bind(this);
}
handleEmailInput = e => {
const email = e.target.value;
this.setState({ email: email});
console.log(this.state.email);
this.checkFormStatus();
};
handleSubmit() {
if (this.checkFormStatus()) {
alert("Form is OK")
}
}
checkFormStatus() {
// form validation middleware
const { email } = this.state;
const emailErr = validateEmail(email);
if (!emailErr.status) {
this.setState({isDisabled:false})
return true;
} else {
this.setState({
emailErr,
});
return false;
}
}
render() {
return (
<div>
<Box className="welcomePageWelcomeInnerLoginButton">
<TextInputField
marginTop={15}
width={200}
onChange={this.handleEmailInput}
value={this.state.email}
type="email"
placeholder="Your email-address"
inputHeight={40}
/>
</Box>
<Button height="40" appearance="primary" marginBottom={5} className="welcomePageWelcomeInnerLoginButtonWidth" disabled={this.state.isDisabled} onClick={this.handleSubmit}>Notify Me</Button>
</div>
);
}
}
export default ComingSoonForm;
But this case doesn't work correctly. So when the command console.log(this.state.email) in the handleEmailInput Function run, I get the following data in the console:
I type one letter (t) and I get:
//ComingSoonForm.js:25
I type a second letter (t) and I get:
t //ComingSoonForm.js:25
t //FormValidator.js:10
Why do I have to enter two letters in order for one to appear in the console?
setState is asynchronous, you can pass a callback method as a second parameter like this:
handleEmailInput = e => {
const email = e.target.value;
this.setState({ email: email }, () => console.log(this.state.email));
this.checkFormStatus();
};