React, injecting dialogs with Redux, CRUD dialogs with Axios Flask Api interaction.
Tags: react, redux, materialui
Published: 2021-09-30 19:26:38


React, injecting dialogs with Redux, CRUD dialogs with Axios Flask API interaction.
----

Whether we like it or not the dialogues are an important part of our application.They allow us to perform simple or complicated actions in a place apart from the main content.

Either to avoid complexity or laziness we always try to use the minimum amount of third-party libraries. In this case we will only use the classic MATERIAL UI who will be in charge of generating the dialogue.
MATERIAL UI providee a dialog whith basic behaviour, like: closing whith scape key or when click outside, it can go fullscreen and also darken the page behind.

We will control the opening and closing of the dialogue with Redux. Also the asynchronic interaction of the dialog with the API will be handled by Redux. The dialog make the API request, get the API response and then if is all is ok it close by self, if not error advices are showed.

Bassically reduce all to a single line of code:

**Opening:**
  `this.props.dispatch(showDialog(ClientEditDlg,{id,someCallback}))`

**Closing:**
  `this.props.dispatch(actCloseDlg()); `


It is important to note that this methodology can be applied in other types of controls such as: toast, snack bar, banners, or also side columns contents.



![Dialog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ztubea8klmokwnb8ogw.png)


![Api response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dpqx3od4e602l0rpm9eq.png)



