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

3

6

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

Ejecta answered 13/8, 2021 at 20:43 Comment(0)
A
4

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

Astrid answered 16/8, 2021 at 11:56 Comment(3)
Initially I was returning response.data in the api service. I changed it to just return response and then changed the payload like you have. After making a small change in the way I was checking the length property of the array I now have data in my table. Thank youEjecta
@MikeV Ahah no problem. After that I will encourage you to set up TypeScript in your project that really get ride of all this tiny errors :) It really definitely taking the time to learn it.Astrid
@MikeV Could you please also upvote my answer so I can get the bounty in case user2315985 forget to manually award it :)Astrid
J
2

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]
})

Jesse answered 16/8, 2021 at 2:44 Comment(2)
There is here no advantage to use the spread operator on response.data. Using payload: response.data is completly valid.Astrid
While this does work, it was only after I corrected the casing issue that @Astrid mentioned belowEjecta
T
2

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
  }
}
Tocsin answered 16/8, 2021 at 2:55 Comment(6)
Why would you do that the initial implementation was correct and please don't do a JSON.parse(JSON.stringify(...)) of a react state in this case :/ See my answer the problem come from the typo of filelogs that should be fileLogs.Astrid
I see, the problem was with the typo. I will update my answer. And also, you can use my implementation of reducer too, this looks a lot cleaner and readable :)Tocsin
Sorry but had to downvote your answer. Using JSON.parse(JSON.stringify(state)) is in No Way 'cleaner'. On each state update you perform 2 costly operations for absolutly no reason (no need to have a cloned state in this case). Please follow the original implementation that use the spread operator on state directly.Astrid
@Astrid I have edited my answer. But can you explain why JSON.parse(JSON.stringify(state)) is a costly operation?Tocsin
@Astrid After my own research, I found that JSON.parse(JSON.stringify(state)) and using spread operator directly, provide the same level of performance! If you care about performance, then check out powerful tools like lodash. They are much better.Tocsin
JSON.parse(JSON.stringify(state)) will first parse all the object recursively to transform it into a string and then re-parse the complete string to put it back to an object. A string parsing as always be a costly operation in Computer science. On the other end the spread operator only do a shallow copy of the object. Here is a benchmark from jsben.ch where state is a huge object generated like app.json-generator.com/e-xfqcib0-Bf. As you can see it's not even comparable. Please next time also share your source.Astrid

© 2022 - 2024 — McMap. All rights reserved.