valueFormatter executes before useEffect - javascript

I have a Country table on db and I want to render this on a grid. The table looks like below
CountryName Country3AlpCode CurrencyId
Argentina ARG 7
Austria AUT 49
and i have a Currency table on db also:
Id CurrencyAlphaCode CurrencyName
7 ARS Argentine peso
49 EUR Euro
I want to render the country table to a grid and instead currency, i need currencyAlphaCode. I declared two states for lists of datas and inside use effect I fetched them with API calls and set them to states. Passing country definitions as RowData props. Inside column definitions, i used valueFormatter as below and it doesn't work. It still renders currencyId instead currencyAlphaCode
const [countryDefinitions, setCountryDefinitions] = useState<Country[]>();
const [currencyCodeList, setCurrencyCodeList] = useState<Currency[]>();
useEffect(() => {
const onStartup = async () => {
const currencyCodeList = await commonAPI.get<Currency[]>("currency");
const countryList = await commonAPI.get<Country[]>("country");
setCurrencyCodeList(currencyCodeList);
setCountryDefinitions(countryList);
};
onStartup();
}, []);
const columnDefs: any =
[
{
field: "currencyId",
headerName: "Currency",
valueFormatter: (params: any) => {
console.log(currencyCodeList); // returns undefined
return currencyCodeList?.find((currency) => {
return currency.id === params.value;
})?.currencyAlphaCode;
}
},

Related

Problem with createActionThunk redux toolkit

Hello I made a createAsyncThunk function which is manipulating the actual state (it removes value with specific index). It executes but it doesn't update the firebase database and I'm not getting the value of categories array in the extraReducers. I don't know why because when I use console.log the categories array has got values. I'm using typescript and redux toolkit.
export const removeCategory = createAsyncThunk(
"categories/removeCategory",
async (index: number, thunkAPI) => {
const state = thunkAPI.getState() as RootState;
const categories = [...state.categories];
categories.splice(index, 1);
console.log(categories);
const updates = { categories: [] as string[] };
updates["categories"] = [...categories];
update(ref(database), categories);
return categories;
}
);
In this part of the code, are you sure you don't want to send the updates object instead of the categories?
const updates = { categories: [] as string[] };
updates["categories"] = [...categories];
update(ref(database), updates);
return categories;

React hook useEffect returns empty array at first

I get the data from my database like so:
const clicked = props.clicked;
const [allEmployees, setAllEmployees] = useState([]);
const [list, setList] = useState([]);
useEffect(()=>{ //getting employees
Axios.get(
PATH + `/${employees}`
).then((data) => {
setAllEmployees(data.data);
});
},[]);
useEffect(()=>{ //getting list of employees who should be selected
Axios.get(
PATH + `/${list}`
).then((data) => {
setList(data.data);
setList(
allEmployees.map(t=>{
if(list.includes(t.id)){
return{
select: true,
id: t.id,
name: t.name
}
}else{
return{
select: false,
id: t.id,
name: t.name
}
}
})
)
console.log(allEmployees);// <<
console.log(list);// <<
});
},[clicked]);
My problem is that the fist time I click on the buttons, activating clicked.props, both console.log() show empty arrays. After the second click and on, they work and show the arrays. I'm guessing I need to update them in a better way, but don't know how. (I'm trying to show the data but it really shows nothing on the first click of the button).
this is because they actually are empty arrays at first. Axios sends an async request to the server, fetching data. The state hooks are rendered right after the first page render, so basically at the same time. The data from the server will return when it returns. (as it is a promise). Once the data returns as a promise, you can resolve it and add the resolved data to the state.
It also seems like you're trying to set the list state twice with different data in the second useEffect. You are using the fetched data from axios first, and using the the data from the first useEffect after (allEmployees), it's kind of hard for me to really understand your thought process here.
the reason why you get the console.logs return empty on first click is because of the way useState works , when you set state you'll have to wait until the component reaload to see the new values of your state . so when you first click the state was empty so you get empty logs but on the second click you get none empty values but actually their just the new values from your first click :
const [value,setValue] = useState(0)
useEffect(()=>{ //getting list of employees who should be selected
Axios.get(PATH + `/${list}` )
.then((data) => {
setValue(value+1 )
});
console.log(value);//this will output 1
},[]);
useEffect(()=>{ //getting list of employees who should be selected
console.log(value);//this will output 2 on the next component render
},[value]);
so in your case you can check if list and employes are not empty if so return null :
const clicked = props.clicked;
const [allEmployees, setAllEmployees] = useState([]);
const [list, setList] = useState([]);
useEffect(()=>{ //getting employees
Axios.get(
PATH + `/${employees}`
).then((data) => {
setAllEmployees(data.data);
});
},[]);
useEffect(()=>{ //getting list of employees who should be selected
Axios.get(
PATH + `/${list}`
).then((data) => {
setList(
allEmployees.map(t=>{
if(list.includes(t.id)){
return{
select: true,
id: t.id,
name: t.name
}
}else{
return{
select: false,
id: t.id,
name: t.name
}
}
})
)
console.log(allEmployees);// <<
console.log(list);// <<
});
},[clicked]);
if(!allEmployees.length || !list.length) return null
// after this line you're garanted that allEmployees and list are not empty
render <div>{list.map(your implemntation)}<div>

Re-initializing react state array of objects leads to undefined values

I have two ReactJS components, a page called Projects with a Table component. When I initialize the state that has Table's data like so, the Table component renders fine:
const [tableData, setTableData] = useState([{
client: "anna",
name: "no",
email: "no",
date: new Date(),
progress: 0
}])
where the Table component just runs:
<tbody>
{props.data.map((obj,index) =>
<tr onClick = {(event) => {props.updateIndex(index)}}>
<td>{obj.client}</td>
<td>{obj.name}</td>
<td>{obj.email}</td>
<td>{obj.date}</td>
<td>{obj.progress}</td>
</tr>
)}
</tbody>
However, once I attempt to overwrite this state with database data, like this, although the array of objects prints out correctly, the table cannot read the data:
const userInfo = async () => {
await Promise.all(res.data.map(async (el, index) => {
const contents = await axios.get('/api/user/'+ el.userIDS[0] + '/info');
let clientName = contents.data.firstname + ' ' + contents.data.lastname;
let email = contents.data.email;
let info = {
client: clientName,
name: el.Name,
email: email,
date: date,
progress: el.progress
};
temp.push(info)
}));
}
setTableData(temp)
userInfo();
Both times, the state array prints out nicely. This screenshot shows the state (tableData) both at initialization and after the userInfo() has been run, and the format seems to be the same:
I guess I'm just confused as to what the mistake is that I am making, since in the console both object arrays look alike, but are still off from one another. I have tried changing the structure from a 2D array to an array of objects, which I like better, but both still cause issues.
You should call setTableData(temp) inside the userInfo function:
const userInfo = async () => {
const data = await Promise.all(
res.data.map((el, index) => {
return axios.get("/api/user/" + el.userIDS[0] + "/info");
})
);
const temp = data.map(contents => {
let clientName = contents.data.firstname + " " + contents.data.lastname;
let email = contents.data.email;
let info = {
client: clientName,
name: el.Name,
email: email,
date: date,
progress: el.progress
};
return info;
});
setTableData(temp);
};
Also in my suggestion, all you async call should go inside useEffect react function

How to call async function from valueFormatter in ag-grid

I have an ag-grid table, and I have a column in it with value formatter:
{
headerName: "My column",
valueFormatter: getFormattedIndexValue,
...
}
From getFormattedIndexValue I try to call async function:
async function getFormattedIndexValue (params) {
if (!params.value) return;
return await getDecodedValue(table, params.colDef.field, params.value);
}
This is the code of async function, I try to call:
async function getDecodedValue(table, field, value) {
const query = `function=decode&table=${table}&field=${field}&value=${value}`;
const response = await fetch('routines.php', { method: 'post', body: query, headers: {"Content-Type": "application/x-www-form-urlencoded"}});
return response.text();
}
But in this way valueFormatter doesn't return correct value, resulting in [Object Promise]. Is there a way to call async function from valueFormatter and to get proper result
this is an old post but I hope this answer can help others because it helped me a lot.
I was using an async function to retrieve the data via Fetch to the Cell of the table. No matter what I did, I always got [Object Promise]
Then I used cellRenderer instead of ValueFormatter
I give an example here. I have some data with some people and their company ID's, I don't want the user to see company IDs in the table, I want them to see the company names instead.
These are the columns, I set the "company_id" field with cellRenderer":
var columnDefsCuotas = [
{ field: 'user_id', width: 150 },
{ field: 'name', width: 250 },
{ field: 'company_id', width: 250,
cellRenderer: GetCompanyName
}
]
Then I create a class called GetCompanyName Here I can render each cell of the table to print the name of the company instead of the ID of the company. I put the fetch in this class to retrieve the data:
class GetCompanyName {
// gets called once before the renderer is used
async init(params) {
// create the cell
this.eGui = document.createElement('div');
this.eGui.innerHTML = `<span class="my-value"></span>`;
// get references to the elements we want
this.eValue = this.eGui.querySelector('.my-value');
// set value into cell
this.cellValue = this.getValueToDisplay(params);
this.eValue.innerHTML = await getName(this.cellValue);
async function getName(company_id) {
let result = await fetch(`/fetch_companies?company_id=${company_id}`)
let response = await result.json()
console.log(response) // See if it is fetching the desired response data
return response.company_name // I return only the parameter of the object I need to see
}
}
getGui() {
return this.eGui;
}
getValueToDisplay(params) {
return params.valueFormatted ? params.valueFormatted : params.value;
}
}
I hope it helps. It worked like a charm for me!
valueFormatter doesn't suit for it, but instead, you can create cellRenderer and you would be able to handle needed logic inside.
but as I understood, you wanna have kind of reference (key to value, id(as key) on database and value(as displayed value) on view grid) - am I right?
If so, (valueFormatter and valueParser should be used) but without an async call,
you need to have your key-value pair (dictionary) on grid-init process.
Here is my example:
on ag-grid init process I'm saving dictionary for columns in config.ComboData
valueFormatter = (params) => {
return this.lookupValue(config.ComboData['columnNameHere'], params.value);
};
valueParser = (params) => {
return this.lookupKey(config.ComboData['columnNameHere'], params.newValue);
}
valueFormatter and params.value - used when ag-grid rendering the value.
valueParser and params.newValue - when you are updating the cell value.
and here looupValue and lookupKey functions from ag-grid doc and the sample
lookupValue(mappings, key:string) {
return mappings[key];
}
lookupKey(mappings, name) {
for (var key in mappings) {
if (mappings.hasOwnProperty(key)) {
if (name === mappings[key]) {
return key;
}
}
}
}

React - updating an object array in the state with setState

I'm working on a table planner app where guests can be assigned to dinner tables.
I have created an object array in the state called tabledata, which will contain objects like so:
this.state = {
tabledata: [
{
name: "Top Table",
guests: ["guest1", "guest2", "guest3"]
},
{
name: "Table One",
guests: ["guest3", "guest4", "guest5"]
}
]
}
I am then creating a drag and drop interface where guests can move between tables. I have attempted to update the state like so:
updateTableList (tablename, guest) {
const selectedTableObj = this.state.tabledata.filter((tableObj) => tableObj.name === tablename);
const otherTableObjs = this.state.tabledata.filter((tableObj) => tableObj.name !== tablename);
selectedTableObj[0].guests.push(guest);
const updatedObjectArray = [...otherTableObjs, selectedTableObj];
this.setState({
tabledata: [...otherTableObjs, ...selectedTableObj]
});
}
This works but because I am removing selectedTableObj from the state and then adding it to the end of the array I'm getting some funky results on screen. The updated table always goes to the bottom of the page (as you'd expect).
How can I update the object without changing its position within the array?
Find the index of the table you want to update using Array.findIndex(). Create a new tabledata array. Use Array.slice() to get the items before and after the updated table, and spread them into the new tabledata array. Create a new table object using object spread, add the updated guests array, and add the table object between the previous items:
Code (not tested):
updateTableList(tablename, guest) {
this.setState((prevState) => {
const tableData = prevState.tabledata;
const selectedTableIndex = tableData.findIndex((tableObj) => tableObj.name === tablename);
const updatedTable = tableData[selectedTableIndex];
return {
tabledata: [
...prevState.tabledata.slice(0, selectedTableIndex),
{
...updatedTable,
guests: [...updatedTable.guests, guest]
},
...prevState.tabledata.slice(selectedTableIndex + 1)
]
};
});
}
selectedTableObj[0].guests.push(guest) directly mutates the state which is not encouraged in React.
Try this:
this.setState((prevState) => {
const newData = [...prevState.tabledata];
// if you pass in `index` as argument instead of `tablename` then this will not be needed
const index = prevState.tabledata.findIndex(table => tableObj.name === tablename);
newData[index] = {
...newData[index],
guests: newData[index].guests.concat([guest]),
};
return { tabledata: newData };
});
You also did not remove the guest from its previous table so you need to modify for that.
You can do it with a Array.reduce
let newState = this.state
// let newState = {...this.state} // in case you want everything immutable
newState.tableData = newState.tableData.reduce((acc, table) =>
if(table.name === tableName) {
return acc.concat({...table, guests: table.guests.concat(newGuest)})
} else {
return acc.concat(table)
}
)

Categories