What state data do I need to pass to mapStateToProps

I am trying to put in a form component in my app using Redux for the first time and I am trying to get my head around what I have to create Reducers / Actions for.

In other components I have my user and messages passed into mapStateToProps and they work correctly. However in this component I am pulling data from my backend for table fields in the componentDidMount method and I am not sure if it is only data that is to be changed that get stored in Redux.

Do I need to create a reducer for the form as well? or does it get posted straight to the backend / node / postgresql. I intend to have a table that updates with all the most recent data so I can see the logic of it being automatically added to retrieved data.

I am pretty new to React / JavaScript so my logic may be a bit off so any advice would be appreciated.

diveLogForm.component.js

export class DiveLogForm extends Component  {

        constructor(props){

            super(props);
            this.handleSubmitDive = this.handleSubmitDive.bind(this);
            this.onChangeDiveType = this.onChangeDiveType.bind(this);
            this.onChangeSchoolName = this.onChangeSchoolName.bind(this);
            this.onChangeCurrent = this.onChangeCurrent.bind(this);
            this.onChangeVisibility = this.onChangeVisibility.bind(this);
            this.onChangeDiveDate = this.onChangeDiveDate.bind(this);
            this.onChangeMaxDepth = this.onChangeMaxDepth.bind(this);
            this.onChangeDiverUserNumber = this.onChangeDiverUserNumber.bind(this);
            this.onChangeVerifiedBySchool = this.onChangeVerifiedBySchool.bind(this);
            this.onChangeDiveNotes = this.onChangeDiveNotes.bind(this);
            this.onChangeDivePoint = this.onChangeDivePoint.bind(this);

            this.state = {
                diveTypeID: "",
                diveSchoolNameID: "",
                diveCurrentID: "",
                diveVisibilityID: "",
                diveDate: "",
                diveMaxDepth: "",
                diverUserNumber: "",
                diveVerifiedBySchool: "",
                diveNotes: "",
                divePoint: "",
                currentList: [],
                regionList: [],
                diveTypeList: [],
                visibilityList: [],
                diveSpotList: [],
                currentUser: [],
                loading: false,
            };
        }

        componentDidMount() {
            pullCurrentFields().then((response) => {
                const { data } = response;
                this.setState({ currentList: data.data });
            });
            pullRegionFields().then((response) => {
                const { data } = response;
                this.setState({ regionList: data.data });
            });
            pullDiveTypeFields().then((response) => {
                const { data } = response;
                this.setState({ diveTypeList: data.data });
            });
            pullVisibilityFields().then((response) => {
                const { data } = response;
                this.setState({ visibilityList: data.data });
            });
            pullDiveSpotFields().then((response) => {
                const { data } = response;
                this.setState({ diveSpotList: data.data });
            });

            //this.props.userDiveLogList();
        }

        onChangeDiveType(e) {
            this.setState({
                diveTypeID: e.target.value,
            });

        }

        onChangeSchoolName(e) {
            this.setState({
                diveSchoolNameID: e.target.value,
            });
        }

        onChangeCurrent(e) {
            this.setState({
                diveCurrentID: e.target.value,
            });
        }

        onChangeVisibility(e){
            this.setState({
                diveVisibilityID: e.target.value,
            });
        }

        onChangeDiveDate(e) {
            this.setState({
                diveDate: e.target.value,
            });
        }

        onChangeMaxDepth(e){
            this.setState({
                diveMaxDepth: e.target.value,
            });
        }

        onChangeDiverUserNumber(e){
            this.setState({
                diverUserNumber: e.target.value,
            });
        }

        onChangeVerifiedBySchool(e){
            this.setState({
                diveVerifiedBySchool: e.target.value,
            });
        }

        onChangeDiveNotes(e) {
            this.setState({
                diveNotes: e.target.value,
            });
        }

        onChangeDivePoint(e){
            this.setState({
                divePoint: e.target.value,
            });
        }

        handleSubmitDive(e) {

            e.preventDefault();

            this.setState({
                loading: true,
            });
            this.form.validateAll();

            //const {dispatch, history} = this.props;

            if (this.checkBtn.context._errors.length === 0) {
                this.props
                    .dispatch(registerUserDive(

                        this.state.diveTypeID,
                        this.state.diveSchoolNameID,
                        this.state.diveCurrentID,
                        this.state.diveVisibilityID,
                        this.state.diveDate,
                        this.state.diveMaxDepth,
                        this.state.diverUserNumber,
                        this.state.diveVerifiedBySchool,
                        this.state.diveNotes,
                        this.state.diveNotes))

                    .then(() => {
                        window.history.push("/divelogtable");
                        window.location.reload();
                    })
                    .catch(() => {
                        this.setState({
                            loading: false
                        });
                    });
            }
        }


