I am trying to send user input in a form component to the component managing the state. I tried to use a callback but my input is not being sent.
Tried to use the form object in a callback
//here is the form component
class ListForm extends React.Component {
constructor(props) {
super(props);
this.state = {
NewItem: {
itemText: "",
id: Date.now(),
completed: false
}
};
}
handleChanges = e => {
this.setState({...this.state,
NewItem: { ...this.state.NewItem, [e.target.name]: e.target.value }
});
};
submitItem = e => {
this.setState({ itemText: "" });
this.props.addItem(e, this.state.NewItem);
};
render() {
return (
<form onSubmit={this.submitItem}>
<input
type="text"
value={this.state.NewItem.itemText}
name="itemText"
onChange={this.handleChanges}
/>
<button>Add</button>
</form>
);
}
}
export default ListForm;
addItem is in the parent component and looks like this. Groceries is just an array of objects with itemText, id and completed
addItem = (e, item) => {
e.preventDefault();
this.setState({
...this.state,
groceries: [...this.state.groceries, item]
});
};
I can enter in the form, but when I hit enter, nothing is added to the list
Very first thing to try is to add e.persist to your addItem() function
addItem = (e, item) => {
e.preventDefault();
e.persist()
this.setState({
...this.state,
groceries: [...this.state.groceries, item]
});
};
Another option is using redux to save the form data in the Global state and then accessing it in the parent component through redux. Here is an example of what that might look like:
import React, { Component } from 'react';
import * as ACTIONS from '../store/actions/actions';
import { connect } from 'react-redux';
class Form1 extends Component {
state ={
value: ''
}
handleChange = (event) => (
this.setState({value: event.target.value})
)
handleSubmit = (event) => {
event.preventDefault()
this.props.input_action_creator(event.target.name.value)
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input id="name" onChange={this.handleChange} type="text" />
<button type="submit"> Submit </button>
</form>
<br />
</div>
)}
}
function mapDispatchToProps(dispatch) {
return {
input_action_creator: (text) => dispatch(ACTIONS.user_input(text))
}
}
export default connect(mapDispatchToProps)(Form1);
class Parent extends Component {
access form data with
this.props.user_Text
}
function mapStateToProps(state) {
return {
user_text: state.user_reducer.user_text
}
}
here is a fully funcitoning react-redux project if you are interested
https://github.com/iqbal125/modern-react-app-sample
Related
I am building a component that is supposed to rerender after a form submission, and it sends the database perfectly fine, but I want the component to rerender after it submits and not having to refresh the page.
I have heard React rerenders after state is changed, and I have tried to set the state of the form inputs on submit.
import React, { Component } from 'react';
import { withFirebase } from '../Firebase';
import { FirebaseContext } from '../Firebase';
import { Link } from 'react-router-dom';
import AddNew from '../AddNew';
import { compose } from 'recompose';
import Firebase from '../Firebase';
import * as ROUTES from '../../constants/routes';
import { throwStatement, thisExpression, tsExpressionWithTypeArguments } from '#babel/types';
class Home extends Component {
constructor(props) {
super(props)
this.state = {
loading: false,
isHidden: false,
name: '',
image: '',
data: []
}
this.baseState = this.state
this.toggleAddNew = this.toggleAddNew.bind(this);
}
getPosts() {
this.props.firebase.getClients().then(snapshot => {
this.setState({
data: snapshot.docs
})
});
}
// Component lifecycle methods
componentWillMount() {
this.getPosts()
}
componentDidUpdate(){
console.log('updated')
}
toggleAddNew() {
this.setState({
isHidden: !this.state.isHidden
})
}
updateInput = e => {
this.setState({
[e.target.name]: e.target.value
});
}
resetForm = () => {
this.setState(this.baseState)
}
deletePost = (id) => {
this.props.firebase.deleteClient(id);
}
addClient = e => {
e.preventDefault();
this.props.firebase.addClient().add({
name: this.state.name,
image: this.state.image
})
this.setState({
name: '',
image: ''
});
this.resetForm();
};
render() {
const renderPosts = this.state.data.map((item) => (
<li data-id={item.id} className="client-wrapper col-sm-4">
<button onClick={() => this.deletePost(item.id)}>X</button>
<Link to={`/dates/${item.id}`}>
<h2>{item.data().name}</h2>
</Link>
<Link to={`/dates/${item.id}`}>
<img src={item.data().image} />
</Link>
</li>
));
return (
<div>
<ul id="client-list" className="row">{renderPosts}</ul>
<button onClick={this.toggleAddNew.bind(this)}>Add New</button>
{this.state.isHidden ?
<div id="add-new-form-wrapper">
<button onClick={this.toggleAddNew.bind(this)} id="x-add-new">X</button>
<form onSubmit={this.addClient.bind(this)} id="add-new-form">
<input type="text" name="name" placeholder="Name" onChange={this.updateInput} value={this.state.name} />
<input type="text" name="image" placeholder="Image" onChange={this.updateInput} value={this.state.image} />
<button type="submit">Submit</button>
</form>
</div> :
''}
</div>
)
}
}
export default compose(
withFirebase,
)(Home);
This
this.baseState = this.state
only makes a copy of object reference, not a copy of state object (with property values).
When we have a reference copy
resetForm = () => {
this.setState(this.baseState)
}
can work like state = state, does nothing.
The copy of object (with current property values) can be done (f.e.) this way:
this.baseState = {...this.state}
With this small fix it should work ...
... if not, try
resetForm = () => {
this.setState({...this.baseState})
}
You can also update some state field with current time to force rerender or simply call this.forceUpdate() (see docs).
BTW - resetForm shouldn't overwrite data. Luckily we have a copy of data object reference in baseState ;)
I am not able to pass the value from the child to parent component. I am new to react please guide me.
This is Greeting component -
/* eslint-disable react/prop-types */
import React from "react";
const Greeting = props => {
const isLoggedIn = props.isLoggedIn;
const name = props.name;
if (isLoggedIn) {
return <h1> Welcome Back {name}</h1>;
} else {
return <LoginInfo name={name} onChange={props.onChange} />;
}
};
function LoginInfo(props) {
return (
<div>
<h1> Please Login</h1>
<input type="text" value={props.name} onChange={props.onChange} />
</div>
);
}
export default Greeting;
This is login component -
import React, { Component } from "react";
import Greeting from "./Greeting";
import LogoutButton from "./LogoutButton";
import LoginButton from "./LoginButton";
class Login extends Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {
isLoggedIn: false,
name: ""
};
}
handleLoginClick() {
this.setState({ isLoggedIn: true });
}
handleLogoutClick() {
this.setState({ isLoggedIn: false });
}
onChange = e => {
this.setState({
name: e.target.value
});
};
render() {
const isLoggedIn = this.state.isLoggedIn;
const name = this.state.name;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting
isLoggedIn={isLoggedIn}
name={name}
onChange={this.onChange}
/>
{button}
</div>
);
}
}
export default Login;
I am using in my app.
<Login />
In greeting component How whatever user entered i can store in state and display in welcome back line with name.
I think you need to maintain state in your parent component and emit change event from child component and change state in parent.
like
In login component
nameChange = (e) => {
this.setState({
name: e.target.value
})
}
<Greeting isLoggedIn={isLoggedIn} name={this.state.name} nameChange={this.nameChange}/>
and in child
<input
type="text"
name="name"
value={this.props.name}
onChange={this.props.nameChange}
/>
So this is how I would refactor your code for the LogInInfo component to pass the values of the user to your LogIn component
First of all you need a state in LogInInfo since you want to store the values of the user, you will use the state for that
/* eslint-disable react/prop-types */
import React from "react";
const Greeting = props => {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <h1> Welcome Back</h1>;
} else {
return <LoginInfo submitForm={props.handleSubmitForm}/>;
}
};
class LoginInfo extends React.Component {
constructor(props) {
this.state = {
name: ""
}
}
handleChange = (event) => {
this.setState({ name: event.target.value })
}
render(){
return (
<div>
<h1> Please Login</h1>
// e is the event that is passed automatically to the function in onSubmit
<form onSubmit={e => this.props.submitForm(e, this.state.name)}>
<input
type="text"
name="name"
value={name}
onChange={this.handleChange}
/>
<input type="submit" />
</form>
</div>
);
}
}
export default Greeting;
At this point you have the value you want to pass to the parent component LogIn to pass this value up, you need a function in your parent component that would get this value for you and pass it to the child component so it can be used, we will do this via props. We also need a variable in the parent's state that will store this value
import React, { Component } from "react";
import Greeting from "./Greeting";
import LogoutButton from "./LogoutButton";
import LoginButton from "./LoginButton";
class Login extends Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {
isLoggedIn: false,
username: ""
};
}
handleLoginClick() {
this.setState({ isLoggedIn: true });
}
handleLogoutClick() {
this.setState({ isLoggedIn: false });
}
// when you use arrow functions they automatically bind to your component
handleSubmitForm = (e, value) => {
// the following line prevents the page from refreshing when you submit any form
e.preventDefault()
this.setState({ username: value })
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} submitForm={this.submitForm}/>
{button}
</div>
);
}
}
export default Login;
when you click the submit button it should update the state in the LogIn component. Hope this helps
You need to do 2 things:
create a function which will handle changes of the input in Login component, send it to Greeting and then to LoginInfo. the function would look something like:
onChange = (event) => this.setState({event.target.name: event.target.value})
note: event.target.name equals to name prop of input. the function above will work only if the name of the state that stores that input value equals to the input name, in your case name.
send the state that stores the value of the input to the LoginInfo. the input element should look like this:
<input type="text" name="name" value={valueFromLogin} onChange={props.onChangeFromLogin} />
I'm trying to get data from a props.children[Input] to it's parent[Form], to build an all in one form component. I am currently stuck at getting data to Input component to Form component.
This is what i have done so far =>
edit.js =>
export default class Edit extends Component {
constructor(props) {
super(props)
this.state = {
formFields: {
name: '',
email: ''
}
}
}
render() {
return (
<Form
id={ this.props.match.params.id }
onReceiveFormData={ this.onFiledDataChange.bind(this) }
>
<Input
label='name'
name='name'
value='some'
/>
<Input
label='email'
name='email'
value='email#gmail'
/>
</Form>
);
}
}
Input.js =>
export default class Input extends Component {
constructor(props) {
super(props)
this.state = {
value: this.props.value
}
}
static setValue(val) {
return val ? val : ''
}
handleInputChange = (event) => {
this.setState({
value : event.target.value
})
this.props.onInputChange({
[event.target.name] : event.target.value
})
}
render() {
return (
<input
name={ this.props.name }
value={ Input.setValue(this.state.value) }
/>
);
}
}
Form.js =>
export default class Form extends Component {
constructor(props) {
super(props)
}
saveClick() {
/**
Save logic => I want to get the field data here
**/
}
render() {
return (<div>
{
this.props.children
}
<Button
onClick={ () => this.saveClick() }
>Save</Button>
</div>
);
}
}
For example, If post -> edit page and user -> edit page =>
I would like to do setting the input value inside the Form.js, Form.js will be responsible for setting the values inside those Inputs when it is mounted and it will be responsible for sending the data to the server.
I would like to the know, the scenario I have suggest is possible or is there a better way to do it?
Invert control (make Input a controlled component). Keeping state inside components unnecessarily is generally a bad design practice. Look at the existing <input> it takes a value and an onChange. Your component should do too.
const Input = ({
className,
...rest
}) => (
<input
className={classNames('Input', className)}
type="text"
{...rest}
/>
);
Then the parent has the state for when the form is submitted in Edit.
onFormSubmit = e => {
e.preventDefault();
console.log(this.state.name, this.state.email);
}
onChange = name => e => this.setState({ [name]: e.target.value })
// ...
<Input onChange={this.onChange('name')} value={this.state.name} />
<Input onChange={this.onChange('email')} value={this.state.email} />
If you want Form to listen in on state changes you could do this:
onChange = (e, originalOnChange) => {
console.log('onChange called:', e.target.value);
originalOnChange && originalOnChange(e);
}
// ...
const children = React.Children.map(this.props.children, child =>
React.cloneElement(child, { onChange: e => this.onChange(e, child.props.onChange) })
);
It's a bit unorthodox but technically achievable.
After I'm done creating an article via CreateArticle component (which works fine), I want to display only what the user has written (its value) in <textarea value={value}> in the SearchArticle component via displayName() function.
In other words, in CreateArticle component when the user's done typing something in <textarea/> followed by clicking Submit (which's a button that saves what the user wrote), I want to only display what the user has written in Search Article inside displayName() function.
What am I doing wrong and how can I fix it?
Here's CreateArticle:
import React, { Component } from 'react';
import {connect} from "react-redux";
import * as actionType from "../../store/actions/actions";
class CreateArticle extends Component {
constructor(props) {
super(props);
}
handleSubmit = event => {
this.setState({storyTextValue: event.target.value});
this.props.storyTextValueRedux(event.target.storyTextValue);
event.preventDefault();
}
handleStoryText = event => {
event.preventDefault();
this.setState({value: event.target.value});
}
onSubmit = () => {
if(this.state.value === "") {
alert("Please enter the value and then click submit");
} else {
alert("Article saved " + '\n' + this.state.value + this.props.articleIdValue);
}
}
render() {
return(
<div>
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.props.cityCodeValue} type="text" placeholder="city code"/>
<input type="text" placeholder="author name"/>
<textarea value={this.props.storyTextValue} onChange={this.handleStoryText} rows="2" cols="25" />
<button type="submit" value="Submit" onClick={() => this.onSubmit()}>Submit</button>
</form>
</div>
);
}
}
const mapStateToProps = state => {
return {
articleIdValue: state.articleIdValue.articleIdValue,
storyTextValue: state.storyTextValue.storyTextValue
};
};
const mapDispatchToProps = dispatch => {
return {
articleIdValueRedux: (value) => dispatch({type: actionType.ARTICLE_ID_VALUE, value}),
storyTextValueRedux: (value) => dispatch({type: actionType.STORY_VALUE, value})
};
};
export default connect(mapStateToProps, mapDispatchToProps)(CreateArticle);
Here's SearchArticle:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actionType from '../../store/actions/actions';
import ArticleText from '../../containers/ArticleText/ArticleText';
class SearchArticle extends Component {
constructor(props) {
super(props);
this.state = {
flag: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
this.props.CityCodeReducerRedux(event.target.value);
}
handleSubmit(event) {
this.setState({flag: true});
event.preventDefault();
}
displayName = () => {
if(this.props.cityCodeValue === "nyc" || this.props.articleIdValue === 1) {
return(
<div>
<p>author name: {this.props.authorNameValue}</p>
<p>article text: {<ArticleText/>}</p>
</div>
);
}
}
render() {
return(
<div>
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.props.cityCodeValue} type="text" placeholder="city code"/>
<input onChange={this.handleChange} value={this.props.articleIdValue} placeholder="article id"/>
<button onClick={() => this.displayName} value="Search">Submit</button>
{this.state.flag ? this.displayName() : null}
</form>
</div>
);
}
}
const mapStateToProps = state => {
return {
cityCodeValue: state.cityCodeValue.cityCodeValue,
authorNameValue: state.authorNameValue.authorNameValue,
articleIdValue: state.articleIdValue.articleIdValue
};
};
const mapDispatchToProps = dispatch => {
return {
CityCodeReducerRedux: (value) => dispatch({type: actionType.CITY_CODE_VALUE, value}),
articleIdValueRedux: (value) => dispatch({type: actionType.ARTICLE_ID_VALUE, value})
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SearchArticle);
Here's StoryTextReducer:
import * as actionType from '../store/actions/actions';
const initialState = {
storyTextValue: ''
};
const StoryTextReducer = (state = initialState, action) => {
switch (action.type) {
case actionType.STORY_VALUE:
return {
...state,
storyTextValue: action.value
};
default:
return state;
}
};
export default StoryTextReducer;
You're looking at 2 different instances of <ArticleText>. Changing properties of the instance in CreateArticle will not change the instance in SearchArticle
Instead of trying to share a value by using the same class, you should be sharing a value by whats in the redux store.
You should define an action that dispatches and event like {type: updateArticleText, articleText: foo}, then in your reducer you can set the redux state to have a property ArticleText equal to event.articleText.
Now that your value is stored in redux store rather than component state, your components simply get props from the redux store in the mapStateToProps function.
I am making a simple to-do list app in React. I have 3 states, inputText (the task the user enters), triggerAnimation(to trigger animations), and tasks (the list of tasks user has entered). However I don't know how to update the tasks state (which is an array) to push the new tasks. Here is the code.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
}
//The function triggered by button which sends the task the user has entered to the tasks array state:
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein', tasks:
this.state.inputText
})
}
render() {
//Where User enters task:
return (
<div className="App">
<main>
<div className="enterTask">
<input type="text" className="inputclass" id="textfield1"
placeholder='Enter a task.'
onChange={event => this.setState({
inputText: event.target.value })}
onKeyPress={event => {
if(event.key === 'Enter') {
this.addItem();
}
}}
/>
<br />
<br />
<button className="button"
onClick={() => this.addItem()} data-
toggle='fadein' data-target='list'>+
</button>
</div>
<!-- Where tasks will appear: -->
<div className="log">
<p className='list'>
<span class={this.state.triggerAnimation}>
{this.state.tasks}
</span>
</p>
<button className="button">-</button>
</div>
</main>
</div>
)
}
}
export default App;
However I don't know how to update the tasks state (which is an array) to push the new tasks.
Probably the cleanest way to "push to an array" in state is to use ES6 array spread. The best practice would also be to use the setState callback syntax to ensure the correct state is committed before you push the new task:
this.setState(prevState => ({
tasks: [...prevState.tasks, newTask]
}));
Seems like what you want is this..
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein',
tasks: this.state.tasks.concat(this.state.inputText)})
}
You can use .concat method to create copy of your array with new data:
addTask() {
this.setState({tasks: this.state.tasks.concat(["new value"])})
}
You also need to bind this to addTask in your constructor:
this.addTask = this.addTask.bind(this)
See my example:
https://jsfiddle.net/69z2wepo/103069/
Documentation: https://reactjs.org/docs/react-component.html#setstate
try this
import React from 'react';
class Todo extends React.Component {
constructor(props) {
super();
this.state = {
value: '',
items: []
}
}
onChange = e => this.setState({ value: e.target.value })
onEnter = e => {
if(e.charCode !== 13) return;
this.addItem();
};
onClick = e => {
this.addItem()
};
addItem = () => {
const { value } = this.state;
if(!!value.trim()) return;
this.setState(prev => ({ items: [...prev.items, value], value: '' }))
};
render() {
const { value } = this.state
return (
<div>
<div>
<input
type="text"
value={value}
name="abc"
onChange={this.onChange}
onKeyPress={this.onEnter}
/>
</div>
<button onClick={this.onClick}>Add</button>
</div>
)
}
}
FTFY better to just use comments in the code, regarding the problem(s) you want to get the tasks array then can concat the stuff to get a new array.
setState({tasks:this.state.tasks.concat([this.state.inputText])})
Wouldn't hurt to clean up the code some too... learning react myself the book "the road to learning react" has some good tips on how to set things up to be a bit more readable.
Edit actually put the right code here now...
With react, you're almost always going to have to store form field information in state (controlled components) so, how about turning todo task input field into a controlled component, like so:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
this.onInputChange = this.onInputChange.bind(this);
this.onInputKeyPress = this.onInputKeyPress.bind(this);
this.addItem = this.addItem.bind(this);
}
onInputChange(e) {
this.setState({ inputText: e.target.value });
}
onInputKeyPress(e) {
if (e.key === "Enter") {
this.addItem();
}
}
addItem() {
const itemToAdd = this.state.inputText;
const tasks = this.state.tasks;
this.setState({
inputText: "",
tasks: tasks.concat(itemToAdd);
});
}
render() {
const { inputText } = this.state;
return(
<div>
<input type="text" className="inputclass" id="textfield1" placeholder='Enter a task.'
value={inputText} onChange={this.onInputChange} onKeyPress={this.onInputKeyPress} />
<br />
<br />
<button className="button" onClick={this.addItem} data-
toggle='fadein' data-target='list'>+</button>
</div>
);
}
}
Notice how input state is controlled via component state