reactjsreact-hooksreact-router-domreact-bootstrap-table

Get location path from use Location hook inside a column renderer from react-bootstrap-table2


Story

I'm creating 2 pages (Summary and Cycles pages) using react.js.

On the Summary page, there is a column named CN that every item links to the Cycles page.

Summary page has a path /route/summary/location=abc and Cycles page has a path /route/cycle/location=abc/deviceId=4410

For example, if I click the value from CN column in the first row of the table inside the Summary page, I will be redirected to the Cycles page with the path /route/cycle/location=abc/deviceId=4410.

In the Summary page, I use https://github.com/react-bootstrap-table/react-bootstrap-table2 for the table component and I use a columnRenderer function inside columns.js to render a custom item inside the table like this one:

enter image description here

Question

How can I put the pathname (example "abc") to a Link component inside cnColumnRenderer function in columns.js?

Ideal Condition I wanted:

Summary page with the path: /route/summary/location=abc

Summary page

Cycles page with the path: /route/cycle/location=abc/deviceId=4410

Cycles page

Actual Condition:

Error because of invalid hook call while rendering the Summary page

Error

My Code:

table code inside Summary page (inside Summary.js):

hint: focus on columns variable from './columns' and its implementation

import React from "react"
import { useLocation } from "react-router-dom"

import BootstrapTable from 'react-bootstrap-table-next';
import paginationFactory, {
    PaginationProvider,
    PaginationListStandalone
} from 'react-bootstrap-table2-paginator';
import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';

import columns from './columns'

const Summary = () => {    
    const location = useLocation()
    const locationName = location['pathname'].replace('/route/summary/location=', '')
    // console.log('Summary, location:', locationName)
    // example: location = "/route/summary/location=abc" and locationName = "abc"

    // ...some code here...

    return (
        <React.Fragment>
            <div className="container-fluid ppa-route-summary-root">
                <Row>
                    <Col className="col-12">

                        {/* ...some code here... */}

                        {/* TABLE CARD */}
                        <Card>
                            <CardBody>
                                <PaginationProvider
                                    pagination={paginationFactory(paginationOptions)}
                                    keyField='id'
                                    columns={columns}
                                    data={tableData}
                                >
                                    {
                                        ({ paginationProps, paginationTableProps }) => (
                                            <ToolkitProvider
                                                keyField='id'
                                                columns={columns}
                                                data={tableData}
                                                search
                                            >
                                                {
                                                    toolkitProps => (
                                                        <React.Fragment>

                                                            {/* ...some code here... */}

                                                            {/* TABLE ITSELF */}
                                                            <Row>
                                                            <Col xl="12">
                                                                <div className="table-responsive">
                                                                    {
                                                                        isTableLoading ? 
                                                                        <ReactLoading 
                                                                            type={'spin'} 
                                                                            color={'crimson'} 
                                                                            height={'5rem'} 
                                                                            width={'5rem'} 
                                                                            className='table-loading'
                                                                        /> :
                                                                        <BootstrapTable
                                                                            keyField={"id"}
                                                                            responsive
                                                                            bordered={false}
                                                                            striped={false}
                                                                            // defaultSorted={defaultSorted}
                                                                            // selectRow={selectRow}
                                                                            classes={
                                                                                "table align-middle table-nowrap"
                                                                            }
                                                                            headerWrapperClasses={"thead-light"}
                                                                            {...toolkitProps.baseProps}
                                                                            {...paginationTableProps}
                                                                        />
                                                                    }
                                                                </div>
                                                            </Col>
                                                            </Row>

                                                            {/* ...some code here... */}

                                                        </React.Fragment>
                                                    )
                                                }
                                            </ToolkitProvider>
                                        )
                                    }
                                </PaginationProvider>
                            </CardBody>
                        </Card>
                    </Col>
                </Row>
            </div>
        </React.Fragment>
    )
}

export default Summary

columns.js:

import React from 'react'
import { Link, useLocation } from 'react-router-dom'

// IMAGES
import IconLocation from '../../../images/icons/location.svg'

const cnColumnRenderer = (cell, row, rowIndex, formatExtraData) => {
    // console.log('columns, cnColumnRenderer:', cell, row, rowIndex, formatExtraData)
    const deviceVersion = cell.split('-')[1] // example: deviceVersion = "4410"

    const location = useLocation()
    // console.log('Summary columns, location:', location['pathname'])

    // here is the pathname I wanted: "/route/cycle/location=abc" so I can take the "location" path value as below:
    const locationName = location['pathname'].replace('/route/summary/location=', '') // the result is locationName = "abc"
    // then put the locationName inside the Link component

    return(
        <div className='route-summary-cn'>
            <img src={IconLocation} alt='' className='icon-location'/>
            {/* below is the pathname I wanted: "/route/cycle/location=abc/deviceId=4410" */}
            <Link to={`/route/summary/location=${locationName}/deviceId=${row['deviceId']}`}>
                {deviceVersion}
            </Link>
        </div>
    )
}

const columns = [
    {
        dataField: 'deviceName',
        text: 'CN',
        formatter: cnColumnRenderer,
        sort: true
    },
    {
        dataField: 'convertedTotalCycle',
        text: 'Cycle',
        sort: true,
    },
    // ...some code here...
]

export default columns

Note: let me know if the question is confusing. I will try to update it.


Solution

  • React hooks are only valid in React functional components, not in any callbacks, loops, conditional blocks. If you need the location data in the callback it needs to be passed in.

    From what I can tell it seems you need to move the columns.js code into the main component so the location values can be closed over in scope.

    const Summary = () => {    
      const location = useLocation();
    
      const locationName = location['pathname'].replace('/route/summary/location=', '')
      // example: location = "/route/summary/location=abc" and locationName = "abc"
    
      // ...some code here...
    
      const cnColumnRenderer = (cell, row, rowIndex, formatExtraData) => {
        // console.log('columns, cnColumnRenderer:', cell, row, rowIndex, formatExtraData)
        const deviceVersion = cell.split('-')[1] // example: deviceVersion = "4410"
    
        // then put the locationName inside the Link component
    
        return(
          <div className='route-summary-cn'>
            <img src={IconLocation} alt='' className='icon-location'/>
            {/* below is the pathname I wanted: "/route/cycle/location=abc/deviceId=4410" */}
            <Link to={`/route/summary/location=${locationName}/deviceId=${row['deviceId']}`}>
              {deviceVersion}
            </Link>
          </div>
        );
      };
    
      const columns = [
        {
          dataField: 'deviceName',
          text: 'CN',
          formatter: cnColumnRenderer,
          sort: true
        },
        {
          dataField: 'convertedTotalCycle',
          text: 'Cycle',
          sort: true,
        },
        // ...some code here...
      ];
    
      return (
        ...
      );