How to put a loader in a nested table in antd? - reactjs

I have a nested table in antd, wherein when I click on a row, it expands and a new table opens open. Illustrated here.
But it takes some time to fetch the data for the nested table ( I fetch it through an API). I need to put a loader/spinner in the nested table to indicate that the data is still not available to displayed. How can I achieve that in antd?
I have tried doing the following, but it didnt work:
//(Outer table)
<Table key="campaignListByDate-table"
columns={campaignDateColumns}
expandable={{ expandedRowRender }}
loading={{
indicator: <div><Spin /></div>,
spinning: !campaignList
}}
dataSource={campaignList}
/>
//Inner table ( The one that opens when u click on the "+" sign of a row.
<Table
columns={columnsExpanded}
dataSource={emailRate}
pagination={false}
loading={{
indicator: <div><Spin /></div>,
spinning: !emailRate
}}/>
CampaignList:
[
{
"id": "27813f63-aee2-4c69-bf5d-9e4ac8",
"name": "bnce",
"templateId": "ae7e094f-1735-4a31-bc67-95bd3d",
"userId": "3122be78-703d-4621-92f0-8a2bd8",
"createdAt": 1604984929337,
},
{
"id": "438e0cd9-a550-453a-8a5b-4bd37",
"name": "asd",
"templateId": "ae7e094f-1735-4a31-bc67-95bd3",
"userId": "3122be78-703d-4621-92f0-8a2bd87",
"createdAt": 1604985347370,
},
]
EmailRate:
[
{
"time": "2020-11-11 11 : 39",
"count": 3,
"key": "2020-11-11 11 : 39"
}
]

From your code, I assume you are trying to set ![ ] to spinning when you do not have any data. But ![ ] will not return true.
Example:
//When you have some data
console.log(!['something to avoid empty array'])
//when you do not have any data
console.log(![])
Both cases will return false and your loader is not getting activated.
Try using boolean state variables and assign true and false to those variables before and after your data fetch and assign it to the table spinner

Related

How to add child entities without id to parent in state normalized with normalizr

