Related
I have a simple React app that uses react-dnd to build a grid of draggable squares that have an initial color and text value and two components to change the color and change the text.
The color change (via ColorPicker2, using react-color library) works okay. The text change (using TextInput from #carbon/react) doesn't work as desired.
I thought I was applying the same logic with both components, but whilst the color-picker updates the color and retains that color when the square is moved, the text seems to render inside the TextInput and not the square itself and I can't figure out the logical difference.
The Code Sandbox is here: https://codesandbox.io/s/wild-waterfall-z8s1de?file=/src/App.js
This is the current code:
ColorPicker2.js
import "./styles.css";
import React from "react";
import { BlockPicker } from "react-color";
const presetColors = ["#9E9E9E", "#4CAF50", "#FFEB3B", "#F44336", "#2196F3"];
const ColorPicker2 = (props) => {
const handleChangeComplete = (color) => {
if (props.updateColor) {
props.updateColor(color.hex);
}
};
return (
<div className="palette">
<BlockPicker
className="palette"
colors={presetColors}
onChangeComplete={handleChangeComplete}
presetColors={Object.values(presetColors)}
color={props.currentColor}
/>
</div>
);
};
export default ColorPicker2;
App.js
import "./styles.css";
import React, { useState } from "react";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import edit from "./edit.svg";
import palette from "./palette.svg";
import ColorPicker2 from "./ColorPicker2";
import { TextInput } from "#carbon/react";
const DndWrapper = (props) => {
return <DndProvider backend={HTML5Backend}>{props.children}</DndProvider>;
};
const DraggableSquare = ({ index, text, color, moveSquare }) => {
const [{ isDragging }, drag] = useDrag({
type: "square",
item: { index },
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
});
const [isHovered, setIsHovered] = useState(false);
const [, drop2] = useDrop({
accept: "square",
drop: (item, monitor) => {
const didDrop = monitor.didDrop();
if (!didDrop) {
moveSquare(item.index, index);
}
},
hover: (item, monitor) => {
setIsHovered(monitor.isOver());
},
collect: (monitor) => {
setIsHovered(monitor.isOver());
}
});
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
const [isTextInputOpen, setIsTextInputOpen] = useState(false);
const [newText, setNewText] = useState(text);
const opacity = isDragging ? 0.5 : 1;
return (
<div className="square-div" ref={drop2}>
<div
className="grey-square"
ref={drag}
style={{
opacity,
backgroundColor: color,
width: "200px",
height: "200px",
textAlign: "center",
paddingTop: "30px",
position: "relative",
border: isHovered ? "3px solid blue" : "none",
borderRadius: "5px",
}}
onMouseOver={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<img
src={edit}
onClick={() => {
setIsTextInputOpen(!isTextInputOpen);
if (!isTextInputOpen) {
moveSquare(index, newText, color, undefined);
}
}}
style={{
width: "15px",
height: "15px",
position: "absolute",
right: "5px",
top: "5px"
}}
alt="edit icon"
/>
{isTextInputOpen && (
<TextInput
id="newtext"
labelText=""
value={newText}
onChange={(e) => setNewText(e.target.value)}
/>
)}
<img
src={palette}
onClick={() => setIsPaletteOpen(!isPaletteOpen)}
style={{
width: "15px",
height: "15px",
position: "absolute",
right: "25px",
top: "5px"
}}
alt="palette icon"
/>
{isPaletteOpen && (
<ColorPicker2
className="palette"
currentColor={color}
updateColor={(newColor) =>
moveSquare(index, index, newText, newColor)
}
/>
)}
</div>
</div>
);
};
const Grid = () => {
const [grid, setGrid] = useState([
{ text: "1", color: "grey" },
{ text: "2", color: "grey" },
{ text: "3", color: "grey" },
{ text: "4", color: "grey" },
{ text: "5", color: "grey" },
{ text: "6", color: "grey" },
{ text: "7", color: "grey" },
{ text: "8", color: "grey" },
{ text: "9", color: "grey" },
{ text: "10", color: "grey" },
{ text: "11", color: "grey" },
{ text: "12", color: "grey" },
{ text: "13", color: "grey" },
{ text: "14", color: "grey" },
{ text: "15", color: "grey" }
]);
const moveSquare = (fromIndex, toIndex, newText, newColor) => {
setGrid((grid) => {
const newGrid = [...grid];
const item = newGrid[fromIndex];
newGrid.splice(fromIndex, 1);
newGrid.splice(toIndex, 0, {
text: newText || item.text,
color: newColor || item.color
});
return newGrid;
});
};
return (
<>
<DndWrapper>
<div
className="grid"
style={{
display: "grid",
gridTemplateColumns: "repeat(5, 190px)",
gridGap: "15px",
gridColumnGap: "20px",
gridRowGap: "10px",
position: "absolute"
}}
>
{grid.map((square, index) => (
<DraggableSquare
key={index}
index={index}
text={square.text}
color={square.color}
moveSquare={moveSquare}
//grid={grid}
//setGrid={setGrid}
/>
))}
</div>
</DndWrapper>
</>
);
};
export default Grid;
Any thoughts from fresh eyes would be helpful.
I think this is just a simple issue with using index as the key while mapping. Adjusting your code pen to have a unique key fixed it for me but the input text is not being saved anywhere so returned to the default text={square.text} when moved as expected.
Unique Id in objects:
const [grid, setGrid] = useState([
{ text: "1", color: "grey", id: crypto.randomUUID() },
{ text: "2", color: "grey", id: crypto.randomUUID() },
{ text: "3", color: "grey", id: crypto.randomUUID() },...])
Adding key to mapped object:
{grid.map((square, index) => (
<DraggableSquare
key={square.id}
index={index}
text={square.text}
color={square.color}
moveSquare={moveSquare}
//grid={grid}
//setGrid={setGrid}
/>}
so I have Texfield from Material UI, but i notice one things, every time i type, i stop and not continue to next letter, so i have to click again on it..., but when i remove the onChange method, the data continue to type without stopping..., is there because of the useEffect Component that prevent it from continously typing?
Here is my code:
import React from "react";
import { Search } from "#mui/icons-material";
import { IconButton, TextField, InputAdornment } from "#mui/material";
import {
GridToolbarDensitySelector,
GridToolbarContainer,
GridToolbarExport,
GridToolbarColumnsButton,
} from "#mui/x-data-grid";
import FlexBetween from "./FlexBetween";
const DataGridCustomToolbar = ({ searchInput, setSearchInput, setSearch }) => {
return (
<GridToolbarContainer>
<FlexBetween width="100%">
<FlexBetween>
<GridToolbarColumnsButton />
{/* <GridToolbarDensitySelector /> */}
<GridToolbarExport />
</FlexBetween>
<TextField
label="Search..."
sx={{ mb: "0.5rem", width: "15rem" }}
onChange={(e) => setSearchInput(e.target.value)}
value={searchInput}
variant="standard"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => {
setSearch({ value: searchInput });
setSearchInput("");
}}
>
<Search />
</IconButton>
</InputAdornment>
),
}}
/>
</FlexBetween>
</GridToolbarContainer>
);
};
export default DataGridCustomToolbar;
Here is what its being use on the datagrid
const BOM = () => {
const theme = useTheme();
// values to be sent to the backend
const [page, setPage] = useState(0);
const [limit, setlimit] = useState(20);
const [order, setOrder] = useState([
{
column: "1",
dir: "asc",
},
]);
const [search, setSearch] = useState({
value: "",
});
const [data, setData] = useState([]);
const [total, setTotal] = useState(0);
const setDataInput = (value) => {
setDataInput(value);
};
useEffect(() => {
dataService.getBomList(page, limit, order, search).then((response) => {
console.log("BOM");
setData(response.data.data.data);
setTotal(response.data.data.recordsFiltered);
return response.data.data.data;
});
}, [page, limit, order]);
console.log(data);
const [searchInput, setSearchInput] = useState("");
const columns = [
{
field: "sku",
headerName: "SKU",
width: 100,
},
{ field: "sku_name", headerName: "SKU NAME", width: 300 },
{
field: "plant",
headerName: "Plant",
width: 80,
align: "center",
headerAlign: "center",
},
{ field: "base_qty", headerName: "Base QTY", width: 100 },
{
field: "item",
headerName: "Item",
width: 60,
align: "center",
headerAlign: "center",
},
{ field: "uom_sku", headerName: "UOM SKU", width: 100, align: "center" },
{ field: "material", headerName: "Material", width: 150 },
{ field: "material_name", headerName: "Material Name", width: 300 },
{ field: "qty_material", headerName: "Material QTY", width: 100 },
{ field: "uom_material", headerName: "Material UOM", width: 100 },
{ field: "alt_bom", headerName: "BOM ALT", width: 80 },
{ field: "valid_from", headerName: "Valid From", width: 210 },
{ field: "valid_to", headerName: "Valid to", width: 210 },
{ field: "status", headerName: "Status", width: 80 },
];
return (
<Box m="5px">
<Box height="80vh">
{console.log("data", order, page, limit, search, searchInput)}
<StripedDataGrid
// loading={isLoading || !data}
getRowId={(row) => row.id}
columns={columns}
rowCount={(limit && total) || 0}
rowsPerPageOptions={[20, 50, 100]}
rows={data || []}
pagination
paginationMode="server"
sortingMode="server"
page={page}
pageSize={limit}
onPageSizeChange={(newPageSize) => setlimit(newPageSize)}
onPageChange={(newPage) => setPage(newPage)}
onSortModelChange={(newSortModel) => setOrder(...newSortModel)}
components={{ Toolbar: DataGridCustomToolbar }}
componentsProps={{
toolbar: { searchInput, setSearchInput, setSearch },
}}
/>
</Box>
</Box>
);
};
export default BOM;
Can someone tellme where did i do wrong here....
This is my first time using react-table, after I decided to redesign a component in my app. I'm almost there, but I found myself stuck while trying to pass some data to a dialog pop-up on button click.
In my previous table design, using MUI Table, I used array.map() to render each row entry. Then, all I needed to do was using props to pass data to the Dialog components, so every action button would load the data from their respective entries (including data not displayed, like _id). Now with react-table I don't know how to achieve the same result, due to the logic change. The Edit and Delete buttons are there, they trigger the dialog opening, but my progress ends here.
Entries.jsx
function Entries(props) {
const navigate = useNavigate();
const [openEdit, setOpenEdit] = useState(false);
const [openDelete, setOpenDelete] = useState(false);
const handleEdit = () => {
setOpenEdit(true);
};
const handleDelete = () => {
setOpenDelete(true);
};
const handleClose = () => {
setOpenEdit(false);
setOpenDelete(false);
};
const data = props.data;
const columns = useMemo(
() => [
{
Header: "#",
id: "row",
Cell: ({ row }) => {
return <div>{row.index + 1}</div>;
},
},
{
Header: "Title",
accessor: "title",
className: "column-left",
Cell: ({ cell: { value }, row: { original } }) => (
<Link
sx={{
fontSize: "14px",
fontWeight: 500,
"&:hover": { color: "#00D67E" },
}}
color="#fff"
underline="none"
style={{ cursor: "pointer" }}
onClick={() => navigate(`/details/${original.movieId}`)}
>
{value}
</Link>
),
},
{
Header: "Year",
accessor: "year",
className: "column-right",
},
{
Header: "Rating",
accessor: "details[0].rating",
className: "column-right",
Cell: ({ cell: { value } }) => (
<Rating
size="small"
value={value}
readOnly
emptyIcon={<StarIcon fontSize="inherit" />}
/>
),
},
{
Header: "Watched",
accessor: "date",
className: "column-right",
Cell: ({ cell: { value } }) => format(new Date(value), "MMM dd, yyyy"),
},
{
Header: "View Count",
accessor: "details[0].view_count",
className: "column-right",
},
{
Header: "Review",
accessor: "details[0].review",
className: "column-right",
Cell: ({ cell: { value } }) =>
!value ? null : <CheckIcon color="primary" />,
},
{
Header: "Actions",
className: "column-right",
Cell: () => (
<div>
<IconButton
aria-label="edit"
style={{ color: "#e0e0e0" }}
onClick={handleEdit}
>
<EditIcon fontSize="small" />
</IconButton>
<IconButton
aria-label="delete"
style={{ color: "#e0e0e0", paddingRight: 0 }}
onClick={handleDelete}
>
<DeleteIcon fontSize="small" />
</IconButton>
</div>
),
},
],
[]
);
return (
<div>
{/* TABLE */}
<CustomTable columns={columns} data={data} />
{/* DIALOGS */}
<EditDialog
isOpen={openEdit}
onClose={handleClose}
id={data._id}
movieId={data.movieId}
date={data.date}
rating={data.details[0].rating}
review={data.details[0].review}
onUpdate={props.onUpdate}
/>
<DeleteDialog
isOpen={openDelete}
onClose={handleClose}
title="Delete this entry?"
message={
<span>
<strong>
{data.title} ({data.date})
</strong>{" "}
will be removed. This action cannot be undone.
</span>
}
onRemove={() => {
props.onRemove(data._id, data.movieId);
}}
/>
</div>
);
}
export default Entries;
Later on I even tried something to mimic the logic of my previous design, since the mapping is handled by the rows.map((row) => { prepareRow(row) } in my CustomTable.jsx component. Then I came up with this:
{
Header: "Actions",
className: "column-right",
Cell: () => (
<div>
<IconButton
aria-label="edit"
style={{ color: "#e0e0e0" }}
onClick={({row}) => {
handleEdit();
return (
<EditDialog
isOpen={openEdit}
onClose={handleClose}
id={row._id}
movieId={row.movieId}
date={row.date}
rating={row.details[0].rating}
review={row.details[0].review}
onUpdate={props.onUpdate}
/>
);
}}
>
<EditIcon fontSize="small" />
</IconButton>
<IconButton
aria-label="delete"
style={{ color: "#e0e0e0", paddingRight: 0 }}
onClick={handleDelete}
>
<DeleteIcon fontSize="small" />
</IconButton>
</div>
),
}
But it didn't work either. It stops right at the first prop and throws an error saying it can't read properties row._id. That was my last shot.
That is simple, you can store the selected row to the state and then use it in your Edit and Delete components. Try this
function Entries(props) {
const navigate = useNavigate();
const [openEdit, setOpenEdit] = useState(false);
const [openDelete, setOpenDelete] = useState(false);
const [selectedRow, setSelectedRow] = useState();
const handleEdit = (row) => {
setOpenEdit(true);
setSelectedRow(row);
};
const handleDelete = (row) => {
setOpenDelete(true);
setSelectedRow(row);
};
const handleClose = () => {
setOpenEdit(false);
setOpenDelete(false);
setSelectedRow();
};
const data = props.data;
const columns = useMemo(
() => [
{
Header: "#",
id: "row",
Cell: ({ row }) => {
return <div>{row.index + 1}</div>;
}
},
{
Header: "Title",
accessor: "title",
className: "column-left",
Cell: ({ cell: { value }, row: { original } }) => (
<Link
sx={{
fontSize: "14px",
fontWeight: 500,
"&:hover": { color: "#00D67E" }
}}
color="#fff"
underline="none"
style={{ cursor: "pointer" }}
onClick={() => navigate(`/details/${original.movieId}`)}
>
{value}
</Link>
)
},
{
Header: "Year",
accessor: "year",
className: "column-right"
},
{
Header: "Rating",
accessor: "details[0].rating",
className: "column-right",
Cell: ({ cell: { value } }) => (
<Rating
size="small"
value={value}
readOnly
emptyIcon={<StarIcon fontSize="inherit" />}
/>
)
},
{
Header: "Watched",
accessor: "date",
className: "column-right",
Cell: ({ cell: { value } }) => format(new Date(value), "MMM dd, yyyy")
},
{
Header: "View Count",
accessor: "details[0].view_count",
className: "column-right"
},
{
Header: "Review",
accessor: "details[0].review",
className: "column-right",
Cell: ({ cell: { value } }) =>
!value ? null : <CheckIcon color="primary" />
},
{
Header: "Actions",
className: "column-right",
Cell: ({ row }) => (
<div>
<IconButton
aria-label="edit"
style={{ color: "#e0e0e0" }}
onClick={() => handleEdit(row.original)}
>
<EditIcon fontSize="small" />
</IconButton>
<IconButton
aria-label="delete"
style={{ color: "#e0e0e0", paddingRight: 0 }}
onClick={() => handleEdit(row.original)}
>
<DeleteIcon fontSize="small" />
</IconButton>
</div>
)
}
],
[]
);
return (
<div>
{/* TABLE */}
<CustomTable columns={columns} data={data} />
{/* DIALOGS */}
<EditDialog
isOpen={openEdit}
onClose={handleClose}
id={selectedRow?._id}
movieId={selectedRow?.movieId}
date={selectedRow?.date}
rating={selectedRow?.details[0]?.rating}
review={selectedRow?.details[0]?.review}
onUpdate={props.onUpdate}
/>
<DeleteDialog
isOpen={openDelete}
onClose={handleClose}
title="Delete this entry?"
message={
<span>
<strong>
{selectedRow?.title} ({selectedRow?.date})
</strong>{" "}
will be removed. This action cannot be undone.
</span>
}
onRemove={() => {
props.onRemove(selectedRow?._id, selectedRow?.movieId);
}}
/>
</div>
);
}
export default Entries;
i'm using the following ant component : https://ant.design/components/table/
and i'm really struggling to add the following search option in my table :
here's the table i've created so far :
i'm pretty new to both React and Typescript , since the project is not in js but in typescript i cannot get my code to work properly looking at ant documentation ; i've turned my component to a functional component as i'm gonna be using hooks and added some code to get out most of the compiler errors (type declaration for ts) , still have some syntax errors :
heres my code:
import React, { Component, useState } from 'react';
import {connect} from "react-redux";
// #ts-ignore
import Highlighter from 'react-highlight-words';
//**External Libraries */
//REACT MOSAIC
import {ExpandButton, MosaicWindow, RemoveButton} from "react-mosaic-component";
import {MosaicBranch} from "react-mosaic-component/src/types";
//BLUEPRINTJS
import {InputGroup} from "#blueprintjs/core";
//ANTDESIGN
import { Table , Button ,Tag , Input , Space} from "antd";
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
import {CaretRightOutlined , SearchOutlined} from '#ant-design/icons';
//STYLES
import './styles.css'
//API
import {useGetAllUtenti} from '../../../api/index';
import { Utente } from '../../../api/types';
const UserSummaryWindow: React.FC<any> = (props) => {
//HOOKS STATE FOR TABLE SEARCH
const [searchText , setSearchText] = useState('');
const [searchedColumn , setSearchedColumn] = useState('');
const {path} = props;
const dataSource: Object | any = useGetAllUtenti().data;
const ruoli: any = [
{
text: 'User',
value: 'user',
},
{
text: 'Administrator',
value: 'administrator',
},
{
text: 'NumeroVerde',
value: 'numeroVerde',
},
]
const stati: any = [
{
text: 'Attivo',
value: 1,
},
{
text: 'Non Attivo',
value: 0,
}
]
const columns: any = [
{
title: 'Nome',
dataIndex: 'nome',
key: 'nome',
defaultSortOrder: 'descend',
sorter: (a:any, b:any) => { return a.nome.localeCompare(b.nome)},
},
{
title: 'Cognome',
dataIndex: 'cognome',
key: 'cognome',
sorter: (a:any, b:any) => { return a.cognome.localeCompare(b.cognome)},
},
{
title: 'Ruolo',
dataIndex: 'ruolo',
key: 'ruolo',
filters: ruoli,
onFilter: (value:any, record:any) => record.ruolo.indexOf(value) === 0,
sorter: (a:any, b:any) => { return a.ruolo.localeCompare(b.ruolo)},
render: (text: string) => (
<>
{
<Tag color={renderTagColor(text)}>
{text.toUpperCase()}
</Tag>
}
</>)
},
{
title: 'Gestore',
dataIndex: 'gestore',
key: 'gestore',
sorter: (a:any, b:any) => { return a.gestore.localeCompare(b.gestore)},
},
{
title: 'Username',
dataIndex: 'username',
key: 'username',
sorter: (a:any, b:any) => { return a.username.localeCompare(b.username)},
},
// {
// title: 'Password',
// dataIndex: 'password',
// key: 'password',
// },
// {
// title: 'IDEnte',
// dataIndex: 'idEnte',
// key: 'idEnte',
// },
{
title: 'Tipo',
dataIndex: 'tipo',
key: 'tipo',
sorter: (a:any, b:any) => a.tipo - b.tipo,
},
{
title: 'Stato',
dataIndex: 'stato',
key: 'stato',
filters: stati,
onFilter: (value:any, record:any) => record.stato.indexOf(value) === 0,
sorter: (a:any, b:any) => a.stato - b.stato,
render :(stato: number) => (
<>
{
(stato == 1) ?
(
<Tag color="#00cc00">
Attivo
</Tag>
) :
(
<Tag color="#ff0000">
Non Attivo
</Tag>
)
}
</>)
},
{
title: 'Ultimo aggiornamento password',
dataIndex: 'ultimoAggiornamentoPassword',
key: 'ultimoAggiornamentoPassword',
sorter: (a:any, b:any) => a.ultimoAggiornamentoPassword.localeCompare(b.ultimoAggiornamentoPassword)
},
{
title: '',
dataIndex: 'idUtente',
key: 'idUtente',
render: () => (
//da inserire link per andare al dettaglio / modifica utente
<Button type="primary"><CaretRightOutlined /></Button>
),
},
]
const toolbarControls = React.Children.toArray([
<ExpandButton/>,
<RemoveButton/>
]);
function renderTagColor(text:string): string {
switch(text) {
case 'user':
return 'gold';
case 'administrator':
return 'red';
case 'numeroVerde':
return 'green';
default:
return '';
}
}
getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={node => {
this.searchInput = node;
}}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: (text:string) =>
searchText === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text.toString()}
/>
) : (
text
),
});
function handleSearch (selectedKeys:any, confirm:any, dataIndex:any) {
confirm();
setSearchText(selectedKeys[0]);
setSearchedColumn(dataIndex);
};
function handleReset (clearFilters:any) {
clearFilters();
setSearchText('');
};
return (
<MosaicWindow<string>
title="Riepilogo Utenti"
path={path}
toolbarControls={toolbarControls}
>
<Table
dataSource={dataSource}
columns={columns}
bordered={true}
//pagination={{ pageSize:3,position: ['bottomCenter'] }}
pagination={{position: ['bottomCenter'] }}
rowKey={'idUtente'}
//stile per righe striped
rowClassName={(record, index) => index % 2 === 0 ? 'table-row-light' : 'table-row-dark'}
/>
</MosaicWindow>
);
};
export default UserSummaryWindow;
The part which is giving me headeache is (need to convert it to typescript [strict option in config file is enabled]):
getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={node => {
this.searchInput = node;
}}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: (text:string) =>
searchText === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text.toString()}
/>
) : (
text
),
});
I'm not here asking you to correct my code , just wanted to know if anyone could share a typescript implementation of that getColumnSearchProps function , or an example of the table component from ant design with typescript code..
Sorry for such a late reply, i was having the same problem.
I found the solution on a github repo someone made. i really wonder why the antd devs don't support typescript in these days.
anyways, here is the repo.
https://github.com/freewind-demos/typescript-react-antd-table-search-column-demo
Basically All the hardwork of moving the old JS code to typescript is done, although there are some linting errors here and there.
I hope anyone who comes looking for this answer really questions their decision to use antd instead of react-table or material-design.
Same answer as Lav Hinsu, but I'll paste the full code here, just in case that link dies.
import "antd/dist/antd.css";
import React from "react";
import ReactDOM from "react-dom";
import { SearchOutlined } from "#ant-design/icons";
import { Table, Button, Input } from "antd";
import { ColumnType } from "antd/lib/table";
function tableColumnTextFilterConfig<T>(): ColumnType<T> {
const searchInputHolder: { current: Input | null } = { current: null };
return {
filterDropdown: ({
setSelectedKeys,
selectedKeys,
confirm,
clearFilters,
}) => (
<div style={{ padding: 8 }}>
<Input
ref={(node) => {
searchInputHolder.current = node;
}}
placeholder="Search"
value={selectedKeys[0]}
onChange={(e) =>
setSelectedKeys(e.target.value ? [e.target.value] : [])
}
onPressEnter={() => confirm()}
style={{ width: 188, marginBottom: 8, display: "block" }}
/>
<Button
type="primary"
onClick={() => confirm()}
icon={<SearchOutlined />}
size="small"
style={{ width: 90, marginRight: 8 }}
>
Search
</Button>
<Button size="small" style={{ width: 90 }} onClick={clearFilters}>
Reset
</Button>
</div>
),
filterIcon: (filtered) => (
<SearchOutlined style={{ color: filtered ? "#1890ff" : undefined }} />
),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => searchInputHolder.current?.select());
}
},
};
}
type Data = {
key: string;
name: string;
};
const data: Data[] = [
{
key: "1",
name: "John Brown",
},
{
key: "2",
name: "Jim Green",
},
{
key: "3",
name: "Joe Black",
},
];
function Hello() {
return (
<div>
<Table
columns={[
{
title: "Name",
dataIndex: "name",
render: (text: string) => text,
...tableColumnTextFilterConfig<Data>(),
onFilter: (value, record) => {
return record.name
.toString()
.toLowerCase()
.includes(value.toString().toLowerCase());
},
},
]}
dataSource={data}
/>
</div>
);
}
ReactDOM.render(<Hello />, document.body);
In short, you can just extract the tableColumnTextFilterConfig function and implement the onFilter property on the desired column.
I am trying to add a row in a material-table and am getting an error
The example mentioned is in the material tables docs
package.json
"dependencies": {
"#material-ui/core": "^4.1.0",
"#material-ui/icons": "^4.1.0",
"classnames": "^2.2.6",
"material-table": "^1.39.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.1",
"react-scripts": "3.0.1"
},
testingList.js
<div className={classes.root}>
<MaterialTable
title="Testing"
icons={tableIcons}
columns={this.state.columns}
data={this.state.data}
editable={{
onRowAdd: (newData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
const data = this.state.data
data.push(newData)
this.setState({ data }, () => resolve())
}
resolve()
}, 1000)
}),
}}
/>
</div>
Error:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of MTableAction.
If I remove on add method the function works as expected.
Also it works fine if I remove the line icons={tableIcons} where tableIcons is
const tableIcons = {
FirstPage: FirstPage,
LastPage: LastPage,
NextPage: ChevronRight,
PreviousPage: ChevronLeft,
};
import React, { useState, useEffect } from 'react';
import Search from "#material-ui/icons/Search";
import ViewColumn from "#material-ui/icons/ViewColumn";
import SaveAlt from "#material-ui/icons/SaveAlt";
import ChevronLeft from "#material-ui/icons/ChevronLeft";
import ChevronRight from "#material-ui/icons/ChevronRight";
import FirstPage from "#material-ui/icons/FirstPage";
import LastPage from "#material-ui/icons/LastPage";
import Add from "#material-ui/icons/Add";
import Check from "#material-ui/icons/Check";
import FilterList from "#material-ui/icons/FilterList";
import Remove from "#material-ui/icons/Remove";
import ArrowDownward from "#material-ui/icons/ArrowDownward";
import Clear from "#material-ui/icons/Clear";
import DeleteOutline from "#material-ui/icons/DeleteOutline";
import Edit from "#material-ui/icons/Edit";
import MaterialTable from "material-table";
import UserService from "../../services/user.service";
import ToastServive from "react-material-toast";
import userService from '../../services/user.service';
var _table_Data = [];
const toast = ToastServive.new({
place: "topRight",
duration: 2,
maxCount: 8,
});
const server_Data = userService._get_data().then((response) => {
return response.data.data
})
export default function InputTable(props) {
useEffect(() => {
UserService._get_data().then((response) => {
const dataUpdate = [...response.data.data];
setData([...dataUpdate]);
})
})
const [columns] = useState([
{
title: "Name",
field: "Name",
cellStyle: {
width: "15%",
maxWidth: "15%",
fontSize: 12,
},
headerStyle: {
width: "15%",
maxWidth: "15%",
},
},
{
title: "Exposure",
field: "EAD",
cellStyle: {
width: "20%",
maxWidth: "20%",
fontSize: 12,
textAlign: "left",
},
headerStyle: {
width: "20%",
maxWidth: "20%",
textAlign: "left",
},
},
{
title: "Def. Prob.",
field: "PD",
cellStyle: {
width: "25%",
maxWidth: "25%",
fontSize: 12,
textAlign: "left",
},
headerStyle: {
width: "25%",
maxWidth: "25%",
textAlign: "left",
},
},
{
title: "LGD",
field: "LGD",
cellStyle: {
width: "30px",
maxWidth: "30px",
fontSize: 12,
textAlign: "left",
},
headerStyle: {
width: 20,
maxWidth: 12,
textAlign: "left",
},
},
{
title: "Corr",
field: "w",
cellStyle: {
width: 20,
maxWidth: 20,
fontSize: 12,
textAlign: "left",
},
},
]);
const [data, setData] = useState(_table_Data);
return (
<div
style={{
fontSize: 10,
maxWidth: "100%",
margin: "auto",
padding: "0 0",
}}
>
<MaterialTable
icons={{
Check: Check,
DetailPanel: ChevronRight,
Delete: DeleteOutline,
Export: SaveAlt,
Filter: FilterList,
FirstPage: FirstPage,
LastPage: LastPage,
NextPage: ChevronRight,
PreviousPage: ChevronLeft,
Search: Search,
ThirdStateCheck: Remove,
Add: Add,
SortArrow: ArrowDownward,
Clear: Clear,
Edit: Edit,
ViewColumn: ViewColumn,
ResetSearch: Clear,
}}
options={{
actionsColumnIndex: -1,
headerStyle: {
fontSize: 12,
fontWeight: "bold",
backgroundColor: "white",
color: "black",
},
rowStyle: {
color: "black",
backgroundColor: "#eeeeee",
height: "50px",
},
actionsCellStyle: {
fontSize: "small", //doesn't work
},
showTitle: true,
search: true,
padding: "dense",
exportButton: true,
}}
title="Editable Preview"
columns={columns}
data={data}
editable={{
onBulkEditRow: (changes) => {
console.log(changes);
},
onBulkUpdate: (changes) =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
}),
onRowAddCancelled: (rowData) => console.log("Row adding cancelled"),
onRowUpdateCancelled: (rowData) =>
console.log("Row editing cancelled"),
onRowAdd: (newData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
const _sendData = newData;
UserService.datamanage(_sendData).then((response) => {
toast.success(JSON.parse(JSON.stringify(response.data.message)));
}).catch(() => {
toast.error("failed")
})
resolve();
}, 1000);
}),
onRowUpdate: (newData, oldData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
const dataUpdate = [...data];
const index = oldData.tableData.id;
dataUpdate[index] = newData;
setData([...dataUpdate]);
resolve();
}, 1000);
}),
onRowDelete: (oldData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
const dataDelete = [...data];
const index = oldData.tableData.id;
dataDelete.splice(index, 1);
setData([...dataDelete]);
resolve();
}, 1000);
}),
}}
/>
</div>
);
}
I had the same issue and I solved it by adding Actions under components so that the code becomes:
<Table
title="Editable Preview"
columns={this.state.columns}
data={this.state.data}
editable={{
isEditable: rowData => rowData.name === "a", // only name(a) rows would be editable
isDeletable: rowData => rowData.name === "b", // only name(a) rows would be deletable
onRowAdd: newData => new Promise((resolve) => console.log("onrowadd", newData)),
onRowUpdate: (newData, oldData) => new Promise((resolve) => console.log("onRowUpdate", newData, oldData)),
onRowDelete: (oldData) => new Promise((resolve) => console.log("onRowDelete", oldData)),
}}
components={{
Actions: props => (
<div>
<IconButton
onClick={() =>
this.props.history.push(`/teams/${props.data.uuid}`)
}
>
<EditIcon />
</IconButton>
<IconButton
onClick={() =>
this.props.history.push(`/teams/${props.data.uuid}`)
}
>
<EditIcon />
</IconButton>
</div>
)
}}
/>
Hope this helps!
I solved it by downgrading to version 1.54.0