    render() {

        const { classes } = this.props;
        const { user: currentUser } = this.props;

        if (this.state.currentList.length > 0) {
            console.log("currentList", this.state.currentList);
        }
        if (this.state.regionList.length > 0) {
            console.log("regionList", this.state.regionList);
        }
        if (this.state.diveTypeList.length > 0) {
            console.log("diveTypeList", this.state.diveTypeList);
        }
        if (this.state.visibilityList.length > 0) {
            console.log("visibilityList", this.state.visibilityList);
        }
        if (this.state.diveSpotList.length > 0) {
            console.log("diveSpotList", this.state.diveSpotList);
        }         

        return (

 ...materialUI form code

function mapStateToProps(state){
    const { user } = state.auth;
    const { regionList } = state.region;
    const { currentList } = state.current;
    const { diveTypeList } = state.diveType;
    const { visibilityList } = state.visibility;
    const { diveSpotList } = state.diveSpot;
    return {
        user,
        regionList,
        currentList,
        diveTypeList,
        visibilityList,
        diveSpotList,
    };
}

export default compose(
    connect(
        mapStateToProps,
    ),
    withStyles(useStyles)
)(DiveLogForm);

I am pretty new to React / JavaScript so my logic may be a bit off so any advice would be appreciated.

Yeah, React is weird. And then redux is a whole different kind of weird. Have you done the FCC curriculum for React and Redux?

I am trying to put in a form component in my app using Redux for the first time and I am trying to get my head around what I have to create Reducers / Actions for.

You have to create a reducer for every leaf on your redux state tree. You need an action for every way you want to permute the state in that leaf reducer. The action is an object with at least a type . Often we use action creators, a function that returns an action object.

In other components I have my user and messages passed into mapStateToProps and they work correctly.

You don’t pass anything into mstp - redux (connect will pass in the entire state tree).

And just to avoid confusion here - mstp will get the entire state tree. If you have only one reducer, then it will get the same state tree. If you have more than one reducer (using combineReducer) then the reducer will only get the leaf of that tree that it manages.

However in this component I am pulling data from my backend for table fields in the componentDidMount method and I am not sure if it is only data that is to be changed that get stored in Redux.

That is a common pattern. The data is “empty” when the component mounts (make sure your UI can handle that) and then in CDM, you make your asynchronous data calls. (You’re doing them in parallel, nice.) [Edit: actually, I misunderstood what is going on here, see below.]

The issue depends on what you want to do with that data. Do you want to store it on the component state or the redux state (I wish redux had chosen a different word to avoid this confusion.) Right now you are storing it on the component state. Depending on what your needs are, this could be a valid option. Why might someone want to store it on redux state instead? If that data is needed somewhere in the app, or needs to get persisted, or whatever. This is API data - I would probably persist it by default. I generally only use component state for things that are very specific to that component, like a toggle state or a controlled input. Things that have to do with the large app, I tend to put into the redux state, but others may draw that line somewhere else.

If you are going to store them in redux state, then instead of calling setState you would call a dispatching function (set up with mapDispatchToProps). This would fire off an action creator which would send an action object through all the reducers. The reducer that is listening for that action type would catch that and return the new reducer state which would cause redux to update it on its tree. That would cause mstp to fire and it would map the state values that this component needs and inject it into this component as props. This will cause the component to rerender, using the new values. But of course, these values would be on props, not the component state. So you wouldn’t access them as this.state.visibilityList but as this.props.visibilityList, assuming that is how you map it.

Do I need to create a reducer for the form as well?

Not necessarily specifically for the form. Some form libraries will do that automatically, in their own section of the redux state, but you don’t have to. I would probably at least store it in some kind of reducer that handles user data.

Wait, never mind, I misunderstood, I didn’t see that you’d redacted your MUI code and that this is just MUI form. I considered deleting what I wrote, but it is good info so I left it. I thought that cdm was making API calls - I didn’t look very closely.

So, then my question would be why you are trying to get the form data in CDM? Wouldn’t you want to get it after you submit? I assume that if you want, you can call a dispatching function there to store it on redux state.

or does it get posted straight to the backend / node / postgresql.

Depending on what your app needs, it may submit this data to the backend, it may save it in component state, it may save it in redux state, or it may do some combination of these.

I see in the docs that it can be wired up to redux. I would presume that it adds it to the redux state tree. You can check it in your redux dev tools or by putting a log statement in mstp, console.log(state).

One little suggestion, you don’t really need a constructor here. React will just use a default constructor. You need to initialize state and bind this. You can set state directly as a class property:

  export class DiveLogForm extends Component  {
    state = {
      diveTypeID: "",
      diveSchoolNameID: "",
      diveCurrentID: "",
      diveVisibilityID: "",
      diveDate: "",
      diveMaxDepth: "",
      diverUserNumber: "",
      diveVerifiedBySchool: "",
      diveNotes: "",
      divePoint: "",
      currentList: [],
      regionList: [],
      diveTypeList: [],
      visibilityList: [],
      diveSpotList: [],
      currentUser: [],
      loading: false,
  };
  // ...

You could even create that object elsewhere to unclutter your class.

And binding this, you don’t need to do it if you use arrow functions for your methods - arrow methods just inherit the this from the class so no binding is necessary. So, instead of

        onChangeDiveType(e) {
            this.setState({
                diveTypeID: e.target.value,
            });
        }

You would have

        onChangeDiveType = (e) => {
            this.setState({
                diveTypeID: e.target.value,
            });
        }

That would shorten your file by about a dozen lines.

1 Like

Thanks very much for taking the time for such a comprehensive answer. :smiley: I am a lot clearer now.

For the form data I am trying to pull. It relates to the drop down menu I am putting into the form. The likes of visibility, current, diveType have already been initialised with Sequelize in my backend so they have a set amount of options. However for the diveSpots field there is another component that has a CRUD function so this data is going to be added to, deleted etc so it definitely needs to be pulled each time for the users form.

Also the Redux vs component state split makes a lot more sense. Most of the components in my app are inter-dependent due to differing roles so I would prefer the Redux store. I will have to get working on my Redux state tree then go from there.

Right, any state that might be needed in a different component or generally has to do with the state of the app should be in redux. Anything that only that component (and maybe its children) need to know should be stored in local state.

And like I said, if this forms library works like others I’ve used, the form data might already be stored on redux state.

1 Like