Search code examples
reactjsaxiosreact-admin

React listen for updated data in service then pass the data to Admin dataProvider in another component


Still new to React. As the subject states, I want to listen for data that comes through my service, which is the following:

import axios from "axios"

const base_url = 'https://localhost:5001/OrderItem';

export let orderItemByDivision = {
    data: []
}

export const getOrderItemByDiv = (div, yr) => {
    orderItemByDivision.data = axios.get(base_url + "/" + div + "/" + yr);
    return axios.get(base_url + "/" + div + "/" + yr)
}

The aforementioned service is triggered by selecting a year from a drop down menu, followed by selecting a division, which sends a get to an API responding with JSON

import React from 'react';
import { FormGroup, FormControl, Button, IconButton, Menu, MenuItem } from '@material-ui/core';
import { getDivision } from '../services/division-service';
import { getOrderItemByDiv } from '../services/order-item-service';
import { MuiThemeProvider } from '@material-ui/core/styles';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import theme from '../theme';

export  default class DivisionDropDownComponent extends React.Component {
    state = {
        divData: [],
        divValue: 'Select Division',
        divOpen: false,
        divAnchorEl: null,
        yrValue: '2020',
        yrOpen: false,
        yrAnchorEl: null,
        yrs: ['2020','2019','2018','2017','2016','2015','2014','2013','2012']
    }

    constructor(props) {
        super(props);

        this.handleDivChange = this.handleDivChange.bind(this);
    }

    componentDidMount() {
        getDivision()
            .then((res) => {
                this.setState({ divData: res.data });
            })
            .catch(error => {
                console.error(error);
            });
    }

    handleDivChange = (divData) => {
        // this.setState({value: event.target.value ? event.target.value : ''});
        console.log("divData selected: " + divData.code_division); 
        getOrderItemByDiv(divData.code_division, this.state.yrValue)
            .then((res) => {
                console.log(res.data);
            })
            .catch(error => {
                console.error(error);
            });
        this.onCloseDiv();
    }

    handleYrChange = (event) => {
        // this.setState({value: event.target.value ? event.target.value : ''});
        this.setState({ yrValue: event.target.value });
        console.log("divData selected: " + event.target.value);
        this.onCloseYr();
    }

    handleDivClick = (event) => {
        this.setState({ divAnchorEl: event.target })
        this.setState({ divOpen: true });
    }

    handleYrClick = (event) => {
        this.setState({ yrAnchorEl: event.target })
        this.setState({ yrOpen: true });
    }

    onCloseDiv = () => {
        this.setState({ divOpen: false });
    }

    onCloseYr = () => {
        this.setState({ yrOpen: false });
    }

    render(){
        let arrayOfData = this.state.divData;
        let dropdowns = arrayOfData.map((divData) =>               
            <MenuItem onClick={(event) => this.handleDivChange(divData)} key={divData.code_division}
            value={divData.code_division} text={divData.code_division}>
                {divData.code_division}
            </MenuItem>
        );
        let arrayOfYrs = this.state.yrs;
        let yrDropDown = arrayOfYrs.map((yrs) =>
            <MenuItem onClick={(event) => this.handleYrChange(event)} value={yrs} key={yrs}>
                {yrs}
            </MenuItem>           
        );
        return (
            <MuiThemeProvider theme={theme}>
                <FormGroup column='true'>
                    <FormControl>
                    <IconButton
                        aria-label="more"
                        aria-controls="long-menu"
                        aria-haspopup="true"
                        onClick={this.handleDivClick}
                    >
                        <MoreVertIcon />
                    </IconButton>
                        <Menu id="div-menu" anchorEl={this.state.divAnchorEl} open={this.state.divOpen} onClose={this.onCloseDiv}
                        className='dropDownDiv' defaultValue={this.state.divValue ? this.state.divValue: ''} >
                            <MenuItem value="Select Division">
                                Select Division
                            </MenuItem>
                            {dropdowns}
                        </Menu>
                        <Button aria-controls="simple-menu" aria-haspopup="true" onClick={this.handleYrClick}>
                            Select Year
                        </Button>
                        <Menu id="yrs-menu" open={this.state.yrOpen}
                        anchorEl={this.state.yrAnchorEl}  onClose={this.onCloseYr}
                        defaultValue={this.state.yrValue ? this.state.yrValue: ''} >
                            {yrDropDown}
                        </Menu>
                    </FormControl>
                </FormGroup>
            </MuiThemeProvider>
        )
    }

}

