react ant design table with json - updating data without key problem - reactjs

I would like to display some json data (coming from my backend and handled in a hook) in a similar way to this : https://ant.design/components/table/#components-table-demo-tree-data
I have a column with a checkbox and a column with an input (both must be editable by the user) and the final json state must be updated with the new datas.
Here you can find the structure of my json : https://pastebin.com/wA0GCs1K
Here you have a screen of the final result :
The code I used to fetch the data :
const [dataServiceInterface, setDataServiceInterface] = useState(null);
useEffect(() => {
CreateApiService.fetchDataServiceInterface(questionFiles, responseFiles).then((result) => {
if (result != null) {
setDataServiceInterface(result);
}
});
}, []);
Here you have the code I used to update the attributes (column constant for the second part) :
const onInputChange = (key, record, index) => (e) => {
e.preventDefault();
console.log(key);
console.log(index);
console.log(record);
console.log(e.target.value);
dataServiceInterface.itemsQ[index][key] = e.target.value;
};
// some other code here
{
title: "Json Name",
dataIndex: "name",
key: "name",
render: (text, record, index) => (
<Input
//defaultValue={text}
value={text}
onChange={onInputChange("name", record, index)}
/>
),
},
Problem is (I think) : As I dont have a key defined in my json datas (parents and children), when I try to update the children it dosent work. I can't change the structure of the json because it's a business constraint. I was thinking of copying my json data in another state, then add the keys ... and still didn't try this solution and don't know if it works. I will update this if it's the case.
Meanwhile, if someone had the same issue and has any idea/hint/suggestion, would appreciate very much. Thx.

I think the problem here is that the component is not rendering the data once you have it. This can be solved by using useState hook, you should lookup for the docs.
I would love to get a little bit more of the component that you are building. But the approach to this would be something like this:
const Component = () {
const [data, useData] = useState([]);
const onInputChange = (key, record, index) => (e) => {
e.preventDefault();
const data = // You get fetch the data here
useData(data);
};
return <>
{
data && data.itemsQ.map((item) => {
// here you display the information contained in each item
})
}
</>
}

Related

Mui DataGrid > renderCell > useValue from renderCell

