I have the following child component:
class SignIn extends React.Component {
constructor(props) {
super(props);
this.onClick = this.handleClick.bind(this);
this.state = {
email: '',
password: ''
};
}
handleClick = () => {
this.props.onClick(this.state.email, this.state.password);
}
handleEmailChange = (e) => {
this.setState({email: e.target.value});
}
handlePasswordChange = (e) => {
this.setState({password: e.target.value});
}
render() {
return (
...
<Input id="email" name="email" autoComplete="email" autoFocus
value={this.state.email} onChange={this.handleEmailChange}/>
<Input name="password" type="password" id="password" autoComplete="current-password"
value={this.state.password} onChange={this.handlePasswordChange}/>
...
);
}
}
Now from the parent I have the following component:
class App extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
email: "",
password: ""
}
}
handleClick(e, p, request) {
request();
}
render() {
const { email, password } = this.state;
console.log('render', email, password); // here I see the right state after click
return (
<ApolloProvider client={client}>
<Mutation mutation={LOGIN} variables={{ email: email, password: password }} onError={() => {}}>
{(request, result) => {
const { data, loading, error, called } = result;
if(!called) {
return <SignIn onClick={(e, p) => this.handleClick(e, p, request)} />;
}
if(error) {
return <div>Error</div>;
}
if(loading) {
return <div>Loading...</div>;
}
...
return <div>Mutation processed</div>;
}}
</Mutation>
</ApolloProvider>
);
}
}
What I wanted to achieve is separate handler after button click and initiate mutation send after some logic. However, this way variables(email, password) are always sent empty to the network. If I put request directly into handle, then it works.
How can I have a handler outside of render function to initiate mutation request with correct variable values? I would also very much like to know why this construction doesn't work and variables are empty.
I think the problem here lies with the line:
this.onClick = this.handleClick.bind(this);
With incorrect binding you're not going to trigger the method you want. This should be:
this.handleClick = this.handleClick.bind(this);
The following achieves what you want. I've slimmed it down because I don't know anything about your Apollo implementation, but hopefully you'll get the gist:
// SignIn.jsx
import React, { Component } from "react";
class SignIn extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
email: "",
password: ""
};
}
handleClick = () => {
this.props.onClick(this.state.email, this.state.password);
};
handleEmailChange = e => {
this.setState({ email: e.target.value });
};
handlePasswordChange = e => {
this.setState({ password: e.target.value });
};
render() {
return (
<div>
<input
id="email"
name="email"
autoComplete="email"
autoFocus
value={this.state.email}
onChange={this.handleEmailChange}
/>
<input
name="password"
type="password"
id="password"
autoComplete="current-password"
value={this.state.password}
onChange={this.handlePasswordChange}
/>
<button onClick={this.handleClick}>Click Me</button>
</div>
);
}
}
export default SignIn;
// App.jsx
import React, { Component } from "react";
import ReactDOM from "react-dom";
import SignIn from "./SignIn";
class App extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
email: "",
password: ""
};
}
handleClick(e, p, request) {
console.log("Requesting: ", request);
}
render() {
const { email, password } = this.state;
const request = "Your Apollo Request";
console.log("render", email, password); // here I see the right state after click
return (
<div>
<SignIn onClick={(e, p) => this.handleClick(e, p, request)} />;
</div>
);
}
}
export default App;
Related
I'm currently working on a project that uses QuillJS for a rich text editor. I need to post the rich text content to my backend but I'm not sure how to access the QuillJS output.
In RichTextEditor.js
import React, { Component } from "react";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
class RichTextEditor extends Component {
constructor(props) {
super(props);
// this.formats = formats;
this.state = { text: "" }; // You can also pass a Quill Delta here
this.handleChange = this.handleChange.bind(this);
}
handleChange(value) {
this.setState({ text: value });
const text = this.state;
console.log(text);
}
render() {
return (
<ReactQuill
value={this.state.text}
onChange={this.handleChange}
formats={this.formats}
modules={this.modules}
/>
);
}
}
export default RichTextEditor;
The console.log(text) basically just outputs the content of the rich text editor. Something like this "<p><em>aasdasdasd</em><strong><em>asdasdasdasd</em></strong></p>"
In Post.js
import React, { Component } from "react";
import RichTextEditor from "./RichTextEditor.js";
import "../../css/Post.css";
class Post extends Component {
constructor(props) {
super(props);
this.state = {
question: "",
};
}
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
console.log(this.state);
};
handleSubmit = (e) => {
e.preventDefault();
const { question } = this.state;
console.log("Question");
console.log(question);
render() {
const { question } = this.state;
return (
<div className="post">
<div className="post__container">
<form onSubmit={this.handleSubmit}>
<div className="post__richTextEditor">
<RichTextEditor value={question} onChange={this.onChange} name="question" />
</div>
</form>
</div>
</div>
);
}
}
export default Post;
I'm trying to update the state of the question but it doesn't seem to be updating. console.log(question) only outputs a single string.
How can I access the same string output from RichTextEditor.js?
Your RichTextEditor component should not handle change, it should only receive props from higher component:
class RichTextEditor extends Component {
constructor(props) {
super(props);
}
render() {
return (
<ReactQuill
value={this.props.value}
onChange={this.props.onChange}
formats={this.formats}
modules={this.modules}
/>
);
}
}
export default RichTextEditor;
Then your Post component pass value and onChange props to RichTextEditor:
class Post extends Component {
constructor(props) {
super(props);
this.state = {
question: "",
};
this.onChange = this.onChange.bind(this);
}
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
console.log(this.state);
};
handleSubmit = (e) => {
e.preventDefault();
const { question } = this.state;
console.log("Question");
console.log(question);
render() {
const { question } = this.state;
return (
<div className="post">
<div className="post__container">
<form onSubmit={this.handleSubmit}>
<div className="post__richTextEditor">
<RichTextEditor value={question} onChange={this.onChange} name="question" />
</div>
</form>
</div>
</div>
);
}
}
in RichTextEditor.js
handleChange(value) {
this.setState({ text: value });
const text = this.state;
console.log(text);
props.onChange(text); // passing the inner State to parent as argument to onChange handler
}
Now in Post.js
onChange = (newStateString) => {
//this.setState({ [e.target.name]: e.target.value });
console.log(newStateString); // you should get the string here
};
I'm trying to do a react app where you have a "login" screen (just add a username to go in).
I have used this tutorial to do what I want(basically after submitting a username, it should change the component rendering)
My problem is that each time I click the submit button and the main component re-renders it will change the state of the main component back to default. The result is that it will show the Game component, which is a generic one, for a split second then go back to rendering the login component. What am I doing wrong?
Main component:
class Main extends React.Component {
constructor(props){
super(props);
this.login = this.login.bind(this);
this.state = {
loggedIn : false
};
}
login() {
this.setState({loggedIn: true});
}
render() {
const isLoggedIn = this.state.loggedIn;
return (
<div>
{isLoggedIn
? <Game/>
: <Login login={this.login}/>}
</div>
);
}
}
The login component:
class Login extends React.Component {
constructor(props) {
console.log("HELLO")
super(props);
this.state = {
value: '',
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
event.preventDefault();
}
handleSubmit(event) {
// login/" + this.state.value
fetch("my.api.example")
.then(res => res.json())
.then(
(result) => {
this.onLogin();
},
(error) => {
console.log(error)
}
)
}
onLogin = () => {
this.props.login();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
Your handleSubmit doesn't have an event.preventDefault() so the form is submitting and refreshing the page.
I have the following code:
import React, { Component } from 'react'
import axios from 'axios'
import Navbar from '../Navbar'
import { Avatar, TextField, Button, Container, CircularProgress } from '#material-ui/core'
import Alert from '#material-ui/lab/Alert'
class PrivateProfile extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
id: null,
image: null,
pp: null,
username: 'AnonymousUser',
showSuccess: false
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleFileChange = this.handleFileChange.bind(this)
}
componentDidMount() {
axios.get('http://127.0.0.1:8000/users/profile')
.then(res => {
this.setState({
user: res.data,
id: res.data.id,
username: res.data.username,
pp: res.data.pp
})
})
.catch(err => console.log(err))
}
handleSubmit(e) {
e.preventDefault()
const fd = new FormData()
fd.append('pp', this.state.image)
fd.append('username', this.state.user.username)
fd.append('email', this.state.user.email)
fd.append('bio', this.state.user.bio)
const d = {
pp : this.state.image,
username : this.state.user.username,
email : this.state.user.email,
bio : this.state.user.bio
}
console.log('d', d)
console.log('fd', fd)
axios.put(`http://127.0.0.1:8000/users/profile/update/${this.state.id}/`, fd, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then(res => {
this.setState({
user: res.data,
id: res.data.id,
pp: res.data.pp,
image: null,
username: res.data.username,
showSuccess: true
})
})
.catch(err => console.log(err))
}
handleChange(e) {
this.setState({
user: {
[e.target.name]: e.target.value
}
})
}
handleFileChange(e) {
this.setState({image: e.target.files[0]})
}
render() {
let message
let alert
if (this.state.user !== null) {
if (!this.state.user.bio) {
message = <h4>Please update your profile below.</h4>
}
if (this.state.showSuccess) {
alert = <Alert action={<Button onClick={() => this.setState({showSuccess: false})}>Close</Button>} severity='success'>Profile Successfully Updated</Alert>
}
return (
<div>
<Navbar />
<Container style={{background: '#f7f4e9'}}>
<div style={{height: '60px'}}></div>
<h2>Your Profile</h2>
<Avatar src={this.state.user.pp} alt={this.state.user.username} />
{message}
{alert}
<h4>Your data:</h4>
<form onSubmit={this.handleSubmit}>
<p>Profile Pic</p>
<input type="file" onChange={this.handleFileChange}/>
<br></br>
<br></br>
<TextField label='Username' name="username" onChange={this.handleChange} type="text" value={this.state.user.username} />
<br></br>
<br></br>
<TextField label='Email' name="email" onChange={this.handleChange} type="email" value={this.state.user.email} />
<br></br>
<br></br>
<TextField label='Bio' name="bio" onChange={this.handleChange} type="text" value={this.state.user.bio} />
<br></br>
<br></br>
<br></br>
<Button type="submit" value="submit">Update</Button>
</form>
</Container>
</div>
)
} else {
return <CircularProgress />
}
}
}
export default PrivateProfile
I get the error saying: Warning: A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
Can someone help me fix it.
Since you're initializing state values with null and using it like value={this.state.user.username}, and update the state, you'll get such error:
Warning: A component is changing a controlled input of type text to be uncontrolled.
To control it's state, use it like:
value={this.state.user.username || ''}
As per my comment, you have issue here:
handleChange(e) {
this.setState({
user: {
[e.target.name]: e.target.value
}
})
}
The user state will always change on your any input changes, you will need like:
handleChange(e) {
this.setState({
user: {
...this.state.user,
[e.target.name]: e.target.value
}
})
}
I'm not sure what is causing the problem, as I have another component that's almost identical except that the other component is stateless. I'm not sure if that makes a problem? It shouldn't right?
The following code gives me: Uncaught TypeError: this.props.dispatch is not a function at Signup.handleRegister, when trying to submit the form.
import React from 'react';
import { connect } from 'react-redux';
import { registerUser } from '../../actions/index';
export class Signup extends React.Component {
constructor(props){
super(props);
this.state = {
displayname: "",
username: "",
password: ""
}
}
handleRegister = e => {
e.preventDefault();
console.log('triggered handle register'); //logs: 'triggered handle register'
console.log(this.state); //logs: {displayname: "", username: "", password: ""}, as intended with empty inputs
console.log(this.props); //logs: {}
this.props.dispatch(registerUser(this.state));
}
render(){
return (
<div className="form-container sign-up-container">
<form className="sign-up-form" onSubmit={this.handleRegister}>
<h2>Create Account</h2>
<input type="text" placeholder="Display Name" onChange={e => this.setState({ displayname: e.target.value })} />
<input type="text" placeholder="Username" onChange={e => this.setState({ username: e.target.value })} />
<input type="password" placeholder="Password" onChange={e => this.setState({ password: e.target.value })} />
<button className="toggle-btn">Sign Up</button>
</form>
</div>
);
}
}
const mapStateToProps = state => ({});
export default connect(mapStateToProps)(Signup);
Update: Something like this?
const mapDispatchToProps = dispatch => {
return {
//the redux-action here instead of the handleRegister?
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Signup);
Update 2: Following Christopher Ngo suggestion
import React from 'react';
import { connect } from 'react-redux';
import { registerUser } from '../../actions/index';
export class Signup extends React.Component {
constructor(props) {
super(props);
this.state = {
displayname: "",
username: "",
password: ""
}
}
handleRegister = e => {
e.preventDefault();
console.log('triggered handle register'); //logs: 'triggered handle register'
console.log(this.state); //logs: {displayname: "", username: "", password: ""}, as intended with empty inputs
console.log(this);
//logs: Signup {props: {…}, context{…}, refs: {…}, updater: {…}, handleRegister: ƒ, …}
this.props.registerUser(this.state);
}
render() {
return (
<div className="form-container sign-up-container">
<form className="sign-up-form" onSubmit={this.handleRegister}>
<h2>Create Account</h2>
<input type="text" placeholder="Display Name" onChange={e => this.setState({ displayname: e.target.value })} />
<input type="text" placeholder="Username" onChange={e => this.setState({ username: e.target.value })} />
<input type="password" placeholder="Password" onChange={e => this.setState({ password: e.target.value })} />
<button className="toggle-btn">Sign Up</button>
</form>
</div>
);
}
}
// const mapStateToProps = state => ({});
const mapDispatchToProps = (dispatch) => {
return {
registerUser: (userInfo) => {
dispatch(registerUser(userInfo))
}
}
}
export default connect(null, mapDispatchToProps)(Signup);
I changed the console log in the handle register to check this and it looks like the Signup component still does not have props or dispatch available to it.
Your connected component is exported as a default export so you need to make sure that you are importing Signup as a default import in your other files and not a named export. In such scenarios its better to not export unconnected components to avoid such mistakes.
Import your signup component like
import Signup from 'path/to/Signup'
Try it like this:
import React from 'react';
import { connect } from 'react-redux';
import { registerUser } from '../../actions/index';
class Signup extends React.Component {
constructor(props){
super(props);
this.state = {
displayname: "",
username: "",
password: ""
}
}
handleRegister = e => {
e.preventDefault();
console.log('triggered handle register'); //logs: 'triggered handle register'
console.log(this.state); //logs: {displayname: "", username: "", password: ""}, as intended with empty inputs
console.log(this.props); //logs: {}
this.props.registerUser(this.state);
}
render(){
return (
<div className="form-container sign-up-container">
<form className="sign-up-form" onSubmit={this.handleRegister}>
<h2>Create Account</h2>
<input type="text" placeholder="Display Name" onChange={e => this.setState({ displayname: e.target.value })} />
<input type="text" placeholder="Username" onChange={e => this.setState({ username: e.target.value })} />
<input type="password" placeholder="Password" onChange={e => this.setState({ password: e.target.value })} />
<button className="toggle-btn">Sign Up</button>
</form>
</div>
);
}
}
const mapStateToProps = state => ({});
const mapDispatchToProps = (dispatch) => {
return{
registerUser: (userInfo) => {
dispatch(registerUser(userInfo))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Signup);
I have the following component in React:
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {username: '', password: '', redirectToReferrer: false};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
const value = event.target.value;
const name = event.target.name;
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
console.log('A name was submitted: ' + this.state.username);
Auth.authenticate(this.state.username, this.state.password, () => {
this.setState({ redirectToReferrer: true })
})
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
const { redirectToReferrer } = this.state
if (redirectToReferrer) {
return (
<Redirect to={from}/>
)
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<form id='loginForm'>
<input type="text" name="username" onChange={this.handleChange} />
<input type="password" name="password" onChange={this.handleChange} />
<button onClick={this.handleSubmit}>Log in</button>
</form>
</div>
)
}
}
When I use the browser auto-complete feature (instead of typing 'admin' I type just 'a' and let browser to fill the rest) the component's state is not update and it submits incorrect value. When I type the username/password all by hand it works correctly. How can I fix this? It's pretty common use case...
It looks like that some browsers have a bug.
You can try to workaround it with Autofill polyfill:
A polyfill to fire a change event when the browser auto fills form fields