I've recently started using normalizr with zustand in a new React app. It's been a very good experience so far, having solved most of the painful problems I've had in the past.
I've just bumped into an issue I can't think of a clean way of solving for the past few days.
Imagine I have a normalizr-normalized state looking like:
{
"entities": {
"triggers": {
"1": {
"id": 1,
"condition": "WHEN_CURRENCY_EXCHANGED",
"enabled": true,
"value": "TRY"
},
"2": {
"id": 2,
"condition": "WHEN_CURRENCY_EXCHANGED",
"enabled": true,
"value": "GBP"
},
"3": {
"id": 3,
"condition": "WHEN_TRANSACTION_CREATED",
"enabled": true,
"value": true
}
},
"campaigns": {
"19": {
"id": 19,
"name": "Some campaign name",
"triggers": [
1,
2,
3
]
}
}
},
"result": 19
}
And we have a page that allows a user to add one or more triggers to the campaign and then save them. The problem is that at the time of adding these triggers, they do not have an id until the user clicks the Save button (ids are generated by the database). When the Save button is clicked, the state is being denormalized (via normalizr's denormalize function) and sent as payload to the backend looking like the following:
{
"id": 19,
"name": "Some campaign name",
"triggers": [
{
"id": 1,
"condition": "WHEN_CURRENCY_EXCHANGED",
"enabled": true,
"value": "TRY"
},
{
"id": 2,
"condition": "WHEN_CURRENCY_EXCHANGED",
"enabled": true,
"value": "GBP"
},
{
"id": 3,
"condition": "WHEN_TRANSACTION_CREATED",
"enabled": true,
"value": true
}
]
}
The problem is that if the user adds an entity to the triggers, it does not have an id as ids are generated by the database and I cannot find a proper way to add it to the state (due to the id-based nature of normalized states).
The only workaround I can think of is generating some temporary IDs (e.g. uuid) when a trigger is added on the front-end but is not yet saved and then going over each entity upon denormalization, doing something like if (isUuid(trigger.id)) delete trigger.id, which seems too tedious and workaroundish.
Appreciate your help.
P.S. There is something similar explained here. The problem is that in our case the generateId('comment') logic is happening on the backend.
A simple solution is to split.
The create trigger API call and the add trigger to campaign API call.
Do the first, then save the trigger into the normalized store with the id generated by the backend.
Then add it to the campaign.

Dynamically populating dropdown based on 2 JSON files in React

I have 2 JSON files that contain data for regions and provinces respectively.
Here is a sample data for my regions.json
[
{
"id": 1,
"long_name": "First region in the country",
"name": "firstRegion",
"key": "FR"
},
{
"id": 2,
"long_name": "Second region in the country",
"name": "secondRegion",
"key": "SR"
}
]
And this is my provinces.json
[
{ "id": 1, "name": "province One", "region_key": "FR", "key": "p1" },
{ "id": 2, "name": "province Two", "region_key": "SR", "key": "p2" },
{ "id": 3, "name": "province Three", "region_key": "FR", "key": "p3" },
{ "id": 4, "name": "province Four", "region_key": "SR", "key": "p4" }
]
What I want to happen is when the user chooses a region from the first dropdown, for example, First region in the country, the second dropdown, which is the provinces dropdown, must be dynamically populated with the provinces that belong to First region in the country or provinces with the region_key FR. From this logic, I assume there must be some mapping that could happen. I referred to one of the answers here but unfortunately, when I try to edit the options value from the provinces' selectcomponent, I get an error props.options.map is not a function. Also, my JSON files, I assume, need a different style for mapping? Since I need to match the key from regions.json to region.key in provinces.json but I'm fairly new to rendering data from the database to the front-end using React.
Here is a code snippet for my dropdown:
/* Dropdown for Regions */
<h4 className="mb-3 text-sm">Regions</h4>
<Select
className="basic-multi-select text-sm"
classNamePrefix="select"
defaultValue={selectedRegions}
getOptionLabel={(region) => region.long_name}
getOptionValue={(region) => region.id}
isMulti
name="regions"
options={regions}
onChange={handleChangeRegion}
placeholder="Region select"
/>
/* Dropdown for Provinces */
<h4 className="mb-3 text-sm">Provinces</h4>
<Select
className="basic-multi-select text-sm"
classNamePrefix="select"
defaultValue={selectedProvinces}
getOptionLabel={(province) => province.name}
getOptionValue={(province) => province.id}
isMulti
name="provinces"
options={provinces}
onChange={handleChangeProvince}
placeholder="Province Select"
/>
This is my handleChangeRegion, for context.
const handleChangeRegion = (selectedRegions) => {
setSelectedRegions([...selectedRegions])
regionID = []
for (var i = 0; i < Object.keys(selectedRegions).length; i++) {
regionID[i] = selectedRegions[i].id
}
}
Here is how I would go about this:
use a useState hook for each selection to store the selected value (say active_region and active_province)
use region.key as value for the first dropdown
filter the provinces list based on the selected region_key and use this for the second dropdown:
provinces.filter(o => o.region_key === active_region)
This is, of course, assuming that you are using functional components (hence my mentioning of hooks)

Antd Table render properties inside and array of objects

I have an Antd Table, with data coming from axios API
"data": [
{
"id": 1,
"name": "Package 1",
"services": [
{
"id": 1,
"name": "Evaluation Core",
}
]
},
{
"id": 2,
"name": "Package 2",
"services": [
{
"id": 1,
"name": "Evaluation BizCore",
},
{
"id": 2,
"name": "Certification Fizz"
}
]
}
],
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 20,
"total": 2,
"total_results": 2
}
}
In this Table I'm rendering one column with the name of the Package, and the second column I need to render any name property inside the Services array. That columns has this dataindex:
dataIndex: ['services', 'name'],
If there is more then one property name, should be render separated with ",". I tried differents approaches,but nothing seems to work.
Thanks!!
If I understand correctly you want to render a Services column where each package may have a different amount of services. Each service has a name and you want to display the name property of all services for package aggregated. e.g. Package has Service 1 and Service 2 and it should be displayed Service 1,Service 2.
The simple answer is to use render. The column for Services can look like.
{
title: "Services",
dataIndex: "services",
render: (services) => services.map(service => service.name).join(),
key: "services"
}
https://codesandbox.io/s/basic-antd-4-16-3-forked-q6ffo?file=/index.js
Please comment if this was not the intended result.

Table don't change after state change