data in console

As you can see by the screen shot, the data is coming through in the console. How do I have the Admin dataProvider listen to any changes coming from the order-item-service.jsx file?

Can I place an observable on the orderItemByDivision.data?

As usual, thanks in advance

Editing my post to give more detail to my question. In Angular, I used HttpClient and RxJS to subscribe and observe. I have tried the following in React, but it is having issues with setState:

import { ajax } from "rxjs/ajax";
import { Observable } from "rxjs";

const base_url = "https://localhost:5001/OrderItem";

export let orderItemByDivision = {
data: []
};

export const getOrderItemByDiv = (div, yr) => {
return new Observable(observe => {
    orderItemByDivision.data = ajax
    .get(base_url + "/" + div + "/" + yr)
    .subscribe(resu => {
        setState({ orderItemByDivision.data:  resu });
        observe.next(resu);
    });
});
};

See the "orderItemByDivision.data" it is having issues with the dot notation. Since this is a service and not within a component class, I cannot call this.setState. How can I set the state, so that I can observe and subscribe to the ajax request?

Thanks in advance


Solution

  • OK, I was able emulate what I have done in Angular 6, which is to create an observable, which can then be subscribed (listen for changes)

    First, the order-item-service.jsx:

    import { Subject } from 'rxjs';
    import { ajax } from "rxjs/ajax";
    import { Observable } from "rxjs";
    
    const subject = new Subject();
    const base_url = 'https://localhost:5001/OrderItem';
    
    export let orderItemByDivision = {
        data: []
    }
    
    export const getOrderItemByDiv = (div, yr) => {
        return new Observable(observe => {
            orderItemByDivision.data = ajax
            .get(base_url + "/" + div + "/" + yr)
            .subscribe(resu => {
                orderItemByDivision.data = resu.response ;
                observe.next(resu.response);
            });
        });
    };
    
    export const messageService = {
        sendMessage: message => subject.next({ message }),
        clearMessage: () => subject.next(),
        getMessage: () => subject.asObservable()
    }
    

    As you can see, I tried to use the Subject from rxjs for sending data between components, but using ajax from rxjs/ajax was what ultimately lead me to be able to listen to changes

    I proceeded to the same with the division-service.jsx service:

    import { ajax } from "rxjs/ajax";
    import { Observable } from "rxjs";
    
    const base_url = 'https://localhost:5001/Division';
    
    let state = {
        data: []
    }
    
    export const getDivision = () => {
        return new Observable(observe => {
            state.data = ajax
            .get(base_url)
            .subscribe(resu => {
                state.data = resu.response ;
                observe.next(resu.response);
            });
        });
    }
    

    As you can see in both cases, it creates on observable after it subscribes to the get method - in other words, every time that the API URL is used, it will alert anyone who is subscribed to the two aforementioned methods.

    The following division-dropdown-component.jsx component, receives the division JSON, which fills the drop down menu of divisions. Once the end user clicks on a division, the getOrderItemByDiv is subscribed, which returns the JSON that we are needing to populate the @material-ui Table - listed down below:

        import React from "react";
        import {
        FormGroup,
        FormControl,
        Button,
        Menu,
        MenuItem
        } from "@material-ui/core";
        import { getDivision } from "../services/division-service";
        import { getOrderItemByDiv } from "../services/order-item-service";
        import { MuiThemeProvider } from "@material-ui/core/styles";
        import theme from "../theme";
        import { messageService } from "../services/order-item-service";
    
        export default class DivisionDropDownComponent extends React.Component {
        state = {
            divData: [],
            divValue: "Select Division",
            divOpen: false,
            divAnchorEl: null,
            yrValue: "2020",
            yrOpen: false,
            yrAnchorEl: null,
            yrs: [
            "2020",
            "2019",
            "2018",
            "2017",
            "2016",
            "2015",
            "2014",
            "2013",
            "2012"
            ]
        };
    
        constructor(props) {
            super(props);
    
            this.handleDivChange = this.handleDivChange.bind(this);
        }
    
        componentDidMount() {
            getDivision().subscribe(res => {
            this.setState({ divData: res });
            });
        }
    
        handleDivChange = divData => {
            getOrderItemByDiv(divData.code_division, this.state.yrValue).subscribe(
            res => {
                this.setState({ divData: res });
                messageService.sendMessage(res);
            }
            );
            this.onCloseDiv();
        };
    
        handleYrChange = event => {
            this.setState({ yrValue: event.target.value });
            this.onCloseYr();
        };
    
        handleDivClick = event => {
            this.setState({ divAnchorEl: event.target });
            this.setState({ divOpen: true });
        };
    
        handleYrClick = event => {
            this.setState({ yrAnchorEl: event.target });
            this.setState({ yrOpen: true });
        };
    
        onCloseDiv = () => {
            this.setState({ divOpen: false });
        };
    
        onCloseYr = () => {
            this.setState({ yrOpen: false });
        };
    
        render() {
            let arrayOfData = this.state.divData;
            let dropdowns = arrayOfData.map((divData, index) => (
            <MenuItem
                onClick={event => this.handleDivChange(divData)}
                key={index}
                value={divData.code_division}
                text={divData.code_division}
            >
                {divData.code_division}
            </MenuItem>
            ));
            let arrayOfYrs = this.state.yrs;
            let yrDropDown = arrayOfYrs.map(yrs => (
            <MenuItem
                onClick={event => this.handleYrChange(event)}
                value={yrs}
                key={yrs}
            >
                {yrs}
            </MenuItem>
            ));
            return (
            <MuiThemeProvider theme={theme}>
                <FormGroup column="true">
                <FormControl>
                    <Button
                    aria-controls="simple-menu"
                    aria-haspopup="true"
                    onClick={this.handleYrClick}
                    >
                    Select Year
                    </Button>
                    <Menu
                    id="yrs-menu"
                    open={this.state.yrOpen}
                    anchorEl={this.state.yrAnchorEl}
                    onClose={this.onCloseYr}
                    defaultValue={this.state.yrValue ? this.state.yrValue : ""}
                    >
                    {yrDropDown}
                    </Menu>
    
                    <Button
                    aria-controls="simple-menu"
                    aria-haspopup="true"
                    onClick={this.handleDivClick}
                    >
                    Select Division
                    </Button>
                    <Menu
                    id="div-menu"
                    anchorEl={this.state.divAnchorEl}
                    open={this.state.divOpen}
                    onClose={this.onCloseDiv}
                    className="dropDownDiv"
                    defaultValue={this.state.divValue ? this.state.divValue : ""}
                    >
                    <MenuItem value="Select Division">Select Division</MenuItem>
                    {dropdowns}
                    </Menu>
                </FormControl>
                </FormGroup>
            </MuiThemeProvider>
            );
        }
        }
    

    The following is the order-item-component.jsx component, which fills the material-ui Table:

    import React from "react";
    
    import Table from "@material-ui/core/Table";
    import TableBody from "@material-ui/core/TableBody";
    import TableCell from "@material-ui/core/TableCell";
    import TableContainer from "@material-ui/core/TableContainer";
    import TableHead from "@material-ui/core/TableHead";
    import TableRow from "@material-ui/core/TableRow";
    import Paper from "@material-ui/core/Paper";
    import { TablePagination } from "@material-ui/core";
    import TableFooter from "@material-ui/core/TableFooter";
    import { messageService } from "../services/order-item-service";
    
    export default class OrderItemComponent extends React.Component {
      state = {
        data: [],
        _columns: [],
        Header: [],
        totalCount: 10,
        pageSize: 16,
        page: 0
      };
    
      componentDidMount() {
    
      componentDidMount() {
          this.subscription = messageService.getMessage().subscribe(message => {
              if (message) {
    
                  this.setState({ data: message.message });
                  this.setState({ totalCount: Math.ceil(this.state.data.length / this.state.pageSize) });
                  this.setState({ Header: ['order_id', 'order_item_id', 'product_id', 'code_division', 'code_product', 
                  'quantity_due', 'quantity_shipped', 'price', 'date_shipped', 'date_due', 
                  'customer_id','ship_via','value_due','value_shipped','date_order','date_modified'] });
    
              } else {
                  // do nothing
                  this.setState({ data: [] });
              }
          })
      }
    
      componentWillUnmount() {
          // unsubscribe to ensure no memory leaks
          this.subscription.unsubscribe();
      }
    
      getOrderItem(){
          this.setState({ data: messageService.getMessage() });
      }
    
      handleChangePage = (event, newPage) => {
        this.setState({ page: newPage });
    
        if (this.state.totalCount !== this.state.data.length) {
    
        }
      }
    
      render() {
    
          return (
              <div>
      <TableContainer component={Paper}>
        <Table aria-label="simple table">
          <TableHead>
            <TableRow>
              <TableCell>Order ID</TableCell>
              <TableCell align="right">Qty Due</TableCell>
              <TableCell align="right">Prod ID</TableCell>
              <TableCell align="right">Div</TableCell>
              <TableCell align="right">Prod Code</TableCell>
              <TableCell align="right">Qty Sh</TableCell>
              <TableCell align="right">Price</TableCell>
              <TableCell align="right">Dt SH</TableCell>
              <TableCell align="right">Dt Due</TableCell>
              <TableCell align="right">Cust ID</TableCell>
              <TableCell align="right">Ship Via</TableCell>
              <TableCell align="right">Val Dt</TableCell>
              <TableCell align="right">Val Sh</TableCell>
              <TableCell align="right">Dt Or</TableCell>
              <TableCell align="right">Dt Mod</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {this.state.data.map((row, index) => (
              <TableRow key={ index }>
                <TableCell component="td" scope="row">
                  { row.order_id }
                </TableCell>
                <TableCell align="right">{ row.quantity_due }</TableCell>
                <TableCell align="right">{ row.product_id}</TableCell>
                <TableCell align="right">{ row.code_division }</TableCell>
                <TableCell align="right">{ row.code_product }</TableCell>
                <TableCell align="right">{ row.quantity_shipped }</TableCell>
                <TableCell align="right">{ row.price }</TableCell>
                <TableCell align="right">{ row.date_shipped}</TableCell>
                <TableCell align="right">{ row.date_due }</TableCell>
                <TableCell align="right">{ row.customer_id }</TableCell>
                <TableCell align="right">{ row.ship_via }</TableCell>
                <TableCell align="right">{ row.value_due }</TableCell>
                <TableCell align="right">{ row.value_shipped }</TableCell>
                <TableCell align="right">{ row.date_order }</TableCell>
                <TableCell align="right">{ row.date_modified }</TableCell>
              </TableRow>
            ))}
          </TableBody>
          <TableFooter>
            <TableRow>
              <TablePagination
                rowsPerPageOptions={ [5, 10, 25, { label: 'All', value: -1 }] }
                colSpan={ 3 }
                count={ this.state.data.length }
                rowsPerPage={ this.state.pageSize }
                page={ this.state.page }
                SelectProps={ {
                  inputProps: { 'aria-label': 'rows per page' },
                  native: true,
                } }
                onChangePage={ this.handleChangePage }
              />
            </TableRow>
          </TableFooter>
        </Table>
        </TableContainer>
              </div>
          )
      }
    

    }

    Now, you can see how I use the getMessage method to set the data

    Almost all of it works, except the pagination - when I click on the "more" arrow, it will calculate correctly what the next set of data is, but it will not display the "new" data within the Table. I will have to figure out how to do a refresh on a @material-ui Table when pagination is involved, but nevertheless, I thought I would share in case someone else wanted to know how to create an observable.

    I tried to use the example on @material-ui documentation but setPage = React.useState(5) kept causing an error having to do with invalid hook or something like that - regarding the following function:

      const handleChangePage = (event, newPage) => {
         setPage(newPage);
      };
    

    I will post another question on that topic, but for now, the question that I asked has been answered

    Thanks once more