Reputation: 714
I am building an app with react.js, it has a table built using the react table library.
Within this table I would like to have a button (or whatever) that will open a pop up showing a png. The png is determined by the cell content so may or may not be the same in all pop ups.
It appears I need to use a very simple modal so I decided to build it using the react bootstrap library as follows
import React, { useState } from "react";
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
const PopUp = ({ link }) => {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Button variant="primary" onClick={handleShow} size="sm">
Pathway
</Button>
<Modal
show={show}
onHide={handleClose}
dialogClassName={'IMG_path'}
size="lg"
>
<Modal.Header closeButton>
<Modal.Title>IMG title</Modal.Title>
</Modal.Header>
<Modal.Body closeButton>
<img src={"./img/" + link}
id="id"
alt="description"
className="img-fluid"/>
</Modal.Body>
</Modal>
</>
);
};
export default PopUp;
Then I tested it next to the react table, I mean within the same div, and it worked perfectly fine.
Then I added to the table using the following code
import React from "react";
import { useTable, usePagination, useSortBy } from 'react-table';
import PopUp from "./PopUp";
const MakePopup = ({ values }) => values && <PopUp link={values} /> ;
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
autoResetPage: true,
initialState: { pageIndex: 0 },
},
// hook for sorting
useSortBy,
// hook for pagination
usePagination
)
return (
<>
<table {...getTableProps()} className={"table table-striped table-bordered table-hover"}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
// Add the sorting props to control sorting. For this example
// we can add them into the header props
<th {...column.getHeaderProps(column.getSortByToggleProps())} scope={"col"}>
{column.render('Header')}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
{/*
Pagination can be built however you'd like.
This is just a very basic UI implementation:
*/}
<div className="pagination">
<select
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>{' '}
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{'<<'}
</button>{' '}
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
{'<'}
</button>{' '}
<span className='page-of'>
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</span>
<button onClick={() => nextPage()} disabled={!canNextPage}>
{'>'}
</button>{' '}
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{'>>'}
</button>
</div>
</>
)
}
function MyTable(props) {
const columns = [
{
Header: 'AA',
accessor: 'AA',
},
{
Header: 'BB',
accessor: 'BB',
sortType: "basic"
},
...
...
...
{
Header: 'Img',
accessor: 'url',
sortType: "basic",
disableSortBy: true,
Cell: ({ cell: {value} }) => <MakePopup values={value} />
}
];
const data = props.tableData;
return (
<Table columns={columns} data={data} />
)
}
Now the cells have the button to call the modal but it never appears :(
Searching here I found this and this other answer indicating that the modal should be outside the table instead of next the button within each cell so I modified the code to this
import React, { useState } from "react";
import { useTable, usePagination, useSortBy } from 'react-table';
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
// PopUp
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const [link, setLink] = useState('.png');
const handleClick = ( link ) => {
setShow(true);
setLink(link);
};
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
autoResetPage: true,
initialState: { pageIndex: 0 },
},
// hook for sorting
useSortBy,
// hook for pagination
usePagination
)
return (
<>
<table {...getTableProps()} className={"table table-striped table-bordered table-hover"}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
// Add the sorting props to control sorting. For this example
// we can add them into the header props
<th {...column.getHeaderProps(column.getSortByToggleProps())} scope={"col"}>
{column.render('Header')}
{/* Add a sort direction indicator */}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
{/*
Pagination can be built however you'd like.
This is just a very basic UI implementation:
*/}
<div className="pagination">
<select
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>{' '}
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{'<<'}
</button>{' '}
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
{'<'}
</button>{' '}
<span className='page-of'>
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</span>
<button onClick={() => nextPage()} disabled={!canNextPage}>
{'>'}
</button>{' '}
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{'>>'}
</button>
</div>
</>
)
}
function MyTable(props) {
const columns = [
{
Header: 'AA',
accessor: 'AA',
},
{
Header: 'BB',
accessor: 'BB',
sortType: "basic"
},
...
...
...
{
Header: 'Img',
accessor: 'url',
sortType: "basic",
disableSortBy: true,
Cell: ({ cell: { png } }) => png && (
<Button
variant="primary"
onClick={ () => handleClick(png) }
size="sm"
>
Pathway
</Button>
)
}
];
const data = props.genesData;
return (
<>
<Table columns={columns} data={data} />
<Modal
show={show}
onHide={handleClose}
dialogClassName={'IMG_path'}
size="lg"
>
<Modal.Header closeButton>
<Modal.Title>IMG title</Modal.Title>
</Modal.Header>
<Modal.Body closeButton>
<img src={"./img/" + link}
id="id"
alt="description"
className="img-fluid"/>
</Modal.Body>
</Modal>
</>
)
}
export default MyTable;
Now I get the following compilation error and the app does not start
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
- You might have mismatching versions of React and the renderer (such as React DOM)
- You might be breaking the Rules of Hooks
- You might have more than one copy of React in the same app See react-invalid-hook-call for tips about how to debug and fix this problem.
which points to this part of the code
4 | import Button from 'react-bootstrap/Button';
5 |
6 | // PopUp states
> 7 | const [show, setShow] = useState(false);
8 | const handleClose = () => setShow(false);
9 | // const handleShow = () => setShow(true);
10 | const [link, setLink] = useState('.png');
Now I am unsure of how to address this. I have been considering using a state management library such as redux but I think it will not solve the problem. I am using modals from bootstrap but I would use what-ever-it-is since I think the modals may be a little bit to much for the functionality I need.
Thanks
=========================
Edit based on answer
As recommended by @Afzal Zubair I moved the hooks inside the function as follows :
import React, { useState } from "react";
import { useTable, usePagination, useSortBy } from 'react-table';
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
function Table({ columns, data }) {
// PopUp states
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
// const handleShow = () => setShow(true);
const [link, setLink] = useState('.png');
const handleClick = ( link ) => {
setShow(true);
setLink(link);
};
const {
getTableProps,
getTableBodyProps,
...
...
...
...
return (
<>
<Table columns={columns} data={data} />
<Modal
show={show}
onHide={handleClose}
dialogClassName={'IMG_path'}
size="lg"
>
<Modal.Header closeButton>
<Modal.Title>KEGG pathway</Modal.Title>
</Modal.Header>
<Modal.Body closeButton>
<img src={"./img/" + link}
alt="plant-pathogen_interaction"
className="img-fluid"/>
</Modal.Body>
</Modal>
</>
)
}
Now I get a different compilation error
Failed to compile.
./src/components/table/ReactTable_PopUp.jsx Line 193:27: 'handleClick' is not defined no-undef Line 208:15: 'show' is not defined no-undef Line 209:17: 'handleClose' is not defined no-undef Line 217:32: 'link' is not defined no-undef
Search for the keywords to learn more about each error.
referring to the following code
190 Cell: ({ cell: { png } }) => png && (
191 <Button
192 variant="primary"
193 onClick={ () => handleClick(png) }
194 size="sm"
195 >
196 Pathway
197 </Button>
198 )
and
204 return (
205 <>
206 <Table columns={columns} data={data} />
207 <Modal
208 show={show}
209 onHide={handleClose}
and
216 <Modal.Body closeButton>
217 <img src={"./img/" + link}
218 alt="plant-pathogen_interaction"
219 className="img-fluid"/>
220 </Modal.Body>
Thanks
====================================================================
SOLVED
@Afzal Zubair is right, the hooks needed to be moved to the Functional element, function MyTable
in my code
function MyTable(props) {
// PopUp states
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const [link, setLink] = useState('.png');
const handleClick = ( link ) => {
setShow(true);
setLink(link);
};
const columns = [
{
Header: 'AA',
accessor: 'AA',
},
{
Header: 'BB',
accessor: 'BB',
sortType: "basic"
},
...
...
Now it works!
Upvotes: 0
Views: 6246
Reputation: 211
It seems like you are using useState hooks outside react component.
4 | import Button from 'react-bootstrap/Button';
5 |
6 | // PopUp states
> 7 | const [show, setShow] = useState(false);
8 | const handleClose = () => setShow(false);
9 | // const handleShow = () => setShow(true);
10 | const [link, setLink] = useState('.png');
You should use the modal outside the Table components, but you cannot use react hooks outside the react components.
I suggest using these hooks inside the react component, Functional component to be precise. You can refer to this document for better understanding, link.
Upvotes: 2