Reactjs Redux dispatcher for onChange event resets input field - reactjs

I'm trying to implement a simple login solution using redux pattern and react-router v4.
I'm saving the email input in global state by using onChange event. The function in onchange event dispatches action, passing e.target as its input.
However as soon as the dispatch occurs, the input field gets reset. I have tested the input field by commenting out the dispatcher and it works, thus concluding the dispatcher is somehow resetting input field.
class LoginForm extends React.Component {
constructor(props) {
super(props)
this.state = this.props.store.getState()
}
handleSignup(){
this.setState({showSignup:!this.state.showSignup})
console.log(this.state.showSignup);
}
validatorDispatch(e){
e.preventDefault()
var target = e.target
this.props.store.dispatch(input_validator_action(target));
}
render(){
let emailValidationClassName = "form-control"
let passwordValidationClassName = "form-control"
let confirmPasswordValidationClassName = "form-control"
if (this.state.password){
passwordValidationClassName="form-control " + (this.state.isPasswordValid ? "is-valid":"is-invalid")
}
if (this.state.email){
emailValidationClassName="form-control " + (this.state.isEmailValid ? "is-valid":"is-invalid")
}
if (this.state.confirmPassword){
confirmPasswordValidationClassName="form-control " + (this.state.doesPasswordMatch ? "is-valid":"is-invalid")
}
return (<div>
<div className="row m-5">
<div className="col-md-4"></div>
<div className="col-md-4">
<div className="form-group">
<label htmlFor="emailAddress">Email address: </label>
<input type="email" className={emailValidationClassName} name="email" ref="email" onChange={(e)=>this.validatorDispatch(e)} placeholder="Enter email"/>
{this.state.isEmailValid ? null
: <div className="invalid-feedback">
Please provide a valid email address.
</div>}
</div>
<div className="form-group">
<label htmlFor="password">Password: </label>
<input type="password" className={passwordValidationClassName} name="password" ref="password" onChange={this.validatorDispatch.bind(this)} placeholder="Password"/>
{this.state.isPasswordValid ? null
: <div className="invalid-feedback">
Password length should be greater than 8, and contain lowercase, uppercase, digits and special symbols.
</div>}
<Link to="/forgotpassword"><small>Forgot Password?</small></Link>
</div>
<div className="form-check">
<label className="custom-control custom-checkbox">
<input type="checkbox" className="custom-control-input" checked={this.state.showSignup} onChange={this.handleSignup.bind(this)}/>
<span className="custom-control-indicator"></span>
<span className="custom-control-description">I am a new User</span>
</label>
</div>
<button className="btn btn-info">Submit</button>
</div>
<div className="col-md-4"></div>
</div>
</div>
)
}
}
I'd really appreciate your insights.
Edited validatorDispatch function below
var input = e.target.value
var name = e.target.name
this.props.store.dispatch(input_validator_action(input,name));
My reducer looks like this
import validator from 'email-validator';
var passwordValidator = require('password-validator')
var schema = new passwordValidator();
schema
.has().symbols()
.is().min(8)
.has().uppercase()
.has().lowercase()
.has().digits()
.has().not().spaces()
const initialState = {}
function input_validator(action,state){
let input = action.payload.textInput
let name = action.payload.textName
switch (name) {
case "email":
if(validator.validate(input)){
return Object.assign({},state,{isEmailValid:true,email:input})
}
else{
return Object.assign({},state,{isEmailValid:false,email:input})
}
case "password":
if(schema.validate(input)){
return Object.assign({},state,{isPasswordValid:true,password:input})
}
else{
return Object.assign({},state,{isPasswordValid:false,password:input})
}
case "confirmPassword":
if(input===state.password){
return Object.assign({},state,
{doesPasswordMatch:true,confirmPassword:input})
}
else{
return Object.assign({},state,
{doesPasswordMatch:false,confirmPassword:input})
}
default:
return state
}
}
export default (state=initialState, action) => {
switch(action.type){
case 'INPUT_VALIDATOR':
return input_validator(action,state)
default:
return state
}
}
Action
export const input_validator_action = (input,name) =>{
return{
type:'INPUT_VALIDATOR',
payload:{
textInput:input,
textName:name
}
}
}

