Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 546
Neo Avatar asked Aug 13 '21 20:08

Neo


People also ask

How do I get data from API in react functional component?

Put the fetchData function above in the useEffect hook and call it, like so: useEffect(() => { const url = "https://api.adviceslip.com/advice"; const fetchData = async () => { try { const response = await fetch(url); const json = await response. json(); console. log(json); } catch (error) { console.

Can we call API in useReducer?

AFAIK, useReducer hook does not offer callback functionality like useState does, which we could make API call in callback function.

Why might you use useReducer over useState in a react component?

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.


Video Answer


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

like image 149
johannchopin Avatar answered Oct 17 '22 10:10

johannchopin


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

like image 20
moshfiqrony Avatar answered Oct 17 '22 10:10

moshfiqrony


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
  }
}
like image 26
Pranava Mohan Avatar answered Oct 17 '22 09:10

Pranava Mohan