Access Ant Design table column value - javascript

I am trying to condionally render Bage based on status column value. Even though I can render the table with the status values, I cannot access the status column for each item for conditional rendering of status Badge. How do I access the values listed on the column ?
The "status" value is inside data [ ]
const tableColumns = columns.map((item) => ({
...item,
ellipsis,
}));
const columns = [
{
title: 'Статус',
dataIndex: 'status',
render: () => {dataIndex.status === "in progress" ? <Badge status="processing" text="in progress" /> : <Badge status="success" text="delivered" />}
},
]
<Table
{...tableProps}
pagination={{
position: [top, bottom],
}}
columns={tableColumns}
dataSource={hasData ? data : []}
scroll={scroll}
/>

Related

How can I do dynamic badge on React CoreUI?

I have a problem with a badge on Core UI. I have a Sidebar and one of the elements is Chat. When a new message comes, the badge must show new message. But the old developer have written different ways and I can not change it. I cannot find anything for this.
My codes
Sidebar elements
const _nav = [
{
_tag: "CSidebarNavItem",
name: "Chat",
to: "/chat",
filter: "feedbacks",
icon: "cil-speech",
badge: {
color: "info",
text: "NEW MESSAGE",
},
},
]
My React component
import navigation from "./_nav";
const [filteredNav, setFilteredNav] = useState([]);
const [chatBadge, setChatBadge] = useState(false);
const handleChatBadge = () => {
setChatBadge(true)
}
// it is a sidebar element for user-role
useLayoutEffect(() => {
allwedMenu().then((res) => {
const arr = [navigation[0]];
res.data.items.forEach((element) => {
arr.push(element.name);
});
setFilteredNav(navigation.filter((item) => arr.includes(item.filter)));
});
}, []);
<CSidebarNav>
<CCreateElement
items={filteredNav}
components={{
CSidebarNavDivider,
CSidebarNavDropdown,
CSidebarNavItem,
CSidebarNavTitle,
}}
/>
</CSidebarNav>
I need the badge to show when chatBadge is true. But I don't know how can I write this.
You can only add a condition to show the badge when chatBadge is true.
Based on the Value of ChatBadge, you can use the property of the Component CSideBarNavItem to display the badge and pass the colour and text properties.
Here's the updated code:
<CSidebarNav>
<CCreateElement
items={filteredNav}
components={{
CSidebarNavDivider,
CSidebarNavDropdown,
CSidebarNavItem,
CSidebarNavTitle,
}}
/>
{filteredNav.map((item, index) => (
<CSidebarNavItem
key={index}
name={item.name}
to={item.to}
icon={item.icon}
badge={
item.name === "Chat" && chatBadge
? { color: "info", text: "NEW MESSAGE" }
: null
}
/>
))}
</CSidebarNav>
Hope it helps.

How to make a reusable table component which can handle different data structures and layouts?

