fill ant design Table with data from state - javascript

I'm new in reactJS I stack in this problem :
I have to fill the table with data from API
the problem: I can fill the table only with the const variable, not in the useEffect function.
the code:
function CandidaturesList() {
const [candidatures , setCandidatures]=useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = /*some code to get data*/
setCandidatures(response);
} catch (error) {
// Catch any errors for any of the above operations.
alert(
`Failed to load `,
);
console.error(error);
}
}
fetchData();
},[candidatures]);
// project table start
const project = [
{
title: "COMPANIES",
dataIndex: "name",
width: "32%",
},
{
title: "JOB TITLE",
dataIndex: "jobtitle",
},
{
title: "DECISION",
dataIndex: "decision",
},
];
const candidature = [
{
key: "1",
name: (
<>
<div className="avatar-info">
<Title level={5}>Spotify Version</Title>
</div>
</>
),
jobtitle: (
<>
<div className="semibold">Manager</div>
</>
),
decision: (
<>
<div className="ant-progress-project">
<Tag icon={<CheckCircleOutlined />} color="#87d068">
Valid
</Tag>
</div>
</>
),
},
{
key: "2",
name: (
<>
<div className="avatar-info">
<Title level={5}>Progress Track</Title>
</div>
</>
),
jobtitle: (
<>
<div className="semibold">Developer full stack</div>
</>
),
decision: (
<>
<div className="ant-progress-project">
<Tag icon={<SyncOutlined spin />} color="#108ee9">
Processing
</Tag>
</div>
</>
),
},
]
return (
<div className="table-responsive">
<Table
columns={project}
dataSource={candidature}
pagination={false}
className="ant-border-space"
/>
</div>
);}
So the const candidature is just having a fake data
and the candidatures state is having the real data to put in the table
the question is: how can I map the "candidatures" state to put the data in the table with the same style as the "const candidature" or how can I put the data of "candidatures" in this const?
One thing to note here is that candidatures state variable is array of objects, like
{[titre_poste: 'developeur full stack', nomCompagnie: 'nomCompagnie',decision: "none",id: "2"],[ ... ] }```

Firstly, i see that candidatures is your data and projects is your table schema
you shouldn't put html within your data array, you should instead use the render key within your schema array which in your case is projects.
it should look something like this
// project table start
const project = [
{
title: "COMPANIES",
dataIndex: "name",
width: "32%",
render: text => (
<div className="avatar-info">
<Title level={5}>{text}</Title>
</div>
)
}
]
const candidature = [
{
key: "1",
name: "Some string value"
}
]
secondly, in your useEffect function, you have created a recursion via your dependency of candidatures.
You are listening for changes in candidatures while upon every change you trigger an api call which changes the candidatures state which in turn again triggers the useEffect call.
to prevent this, remove candidatures as useEffect dependency.

Related

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>
)
}

How to .Map over different props that are passed into a component?

I'm new to React but hopefully someone can help!
So I've just created a component that takes in a value (via prop) and then .maps over that value creating an Image slider. The props are all an array of objects that contain different values such as :
const Songs = [
{
artist: 'Artist Name',
song: 'Song Name',
lenght: '2:36',
poster: 'images/....jpg'
},
{
artist: 'Artist Name',
song: 'Song Name',
lenght: '2:36',
poster: 'images/....jpg'
},
]
I have been making the same component over and over again because I don't know how to make the 'prop'.map value dynamic. Essentially I don't know how to change the value before the .map each different prop.
Here's an example. I want to make 'Songs'.map dynamic so the new props can replace that so they can also be mapped. Maybe there's another way. Hopefully some can help.
import React from 'react';
import { FaCaretDown } from 'react-icons/fa';
function ImageSlider({Songs, KidsMovies, Movies, TvShows}) {
return (
<>
{Songs.map((image, index) => (
<div className="movie-card">
<img src={'https://image.tmdb.org/t/p/w500' + image.poster_path}
className='movie-img' />
<h5 className='movie-card-desc'>{image.original_title}</h5>
<p className='movie-card-overview'>{movie.overview}</p>
</div>
))}
</>
);
}
export default ImageSlider;
Given your example,
I feel like all you need is render ImageSlides for each array
function ImageSlider({ items }) {
return (
<>
{items.map((item, idx) => (
<div ... key={idx}> // be careful to not forget to put a key when you map components
...
</div>
))}
</>
);
}
When rendering your component
function OtherComponent({ songs, kidsMovies, movies, tvShows }) {
return (
<div>
<ImageSlider items={songs} />
<ImageSlider items={kidsMovies} />
<ImageSlider items={movies} />
<ImageSlider items={tvShows} />
</div>
);
}

Getting content of currently active Text component wrapped inside popover of antd

I am using antd components for my react app. I have a Text component wrapped inside of Popover component. Now in my case this Popover is applied to one particular column of table, i.e. every row-element in that column has a Popover component rendered for it upon mouse hovering.
title: "Name",
dataIndex: "name",
key: "name-key",
sortType: "string",
sortDirections: ["descend", "ascend"],
sorter: (a, b) => a.name.length - b.name.length,
render: (text, record) => (
<Popover>
<Text onMouseOver={handleOnMouseOverCommitId}> {name} </Text>
</Popover>
)
I want to get hold of the row-element's value, the one contained by the above Text component whenever I hover over it. In this case the value denoted by {name} above.
I tried getting it with e.target.value via onMouseOver event, but it returned undefined.
I think I get the reason behind it, because the event.target returns an html node of type <span>.
With a normal div element e.target.value has worked in the past for me. But doing the same thing with a predefined component like antd's Text seems a bit trickier.
Just to elaborate, the Popover has two buttons and based on which button user clicks, I need to render some other components, something like an overlay component.
But in order to do that I would also need to get hold of the text value which originally triggered the Popover.
Below is the code(most of the things removed for preciseness).
record.name is what I ultimately need to capture.
<Popover
content={
<>
<Space>
<Button onClick={showSomeOverlayPaneForName}>
{"View Details for record.name"}
</Button>
<Button href={"https://abc.xyz.com/" + record.role}>
{"View Role Details"}
</Button>
</Space>
</>
}
trigger={"hover"}
>
<Text style={{"color": blue.primary}} copyable={true} onMouseOver={handleOnMouseOverName}>{record.name}</Text>
</Popover>
The handleOnMouseOverName function(which doesn't work anyway) :
const handleOnMouseOverName = (e) => {
//console.log("e.target.value :--- ", e.target.value);
setCurrentActiveName(e.target.value)
}
And once my currentActiveName variable is set(via useState), I use that value inside my function showSomeOverlayPaneForName
const showSomeOverlayPaneForName = (e) => {
axios
.get(
`some-url`,
{
params: {name: currentActiveName}
}
)
.then((response) => {
setData(response.data);
}).catch(reason => {
//xyz
});
}
You need to pass on the record of the enclosing render function to the handleOnMouseOverName function.
Check the following example
import React from 'react';
import 'antd/dist/antd.css';
import './index.css';
import { Space, Table, Button, Popover } from 'antd';
const App = () => {
const data = [
{
key: '1',
name: 'John Brown',
address: 'New York No. 1 Lake Park',
role: 'admin',
},
{
key: '2',
name: 'Jim Green',
address: 'London No. 1 Lake Park',
role: 'user',
},
{
key: '3',
name: 'Joe Black',
address: 'Sidney No. 1 Lake Park',
role: 'manager',
},
];
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: (name, record) => {
const content = (
<>
<Space>
<Button
onClick={() => {
viewDetail(record);
}}
>
{'View Details for ' + record.name}
</Button>
<Button href={'https://abc.xyz.com/' + record.role}>
{'View Role Details'}
</Button>
</Space>
</>
);
return (
<>
<Popover content={content} title="Details">
<div
onMouseOver={() => {
handleOnMouseOverName(record);
}}
>
{name}
</div>
</Popover>
</>
);
},
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
];
const handleOnMouseOverName = (record) => {
console.log(record);
};
const viewDetail = (record) => {
console.log(record);
};
return <Table columns={columns} dataSource={data} />;
};
export default App;
Output:
I hope this helps.
From antd docs: https://ant.design/components/popover/#header
Apparently you're supposed to render the <Popover/> with a content={content}-prop
For example
const content = <div>Content to render under title</div>
const App = () => {
const value = "Text to hover";
return (
<Popover content={content} title="Title">
<Text>{value}</Text>
</Popover>
)
}

How to send a property from an array of object from a child component to the parent component?

I have App, that is the parent component and I have the Child component:
The Child component gets a props called items so it can be reused depending on the data. It the example there is data, data1 and data2.
The thing is that I want to set a cookie from the parent component, to set the cookie I need the property link from data2, but I am already mapping data2 in the Child component.
What can I do to obtain the value of the property link in the parent component to pass it as an arguement here:
<Child
onClick={() =>
handleUpdate('How can I obtain here the string from link of data2?')
}
items={data2}
/>
This is the whole example code:
import * as React from 'react';
import './style.css';
const data = [
{ title: 'hey', description: 'description' },
{ title: 'hey1', description: 'description' },
{ title: 'hey2', description: 'description' },
];
const data1 = [
{ title: 'hey', description: 'description' },
{ title: 'hey1', description: 'description' },
{ title: 'hey2', description: 'description' },
];
const data2 = [
{ title: 'hey', link: 'link/hey' },
{ title: 'hey1', link: 'link/he1' },
{ title: 'hey2', link: 'link/he2' },
];
export default function App() {
const [, setCookie] = useCookie('example');
const handleUpdate = (cookie) => {
setCookie(null);
setCookie(cookie);
};
return (
<div>
<h2>App - Parent</h2>
<Child items={data} />
<Child items={data1} />
<Child
onClick={() =>
handleUpdate('How can I obtain here the string from link of data2?')
}
items={data2}
/>
</div>
);
}
export function Child({ items }) {
return (
<div>
<h2>Child</h2>
<ul>
{items.map((item) => {
return (
<>
<p>{item.title}</p>
<a href={item.link}>Go to title</a>
</>
);
})}
</ul>
</div>
);
}
Thank you!
If you want to get the link from the Child component you can simply add a link parameter in the callback:
<Child
onClick={(link) => handleUpdate(link)}
items={data2}
/>
Then from the Child you just need to call the onClick prop:
export function Child({ items, onClick }) { // here make sure to add the prop while destructuring
<a href={item.link} onClick={() => onClick(item.link)}>Go to title</a>
The map method doesn't change the array that it is called on, it just returns a new array, do the items array doesn't get affected at all here, so you can just call it normally like so:
return (
<div>
<h2>App - Parent</h2>
<Child items={data} />
<Child items={data1} />
<Child
onClick={() =>
handleUpdate(data2[0].link)
}
items={data2}
/>
</div>
);
Also, your Child component needs to accept the onClick function as a prop like so:
export function Child({ items, handleClick }) {
return (
<div onClick={handleClick}>
<h2>Child</h2>
<ul>
{items.map((item) => {
return (
<>
<p>{item.title}</p>
<a href={item.link}>Go to title</a>
</>
);
})}
</ul>
</div>
);
}

map function not showing elements on screen

i have this part of code the map function did not show any element of the array, if i console.log the variable it shows me the elements but for some reasons i can't show the elements on the screen.
Code
function Solution({list}){
const data = list
console.log(data);
return(
<div>
{
data?.map((item) => {
return (
<div>
<p> {item.title} </p>
</div>
)
})
}
</div>
)
}
export default Solution;
const list = [
{
title: "Home"
},
{
title: "Service",
subItem: ["Clean", "Cook"]
},
{
title: "Kitchen",
subItem: ["Wash", "Dish"]
},
];
Solution({list})
Please, just pass "list" link this.
<Solution list={list}/>
Hope will help you, Thanks)
Check this out
import React from 'react';
function Solution({list}){
const data = list
console.log(list);
return(
<div>
{
data?.map((item) => {
return (
<div key={item.id}>
<p> {item.title} </p>
</div>
)
})
}
</div>
)
}
export function App(props) {
const list = [
{
id:1,
title: "Home"
},
{
id:2,
title: "Service",
subItem: ["Clean", "Cook"]
},
{
id:3,
title: "Kitchen",
subItem: ["Wash", "Dish"]
},
];
return (
<div className='App'>
<Solution list={list} />
</div>
);
}
// Log to console
console.log('Hello console')
Have a unique key prop for each element when you map an array and send list array as props to your Solution component

Categories

Resources