Search code examples
node.jsreactjsreact-reduxonchangereact-redux-form

onChange event is only occurring once when state is managed by redux


I am fetching an array with single object from redux store.

this.props.license :[0: {id: 24, domain: "xyz.com", app_url: "https...", purchase_code: "395"}]

And then creating a form to update the value in the react form.

But when trying to change the value the onChange event is only occurring once.

I am managing a new state in the react component to save the changes that I am doing on onChange event.

Is this a correct way in which I am coding?

import React ,{Component} from 'react';
import {connect} from 'react-redux';
import * as actionCreators from '../../store/actions/index';
import Spinner from '../../components/Spinner/Spinner';

const DATABASE_LABELS={
    id:'ID',
    domain:'Domain',
    app_url:'APP URL',
    purchase_code:'Purchase Code',

}

class editLicense extends Component {

    constructor(props){
        super(props);
        this.state={
            editLicense:{}
        }
    }
    onChangeHandler=(event, type)=>{

        // [event.target.name]=[event.target.value]
        let newLicense={...this.state.editLicense}
        newLicense[type]=event.target.value
        console.log(newLicense)
         console.log('before',this.state.editLicense)

        this.setState({
            editLicense :{
                ...this.state.editLicense,
                [event.target.name]:event.target.value
            }
        })
        console.log(this.state.editLicense)
    }

    componentDidMount=()=>{  
        this.props.viewLicenceDetails(this.props.token, this.props.match.params.id)
        this.setState({
            editLicense:this.props.licenses[0]
        })
        console.log(this.state.editLicense)
    }
    render(){
        let formdata=<Spinner/>;
        if(!this.props.loading){
            let license=Object.keys(this.props.licenses[0])
            .map(key=>{
                return [
                    key,
                    this.props.licenses[0][key]
                ]
            })
            let form=license.map((p, index)=>{
                return(
                <div className="form-group" key={p[0]}>
                    <label htmlFor={p[0]}> {DATABASE_LABELS[p[0]]} </label>
                    <input type="text" className="form-control" 
                            id={p[0]} 
                            value={p[1]} 
                            name={p[0]}
                            onChange={(event) => this.onChangeHandler(event, p[0])} />
                </div>)
            })

            formdata=(
                <form>
                    {form}
                    <button type="submit" className="btn btn-primary">Submit</button>
                </form>
            )
        }
        return(
            <div className="container">
                {formdata}
            </div>
    )}
}

const mapStateToProps = (state)=>{
    return({
        token:state.auth.idToken,
        licenses:state.license.licenses,
        loading:state.license.loading,
        err:state.license.error
    })
 }

 const mapDispatchToProps = dispatch=>{
    return({
         updateLicenseData:(token, type, newVal)=>dispatch(actionCreators.updateLicense(token, type, newVal)),
         viewLicenceDetails:(token, id)=>dispatch(actionCreators.fetchOneLicense(token, id))

 })
 }
 export default connect(mapStateToProps, mapDispatchToProps)(editLicense);

Solution

  • The question title is a little misleading. Currently, your state is not fully managed by Redux, but only initially fetched from the Redux state.

    You are currently:

    1. Fetching the Redux state (via props), and copying it to your component state in componentDidMount.
    2. Populating your input's value from the props (from the Redux state).
    3. Updating your local component state via onChange -> onChangeHandler.

    (2) is your problem currently. Your props are not currently being changed (since you're not calling any Redux actions), so the value of your input element is never changing. Its a little unintuitive, but this results in changes only being detected once.

    You need to populate your input value prop using your state. In your render() function, try replacing instances of this.props.licenses[0] with this.state.editLicense:

        if(!this.props.loading){
            let license=Object.keys(this.state.editLicense)
            .map(key=>{
                return [
                    key,
                    this.state.editLicense[key]
                ]
            })
            ...
        }
    

    This way, when your state is updated, the 'current' value of the form will be updated on re-render, and the input component will be fully controlled.

    As a side note:

    this.setState({
        editLicense:this.props.licenses[0]
    })
    console.log(this.state.editLicense)
    

    This is not guaranteed to work. setState should typically be considered asynchronous. If you want to do something in response to an updated state outside of the render cycle, you should provide a callback to setState:

    this.setState({
            editLicense:this.props.licenses[0]
        },
        () => console.log(this.state.editLicense)
    );