I finally figured it out. It helped me understand React Router v4 even better. I was rendering my LoginForm class file (named login.js) from a different file (named EntryRoute.js) as a route using component prop.
i.e.
<Switch>
<Route exact path="/" component={EnteryButton}/>
<Route path="/login" component={()=><Login store={props.store}/>} />
</Switch>
Instead it should be done using render prop.
as in-
<Route path="/login" render={()=><Login store={props.store}/>} />
The concept behind it is that when you pass inline function to component prop, it unmounts and mounts the passed function at every state change. So that's why my input field was being reset, since value inside the field was used inside global redux state.

Related

Setting the default value of an input field after data is retrieved causes the content to overlap and the "onChange" event not to be triggered

I have an "edit category" component in my React application.
The ID is passed through the URL.
When the component is mounted, the action "fetchCategory" is called, which updates the props on the component with the current category.
I have a form which I want to be pre-populated, which I'm currently doing using the defaultValue on the input.
However, this isn't reflected on the state and the label for the text field overlaps the input field.
Any help would be greatly appreciated. I'll leave snippets of my code below which could help with understanding what I'm trying to do.
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchCategory } from "../../store/actions/categoryActions";
class AddOrEditCategory extends Component {
componentDidMount() {
this.props.fetchCategory(this.props.match.params.id);
if (this.props.match.params.id) {
this.setState({
_id: this.props.match.params.id
});
}
}
handleSubmit = e => {
e.preventDefault();
console.log(this.state);
};
handleChange = e => {
this.setState({
[e.target.id]: e.target.value
});
};
render() {
const addingNew = this.props.match.params.id === undefined;
return (
<div className="container">
<h4>{addingNew ? "Add category" : "Edit category"}</h4>
<form onSubmit={this.handleSubmit}>
<div className="input-field">
<input
type="text"
id="name"
defaultValue={this.props.category.name}
onChange={this.handleChange}
/>
<label htmlFor="name">Category name</label>
</div>
<div className="input-field">
<input
type="text"
id="urlKey"
onChange={this.handleChange}
defaultValue={this.props.category.urlKey}
/>
<label htmlFor="urlKey">URL Key</label>
</div>
<button className="btn">{addingNew ? "Add" : "Save"}</button>
</form>
</div>
);
}
}
const mapStateToProps = state => {
return {
category: state.categoryReducer.category
};
};
export default connect(
mapStateToProps,
{ fetchCategory }
)(AddOrEditCategory);
EDIT: Included whole component as requested
You need to replace the 'defaultValue' attribute with 'value' in the inputs.
You are using a controlled vs uncontrolled component. You dont need to use defaultValue.
You can set the initial values on the promise success for fetchCategory
componentDidMount() {
this.props.fetchCategory(this.props.match.params.id).then(response => {
// Set the initial state here
}
}
OR in
componentWillReceiveProps(nextProps) {
// Compare current props with next props to see if there is a change
// in category data received from action fetchCategory and set the initial state
}
React docs
<form onSubmit={this.handleSubmit}>
<div className="input-field">
<input
type="text"
id="name"
onChange={this.handleChange}
value={this.state.name} //<---
/>
<label htmlFor="name">Category name</label>
</div>
<div className="input-field">
<input
type="text"
id="urlKey"
onChange={this.handleChange}
value={this.state.urlKey}
/>
<label htmlFor="urlKey">URL Key</label>
</div>
<button className="btn">{addingNew ? "Add" : "Save"}</button>
</form>

How can I get an input's value on a button click in a Stateless React Component?

I have the following functional component
const input = props => (
<div>
<input placeholder="Type a message..." />
<div onClick={props.send} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
How could I possibly pass the value of the input to props.send()?
I found a solution for this exact scenario on React's official docs: https://reactjs.org/docs/refs-and-the-dom.html#refs-and-functional-components
This approach allows your component to remain stateless and also doesn't require you to update the parent component on every change.
Basically,
const input = props => {
let textInput = React.createRef();
function handleClick() {
console.log(textInput.current.value);
}
return (
<div>
<input ref={textInput} placeholder="Type a message..." />
<div onClick={handleClick} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
}
Edit May 2021: Since this answer seems to be getting some attention, I have updated the answer to use a hooks based approach as well, since that is what I would use now (If using React 16.8 and above).
const input = props => {
const [textInput, setTextInput] = React.useState('');
const handleClick = () => {
console.log(textInput);
props.send(textInput);
}
const handleChange = (event) => {
setTextInput(event.target.value);
}
return (
<div>
<input onChange={handleChange} placeholder="Type a message..." />
<div onClick={handleClick} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
}
There are many ways to do it since you're very much concerned about performance. Here is the implementation, your component will be rendered only when you click on send button which actually means state will be updated once and input value will be displayed in parent component.
const Input = props => {
return (
<div>
<input onChange={props.changeHandler} placeholder="Type a message..." />
<button onClick={props.send}>send</button>
</div>
);
};
class App extends Component {
state = {
inputValue: ""
};
inputValue = '';
send = () => {
this.setState({ inputValue: this.inputValue });
};
changeHandler = event => {
this.inputValue = event.target.value;
};
render() {
console.log("In render");
return (
<React.Fragment>
<Input changeHandler={this.changeHandler} send={this.send} />
<div> {this.state.inputValue}</div>
</React.Fragment>
);
}
}
Since you mentioned that you just started with React, I'd suggest that you work through the documentation (which offers nice explanation).
According to your comment, the usage of a functional component is not a requirement. Therefore I'd recommend to do it that way -->
Your CustomInput component:
import React from "react";
import PropTypes from "prop-types";
class CustomInput extends React.Component {
constructor() {
super();
this.textInput = React.createRef();
}
static propTypes = {
send: PropTypes.func
};
render() {
const { send } = this.props;
return (
<React.Fragment>
<input placeholder="Type a message..." ref={this.textInput} />
<div
onClick={() => send(this.textInput.current.value)}
className="icon"
>
CLICK ME
</div>
</React.Fragment>
);
}
}
export default CustomInput;
If you noticed, I've replaced the empty div with React.Fragment. In that case you can omit the unnecessary <div> wrappings (if those are not required) which will keep your DOM clean (Read more about it here.
Usage:
<CustomInput
send={(prop) => {
console.log(prop)
}}
/>
I just used a dummy function which will log the input value to the console..
You can check the working example (Make sure to trigger the console in the editor) here
Posting this answer, If incase someone is using an earlier release of React 16.3. We can achieve the same thing by using callback refs instead without having to maintain state or having onChange event handler:
const input = props => (
<div>
<input ref={props.myRef} placeholder="Type a message..." />
<div onClick={props.send} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
Calling that Input Component
handleClick = () => console.log(this.inputRef.value);
<Input myRef={el => this.inputRef = el} send={this.handleClick} />

ReactJS search bar child to parent

I have a search bar component which is viewed on all pages of a project. Is there a way that I can have the same search bar component but have a property that I could pass so that it knows which data to search through. My search component is <SearchBar type="PASS THIS VALUE" /> and this is rendered:
import React, { Component } from 'react';
class SearchBar extends Component {
render () {
function handleClick(e) {
e.preventDefault();
console.log("Clicked!!!");
}
return (
<div className="row main-search">
<div className="column">
<form action="">
<fieldset>
<label htmlFor="search">
<input type="text" placeholder="Start typing..." id="search-box" />
</label>
</fieldset>
</form>
</div>
<div className="column">
<div className="float-right"><button className="add-new" onClick={handleClick}>Add New</button></div>
</div>
</div>
)
}
}
export default SearchBar;
If this isn't possible is there a way that the searchBar can know which page the user is on and know which data to search through?
Update:
I am using a router element and the searchBar component is located inside parent App.js. I'm using the router like:
<SearchBar />
<Route
exact path="/" children={({ match, ...rest }) => (
<TransitionGroup component={firstChild}>
{match && <TourList {...rest} />}
</TransitionGroup>
)}/>
You can access the props through this.props and set the value of input accordingly, but beware that the component will become controlled by react flow, you won't be able to change the input, thus you need onChange event handler for that and controlled local state version of that prop instead.
It may go like this:
import React, { Component } from 'react';
class SearchBar extends Component {
state = { // pass default value from "type" prop
type: this.props.type
};
// making sure we don't call setState excessively
// as per https://reactjs.org/docs/react-component.html#componentwillreceiveprops
componentWillReceiveProps(nextProps) {
if (nextProps.type !== this.props.type) {
this.setState({ type: nextProps.type });
}
}
// pulling out handleClick function from render()
// to avoid creating it on every render call
handleClick = (e) => {
e.preventDefault();
console.log("Clicked!!!");
}
// making sure we update our controlled component state
// so we can actually type and get the change back in the value
// more info here: https://reactjs.org/docs/forms.html#controlled-components
handleChange = e => {
this.setState({ type: e.target.value });
};
render () {
return (
<div className="row main-search">
<div className="column">
<form action="">
<fieldset>
<label htmlFor="search">
<input type="text"
placeholder="Start typing..."
id="search-box"
onChange={this.handleChange}
// listening for our local and controlled version
// of "type" prop that is now reacting both to
// parent prop change and to local input change through typing
value={this.state.type}/>
</label>
</fieldset>
</form>
</div>
<div className="column">
<div className="float-right">
<button className="add-new" onClick={handleClick}>Add New</button>
</div>
</div>
</div>
)
}
}
export default SearchBar;

react js - how to populate values based on another DOM element value

I have 2 input boxes. Based on one input(1) box value (on key up event), I am populating another input(2) box value. Currently, I am using document.getElementByID option to retrieve element id to populate the values. Is it recommended in react js ? pls suggest. Like to find a better way to to this in react js.
handleChange(e) {
if(document.getElementById("getId").value.length > 4) {
console.log("a")
document.getElementById("getName").value =
document.getElementById("getId").value
}
}
render () {
return (
<div>
<Card>
<div>
<label>Id</label>
<input type="text" id="getId" onKeyUp= {this.handleChange.bind(this)}/>
<div>
<label>Name</label>
<input type="text" id="getName" readOnly/>
</div>
</div>
</Card>
</div>
);
You could store the value of the first input box in your component state and set the value of the second input box to the value from the state.
Then when the value of the input box changes, update the state, using the handleChange method, which in turn re-renders the component updating the second input box.
...
constructor(props) {
super(props)
this.state = {
inputText: ''
}
this.handleChange = this.handleChange.bind(this)
}
handleChange({ target }) {
if (target.value.length > 4) {
this.setState({
inputText: target.value
})
}
}
render () {
return (
<div>
<Card>
<div>
<label>Id</label>
<input type="text" id="getId" onKeyUp={ this.handleChange } />
<div>
<label>Name</label>
<input type="text" id="getName" value={ this.state.inputText } />
</div>
</div>
</Card>
</div>
)
}
...
You can handle issue with two ways.
First way is to use React refs and DOM.
So in code below I have done two things, I have added ref props to getName input and accessed it from handleChange method by this.refs.inputOfName', as well ase.targetas your DOM input without accessing again bygetElementById`.
handleChange(e) {
let value = e.target.value;
if (value.length > 4) {
this.refs.inputOfName.value = value
}
}
render() {
return (
<div>
<Card>
<div>
<label>Id</label>
<input type="text" id="getId" onKeyUp=
{this.handleChange.bind(this)} />
<div>
<label>Name</label>
<input type="text" id="getName" ref="inputOfName" readOnly />
</div>
</div>
</Card>
</div>
);
You can read more about refs here.
Second way is to use states.
I suggest to use states because it's more React "style" approach as well as one of the React advantages, so spend more time learning about state and lifecycle of React.
You can read more about states here.
handleChange(e) {
let value = e.target.value;
if (value.length > 4) {
this.setState({
name: value
});
}
}
render() {
return (
<div>
<Card>
<div>
<label>Id</label>
<input type="text" id="getId" onKeyUp=
{this.handleChange.bind(this)} />
<div>
<label>Name</label>
<input type="text" id="getName" value={this.state.name} readOnly />
</div>
</div>
</Card>
</div>
);
}
As already mention, It's not common to user getElementById within react component, think of what will happen if you will have 2 components rendered.
You can use component state to update your elements.
constructor(props, context) {
super(props, context);
// This will represent your component state to hold current input value.
this.state = { value: "" };
// Do handler bindings in one place and not inside the render
// function as it will create a new function reference on each render.
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({value: e.target.value});
}
render () {
return (
<div>
<Card>
<div>
<label>Id</label>
<input type="text" id="getId" onKeyUp={this.handleChange}/>
<div>
<label>Name</label>
<input type="text" value={this.state.value} readOnly/>
</div>
</div>
</Card>
</div>
);
}

React/Redux argument becomes undefined after passed to action

I have a simple search bar that has an onChange method to search/filter data.
class ItemSearch extends React.Component {
constructor(){
super();
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(e){
const searchTerm = this.search.value;
console.log(searchTerm);
this.props.fetchSearch(searchTerm);
}
render() {
return (
<div>
<form>
<input type='text' ref={(input) => {this.search = input}} onChange={this.handleOnChange}></input>
</form>
<div>
{this.props.data.map((item, i) => {
return <ListItem
key={i}
item={item}
/>
})}
</div>
</div>
)
}
}
Logging out the search term gives me the correct value. However, after it is passed to the redux action creator it becomes undefined. My action is pretty simple, something like this.
export default function fetchSearch(value){
console.log(value);
return {
type: 'FETCH_SEARCH',
payload: value
}
}
In this log it has become undefined. I assume it has something do to with it being out of scope, but without passing arguments to action creators, there is no way of lifting state up to the store from forms anyway. I have seen this exact thing working in many tutorials and posts so I am at a bit of a loss, although it is probably something silly I missed.
Thanks.
I just forgot to update the mapDipatchToProps in the container.
Once I did that it works fine. Thanks all!
Yes you have made a mistake here. Firstly you have to use redux form to get this thing done. There you have to connect this with the reducer or store like below.
import React, { Component } from 'react';
import { reduxForm } from 'redux-form';
import { createTimer } from '../actions/index';
class TimerNew extends Component {
onSubmit(props){
this.props.createTimer(props);
}
render() {
const { fields: { hours, minutes, seconds, label }, handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.props.createTimer)}>
<h5>Create A New Timer</h5>
<div className={`form-group ${hours.touched && hours.invalid ? 'has-danger' : ''}`}>
<label>Hours</label>
<input type="text" className="form-control" {...hours} />
<div className="text-help">
{hours.touched ? hours.error : ''}
</div>
</div>
<div className={`form-group ${minutes.touched && minutes.invalid ? 'has-danger' : ''}`}>
<label>Minutes</label>
<input type="text" className="form-control" {...minutes} />
<div className="text-help">
{minutes.touched ? minutes.error : ''}
</div>
</div>
<div className={`form-group ${seconds.touched && seconds.invalid ? 'has-danger' : ''}`}>
<label>Seconds</label>
<input type="text" className="form-control" {...seconds} />
<div className="text-help">
{seconds.touched ? seconds.error : ''}
</div>
</div>
<div className={`form-group ${label.touched && label.invalid ? 'has-danger' : ''}`}>
<label>Label</label>
<input type="text" className="form-control" {...label} />
<div className="text-help">
{label.touched ? label.error : ''}
</div>
</div>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
);
}
}
function validate(values) {
const errors = {};
if (!values.hours) {
errors.title = 'Enter hours';
}
if (!values.minutes) {
errors.categories = 'Enter minutes';
}
if (!values.seconds) {
errors.content = 'Enter seconds';
}
if (!values.label) {
errors.content = 'Enter label';
}
return errors;
}
// connect: first argument is mapStateToProps, 2nd is mapDispatchToProps
// reduxForm: 1st is form config, 2nd is mapStateToProps, 3rd is mapDispatchToProps
export default reduxForm({
form: 'TimerNewForm',
fields: ['hours', 'minutes', 'seconds', 'label'],
validate
}, null, { createTimer })(TimerNew);
Here you have to wrap your form with the call to the reduxForm helper and pass in the action creator in my case it is createTimer. Also note that the code may differ based on the redux-forms version you are using. Here I am using 4.3.*. You may check out the redux-forms documentation here for further details. Hope this helps. Happy coding.

Resources