Simple form with Redux and React

 

What is Redux?

It’s a front-end library used for state management in Javascript applications. Moreover it’s a way of organizing your data flow so you can have access to resources all over the application in a easier way than using props to transfer data from class to class.

How to use it?

We need to have three main elements.

Actions

As the name suggests it’s used to describe and dispatch characteristics and data for the actions to be taken such as action type and payload to the store.

Store

Store makes the connection between action and reducer and maintains, stores the needed resources(application state) to be shared.

Reducers

The actions characteristics are sent here and the reducer implements the behavior described by the actions.

Provider

Makes the store visible to all the components that are inside it.

Connect

It’s used by the component to have access to the state from store.

First argument it’s mapStateToProps. This function bind the needed data from the store. The second argument it’s mapDispatchToProps used to bind the functions.

Redux thunk

Redux Thunk is a middleware that lets you call action creators that return a function instead of an object.

Middleware is some code you can put between the request and the response used for a variety of things, including asynchronous API calls.

Let’s put them all together

We have a simple form with an input and a button and we want to use redux to access and transfer data.

Action.js

export const REQUEST_LOADING = "REQUEST_LOADING";
export const GET_REQUEST = "GET_REQUEST";

export function getRequest(id) {
  return dispatch => {
    dispatch({
      type: REQUEST_LOADING,
      payload: "123"
    });
  };
}

export function postRequest(form) {
  return dispatch => {
    dispatch({
      type: GET_REQUEST,
      payload: {name: 'Ioana new name'}
    });
  };
}

Reducer.js

import {
  REQUEST_LOADING,
  GET_REQUEST,
  GET_REQUEST_ERROR
} from "./ExAction";

const initialState = {
  success: false,
  loading: false,
  payload: {
    name: "Ioana"
  },
  error: undefined,
};

const ExReducer = function(state = initialState, action) {
  switch (action.type) {
    case REQUEST_LOADING:
      return {
        ...state,
        success: false,
        loading: true,
      };

    case GET_REQUEST:
      return {
        ...state,
        success: true,
        loading: false,
        type: action.type,
        payload: action.payload,
        error: undefined,
      };

    case GET_REQUEST_ERROR: {
      return {
        ...state,
        success: false,
        loading: false,
        type: action.type,
        error: action.payload.response.data,
      };
    }
    default: {
      return state;
    }
  }
};

export default ExReducer;

RootReducer.js

import { combineReducers } from "redux";
import ExReducer from "./ExReducer";

const RootReducer = combineReducers({
  reduxReducerData: ExReducer,
});

export default RootReducer;

Store.js

import { createStore, applyMiddleware } from "redux";
import RootReducer from "./RootReducer";
import thunk from 'redux-thunk';

const initialState = {};

export const reduxStore = createStore(
  RootReducer,
  initialState,
  applyMiddleware(thunk)
);

App.js

import React from 'react';
import './App.css';

import { Provider } from "react-redux";
import { reduxStore } from "./redux/Store";

import CssBaseline from '@material-ui/core/CssBaseline';
import Container from '@material-ui/core/Container';

import FormPage from "./FormPage"

function App() {
  return (
    <Provider store={reduxStore}>
      <CssBaseline />
      <Container maxWidth="lg">
        <FormPage />
      </Container>
    </Provider>
  );
}

export default App;

FormPage.js

import React, { Component } from "react";
import { connect } from "react-redux";
import {
  Grid,
} from "@material-ui/core";
import { getRequest, postRequest } from "./redux/ExAction"
import BasicForm from "./BasicForm"

class FormPage extends Component {

  constructor(props){
    super(props);

    this.state = {
      localReduxData: props.reduxData
    }
  }

  componentDidMount() {
    this.props.getRequest(1);
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if(this.props.reduxData !== prevProps.reduxData){
      this.setState({ localReduxData: this.props.reduxData });
    }
  }

  onHandleSubmit = (event) => {
    console.log('Submitting ...')
    this.props.postRequest(this.state.localReduxData);
  }

  onHandleChange = (event) => {
    let {name, value} = event.target;

    this.setState(prevState => ({...prevState,
      localReduxData: {...prevState.localReduxData,
        payload: {...prevState.payload, [name]: value}}
    }), () => console.log('State updated ...'))
  }

  render() {
    let { localReduxData } = this.state;

    return(
      <Grid container spacing={3}>
        <Grid item xs={12}>
          Forms w. Redux. Example
        </Grid>
        <Grid item xs={12}>
          <BasicForm
            data = {localReduxData.payload}
            handleSubmit = {this.onHandleSubmit}
            handleChange = {this.onHandleChange} />
        </Grid>
      </Grid>
    )
  }
}

const mapStateToProps = state => ({
  reduxData: state.reduxReducerData
});
export default
  connect(mapStateToProps, { getRequest, postRequest })(FormPage);

BasicForm.js

import React, { useEffect } from "react";
import { ValidatorForm, TextValidator } from "react-material-ui-form-validator";
import {
  Button,
  Grid,
} from "@material-ui/core";

export default function BasicForm(props) {

  let { data } = props;
  console.log(data)

  useEffect(() => {
    console.log('Use effect ...')
  }, []);

  const handleSubmit = event => {
    props.handleSubmit(event);
  };

  const handleChange = event => {
    props.handleChange(event)
  }

  return(
      <ValidatorForm
        onSubmit={handleSubmit}
        onError={errors => null}
      >
        <Grid container spacing={1}>
          <Grid item lg={6}>
            <TextValidator
              className="w-full"
              label="Nume"
              variant="outlined"
              onChange={handleChange}
              type="text"
              name="name"
              value={data.name}
              validators={[
                "required",
                "minStringLength: 3",
                "maxStringLength: 254"
              ]}
              errorMessages={["Numele este necesar"]}
            />
          </Grid>

          <Grid item md={12}>
            <Button color="primary" variant="outlined" type="submit">
              <span className="pl-2 capitalize">Salveaza</span>
            </Button>
          </Grid>
        </Grid>
      </ValidatorForm>
  )
}