I have a field in the Datagrid with renderCell. The Value i have to display will be fetched inside the AlertIssues-Component as the original Data just provides the uuid to fetch the corresponding data (issue amount). So props.row._id is use in AlertIssues with a hook to retrieve the issues of the Alert in this row.
{
field: "issues",
type: "number",
headerClassName: "iconHeader",
// valueFormatter: params => {
// console.log("value formater",params )
// useGetIssueValue cannot be used here: (hook rules)
// let theValue = useGetIssueValue(params.id)
// return theValue
// },
// useGetIssueValue cannot be used here: (hook rules)
// valueGetter: params => useGetIssueValue(params.id)
renderCell: (props: GridRenderCellParams<Number>) => {
// useGetIssueValue is used inside AlertIssues
// and works to display the right amount
return(
<AlertIssues {...props} />
)},
},
export const AlertIssues = (props: GridRenderCellParams<Number>) => {
const { row } = props;
// i am getting my amount here without trouble.
// but it is just the displayed value ...
const alertIssueAmount = useGetIssueValue(row.id);
...
return <>alertIssueAmount</>
i tried to use "valueGetter" or "valueFormatter" to get the amount i need. but inside these functions i am not allowed to call my useData-hook, as they are functions and not React-Components or Hooks.
the row itself does not have the value i got inside of AlertIssues...
i am very lost here, how can i retrieve my issueAmount-value from the hook and use it in Datagrid? (e.g. for filter and sort)

Getting an Empty array in props

I am trying to make table using react-table in which one of the columns will be a toggle-switch in typescript. I am using the following code to create a react-switch.
const tableInstance = useTable({
columns,
data},(hooks)=> {
console.log("setting buildings: ",data);
hooks.visibleColumns.push((columns) => columns.map((column)=>column.id === "cevacrunningstatus"?{...column,Cell:({row})=>
{return <BuildingStartStopSwitch row_={row} data__={buildings} setdata={setbuildings}/>}}:column));
}
)
I am using the following React function component
interface props{
row_:Row.Row<any>;
data__:BuildingControlStatus[];
setdata: React.Dispatch<React.SetStateAction<BuildingControlStatus[]>>;
}
const BuildingStartStopSwitch:React.FC<props> = ({row_,data__,setdata}) => {
const [state,setState] = useState<boolean>(row_.values.runningstatus);
const handleChange = (checked:boolean) => {
setState(checked);
console.log("Data before statechange: ",data__)
setdata(data__.map((data_)=>row_.values.ccid === data_.ccid?({...data_,runningstatus:checked}):data_))
}
console.log("Data after statechange: ",data__)
return (
<Switch onChange={handleChange} checked={state}/>
);
};
export default BuildingStartStopSwitch;
I have the following issue:
The array data__ is turning up as an empty array inside BuildingStartStopSwitch. The data variable which is assigned to data__ contains items, but the same is not reflected inside BuildingStartStopSwitch. I am trying to update the data variable(which is the table data) to reflect the status of toggle switch.
I have an input cell against each toggle switch in a row. When the switch is checked, the input should be enabled and when the switch is unchecked , it should be disabled. I am not sure how to implement.
Thanks in advance!

ReactJS, update State with handler but data is empty

I have this situation:
I want to update some state (an array) which is used to map different React components.
Those componentes, have their own handleUpdate.
But when I call to handleUpdate the state that I need to use is empty. I think is because each handler method was mounted before the state was filled with data, but then, how could I ensure or use the data in the handler? In other words, the handler needs to update the state that fill it's own state:
const [data, setData] = useState([]);
const [deliver, setDeliver] = useState({items: []});
const handleUpdate = (value, position) => {
// This set works
setDeliver({
items: newItems
});
// This doesn't work because "data" is an empty array - CRASH
setData(data[position] = value);
};
useEffect(() => {
const dataWithComponent = originalData.map((item, i) => ({
...item,
entregado: <SelectorComponent
value={deliver?.items[i].delivered}
key={i}
onUpdate={(value) => handleUpdate(value, i)}
/>
}));
setData(dataWithComponent); // This is set after <SelectComponent is created...
}
}, [originalData]);
The value that you pass don't come from originalData, so the onUpdated don't know what it's value
You run on originalData using map, so you need to pass item.somthing the the onUpdate function
const dataWithComponent = originalData.map((item, i) => ({
...item,
entregado: <SelectorComponent
value={deliver?.items[i].delivered} // you can't use items use deliver.length > 0 ? [i].delivered : ""
key={i}
onUpdate={() => handleUpdate("here you pass item", i)}
/>
}));
I'm not sure, but I think you can do something like that. I hope you get some idea to work it.
// This doesn't work because "data" is an empty array - CRASH
let tempData = [...data].
tempData[position] = value;
setData(tempData);

How Can I Setup `react-select` to work correctly with server-side data by using AsyncSelect?

