Search code examples
reactjsreduxreact-reduxreact-state-management

State is not passing correctly with redux


Hey i have two form that i want to keep there state seperatly, i added redux and added reducer to keep the state change, but my problem is when i change on form state it's add to the other form state his state also, i want to keep each state seperately enter image description here

like you see in picture the replace property have been added to the newProjectForm when it only need to be at the roof form.

I have the same reducer on forms because i only need to keep track only onInputChange.

This are my two form: First Form:

import React from 'react'
import {Form, FormGroup, ControlLabel, FormControl, HelpBlock, Button, ButtonToolbar, 
    Toggle } from 'rsuite';
import  InputPicker  from '../input-picker';
import './form.css'
import  {onInputChanged}  from '../../redux/actions/form.actions';
import { connect } from 'react-redux';

//onChange = {props.onInputChanged} formValue = {props.newProjectForm}
const options = ["yes","no"];


function NewProjectForm(props){
    return (
<Form className='form' layout="horizontal" >
    <h1 className='title'>New Project</h1>
    <br/>
    <FormGroup>
      <ControlLabel>Address</ControlLabel>
      <FormControl name="address"  />
      <HelpBlock tooltip>Required</HelpBlock>
    </FormGroup>
    <FormGroup>
      <ControlLabel>Affilate</ControlLabel>
      <InputPicker name="affilate" data ={options}  onChange={props.onInputChanged} style={{width:'300px'}}/>
      <HelpBlock tooltip>Required</HelpBlock>
    </FormGroup>
    <FormGroup>
      <ControlLabel>Size</ControlLabel>
      <FormControl name="size" type="number" />
    </FormGroup>
    <FormGroup>
      <ControlLabel>Bedroom Amount</ControlLabel>
      <FormControl name="bedrooms" type="number" />
    </FormGroup>
    <FormGroup>
      <ControlLabel>Bathrooms amount</ControlLabel>
      <FormControl name="bathrooms" type="number" />
    </FormGroup>
    <FormGroup>
      <ControlLabel>Stories</ControlLabel>
      <FormControl name="stories" type="number" />
    </FormGroup>
    <FormGroup>
      <ControlLabel>Has Gas</ControlLabel>
      <Toggle name="gas"/>
    </FormGroup>
    <FormGroup>
      <ButtonToolbar>
        <Button appearance="primary">Save</Button>
        <Button appearance="default">Discard</Button>
      </ButtonToolbar>
    </FormGroup>
  </Form>
    );
}


const mapStateToProps = (state) => {
  return {
    newProjectForm: state.newProjectForm
  }
};

const mapDispatchToProps = (dispatch) => {
  return {
    onInputChanged: (event) => dispatch(onInputChanged(event))
  }

};


export default connect(
  mapStateToProps,
  mapDispatchToProps
)(NewProjectForm);

Second Form:

import React from 'react'
import {Form, FormGroup, ControlLabel, FormControl, HelpBlock, Button, ButtonToolbar} from 'rsuite';
import './form.css'
import  {onInputChanged}  from '../../redux/actions/form.actions';
import { connect } from 'react-redux';
import  InputPicker  from '../input-picker';



function RoofForm(props){

//replace all-roof type description color

const options = ["yes","no"];
// console.log(props);
//onChange={props.onInputChanged}
  return (
<Form className='form' layout="horizontal" onChange = {props.onInputChanged} formValue = {props.roofForm}>
    <h1 className='title'>Roof</h1>
    <br/>
    <FormGroup>
      <ControlLabel>Replace ?</ControlLabel>
      <InputPicker name="replace" style={{ width: 300 }} data={options} onChange={props.onInputChanged}/>
      <HelpBlock tooltip>Required</HelpBlock>
    </FormGroup>
    <FormGroup>
      <ControlLabel>All Roof?</ControlLabel>
      <InputPicker name="all-roof" style={{ width: 300 }} data={options}  onChange={props.onInputChanged}/>
      <HelpBlock tooltip>Required</HelpBlock>
    </FormGroup>
   {props.roofForm['all-roof'] === 'yes' && (<div><FormGroup>
      <ControlLabel>Type</ControlLabel>
      <InputPicker name="type" style={{ width: 300 }}  />
    </FormGroup>
    <FormGroup>
      <ControlLabel>Description</ControlLabel>
      <InputPicker name="description" style={{ width: 300 }}  />
    </FormGroup>
    {props.roofForm.type === 'shingles' && <FormGroup>
                <ControlLabel>Color</ControlLabel>
                <InputPicker name="color" style={{ width: 300 }} />
    </FormGroup>}
    </div>)
    }
    <FormGroup>
      <ControlLabel>Rain diverter</ControlLabel>
      <FormControl name="rain-diverter" type="number" style={{ width: 300 }} />
    </FormGroup>
    <FormGroup>
      <ControlLabel>Drip edge</ControlLabel>
      <FormControl name="drip-edge" type="number" style={{ width: 300 }}/>
    </FormGroup>
    <FormGroup>
      <ButtonToolbar>
        <Button appearance="primary">Save</Button>
        <Button appearance="default">Discard</Button>
      </ButtonToolbar>
    </FormGroup>
  </Form>
    );
}

const mapStateToProps = (state) => {
  return {
    roofForm: state.roofForm
  }
};

const mapDispatchToProps = (dispatch) => {
  return {
    onInputChanged: (event) => dispatch(onInputChanged(event))
  }

};


export default connect(
  mapStateToProps,
  mapDispatchToProps)
 (RoofForm);

and this is my redux setup:

Form Actions:
export const ON_INPUT_CHANGED = 'ON_INPUT_CHANGED';

export function onInputChanged(event) {
    console.log(event);
    return(
        {
            type: ON_INPUT_CHANGED,
            payload: event
        }
    )
}

and this is my reducer:

import {ON_INPUT_CHANGED} from '../actions/form.actions';



function formReducerWrapper(defaultState){
    return (state=defaultState, action) => {
        switch(action.type){
            case ON_INPUT_CHANGED:
                state = {
                    ...state,
                    ...action.payload
                }
                break;

                default:
                    return state;
        }
        return state;
    }
}

const newProjectDefault = {
    affilate: {label:"", value: ""}
}

const roofDefault ={
    replace:{label:"",value:""}, 
    "all-roof":{label:"",value:""}, 
    type:{label:"",value:""}, 
    description: {label:"",value:""},
    color:{label:"",value:""}
}


export const newProjectReducer = formReducerWrapper(newProjectDefault);
export const roofReducer = formReducerWrapper(roofDefault);

Thanks in Advance:)


Solution

  • It looks like you are trying to merge the input change event directly into redux state. That is not how it works.

    You can access the HTML input element that changed from the change event using event.target and then read the latest value event.target.value.

    If you want the form data to stay separate, then you need to dispatch different information for each form. For example:

    dispatch({ type: 'input-changed', form: 'newProject', field: 'affiliate', value: event.target.value });
    

    In your reducers, it should skip any events for a different form.

    if (event.form !== 'newProject') return state;
    

    If it becomes tedious there are helper packages like redux-form to help structure the application to avoid repetitive code. It also works fine to store form data in local state instead of putting it into redux.