I'm trying to use arrays in Grommet DataTable. My data looks like this :
{
customer: [
'BANANA',
'Banana',
'banana',
'republic of banana'
],
somethingelse: ['ABC','123','DEF']
}
In a regular Grommet Table , I'm able to use every cell by defining the first value from the array as title - for example customer[0] - and create an expandable arrow to show the rest of the data in 'customer' :
But I don't get how to do this on a cell basis for a Grommet DataTable ?
Here is the way I'm using it in the regular Grommet Table :
<TableCell scope="row" pad={{ left: '2px', righ: '3px' }}>
<TextInput name="tags" size="xsmall" />
</TableCell>
</TableRow>
{searchResults.length > 0 &&
searchResults.map((searchResult, index) => (
<TableRow key={index}>
<TableCell>
<Box direction="row">
<Text size="xsmall">{searchResult.customer[0]}</Text>
{searchResult.customer.length > 1 && (
<Button
plain
hoverIndicator={false}
icon={
isExpanded[index] ? (
<FormDown size="18px" />
) : (
<FormNext size="18px" />
)
}
onClick={() => toggleOpen(index)}
/>
)}
</Box>
<Box>
{isExpanded[index] && listElements(searchResult.customer)}
</Box>
</TableCell>
Here is my Form , using DataTable :
return (
<Form value={formData} onSubmit={onSubmit} onChange={onChange}>
...
<DataTable
fill
border={{ body: 'bottom' }}
paginate
columns={columns}
data={searchResults}
select={select}
onClickRow={(e) => console.log(e.datum)}
onSelect={() => {}}
step={8}
rowDetails={(row) => { // I'm able to use rowDetails to expand and display some data , but how can I use this to 1. Use the [0] element of the array as title and 2. apply to all cells in the row/table.
for (const cell in row) {
// if (cell.length > 1) {
// return listElements(cell);
// }
console.log(cell);
}
}}
...
/>
...
</Form>
);
I was able to achieve that by using the render function and passing a CellElement to it, in which I have created my rules :
const columns = [
{
property: 'customer',
header: <FormField label="Customer" name="customer" size="xsmall" />,
render: (datum) => <CellElement val={datum.customer} />,
},
CellElement.js
import { Box, Text, Button } from 'grommet';
import { FormNext, FormDown } from 'grommet-icons';
import React, { useState } from 'react';
const CellElement = ({ val }) => {
const title = Array.isArray(val) ? val[0] : val;
const [isExpanded, setIsExpanded] = useState({});
const toggleOpen = (category) => {
setIsExpanded({
...isExpanded,
[category]: !isExpanded[category],
});
};
const listElements = (arr) => {
return arr.slice(1).map((el, index) => (
<Text key={index} size="xsmall">
{el}
</Text>
));
};
return (
<Box>
<Box direction="row">
<Text size="xsmall">{title}</Text>
{Array.isArray(val) && val.length > 1 && (
<Button
plain
hoverIndicator={false}
icon={
isExpanded[title] ? (
<FormDown size="18px" />
) : (
<FormNext size="18px" />
)
}
onClick={() => toggleOpen(title)}
/>
)}
</Box>
<Box>{isExpanded[title] && listElements(val)}</Box>
</Box>
);
};
export default CellElement;
I'm trying to update value in react functional compoenent through input element but after the first value I'm unable to type
My Code:
import React from "react";
import "./App.css";
const { useState } = React;
function App() {
const [items, setItems] = useState([
{ value: "" },
{ value: "" },
{ value: "" },
]);
const [count, setCount] = useState(0);
const Item = React.memo(({ id, value, onChange }) => {
return (
<div className="item">Item
<input
onChange={(e) => onChange(id, e.target.value)}
value={value}
/>
</div>
);
});
return (
<div className="app">
<h1>Parent</h1>
<p style={{marginTop: '20px'}}>Holds state: {count}, Does't passes to it items</p>
<p style={{marginTop: '20px'}}>{JSON.stringify(items)}</p>
<button onClick={() => setCount((prev) => prev + 1)} style={{marginTop: '20px'}}>
Update Parent
</button>
<ul className="items" style={{marginTop: '20px'}}>
{items.map((item, index) => {
return (
<Item
key={index}
id={index}
value={item.value}
onChange={(id, value) =>
setItems(
items.map((item, index) => {
return index !== id
? item
: { value: value };
})
)
}
/>
);
})}
</ul>
</div>
);
}
export default App;
You should move the Item declaration outside the App component. Having a component declaration inside another one is almost always a bad idea. Explanation below.
import React, { useState } from "react";
const Item = React.memo(({ id, value, onChange }) => {
return (
<div className="item">
Item
<input onChange={(e) => onChange(id, e.target.value)} value={value} />
</div>
);
});
function App() {
const [items, setItems] = useState([
{ value: "" },
{ value: "" },
{ value: "" }
]);
const [count, setCount] = useState(0);
return (
<div className="app">
<h1>Parent</h1>
<p style={{ marginTop: "20px" }}>
Holds state: {count}, Does't passes to it items
</p>
<p style={{ marginTop: "20px" }}>{JSON.stringify(items)}</p>
<button
onClick={() => setCount((prev) => prev + 1)}
style={{ marginTop: "20px" }}
>
Update Parent
</button>
<ul className="items" style={{ marginTop: "20px" }}>
{items.map((item, index) => {
return (
<Item
key={index}
id={index}
value={item.value}
onChange={(id, value) =>
setItems(
items.map((item, index) => {
return index !== id ? item : { value: value };
})
)
}
/>
);
})}
</ul>
</div>
);
}
export default App;
When a component definition is inside another component, React will re-declare the inner component every time the parent re-renders. This means, that any state held by the inner component will be lost.
In your case, since every time there is an entirely new component, the input was not the same input as in the previous render. This means that the input that was in focus in the previous render is not present anymore, and the new input is not focused anymore.
You should also probably change
setItems(
items.map((item, index) => {
return index !== id ? item : { value: value };
})
)
to
prev.map((item, index) => {
return index !== id ? item : { value: value };
})
)
It's a good idea to use the function notation for set state when the new state depends on the old state value.
I'm DELETING data from my database via an API call, then returning data from my database via another API call, but the 'Car' tab doesn't seem to update or refresh showing the new data.
I want to refresh the tab page via STATE or via a page reload and keep the user's selected tab using React Bootstrap, either:
reload just the TAB the user is in
RELOAD the whole page whilst keeping the TAB selected
React Bootstrap Tabs
<Tabs defaultActiveKey="profile" id="uncontrolled-tab-example" className="mb-3">
<Tab eventKey="bus" title="Bus">
<Bus />
</Tab>
<Tab eventKey="car" title="Car">
<Car />
</Tab>
<Tab eventKey="van" title="Van">
<Van />
</Tab>
</Tabs>;
myFile.js:
const Car = () => {
const [car, setCar] = useState([{ '': '' }]);
const [selectedCar, setSelectedCar] = useState('');
useEffect(() => {
async function fetchData() {
try {
const data = await getCompanyCar(car.id);
setCar(data)
console.log(data);
} catch (err) {}
}
fetchData();
}, []);
return (
<>
<CarModal car={selectedCar} onCancel={() => setSelectedCar(null)} />
<Table className="p-4 rounded-3" striped>
<thead className="bold fs-5" style={{ color: 'rgb(80, 80, 80)' }}>
<tr style={{ display: 'flex', justifyContent: 'space-between' }}>
<span className="text-nowrap" style={{ width: '0px' }}>
Car
</span>
<button className="addButton" onClick={null}>
Add
</button>
</tr>
</thead>
<tbody>
{/* TODO:PAGINATE ROWS */}
{car.map((car) => (
<React.Fragment key={car.client_id}>
<CarRow car={car} setSelectedCar={setSelectedCar} />
</React.Fragment>
))}
</tbody>
</Table>
</>
);
};
function CarFunction({ car, setSelectedCar }) {
const [showConfirmResetModal, setShowConfirmResetModal] = useState(false);
const handleDelete = async (event) => {
try {
const data = await deleteCompanyCar(Car.id);
if (data[0].completed === 'deleted') {
getCompanyCar(Car);
window.location.reload();
}
} catch (err) {
console.log(err);
alert('Error, unable to delete');
}
};
return (
<ConfirmationModal
header="Confirm Deletion"
show={showConfirmResetModal}
onHide={() => setShowConfirmResetModal(false)}
onConfirm={handleDelete}
annimation
>
Are you sure you want to delete this car?
</ConfirmationModal>
);
}
export default CarFunction;
Thanks in advance!
I have an array of objects file and displaying data on frontend side & I applied on a onClick function on that data which I am fetching, but the issue is when click on that button I get last array of object value but I want that specific data,
data file -
const Data = [
{
id: 1,
HEXCode: "#FF6263",
RGBACode: "rgba(255, 98, 99, 1)",
},
........
]
my code -
const HashCode = () => {
const [redvalue, setRedValue] = useState(Data);
const [ButtonPopup, setButtonPopup] = useState(false);
return (
<Wrap>
<Home
code={"Hash Code"}
link={"/"}
link2={"/RGBCode"}
link3={"/Gradients"}
link4={"/TwoColorCombination"}
/>
<Content>
{redvalue.map((element, index) => {
return (
<div key={index}>
<Button
type="button"
style={{ background: `${element.HEXCode}` }}
onClick={() => setButtonPopup(true)}
>
{element.HEXCode}
</Button>
<Popup trigger={ButtonPopup} setTrigger={setButtonPopup}>
<CopyToClipboard text={element.HEXCode}>
<button type="btn">{element.HEXCode}</button>
</CopyToClipboard>
<CopyToClipboard text={element.RGBACode}>
<button type="btn">{element.RGBACode}</button>
</CopyToClipboard>
</Popup>
</div>
);
})}
</Content>
</Wrap>
);
};
export default HashCode;
Popup code -
const Popup = (props) => {
return props.trigger ? (
<Wrap className="popup">
<div className="popup_inner">
<button className="close_btn" onClick={() => props.setTrigger(false)}>
close
</button>
{props.children}
</div>
</Wrap>
) : (
""
);
};
export default Popup;
You should use index in state ButtonPopup instead boolean to check condition render of popup.
<Button
type="button"
style={{ background: `${element.HEXCode}` }}
onClick={() => setButtonPopup(index)}
>
{element.HEXCode}
</Button>
<Popup trigger={ButtonPopup === index} setTrigger={setButtonPopup}>
I am new to using React JS. I have been using react-table to create a component that can filter, sort and paginate some sample data from a JSON file.
Here is the link to the tutorial I have been following:
https://www.freakyjolly.com/react-table-tutorial/#.YBfqqZP7SL4
Here is what I am seeing at the moment, the pagination appears to be broken, I am seeing all of the data appear (1000 rows). I am trying to have around 5-10 records showing at a time.
Here is the App.js code.
import React from 'react';
import logo from './logo.svg';
import './App.css';
import FilterTableComponent from './components/filter.pagination.sorting';
function App() {
return (
<div className="App">
<h3>Filter Table using <code>react-table</code></h3>
<FilterTableComponent />
</div>
);
}
export default App;
Here is the filter.paginate.sorting.js code
import React from "react";
import { useTable, useSortBy, usePagination, useFilters, useGlobalFilter, useAsyncDebounce } from 'react-table';
import 'bootstrap/dist/css/bootstrap.min.css';
import JSONDATA from './MOCK_DATA.json';
// Define a default UI for filtering
function GlobalFilter({
preGlobalFilteredRows,
globalFilter,
setGlobalFilter,
}) {
const count = preGlobalFilteredRows.length
const [value, setValue] = React.useState(globalFilter)
const onChange = useAsyncDebounce(value => {
setGlobalFilter(value || undefined)
}, 200)
return (
<span>
Search:{' '}
<input
className="form-control"
value={value || ""}
onChange={e => {
setValue(e.target.value);
onChange(e.target.value);
}}
placeholder={`${count} records...`}
/>
</span>
)
}
function DefaultColumnFilter({
column: { filterValue, preFilteredRows, setFilter },
}) {
const count = preFilteredRows.length
return (
<input
className="form-control"
value={filterValue || ''}
onChange={e => {
setFilter(e.target.value || undefined)
}}
placeholder={`Search ${count} records...`}
/>
)
}
function Table({ columns, data }) {
const defaultColumn = React.useMemo(
() => ({
// Default Filter UI
Filter: DefaultColumnFilter,
}),
[]
)
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
rows,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state,
state: { pageIndex, pageSize },
preGlobalFilteredRows,
setGlobalFilter,
} = useTable(
{
columns,
data,
defaultColumn,
initialState: { pageIndex: 0, pageSize: 10 }
},
useFilters,
useGlobalFilter,
useSortBy,
usePagination
)
return (
<div>
<ul className="pagination">
<li className="page-item" onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
<a className="page-link">First</a>
</li>
<li className="page-item" onClick={() => previousPage()} disabled={!canPreviousPage}>
<a className="page-link">{'<'}</a>
</li>
<li className="page-item" onClick={() => nextPage()} disabled={!canNextPage}>
<a className="page-link">{'>'}</a>
</li>
<li className="page-item" onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
<a className="page-link">Last</a>
</li>
<li>
<a className="page-link">
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</a>
</li>
<li>
<a className="page-link">
<input
className="form-control"
type="number"
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
style={{ width: '100px', height: '20px' }}
/>
</a>
</li>{' '}
<select
className="form-control"
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value))
}}
style={{ width: '120px', height: '38px' }}
>
{[5, 10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</ul>
<GlobalFilter
preGlobalFilteredRows={preGlobalFilteredRows}
globalFilter={state.globalFilter}
setGlobalFilter={setGlobalFilter}
/>
<pre>
<code>
{JSON.stringify(
{
pageIndex,
pageSize,
pageCount,
canNextPage,
canPreviousPage,
},
null,
2
)}
</code>
</pre>
<table className="table" {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render('Header')}
{/* Render the columns filter UI */}
<div>{column.canFilter ? column.render('Filter') : null}</div>
<span>
{column.isSorted
? column.isSortedDesc
? ' '
: ' '
: ''}
</span>
</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>
<br />
<div>Showing the first 20 results of {rows.length} rows</div>
<div>
<pre>
<code>{JSON.stringify(state.filters, null, 2)}</code>
</pre>
</div>
</div>
)
}
function FilterTableComponent() {
const columns = React.useMemo(
() => [
{
Header: 'Name',
columns: [
{
Header: 'WIP_ID',
accessor: 'WIP_ID',
},
{
Header: 'PRODUCT_SERIAL_NUMBER',
accessor: 'PRODUCT_SERIAL_NUMBER'
},
],
},
{
Header: 'Info',
columns: [
{
Header: 'DATE',
accessor: 'DATE'
},
{
Header: 'BUCKET_NAME',
accessor: 'BUCKET_NAME'
},
{
Header: 'CREATED_TS',
accessor: 'CREATED_TS'
},
{
Header: 'FILE_NAME',
accessor: 'FILE_NAME'
},
],
},
],
[]
)
const data = JSONDATA
return (
<Table columns={columns} data={data} />
)
}
export default FilterTableComponent;
Here is a sample of what the JSON file looks like. Named - MOCK_DATA.json
[{"WIP_ID":"56c97f3e-1c3f-4463-beb2-1af58ebe0db0","PRODUCT_SERIAL_NUMBER":"eab8304c-43e2-4f70-a23a-2db75bf2ce50","DATE":"29/06/2020","BUCKET_NAME":"Bytecard","CREATED_TS":"03/10/2020","FILE_NAME":"elit_proin.tiff"},
{"WIP_ID":"b358a03b-fee6-4957-9017-1de3d9846264","PRODUCT_SERIAL_NUMBER":"b974e89e-9bf9-4329-bc13-1afc3bdd52e0","DATE":"20/12/2020","BUCKET_NAME":"Vagram","CREATED_TS":"25/11/2020","FILE_NAME":"condimentum_id_luctus.mov"},
{"WIP_ID":"9fab6d70-72bc-40ae-a99f-5ae31dc47aec","PRODUCT_SERIAL_NUMBER":"8f9f70ce-b940-486b-a003-5c9db031e54e","DATE":"15/02/2020","BUCKET_NAME":"Domainer","CREATED_TS":"06/05/2020","FILE_NAME":"interdum_in_ante.tiff"},
{"WIP_ID":"5bb40cfb-99dc-413a-8b5f-0b6612e45d34","PRODUCT_SERIAL_NUMBER":"3bab0d2b-5464-4c5d-b1c8-0ba6b32e60fa","DATE":"14/03/2020","BUCKET_NAME":"Lotlux","CREATED_TS":"11/05/2020","FILE_NAME":"diam_nam_tristique.avi"},
{"WIP_ID":"95ae9754-e288-4ceb-b7cf-1b27892e7ace","PRODUCT_SERIAL_NUMBER":"1280dfb1-152d-44fd-aed7-4de8b8b573a6","DATE":"04/04/2020","BUCKET_NAME":"Gembucket","CREATED_TS":"14/10/2020","FILE_NAME":"vehicula.pdf"},
I think you need to change this in your tbody
{rows.map((row,i) =>
to
{page.map((row,i) =>