I would like to setup a component react-select to work server-side data and do server-side filtering, but it doesn't work for a plethora of reasons.
Can you explain it and also show working code?
react-select has several examples in the documentation including an entire section dedicated to AsyncSelect which include inline code examples with codesandbox links.
It's worth noting that there are three unique props specific to the AsyncSelect
loadOptions
defaultOptions
cacheOptions
The primary difference between AsyncSelect and Select is that a Select is reliant on an options prop (an array of options) whereas the AsyncSelect is instead reliant on a loadOptions prop (an async function which provides a callback to set the options from an api).
Often api autocomplete lookups filter results on the server so the callback on the loadOptions does not make assumptions on filtering the results returned which is why they may need to be filtered client-side prior to passing them to the AsyncSelect state.
Here is a simple code example.
import React from 'react';
import AsyncSelect from 'react-select/async';
const filterOptions = (options, inputValue) => {
const candidate = inputValue.toLowerCase();
return options.filter(({ label }) => label.toLowerCase().includes(candidate);
};
const loadOptions = (inputValue, callback) => {
const url = `www.your-api.com/?inputValue=${inputValue}`;
fetch(url).then(resp => {
const toSelectOption = ({ id, name }) => ({ label: name, value: id });
// map server data to options
const asyncOptions = resp.results.map(toSelectOption);
// Filter options if needed
const filtered = filterOptions(asyncOptions, inputValue);
// Call callback with mapped and filtered options
callback(filtered);
})
};
const AsyncLookup = props => (
<AsyncSelect
cacheOptions
loadOptions={loadOptions}
defaultOptions
{...props}
/>
);
export default AsyncLookup
Let's start by me expressing the opinion that react-select seems great, but not very clearly documented. Personally I didn't fall in love with the documentation for the following reasons:
No search
All the props and put on a single page. If I do CTRL+F on something everything lights up. Pretty useless
Most descriptions are minimal and not describing the important edge cases, some are even missing
There are some examples, but not nearly enough to show the different varieties, so you have to do guesswork
And so I will try to help a bit with this article, by giving steps by steps, code and problems + solutions.
Step 1: Simplest form react-select:
const [options, setOptions] = useState([
{ id: 'b72a1060-a472-4355-87d4-4c82a257b8b8', name: 'illy' },
{ id: 'c166c9c8-a245-48f8-abf0-0fa8e8b934d2', name: 'Whiskas' },
{ id: 'cb612d76-a59e-4fba-8085-c9682ba2818c', name: 'KitKat' },
]);
<Select
defaultValue={options[0]}
isClearable
options={options}
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
/>
It generally works, but you will notice that if I type the letter d which doesn't match any of the choices anywhere, choices stay, instead of showing "no options" as it should.
I will ignore this issue, since it is minor and seems unfixable.
So far so good, we can live with that small issue.
Step 2: Convert static data to server data
Our goal is now to simply swap the static data with server loaded data. Meh, how difficult could it be?
We will first need to swap <Select/> for <AsyncSelect/>. Now how do we load data?
So looking at the documentation there are multiple ways of loading data:
defaultOptions: The default set of options to show before the user starts searching. When set to true, the results for loadOptions('') will be autoloaded.
and
loadOptions: Function that returns a promise, which is the set of options to be used once the promise resolves.
Reading it carefully you understand defaultOptions needs to be a boolean value true and loadOptions should have a function returning the choices:
<AsyncSelect
defaultValue={options[0]}
isClearable
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
defaultOptions
loadOptions={loadData}
/>
Looks great, we have remote data loaded. But we want to preset our default value now. We have to match it by Id, rather than choosing the first one. Here comes our first problem:
PROBLEM: You can't set the defaultValue in the very beginning, because you have no data to match it against. And if you try to set the defaultValue after component has loaded, then it doesn't work.
To solve that, we need to load data in advance, match the initial value we have, and once we have both of those, we can initialize the component. A bit ugly but that's the only way I could figure it out given the limitations:
const [data, setData] = useState(null);
const [initialObject, setInitialObject] = useState(null);
const getInitial = async () => {
// make your request, once you receive data:
// Set initial object
const init= res.data.find((item)=>item.id=ourInitialId);
setInitialObject(init);
// Set data so component initializes
setData(res.data);
};
useEffect(() => {
getInitial();
}, []);
return (
<>
{data!== null && initialObject !== null ? (
<AsyncSelect
isClearable
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
defaultValue={initialObject}
defaultOptions={options}
// loadOptions={loadData} // we don't need this anymore
/>
) : null}
</>
)
Since we are loading the data ourselves, we don't need loadOptions so we will take it out. So far so good.
Step 3: Make filter with server-side filtering call
So now we need a callback that we can use for getting data. Let's look back at the documentation:
onChange: (no description, from section "StateManager Props")
onInputChange: Same behaviour as for Select
So we listen to documentation and go back to "Select Props" section to find:
onInputChange: Handle change events on the input`
Insightful...NOT.
We see a function types definition that seems to have some clues:
I figured, that string must by my text/query. And apparently it drops in the type of change. Off we go --
const [data, setData] = useState(null);
const [initialObject, setInitialObject] = useState(null);
const getInitial = async () => {
// make your request, once you receive data:
// Set initial object
const init= res.data.find((item)=>item.id=ourInitialId);
setInitialObject(init);
// Set data so component initializes
setData(res.data);
};
useEffect(() => {
getInitial();
}, []);
const loadData = async (query) => {
// fetch your data, using `query`
return res.data;
};
return (
<>
{data!== null && initialObject !== null ? (
<AsyncSelect
isClearable
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
defaultValue={initialObject}
defaultOptions={options}
onInputChange={loadData} // +
/>
) : null}
</>
)
Data gets fetched with the right query, but options don't update as per our server data results. We can't update the defaultOptions since it is only used during initialization, so the only way to go would be to bring back loadOptions. But once we do, we have 2 calls on every keystroke. Blak. By countless hours and miracle of painstaking experimentation, we now figure out that:
USEFUL REVELATION: loadOptions actually fires on inputChange, so we don't actually need onInputChange.
<AsyncSelect
isClearable
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
defaultValue={initialObject}
defaultOptions={options}
// onInputChange={loadData} // remove that
loadOptions={loadData} // add back that
/>
Things look good. Even our d search has automagically been fixed somehow:
Step 4: Update formik or whatever form value you have
To do that we need something that fires on select:
onChange: (no explanation or description)
Insightful...NOT. We have a pretty and colorful definition again to our rescue and we pick up some clues:
So we see the first param (which we don't know what it is can be object, array of array, null, or undefined. And then we have the types of actions. So with some guessing we figure out, it must be passing the selected object:
We will pass setFieldValue function as a prop to the component:
onChange={(selectedItem) => {
setFieldValue(fieldName, selectedItem?.id); // fieldName is also passed as a prop
}}
NOTE: careful, if you clear the select it will pass null for selectedItem and your JS will explode for looking for .id of undefined. Either use optional chaining or as in my case set it conditionally to '' (empty string so formik works).
Step 5: Final code:
And so we are all set with a fully functional reusable Autocomplete dropdown select server-fetching async filtering, clearable thingy.
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/async';
export default function AutocompleteComponent({
fieldName,
initialValue,
setFieldValue,
getOptionLabel,
queryField,
}) {
const [options, setOptions] = useState(null);
const [initialObject, setInitialObject] = useState(null);
// this function only finds the item from all the data that has the same id
// that comes from the parent component (my case - formik initial)
const findByValue = (fullData, specificValue) => {
return fullData.find((e) => e.id === specificValue);
};
const loadData = async (query) => {
// load your data using query HERE
return res.data;
};
const getInitial = async () => {
// load your data using query HERE
const fetchedData = res.data;
// match by id your initial value
const initialItem = findByValue(fetchedData, initialValue);
// Set both initialItem and data options so component is initialized
setInitialObject(initialItem);
setOptions(fetchedData);
}
};
// Hit this once in the beginning
useEffect(() => {
getInitial();
}, []);
return (
<>
{options !== null && initialObject !== null ? (
<AsyncSelect
isClearable
getOptionLabel={getOptionLabel}
getOptionValue={(option) => option.id}
defaultValue={initialObject}
defaultOptions={options}
loadOptions={loadData}
onChange={(selectedItem) => {
const val = (selectedItem === null?'':selectedItem?.id);
setFieldValue(fieldName, val)
}}
/>
) : null}
</>
);
}
AutocompleteComponent.propTypes = {
fieldName: PropTypes.string.isRequired,
initialValue: PropTypes.string,
setFieldValue: PropTypes.func.isRequired,
getOptionLabel: PropTypes.func.isRequired,
queryField: PropTypes.string.isRequired,
};
AutocompleteComponent.defaultProps = {
initialValue: '',
};
I hope this saves you some time.

How to lock a row in a react material-table (with remote data) on edit?

Using material-table i try to lock a row on the server
using a remote api call with a value from the edited row, like an id column, as soon as the user clicks the edit action button on that row.
The onRowUpdate() callback is called when the data is to be written, so too late to be useful here.
What can be used to achieve this pre-Edit callback?
Overriding EditRow somehow...?
Not yet perfect but seems the way to go:
add a new state
const [lock, setLock] = useState({});
and
components={{
EditRow: (props) => <MyTableEditRow onEdit={applyLock} offEdit={removeLock} {...props} />,
}}
to the tabel definition and a new Component MyTableEditRow
const MyTableEditRow = (props) => {
const { onEdit, offEdit, ...rest } = props;
useEffect(() => {
console.log('MyTableEditRow useEffect');
if (onEdit && typeof onEdit === 'function') onEdit(props.data);
return () => {
console.log('MyTableEditRow cleanup');
if (offEdit && typeof offEdit === 'function') offEdit(props.data);
};
}, [onEdit, offEdit, props.data]);
return <MTableEditRow {...rest} />;
};
this components is a small wrapper around the built in edit row component and calls the two given function on create and destroy respectively who should get and remove the lock.
with the two callback to do the lock stuff on the rmeote api like: (add some error handling and stuff, getLock() and deleteLock() are basically axios calls to the remote api...)
const applyLock = (rowData) => {
getLock(api, rowData._id).then((lock_data) => {setLock(lock_data);})
}
};
const removeLock = () => {
deleteLock(api, lock).then((r) => { setLock({}); });
}
};
TODO
What is not working right now is onRowDelete(). the cancel button works, but the save just does nothing. Adding a row works, strangely...
EDIT
onRowDelete works, bug in my code.(do not use a non-existing state...)

Resources