I'm building an app with many tables. Generally they look the same, but some has different columns set or cell content. I'd like to make a reusable table component which can be used through out the entire app.
At the moment I made a table wrapper component which accepts children and table type. Columns are rendered depending on table type. Then I have several components each for different table because data structure may differ and layout might be slightly different. The question is how to make a single table component which can handle different data structures and different layouts?
I think it can be done with conditional rendering (but I do not want excessively use this because it will be hard to maintain and difficult to read):
{tableType === "first" ? <TableCell>{item.name}</TableCell> : null}
{tableType === "second" ? <TableCell>{item.status}</TableCell> : null}
I was told that it can be done in somekind of this way:
<TableCell>{getCellComponent(tableType, value)}</TableCell>
Unfortunatelly I'm not that smart enough to make it myself. I understand the general idea for this approach but don't understand how to make it. Can someone help me with that please?
I made a Codesandbox with two simplified tables:
https://codesandbox.io/s/complex-table-yrrv6c?file=/src/App.tsx
UPD
I went with #Dilshan solution. It works great. Meanwhile there're couple TS errors which I don't know how to fix.
I want to store columns props in a variable. Like this:
const columnObj = {
firstName: {
name: "First Name",
width: "25%",
accessor: (payload: any) => (
<>
<Avatar />
{payload.who.nickname}
</>
)
},
// ...
};
But then what type should I specify for payload?
I'd like to pass onClick handler to the table. Basically I want whole row to be clickable:
<MyTable<PayloadTyep2>
columns={columnObj}
payload={secondTableData}
onClick={(id) => console.log("Row clicked:", id)}
/>
Then in assign it in Table component:
<TableBody>
{payload.map((rowData, index) => {
return (
<TableRow key={index}
onClick={(id) => props.onClick(rowData.id)}> // here get TS error
// ...
</TableRow>
);
})}
</TableBody>
How to fix that?
Here's forked Codesanbox: https://codesandbox.io/s/react-mui-reusable-table-forked-vk7h6o?file=/src/App.tsx
Lets think about your requirement. You need to render a table based on given data payload. Table needs to know what's it columns. We can provide columns as a n input as well,
<MyTable
data={myData}
columns=['col1', 'col2', 'col3']
/>
We can assume col1, col2, col3 are keys of the myData object so the MyTable components can now extract the cell data by simply doing myData[columns[i]] where i is the index of column array item.
Based on your mock data, I can see there are nested objects in your data as well. Therefore simple myData[columns[i]] is not going to work. As a solution we can provide a function to return cell value in the component props.
type TableProps<T extends object> = {
columns: Record<
string,
{
name: string;
width: string;
accessor: (data: T) => ReactNode | string | undefined;
}
>;
payload: T[];
};
export const MyTable = <T extends object>(props: TableProps<T>) => {}
As you can see accessor is a function which has one argument type T which returns a React element or string or nothing.
Now in the table component we can simply do,
<TableRow key={index}>
{Object.keys(columns).map((key) => {
const { accessor } = columns[key];
return (
<TableCell key={key} align="left">
{accessor(rowData)}
</TableCell>
);
})}
</TableRow>
Then when you use the Table component,
<TableWithTitle<PayloadType1>
columns={{
phone: {
name: "Phone",
width: "14%",
accessor: (payload) => payload.phohe
},
notes: {
name: "Notes",
width: "14%",
accessor: (payload) => {
return payload.notes?.map(({ note }) => note).join(", ");
}
}
}}
....
Here is a full code sample
The basic idea is to make code less fragile to changes, i.e everytime you add a new table type or make changes to existing table, the affect on other table types should be minimum, you can read more about SOLID principles later.
use composition to make component more reusable
There is a basic idea of the solution
// create a factory/config to pick the right header columns based on type
const tableOneColumnHeaders = [
{ id: 1, name: "First name", width: "25%" },
{ id: 2, name: "Second name", width: "16%" },
{ id: 3, name: "Address", width: "14%" },
{ id: 4, name: "Phone", width: "14%" },
{ id: 5, name: "Notes", width: "14%" }
];
const tableTwoColumnHeaders = [
{ id: 1, name: "First name", width: "25%" },
{ id: 2, name: "Status", width: "16%" },
{ id: 3, name: "Author", width: "14%" },
{ id: 4, name: "Date", width: "14%" },
{ id: 5, name: "Media", width: "14%" },
{ id: 6, name: "Rating", width: "14%" },
{ id: 7, name: "Project", width: "14%" },
{ id: 8, name: "", width: "3%" }
];
// poor mans factory
const headerColumnsFactory: headerType = {
[TableType.FirstTable]: tableOneColumnHeaders,
[TableType.SecondTable]: tableTwoColumnHeaders
};
// create a row renderer factory/config to pick the right renderer
// each table has a custom body renderer
const TableOneRowsMapper = (props: { data: RowData[] }) => {
const { data } = props;
const rows = data as FirtTableDataType[];
return (
<>
{rows?.map((item) => (
<TableRow key={item.id}>
<TableCell component="th" scope="row">
{item.name}
</TableCell>
<TableCell align="left">{item.address}</TableCell>
...
const TableTwoRowsMapper = (props: { data: RowData[] }) => {
const { data } = props;
const rows = data as SecondTableDataType[];
return (
<>
{rows.map((item) => (
<TableRow key={item.id}>
<TableCell
sx={{
display: "flex",
direction: "row",
gap: "5px",
alignItems: "center"
}}
>
<Avatar />
{item.who.nickname}
...
const TableBodyRowsComponentFactory = {
[TableType.FirstTable]: TableOneRowsMapper,
[TableType.SecondTable]: TableTwoRowsMapper
};
/
/ A component that uses the factories to pick the right renders and render the table
const ExtensibleTable = (props: {
title: string;
type: TableType;
data: any[];
}) => {
const { title, type, data } = props;
// if a switch of if is used, this code becomes fragile
/*
// with introduction of new if else or modification of existing if
// othe tables types can break because of shared variable etc
if (type === '') {
return some columsn
} else if ( type === 'xy') {
}
*/
// but with a bulder the right components are picked
// and changes to each type of component are seperated
// new ones can be added without affecting this common code
// pick the right header columns
const headerColumns: HeaderRowType[] = React.useMemo(
() => headerColumnsFactory[type] ?? [],
[type]
);
// pick the right row renderer
const RowRenderer = React.useMemo(
() => TableBodyRowsComponentFactory[type] ?? TableEmptyRenderer,
[type]
);
return (
<BaseTable
title={title}
headerRow={
<TableRow>
{headerColumns.map(({ name, id, width }) => (
<TableCell align="left" width={width} key={id}>
{name}
</TableCell>
))}
</TableRow>
}
>
<RowRenderer data={data} />
</BaseTable>
);
};
const BaseTable = (props: IBaseTableProps) => {
const { title, children, headerRow } = props;
return (
<Stack
gap={"20px"}
alignItems={"center"}
sx={{ background: "lightblue", padding: "20px", borderRadius: "20px" }}
>
<Typography variant="h3">{title}</Typography>
<Table>
<TableHead>{headerRow}</TableHead>
<TableBody>{children}</TableBody>
</Table>
</Stack>
);
};
I had created a codesandbox example with rest of the example
generally more usable the component becomes, less flexible it becomes
to reduce/handle such issues it helps to apply SOLID principles like Inversion of Control
I am not used to Typescript but I hope helps you in someway and gives you an general idea to make reusable compnents
You can learn and refer to the "antd" Table Components.The component defined a amount of params to show different format content includes text,icon,image,tree even a table.
I give you an example for your reference:
First you may define columnList object:
let columnList = [
{ label: "Post", accessor: "post" },
{ label: "Name", accessor: "name" },
{ label: "Email", accessor: "email" },
{ label: "Primary Phone No", accessor: "primaryPhoneNo" },
{ label: "Secondary Phone No", accessor: "secondaryPhoneNo" }
]
<DataTable
columnList={columnList}
dataList={staffList}
......../>
where
dataList is an array of data,
the label field of the columnList object is store the column label,
and the accessor is the field name in dataList.
In DataTable component,
export default function DataTable({
columnList,
dataList
}) {
return(
<table >
<thead>
<tr> {
columnList.map((column, index) => (
<th> {
column.label
}
</th>
))
}
</tr>
</thead>
<tbody> {
dataList.map((data) => (
<tr >
columnList.map((column, colIindex) => (
<td > {
data[column.accessor]
} </td>
))
</tr>
));
}
</tbody>
</table>
)
}

Fetching data with useEffect()

I am facing an issue with having my res value (array) from the getLogs function to be populated to the DataGrid.
UPDATED CODES:
LogDetails.jsx
const columns = [
{ field: "id", headerName: "ID", width: 30 },
{
field: "name",
headerName: "Name",
width: 250,
},
{
field: "activity",
headerName: "Activity",
width: 350,
},
{
field: "date",
headerName: "Date",
width: 250,
},
];
export default function LogDetails() {
const [logs, setLogs] = useState([]);
useEffect(() => {
function logs() {
getLogs().then((res) => {
setLogs(res);
});
}
logs();
}, []);
return (
<Box sx={{ width: "100%" }}>
{logs.length ? (
<DataGrid
rows={logs}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
disableSelectionOnClick
autoHeight
/>
): null}
</Box>
);
}
function.js
export async function getLogs() {
var rows = [];
const q = query(collection(db, "logs"), orderBy("date", "desc"));
const docQueury = await getDocs(q);
var count = 1;
docQueury.forEach(async (log) => {
const useref = await getDoc(log.data().user);
const date = new Timestamp(
log.data().date.seconds,
log.data().date.nanoseconds
)
.toDate()
.toLocaleString("en-sg");
rows.push({
id: count++,
name: useref.data().name,
activity: log.data().activity,
date: date,
});
});
return rows;
}
Output of "rows" from getLogs function:
Output of states from LogDetails.jsx:
UPDATE:
If I were to run the above codes and then delete what is under useEffect(), the data will be populated.
useEffect(() => {
//delete what is under here
}, []);
In addition, I happened to experiment using const rows in the page itself. The data was able to populate successfully to the grid. Thus, right now I suppose it has to do with how my codes under useEffect() has some issue?
const rows = [
{
id: 1,
name: "test",
activity: "test",
date: "test"
}
]
The correct way to do the conditional render of the DataGrid would be:
return (
<Box sx={{ width: "100%" }}>
{fetchLogs.length ? (
<DataGrid
rows={fetchLogs}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
disableSelectionOnClick
autoHeight
/>
): null}
</Box>
);
fetchLogs && (<DataGrid/>) will always render the DataGrid because an empty array is "truthy". And fetchLogs.length && (<DataGrid/>) would render 0 when the array is empty.
I'm not sure if this solves the OP's problem but at least it would yield the expected results with regard to conditionally rendering the grid. And as per my previous comment, fetchLogs is not a good name for a state variable. It sounds like a function.
Since you are fetching the data from an api, in the beginning the fetchLogs will be undefined.
So first you should check if fetchLogs exists and only then populate to the DataGrid.
Update
As you see, you already get the data in getLogs function, you could do this instead. Now, if you console the logs, data should be there. And if still the table is not populated, it means field names don't match.
Also pay attention to naming the functions and variables. Make them more readable.
useEffect(() => {
const data = getLogs()
setLogs(data)
}, []);
return (
<Box sx={{ width: "100%" }}>
{fetchLogs.length>0 && (
<DataGrid
rows={fetchLogs}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
disableSelectionOnClick
autoHeight
/>
)}
</Box>
);

How to clear an autocomplete material ui field based on another autocomplete field?

Summary of the issue
I have two Autocomplete fields from Material UI. The first one (A) takes in a list of districts in a city, and the second one (B), a list of neighbourhoods, which varies depending on the district selected in A. B is disabled if there is no value selected in A. Everything is working as intended, except for two minor details.
When you select values both on A and B, and then clear the value from A, the value from B isn't cleared.
When you select values both on A and B and then change the value on A, in B you still have a neighbourhood which doesn't belong to the new district. I would like to prevent this from happening.
I have tried setting the neighbourhood to null whenever there is a change on A, but that doesn't change the value displayed on B.
Information and code to reproduce
I have created a custom autocomplete component based on the Autocomplete field from Material UI:
const Autosuggest = props => (
<Autocomplete
options={props.options}
getOptionLabel={option => option.title || option}
value={props.value}
style={props.style}
onChange={props.onChange}
disabled={props.disabled}
renderInput={params => (
<TextField
{...params}
label={props.label}
type={props.type}
variant={props.variant}
/>
)}
/>
);
I included two instances of it in another Wrapper component:
const Wrapper = props => {
// Some variables
const {selectedDistrict} = props
let district = selectedDistrict ? selectedDistrict.title : "";
let neighbourhoods = selectedDistrict ? selectedDistrict.neighbourhoods : [];
return (
<Div>
<Autosuggest
options={props.autosuggestData}
value={district}
onChange={props.setDistrict}
variant="outlined"
label="Distrito"
style={AutosuggestStyle}
/>
<Autosuggest
key={props.selectedNeighbourhood}
options={neighbourhoods}
value={props.selectedNeighbourhood}
disabled={district ? false : true}
onChange={props.setNeighbourhood}
variant="outlined"
label="Barrio"
style={AutosuggestStyle}
/>
</Div>
);
};
Then I use the wrapper in the main App component:
export default class App extends Component {
constructor(props){
super(props)
this.state = {
district: "",
neighbourhood: ""
}
this.setDistrict = this.setDistrict.bind(this);
this.setNeighbourhood = this.setNeighbourhood.bind(this);
}
// Methods
setDistrict = (event, value) => {
this.setState({
district: value,
neighbourhood: null
}, () => console.log("New district set: ", this.state.district))
}
setNeighbourhood = (event, value) => {
let hood = value ? value : null;
this.setState({
neighbourhood: hood
}, () => console.log("New neighbourhood set: ", this.state.neighbourhood))
}
render() {
return (
<Wrapper
autosuggestData={districts}
setDistrict={this.setDistrict}
setNeighbourhood={this.setNeighbourhood}
selectedDistrict={this.state.district}
selectedNeighbourhood={this.selectedNeighbourhood}
/>
)
}
}
This is a sample of the districts and neighbourhoods I'm using:
let districts = [
{
title: "Ciutat Vella",
neighbourhoods : [
{title : "La Barceloneta"},
{title : "El Gòtic"},
{title : "El Raval"},
{title : "Sant Pere"},
{title : "Santa Caterina i la Ribera"}
]
},
{
title: "Eixample",
neighbourhoods : [
{title : "L'Antiga Esquerra de l'Eixample"},
{title : "La Nova Esquerra de l'Eixample"},
{title : "Dreta de l'Eixample"},
{title : "Fort Pienc"},
{title : "Sagrada Família"},
{title : "Sant Antoni"},
]
}
]

How to access style property from the object inside array of objects and apply it to every header of table column in react?

I cannot apply style attribute to table column header. My headers formed dynamically, as array of objects.
tableTh = [
{name: '', style:{width: '50px'}, isCheckbox: true},
{name: i18n.t('ExtendedModalBar.naming'), style:{}},
{name: i18n.t('ExtendedModalBar.profile'), style:{width: '200px'}},
{name: i18n.t('ExtendedModalBar.security'), style:{textAlign: 'center', width: '100px'}},
{name: i18n.t('ExtendedModalBar.manager'), style:{textAlign: 'center', width: '100px'}}
];
render function from TableHeader component:
render(){
...
<th
className={header.id ? 'table__header-item' : ''}
id={index}
key={`tableHeader_${header.id}_${random(1, 100)}`}
ref={node => {this.thRef = node;}}
onClick={() => this.sortColumn(header)}
// onMouseDown={(e) => this.handleMouseDown(e)}
// onMouseUp={(e) => this.handleMouseUp(e)}
// onMouseMove={(e) => this.handleMouseMove(e)}
onMouseOver={(e) => this.handleMouseOver(e)}
style={{whiteSpace: 'nowrap', ...this.props.style}}
>
...
}
I am trying to apply some methods of array, i.e map filter and so on but none of them work. Please, help me. Great thanks in advance!
<TableHeader
headers={tableTh}
onChange={() => this.selectAll()}
checked={this.state.selectedAll}
//thCheckbox={{width: '50px'}}
//key={index.toString()}
style={tableTh.filter((obj, index, tableTh) => tableTh[index].style)} // don't work
/>
Base on your data, I suppose each <th> has different style. But what are you doing is to apply the same style.
Please update the style props to the header render
this.props.headers.map((header, index) => {
return (
<th
...
style={header.style}>
In your <TableHeader>, you don't have to pass style props down. It is already with your tableTh data

Categories

Resources