I have antd table, I am to try to update only a subtable to add one row after api return.
If subtable is empty the row it shows on the table, if subtable have one row and more table subtable don't change. The problem is with expandedRowRender if i click for sorting the new data it shows there
But i cannot make a table to add the new row. When i print the new state for data its ok, i can see the new row there, but the table is the same. Any ideas ? i am not sure if the problem is the setState or something else i am doing wrong.
for (const property in dataGroup) {
if (dataGroup[property].id === new.group_id) {
dataGroup[property].group.push({
id: user.id,
name: user.name,
});
break;
}
}
this.setState({
data: [...dataGroup],
});
console.log(this.state.data);
-------------------------
return (
<Table
columns={columns}
rowSelection={rowSelection}
rowKey={(record) => record.id}
expandable={{ expandedRowRender }}
dataSource={data}
/>
........
Table json
[
{
"id": 97,
"name": "f",
"description": "",
"group": [
{
"id": 90,
"name": "fds",
},
{
"id": 91,
"name": "dsa",
},
]
},
{
"id": 98,
"name": "fddf",
"description": "",
"group": [
{
"id": 96,
"name": "fddd",
},
]
}
]
First of all, never use setState inside a loop or multiple times inside the same execution path because it is useless and restricts performance. call it at the end of your function execution.
The reason your state is not changing is because react thinks your state.data variable hasn't changed since it maintains the same reference. You need to use the spread operator to recreate the same object with a different reference.
for (const property in data) {
if (data[property].id === new.group_id) {
data[property].group.push({
id: user.id,
name: user.name,
});
break;
}
}
this.setState({
data: { ...data },
});
console.log(this.state.data);
Also make sure to set the key prop for your component arrays

React reading nested JSON data fails when loads or refresh

I am developing a screen to show data based on search results. Below is the JSON output.
When I click search result below component is called. In the result screen i need id, name and table1, table2 data.
Table1 and Table2 are nested outputs and these will be displayed in React Table or Data Grid (next step)
Issue: Unable to render StudyData.table1.name
Options Tried
1. UseEffect()
UseEffect() able to read StudyData.studyname but not StudyData.table1.name
Assigned to a variable
Assigned to a state
2. Render
tried using map for subdocuments
Finding: It fails only first time load and refresh. I tried to comment -> save->load -> remove the comments and save. Then works (as component is loaded). I am missing something during the first time load or refresh. Please help
[
{
"id": "DD3",
"studydate": "DDD",
"studydescription": "DD3 Description",
"studyname": "DD3",
"table1": [
{
"no": "1",
"name": "DD3 Name",
"date": "Krishna",
"description\r": "1111\r"
},
{
"no": "2",
"name": "DD3 Nam2",
"date": "Test2",
"description\r": "2222\r"
},
{
"no": "3",
"name": "DD3 Name3",
"date": "Test3",
"description\r": "3333"
}
],
"table2": [
{
"No": "2",
"Study Field1": "21",
"Study Field2": "22",
"Study Field3\r": "23"
}
],
"table3": [
{
"No": "3",
"Study Field5": "T31",
"Study Field6": "T32",
"Study Field7": "T33",
"Study Field 8\r": "T34"
}
],
"_rid": "QeNcANZFTTIKAAAAAAAAAA==",
"_self": "dbs/QeNcAA==/colls/QeNcANZFTTI=/docs/QeNcANZFTTIKAAAAAAAAAA==/",
"_etag": "\"33002e92-0000-0200-0000-5fa6fe320000\"",
"_attachments": "attachments/",
"_ts": 1604779570
}
]
COMPONENT CODE
import React, { useEffect } from "react";
import api from "./UploadStudyFilesapi";
import { DataGrid } from "#material-ui/data-grid";
export default function StudyDisplay(props) {
let myData = props.Study;
const [StudyData, setStudyData] = React.useState([]);
const [Table1Rows, setTable1Rows] = React.useState([]);
useEffect(() => {
// Update the document title using the browser API
api.getStudy(myData.studyname).then((json) => setStudyData(json));
console.log(StudyData.studyname); //works
//console.log(StudyData.table1.no) //doesn't work
let myTable = StudyData.table1;
console.log(myTable);
setTable1Rows(StudyData.table1);
console.log(Table1Rows);
}, [myData]);
return (
<div>
{StudyData.length === 0 ? (
<h1>Loading...</h1>
) : (
StudyData.map((item) => (
<div key={item.studyname}>
{item.studyname}
{/* DOESNT WORK first brose or refresh*/}
{item.table1.map((key2) => (
<div>
{console.log(key2.name)}
{key2.name}
{/* Want to pass Key2 to DataGrid or React-Table */}
{/* <DataGrid rows={key2} columns={{field1:"No"}} /> */}
</div>
))}
</div>
))
)}
</div>
);
}
Thanks for your support. I added conditional rendering based on search selection in parent component. That resolved all the issues

Resources