react: getting api results into a table using useReducer and functional component

Viewed 631

I'm brand spanking new to react. I am officially done beating my head against a wall. I just can't figure this out. This is my situation:

I am trying to get the results of an API call into a table. I have the call to the API working and results coming back. I am stuck on how to get my array updated with the data that came back. After that is done then I can populate the table with the data (at least logically that's the way what my brain tells me).

Initial form state setup:

const initialFormState = {
    fileTypeId : '',
    companyMasterId: '',
    investmentCompanyId: '',
    startDate: '',
    endDate: '',
    result: '',
    fileLogs: []
}

All of the fields above are fields on the form\database. The API call takes these parameters to call a stored procedure that returns a result set based on the search parameters. fileLogs[] is where I want to put the data that comes back. I wasn't sure if I need to move it out of this setup and use useState just for that as a separate thing?

reducer initialization:

 const [formState, dispatch] = useReducer (formReducer, initialFormState)

reducer setup

formReducer.js

import actionTypes from "./actionTypes"

const formReducer = (state, action) => {
    switch (action.type) {
        case actionTypes.handle_input_text:
            return {
                //using the spread operator (…state) to copy across all the properties and values from the state object. 
                //then we can modify the values of specific state properties by explicitly declaring them again with a new value.
                ...state,
                [action.field]: action.payload,
            }
        case actionTypes.toggle_consent:
            return{
                ...state,
                hasConsented: !state.hasConsented
            }
        case actionTypes.on_success:
            return{...state, filelogs: action.payload}     
        default:
            return state
    }
}
export default formReducer

API call

function getFileRouteLogs (e)  {
        e.preventDefault()

        apiService.getFileRouteLogs(
            formState.fileTypeId,
            formState.companyMasterId,
            formState.investmentCompanyId,
            formState.startDate,
            formState.endDate,
            formState.result
        )
         .then((response)=>{
            //  dispatch({
            //     type: actionTypes.on_success,
            //     // payload: [...formState.fileLogs,  response.data]
            //     payload: response.data
            //     })
            formState.fileLogs = response.data
            console.log(response.data)
        }) 

Handler for form input changes

const handleInputChange = (e) => {
        dispatch({
            type: actionTypes.handle_input_text,
            field: e.target.name,
            payload: e.target.value
        })
    }

Form for input

return (
            <div>
                <h1>File Route Log Search</h1>
                <hr />
                <h2>Form field area</h2>
                <Form onSubmit={getFileRouteLogs}>
                    <FormGroup row>
                        <Label for="fileTypeId" sm={2}>FileTypeId</Label>
                        <Col sm={2}>
                            <Input  type="text" name="fileTypeId" value={formState.fileTypeId} onChange={(e) => handleInputChange(e)}></Input>
                        </Col>
                    </FormGroup>
                    <FormGroup row>
                        <Label for="companyMasterId" sm={2}>CompanyMasterId</Label>
                        <Col sm={2}>
                            <Input id="companyMasterId" type="text" name="companyMasterId" value={formState.companyMasterId} onChange={(e) => handleInputChange(e)} ></Input>
                        </Col>
                    </FormGroup> 
...

Attempted table setup to hold data

const FileRouteLogTable = ({formState}) => {
  return (
    <table className="table">
      <thead>
        <tr>
          <th>FileRouteLogId</th>
          <th>CompanyMasterId</th>
          <th>FileTypeId</th>
          <th>Result</th>
          <th>InvestmentCompanyMasterId</th>
          <th>FileName</th>
        </tr>
      </thead>
      <tbody>
      { (formState.length > 0) ? formState.map( (form, index) => {
           return (
            <tr key={ index }>
              <td>{ form.fileRouteLogId }</td>
              <td>{ form.companyMasterId }</td>
              <td>{ form.fileTypeId}</td>
              <td>{ form.result }</td>
              <td>{ form.investmentCompanyMasterId }</td>
              <td>{ form.fileName }</td>
            </tr>
          )
         }) : <tr><td colSpan="5">Enter search parameters...</td></tr> }
      </tbody>
    </table>
  );
}

export default FileRouteLogTable

I was going to try to use a react-table, but I got stuck on updating the data before I could do any table stuff

import { useTable } from 'react-table'
function FileRouteLogTable({ columns, formState }) {
    // Use the state and functions returned from useTable to build your UI
    const {
      getTableProps,
      getTableBodyProps,
      headerGroups,
      rows,
      prepareRow,
    } = useTable({
      columns,
      formState,
    })
  
    return (
      <table {...getTableProps()}>
        <thead>
          {headerGroups.map(headerGroup => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map(column => (
                <th {...column.getHeaderProps()}>{column.render('Header')}</th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {rows.map((row, i) => {
            prepareRow(row)
            return (
              <tr {...row.getRowProps()}>
                {row.cells.map(cell => {
                  return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
                })}
              </tr>
            )
          })}
        </tbody>
      </table>
    )
  }
  export default FileRouteLogTable

Any help would be greatly appreciated. All the examples I look at don't have my combination of factors:

  • functional component
  • use useReducer
  • use axios
  • everything not on one page
  • does not do an API GET call on page load using useEffect
  • does not just display the results in the console log

UPDATE I fixed the casing issue that @johannchopin mentioned and changed the payload. I still didn't have any data in the table so I changed the table logic from:

 { (formState.length > 0) ? formState.map( (form, index) => {

to

 { (formState.fileLogs.length > 0) ? formState.fileLogs.map( (form, index) => {

and data is in the table

3 Answers

Sadly for you it seems to be a simple typo issue with fileLogs. In the formReducer function you assign action.payload to filelogs and not fileLogs. After making this update you can again use the dispatch function:

    .then((response) => {
      dispatch({
        type: actionTypes.on_success,
        payload: response.data
      });
    });

Checkout the live example:

Edit strange-galileo-w59fg

The first thing you need to do is change the dispatch in the then in the api call.

dispatch({
    type: actionTypes.on_success,
    payload: [...formState.fileLogs,  ...response.data]
})

What I did above is because I think your api is returning an array. If not so then the dispatch was ok. But right now the way you assigned the logs directly to the state, you can't do that. If your response contain all the data then you can dispatch like below

dispatch({
    type: actionTypes.on_success,
    payload: [...response.data]
})

As stated by user @johannchopin in the comments, the problem was caused by a typo in the reducer. it was "filelogs" which should have been "fileLogs". I have corrected that.

Change your formReducer.js to this:

const formReducer = (state, action) => {
  
  switch (action.type) {
      case actionTypes.handle_input_text:
          return {...state, [action.field]: action.payload}
          
      case actionTypes.toggle_consent:
          return {...state, hasConsented: !state.hasConsented}
          
      case actionTypes.on_success:
          return {...state, fileLogs: action.payload}
          
      default:
          return state
  }
}
Related