## Table of Contents
* [1-Modal Root](#modalroot)
* [2-Main Layout](#mainlayout)
* [3-Content Area](#contentarea)
* [4-Activity Dialog](#activitydialog)



### 1) Modal Root component <a name="modalroot"></a>


The ModalRoot is an intermediate attendant component, that will render any component and arguments passed in his props. Placed in the main layout, will receive any dialog from any module.

```javascript
//file: /src/modal/ModalRoot.jsx

const ModalRoot = ({ dlgComponent, dlgProps }) => {
  if (!dlgComponent) {
    return null 
  }  
  const Dlg = dlgComponent;//just for Uppercase component naming convention
  return <Dlg {...dlgProps} />
}

export default connect(state => state.modal)(ModalRoot);

```
Tied to his own Redux store, so any action distpached, will be listen and then triger a new render.


## 1.1) ModalRoot, Actions and Reducer.

Only need two action, one to open and one to close:

```javascript
//file: /src/modal/ModalActions.jsx

export const showDialog = (dlgComponent,dlgProps) => ({type:  'SHOW_DLG',  dlgComponent, dlgProps });
export const closeDialog = () => ({type:  'CLOSE_DLG' });  
     
```

The ModalRoot reducer is very simple, just two actions:

```javascript
//file: /src/modal/ModalReducer.jsx

const initialState = {dlgComponent: null, dlgProps: {}}
  
export default  function ModalReducer(state = initialState, action) {
    switch (action.type) {
      case 'SHOW_DLG':
        return { dlgComponent: action.dlgComponent,  dlgProps: action.dlgProps}               
      case 'CLOSE_DLG':
        return initialState        
      default:
        return state
    }
  }

```

### 2) Main Layout <a name="mainlayout"></a>

   The ModalRoot component will be placed in the app main layout, common to all modules. In this case only use the module ClientsMain. But keep in mind that here will be placed the navigation bar and all modules, like notes, orders, invoces. The rendering choice of one or other, will be handled by routing or conditional rendering.  

```javascript
//file: /src/App.js

function App() {
  return (
    <>
    <ModalRoot/>
    <ClientsMain/>     
    </>
  );
}     
```

## 3) Content Area <a name="contentarea"></a>

For demostration purposes we will work over a client directory with name, phone and mail. Where we can edit and delete every item, also add a new client. "The classic CRUD".

The files of the client module:

ClientsMain.jsx // Listing
ClientCreateDlg.jsx // Create new
ClientEditDlg.jsx // Edit
ClientDeleteDlg.jsx // Delete confirmation

ClientsActions.jsx //Redux Files
ClientsReducer.jsx //Redux Files


### 3.1) Data fetching

   The client list will be retrieve with Axios from a Flask endpoint.  When ClientsMain is mounted, trig data fetch from API, dispatching `actClientsFetch()`.

#### Fetch Client actions:

```javascript

//file: /src/clients/ClientsActions.jsx

export function actClientsFetch(f) { 
  return dispatch => {
    dispatch(actClientsFetchBegin());  // for loading message or spinner
    
    axios.post(process.env.REACT_APP_API_BASE_URL+"clientslist",f,{withCredentials: true} )
    .then(response => { dispatch(actClientsFetchSuccess(response.data.items));})
    .catch(error => { dispatch(actClientsFetchError({status:'error',msg:error.message+', ' + (error.response && error.response.data.msg)}))} );    
  };
}

export const actClientsFetchBegin = () => ({
  type: 'CLIENTS_FETCH_BEGIN'
});

export const actClientsFetchSuccess = items => ({
  type: 'CLIENTS_FETCH_SUCCESS',
  payload: { items: items  }
});

export const actClientsFetchError = msg => ({
  type: 'CLIENTS_FETCH_ERROR',
  payload: { msg: msg}
});
     
```
#### Fetch Clients reducer:

Next lines show a code extracted from the reducer. 


```javascript
//file: /src/clients/ClientsReducer.jsx 
// extract :

  case 'CLIENTS_FETCH_BEGIN':  // "loading" show a spinner or Loading msg      
      return {
        ...state,
        status: 'loading'        
      };
    case 'CLIENTS_FETCH_SUCCESS': // All done: set status and load the items from the API
      return {
        ...state,
        status: 'success',        
        items: action.payload.items,
        isDirty : false
      };
    case 'CLIENTS_FETCH_ERROR':  // Something is wrong
      return {
        ...state,
        status: "error",
        msg: action.payload.msg,
        items: []        
      };


```


#### Flask dummy route

  Just to simulate a server request, a Flask route returning static data is implemented.  


```python
@app.route('/clientslist', methods=['POST','GET'])
def clientlist():
   clients= [    {'id':'1','name':'Client 1','mail':' user1@mail.com','phone':'555-555-111'},
                 {'id':'2','name':'Client 2','mail':' user2@mail.com','phone':'555-555-222'},
                 {'id':'3','name':'Client 3','mail':' user3@mail.com','phone':'555-555-333'},
                 {'id':'4','name':'Client 4','mail':' user4@mail.com','phone':'555-555-444'}]
                 
   return {'items':clients}     
```


### 3.2) Automatic reloading:

In order to get data consistence the clients Redux store have a `isDirty` flag, any action over the clients (create,update,delete) will trigger `actClientsSetDirty()` changing `isDirty` flag to TRUE and then trigger data reload.

#### List reload when data is dirty:

```javascript
//file: /src/clients/ClientsMain.jsx

 componentDidUpdate(prevProps, prevState) {
    if (this.props.isDirty && this.props.status !== 'loading')    {
      this.props.dispatch(actClientsFetch());
    }    
  }

```

#### Triggering list reload

```javascript
//file: ClientsActions.jsx

export const actClientsSetDirty = () => ({
  type: 'CLIENTS_SET_DIRTY'
});

```

## 4) Activity Dialog <a name="activitydialog"></a>


The activity dialog is the component that would be injected in the modal root, in this case use the material dialog, but can be any thing: banner, toast, etc... 


### 4.1) Activity Dialog, Actions and Reducer.

The activity could be: create, update or delete clients. Every activity have their related action. In this case all point to the same API route, but in real scenario, every must to have their specific route.

```javascript
//file: /src/clients/ClientsActions.jsx 
// extract :

export function actClientCreate(d) {return actClientsFormApi(d,"clientsresponse")};
export function actClientUpdate(d) {return actClientsFormApi(d,"clientsresponse")};
export function actClientDelete(d) {return actClientsFormApi(d,"clientsresponse")};

function actClientsFormApi(d,url) { 
   
  return dispatch => {
    dispatch(actClientFormSubmit());// for processing advice msg

    axios.post(process.env.REACT_APP_API_BASE_URL+url,d, {withCredentials: true})
    .then(response => { dispatch(actClientFormResponse(response.data));
                        dispatch(actClientsSetDirty()) ;})
    .catch(error => { dispatch(actClientFormResponse({status:'error',msg:error.message+', ' + (error.response && error.response.data.msg)}))
                    })
        
  };
}

export const actClientFormInit = () => ({
  type: 'CLIENT_FORM_INIT'  
});
export const actClientFormSubmit = () => ({
  type: 'CLIENT_FORM_SUBMIT'  
});
export const actClientFormResponse = (resp) => ({
  type: 'CLIENT_FORM_RESPONSE',
  payload : resp
});

```


Next lines show a code extracted from the reducer, where are three action related to form submiting. 

 `CLIENT_FORM_INIT` initialize the `formStatus` to normal,
 `CLIENT_FORM_SUBMIT` to show processing message, 
 `CLIENT_FORM_RESPONSE` is the API response that could be: 'error' or 'success'.
    
```javascript
//file: /src/clients/ClientsReducer.jsx 
// extract :

 case 'CLIENT_FORM_INIT':             
        return {
          ...state,
          formStatus: 'normal',
          formMsg: '',          
        };   
    case 'CLIENT_FORM_SUBMIT':   
        return {
          ...state,
          formStatus: 'loading',
          formMsg: '',          
        };   

    case 'CLIENT_FORM_RESPONSE':
    
    	return {
          ...state,
          formStatus: action.payload.status,
          formMsg: action.payload.msg,
       };  


```

### 4.2) Activity API interaction

The API response is attended by `CLIENT_FORM_RESPONSE`. A `formStatus` is implemented to know the request results from the API. Also a formMsg for API error messages.

```javascript
//file: /src/clients/ClientsReducer.jsx 
// extract :

  case 'CLIENT_FORM_RESPONSE':    
      
      return {
        ...state,
        formStatus: action.payload.status, //response from API
        formMsg: action.payload.msg        
      };   

```

We have three activity dialogs:
     
ClientCreateDlg.jsx // Create new
ClientEditDlg.jsx // Edit
ClientDeleteDlg.jsx // Delete confirmation

The dialog make the API request, if all is ok it close by self, if not error advices are showed.

All have the same internal structure, the important thing to highlight is the `formStatus`.

When axios resolve the API response, it trigger `CLIENT_FORM_RESPONSE`. Then the operation result is stored in `formStatus` that could be: 'error' or 'success'.

For shortness only show 'ClientsCreateDlg'


```javascript
//file: /src/clients/ClientsCreateDlg.jsx 
// extract :  

function ClientCreateDlg(props){

  const initial = {  name:'',phone:'', mail:'',}; 
  const [state, setState] = useState(initial);  
  const fullScreen = useMediaQuery('(max-width:500px)');// if width<500 go fullscreen
  
  useEffect(() => { //Mount - Unmount  
         props.dispatch(actClientFormInit());  //componentMount    
         //console.log("component Mount");
        return () => {
         props.dispatch(actClientFormInit());  //componentWillUnmount    
         // console.log("componentWillUnmount");
        };
      }, []);
 
  //componentDidUpdate  status listener  
  useEffect(() => {
    console.log("status Update", props.status);
    if( props.status==='success') props.dispatch({type: 'CLOSE_DLG' });  //trigger UnMount             
     }, [props.status]); 

  const handleChange = (e) => {
    const {name,value} = e.target;
    setState(prevState => ({...prevState,[name]: value}));  
  };

  const handleSubmit = (e) => {
    console.log("handleSubmit:",state)
    e.preventDefault();  // prevent a browser reload/refresh
    props.dispatch(actClientCreate(state)); 
  };

  const handleCancel = () => {    
    props.dispatch({type: 'CLOSE_DLG' });
  } ;  
    
    const { status, msg } = props; // server API responses   

    var advice = null;         
    if (status === "loading") advice = "Procesing...";    
    if (status === "error") advice =  "Error: " + msg;   
    if (status === "success") {  return null; }        
  
    return (
    <Dialog onClose={handleCancel} fullScreen={fullScreen} open={true}>
        <div style={{minWidth:'300px',padding:"2px",display: "flex" ,flexDirection: "column"}}>
        <DialogTitle ><ViewHeadlineIcon  />Create new client:</DialogTitle>          
        <form onSubmit={handleSubmit} >
        
        <div style={{minWidth:'50%',boxSizing:'border-box',padding:"2px",display: "flex" ,flexDirection: "column",flexGrow:'1'}}>         
          <TextField name="name"  size="small" placeholder="Name"  onChange={handleChange} />  
          <TextField name="phone"  size="small" placeholder="Phone" onChange={handleChange} />  
          <TextField name="mail"   size="small" placeholder="Mail"  onChange={handleChange} />     
        </div>        

        <div style={{ display: "flex", flexDirection: "row",alignItems: "center",justifyContent: "space-around" }}> 

        <IconButton  type="submit"  >   <CheckCircleIcon  color="primary"/> </IconButton>    
        <IconButton onClick={handleCancel}  > <CancelIcon/></IconButton>   
        </div>       
        <Ad l={advice}/>               
        </form>
        </div>
    </Dialog>
);
}

const mapStateToPropsForm = state => ({    
  status:state.clients.formStatus,
  msg:state.clients.formMsg,   
});

export default connect(mapStateToPropsForm)(ClientCreateDlg);


```

### 4.3) Flask response dummy route

In order to show the results of the API endpoint, a route with random responses is implemented.

```python

@app.route('/clientsresponse', methods=['POST','GET'])
def clientrandomresponse():

  responses = [{ 'status': 'success'},
               { 'status': 'error', 'msg': 'Json required'},
               { 'status': 'error', 'msg': 'Missing field '},
               { 'status': 'error', 'msg': 'Data validation fail'}]

  return responses[time.localtime().tm_sec%4]   # only for demostration

```

## Conclusion:

   May be look complex to understand, there are two related mechanisms, one in charge of dialog injections, and other related to API interaction.
   Usually an app can have many modules: clients, notes, order, and are used one at time, so all can share the same dialog root component.
   In this way can open a dialog from whatever place.
  
      
   Get the full code from  [https://github.com/tomsawyercode/react-redux-dialogs-crud](https://github.com/tomsawyercode/react-redux-dialogs-crud)