I am making a App in Reactjs. In it I have an API that gets data from a AWS database table and populates a state array which then populates a tables.
I cant figure out how to let a user in turn update the values of the table and then save those changes and let the API upload it back into the data base.
I have a update method but it gives an error saying that I cant update the state Array or would complain that the column array is undefined.
I have the following code:
export default function Complaints(props) {
const [complaint, setComplaint] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [isInEditMode, setIsEditMode] = useState(false);
const [defaultValue, setDefaultValue] = useState("");
var columsArr = [
{ title: 'Customer ID', field: 'id' },
{ title: 'Issue', field: 'complaintName' },
{ title: 'Description', field: 'complaintDescription'},
{ title: 'Order ID', field: 'complaintOrderId'},
{ title: 'Submitted', field: 'createdAt'},
{ title: 'Updated', field: 'updatedAt'},
{ title: 'Admin Comment', field: 'adminComment'},
];
useEffect(() => {
async function onLoad() {
if (!props.isAuthenticated) {
return;
}
try {
const complaint = await loadComplaint();
setComplaint(complaint);
setState({
columns: [state.columns, ...columsArr],
complaint: [...state.complaint, ...complaint]
});
console.log(complaint)
} catch (e) {
alert(e);
}
setIsLoading(false);
}
onLoad();
}, [props.isAuthenticated]);
function loadComplaint() {
return API.get("kleen", "/Complaint");
}
// function edit(adminComment) {
// setIsEditMode(true);
// setDefaultValue(adminComment);
// console.log("value is"+ adminComment);
// }
// function updateComplaint() {
// return API.put("kleen", `/Complaint/${props.}`);
// }
const [state, setState] = React.useState({
columns: [],
complaint: []
});
return (
<MaterialTable style={{
marginTop: "8rem",
marginLeft: "auto",
marginRight: "auto",
position: "sticky",
}}
title="Complaints"
columns={state.columns}
data={state.complaint}
editable={{
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
if (oldData) {
let key = 1;
setState(prevState => {
complaint: prevState.complaint.map(el => el.key === key ? {...el, adminComment: "testing"}: el)
// const data = [...prevState.data];
// data[data.indexOf(oldData)] = newData;
// return { ...prevState, data };
});
}
}, 600);
}),
}}
/>
);
}
I am very new to Reactjs, any help would be greatly appreciated.
One way that you can achieve is by:
First store the state in a variable
Update the array variable
Restore the variable to the state.
If you want to use state then better to use component class
export default class Complaints extends Component {
//assign initial value to state
state={
propsObj : this.props
}
...
}
If you want to use component you have to import it
import React, { Component } from "react";
inside component class you have to use this.props instead of props
later if you want to change state just use this.setState({ propsObj: 'some thing new'});
Related
i'm sorry for the disturbance,
i'm a trying React Native for the first time ( I'm a Full Stack Engineer React NodeJS ),
i tried by differents tips to put AsyncStorage.getItem inside my state, then display in the map,
but everytime, "Error map undefined", but if i put the value inside my State Array, it's working,
i tried with JSON Stringify, JSON Parse... Like in WEB,
but not working...
Here is my code :
import { useEffect, useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import RadioForm from 'react-native-simple-radio-button';
import AsyncStorage from '#react-native-async-storage/async-storage';
const SelectOption = () => {
const [value, setValue] = useState([]);
const saveOption = (item) => {
try {
setValue([...value, {name: item, id: Math.random()}]);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
AsyncStorage.setItem('option', JSON.stringify(value));
}, [value]);
// Put GetItem in the state
useEffect(() => {
const getOption = async () => {
try {
const jsonValue = await AsyncStorage.getItem('option');
if (jsonValue !== null) {
setValue(JSON.parse(jsonValue));
}
} catch (e) {
console.log(e);
}
};
getOption();
}, []);
AsyncStorage.getItem('option').then((value) => {
console.log(value);
});
const radioProps = [
{label: 'Option 1', value: 'option1'},
{label: 'Option 2', value: 'option2'},
{label: 'Option 3', value: 'option3'}
];
return (
<View style={styles.sectionContainer}>
<RadioForm
radio_props={radioProps}
initial={0}
onPress={(value) => {
saveOption(value);
}}
/>
{value.map((item) => {
return <Text key={item.id}>{item.name}</Text>;
})
}
</View>
);
};
const styles = StyleSheet.create({
sectionContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default SelectOption;
Thanks :pray:
i tried with JSON Stringify, JSON Parse... Like in WEB,
You wants to set value in local storage so, don't need to set the value in useEffect hook. Just add in your saveOption function like
const saveOption = (item) => {
try {
let newValue = [...value, {name: item, id: Math.random()}]
AsyncStorage.setItem('option', newValue);
setValue(newValue);
} catch (e) {
console.log(e);
}
};
And make a getOption async function like
const getOption = async () => {
try {
const jsonValue = await AsyncStorage.getItem('option');
if (jsonValue) {
await setValue(jsonValue);
}
} catch (e) {
console.log(e);
}
};
Then render in JSX like
{value.map((item) => {
return <Text key={item.id}>{item.name}</Text>;
}
I'm a beginner to react and have been trying for hours to debug and resolve this issue without any avail.
I'm using Ant Design tables which contains a "footer" api that return all the values after the data has been filtered. These values are then passed to a function (handledFilteredObj) which then pushes the passed values (currentPageData) to a temporary object. This temporary object is then set to the csvData state variable using it's setter method.
However, when I use the code as mentioned, my app throws this error:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Here is my code:
import "antd/dist/antd.css";
import { Space, Table } from "antd"
import { useEffect, useState } from "react";
import { CSVLink, CSVDownload } from "react-csv";
import styles from "../HeaderComponent/DashBoardComponent/Dashboard.module.css";
const TransactionTableComponent = (props) => {
let f_obj = [];
const [filteredInfo, setFilteredInfo] = useState(null)
const [csvData, setCsvData] = useState("nothing")
const csvDataRef = useRef();
useEffect(() => {
setFilteredInfo({});
}, [])
useEffect(() => {
}, [filteredInfo])
useEffect(() => {
}, [csvData])
const handleChange = (pagination, filters) => {
setFilteredInfo(filters);
}
const handledFilteredObj = (currentPageData) => {
f_obj = [];
if (currentPageData.length > 0) {
currentPageData.forEach(element => {
debugger
console.log(element)
f_obj.push(element)
})
}
setCsvData(f_obj)
return null
}
const transactionTableColumns = [
{
title: 'Transaction ID',
dataIndex: 'TransactionId',
key: 'TransactionId',
width: 150,
sorter: (a, b) => a.TransactionId - b.TransactionId,
},
{
title: 'Transaction Amount',
dataIndex: 'TransactionAmount',
key: 'TransactionAmount',
width: 150,
sorter: (a, b) => a.TransactionAmount - b.TransactionAmount,
ellipsis: true,
},
{
title: 'Date',
dataIndex: 'TransactionDate',
key: 'TransactionDate',
width: 150,
},
{
title: "TransactionType",
dataIndex: "TransactionType",
key: "TransactionType",
filters: [
{ text: "Deposit", value: "DEPOSIT" },
{ text: "Withdrawl", value: "WITHDRAWL" },
{ text: "Bill Payment", value: "BILLPAYMENT" }
],
onFilter: (value, record) => record.TransactionType.includes(value)
},
{
title: 'Transaction Time',
dataIndex: 'TransactionTime',
width: 150,
key: 'TransactionTime',
sorter: (a, b) => a.TransactionTime < b.TransactionTime,
},
{
title: 'CNIC',
dataIndex: 'RiderCNIC',
width: 150,
key: 'RiderCNIC',
}
];
return (
<Space direction='vertical'>
<Table
className={styles.RiderTable}
columns={transactionTableColumns}
dataSource={props.transactionTableData}
size={'middle'}
scroll={{ y: 240 }}
onChange={handleChange}
footer={(currentPageData) =>
handledFilteredObj(currentPageData)
}
/>
<CSVLink data={csvData} className="btn btn-primary">
Extract Data
</CSVLink>
</Space>
)
}
export default TransactionTableComponent
Here is what I have tried. Please note that the code is shortened for brevity.
Passed an event to the handledFilteredObj() method along with the data so as to stop propagation using event.stopPropagation but it always found event undefined.
const handledFilteredObj = (event, currentPageData) => {
f_obj = [];
if (currentPageData.length > 0) {
currentPageData.forEach(element => {
debugger
console.log(element)
f_obj.push(element)
})
}
setCsvData(f_obj)
event.stopPropagation()
return null
}
//inside jsx
//...more code
<Table
className={styles.RiderTable}
columns={transactionTableColumns}
dataSource={props.transactionTableData}
size={'middle'}
scroll={{ y: 240 }}
onChange={handleChange}
footer={(currentPageData, event) =>
handledFilteredObj(currentPageData, event)
}
/>
Using a scope variable using let keyword but this variable's data never changed even though it changed within the function when I consoled it.
//declaring the data variable:
let data = [];
const handledFilteredObj = (currentPageData) => {
f_obj = [];
if (currentPageData.length > 0) {
currentPageData.forEach(element => {
debugger
console.log(element)
f_obj.push(element)
})
}
data = f_obj //.....set data var to f_obj
return null
}
<CSVLink data={data} className="btn btn-primary">
Extract Data
</CSVLink>
I am adding the tradingview charting library into my project and am having troubles getting the chart to re-render when I change the selected symbol.
When the chart loads initially it was calling a componentDidMount to submit parameters to their chart component which returns the chart. This is the charting component and I have a list of securities beside it that update redux state for symbol when clicked.
what I want to do is force the chart to update when the state changes so the correct symbol is displayed.
It is the same issue mentioned in this question, but I'm using hooks instead of class based components and when I try to use useEffect as componentDidUpdate I am getting symbol undefined.
Update:: in other question they said to use something like this in componentDidUpdate
this.tvWidget.chart().setSymbol('BINANCE:' + this.props.selectedSymbol.name)
but I cannot figure out how to do something similar with hooks
charting.js
export function TVChartContainer(props) {
const [symbol, setSymbol] = useState(props.symbol);
const tvWidget = null;
useEffect(() => {
setSymbol(props.symbol)
}, [props.symbol])
const componentDidMount = () => {
// setSymbol(props.symbol)
const widgetOptions = {
symbol: symbol,
//symbol: 'BTC/USDT',
//symbol: 'BTC/USD', //getUrlVars()["symbol"],
datafeed: Datafeed,
container_id: 'tv_chart_container',
library_path: '/charting_library/',
locale: getLanguageFromURL() || 'en',
disabled_features: ['use_localstorage_for_settings'],
enabled_features: ['study_templates'],
charts_storage_url: props.chartsStorageUrl,
charts_storage_api_version: props.chartsStorageApiVersion,
fullscreen: false,
autosize: true,
width: '100%',
timezone: 'America/New_York',
client_id: 'Hubcap',
user_id: 'public_user_id',
auto_save_delay: 10,
theme: 'Light',
loading_screen: { backgroundColor: '#222222', foregroundColor: '#229712' },
custom_indicators_getter: indicators,
};
const tvWidget = new widget(widgetOptions);
// tvWidget = tvWidget;
const thisComponent = props;
tvWidget.onChartReady(() => {
tvWidget.headerReady().then(() => {
const button = tvWidget.createButton();
button.setAttribute('title', 'Click to show a notification popup');
button.classList.add('apply-common-tooltip');
button.addEventListener('click', () =>
tvWidget.showNoticeDialog({
title: 'Notification',
body: 'TradingView Charting Library API works correctly',
callback: () => {
console.log('Noticed!');
},
})
);
button.innerHTML = '';
// thisComponent.getPattern(); //might need to uncomment later
tvWidget
.chart()
.onIntervalChanged()
.subscribe(null, function (interval, obj) {
console.log('On interval change');
thisComponent.getPattern();
});
tvWidget
.chart()
.onSymbolChanged()
.subscribe(null, function (symbolData) {
console.log('Symbol change ' + symbolData);
// thisComponent.getPattern();
});
// tvWidget.chart().createStudy('Strange Indicator', false, true);
// tvWidget.chart().createStudy('ESS Indicator', false, true);
// tvWidget.chart().createStudy('ESL Indicator', false, true);
// tvWidget.chart().createStudy('EPS Indicator', false, true);
// tvWidget.chart().createStudy('EPL Indicator', false, true);
// tvWidget.chart().createStudy('ETS Indicator', false, true);
// tvWidget.chart().createStudy('ETL Indicator', false, true);
});
});
};
const componentWillUnmount = () => {
if (tvWidget !== null) {
tvWidget.remove();
tvWidget = null;
}
};
// useEffect(() => {
// componentDidMount();
// // getPattern();
// // drawPattern();
// // // removeAllShape();
// return () => {
// componentWillUnmount();
// }
// }, [symbol])
useEffect(() => {
setSymbol(props.symbol)
componentDidMount();
// getPattern();
// drawPattern();
// // removeAllShape();
return () => {
componentWillUnmount();
}
}, []);
return <div id="tv_chart_container" className={'TVChartContainer'} />;
main page componenet
const TestPage = ({selected}) => {
const [symbol, setSymbol] = useState('AAPL');
useEffect(() => {
setSymbol(selected)
}, [selected])
return (
<div>
<TVChartContainer symbol={symbol} />
</div>
);
}
const mapStateToProps = (state) => {
return {
selected: state.Watchlist.stock.selected,
}
}
export default connect(mapStateToProps)(TestPage)
watchlist
const Security = ({index, name, stocks, selected}) => {
const dispatch = useDispatch();
const [taskName, setTaskName] =useState(name)
const [prevState, setPrevState] = useState(stocks)
const removeTask = (e) => {
e.stopPropagation()
setPrevState(stocks)
dispatch(removeStock(index))
}
const selectAStock = () => {
dispatch(stockSelected(name))
}
useEffect(() => {
setPrevState(stocks)
}, [])
useEffect(() => {
if(prevState !== stocks) dispatch(updateWatchlist(stocks, selected))
}, [stocks])
return (
<Row className="list-group-item">
<div className="item-titles" onClick={() => selectAStock()}>
{name}
</div>
<button onClick={(e) => removeTask(e)} className="remove-item">
<i className="glyphicon glyphicon-remove"></i>
</button>
</Row>
);
}
const mapStateToProps = (state) => {
return {
stocks: state.Watchlist.stock.watchlist,
}
}
export default connect(mapStateToProps, {removeStock, updateWatchlist, stockSelected})(Security);
this.tvWidget?.setSymbol("BINANCE", "5" as ResolutionString, () => null)
The setSymbol accept 3 parameters.
(symbol: string, interval: ResolutionString, callback: EmptyCallback): void
Symbol: which is a string
Interval: which is of type ResolutionString. ("5" as ResolutionString) use the 'as' to prevent error)
callback: just an empty callback
on componentDidUpdate() you can update the tradingView Widget with the following parameters.
When a user adds additional information, a mutation is made to the database adding the new info, then the local state is updated, adding the new information to the lead.
My mutation and state seem to get updated fine, the issue seems to be that the state of the Material Table component does not match its 'data' prop. I can see in the React Dev tools that the state was updated in the parent component and is being passes down, the table just seems to be using stale data until I manually refresh the page.
I will attach images of the React Devtools as well as some code snippets. Any help would be much appreciated.
Devtools Material Table data prop:
Devtools Material Table State
Material Table Parent Component:
const Leads = () => {
const [leadState, setLeadState] = useState({});
const [userLeadsLoaded, setUserLeadsLoaded] = React.useState(false);
const [userLeads, setUserLeads] = React.useState([]);
const { isAuthenticated, user, loading } = useAuth()
const [
createLead,
{ data,
// loading: mutationLoading,
error: mutationError },
] = useMutation(GQL_MUTATION_CREATE_LEAD);
const params = { id: isAuthenticated ? user.id : null };
const {
loading: apolloLoading,
error: apolloError,
data: apolloData,
} = useQuery(GQL_QUERY_ALL_LEADS, {
variables: params,
});
useEffect(() => {
if (apolloData) {
if (!userLeadsLoaded) {
const { leads } = apolloData;
const editable = leads.map(o => ({ ...o }));
setUserLeads(editable);
setUserLeadsLoaded(true);
};
}
}, [apolloData])
if (apolloLoading) {
return (
<>
<CircularProgress variant="indeterminate" />
</>
);
};
if (apolloError) {
console.log(apolloError)
//TODO: Do something with the error, ie default user?
return (
<div>
<div>Oh no, there was a problem. Try refreshing the app.</div>
<pre>{apolloError.message}</pre>
</div>
);
};
return (
<>
<Layout leadState={leadState} setLeads={setUserLeads} leads={userLeads} setLeadState={setLeadState} createLead={createLead}>
{apolloLoading ? (<CircularProgress variant="indeterminate" />) : (<LeadsTable leads={userLeads} setLeads={setUserLeads} />)}
</Layout>
</>
)
}
export default Leads
Handle Submit function for adding additional information:
const handleSubmit = async (event) => {
event.preventDefault();
const updatedLead = {
id: leadState.id,
first_name: leadState.firstName,
last_name: leadState.lastName,
email_one: leadState.email,
address_one: leadState.addressOne,
address_two: leadState.addressTwo,
city: leadState.city,
state_abbr: leadState.state,
zip: leadState.zipCode,
phone_cell: leadState.phone,
suffix: suffix,
address_verified: true
}
const { data } = await updateLead({
variables: updatedLead,
refetchQueries: [{ query: GQL_QUERY_GET_USERS_LEADS, variables: { id: user.id } }]
})
const newLeads = updateIndexById(leads, data.updateLead)
console.log('New leads before setLeads: ', newLeads)
setLeads(newLeads)
// setSelectedRow(data.updateLead)
handleClose()
};
Material Table Component:
const columnDetails = [
{ title: 'First Name', field: 'first_name' },
{ title: 'Last Name', field: 'last_name' },
{ title: 'Phone Cell', field: 'phone_cell' },
{ title: 'Email', field: 'email_one' },
{ title: 'Stage', field: 'stage', lookup: { New: 'New', Working: 'Working', Converted: 'Converted' } },
{ title: 'Active', field: 'active', lookup: { Active: 'Active' } },
];
const LeadsTable = ({ leads, setLeads }) => {
const classes = useStyles();
const { user } = useAuth();
const [isLeadDrawerOpen, setIsLeadDrawerOpen] = React.useState(false);
const [selectedRow, setSelectedRow] = React.useState({});
const columns = React.useMemo(() => columnDetails);
const handleClose = () => {
setIsLeadDrawerOpen(!isLeadDrawerOpen);
}
console.log('All leads from leads table render: ', leads)
return (
<>
<MaterialTable
title='Leads'
columns={columns}
data={leads}
icons={tableIcons}
options={{
exportButton: false,
hover: true,
pageSize: 10,
pageSizeOptions: [10, 20, 30, 50, 100],
}}
onRowClick={(event, row) => {
console.log('Selected Row:', row)
setSelectedRow(row);
setIsLeadDrawerOpen(true);
}}
style={{
padding: 20,
}}
/>
<Drawer
variant="temporary"
open={isLeadDrawerOpen}
anchor="right"
onClose={handleClose}
className={classes.drawer}
>
<LeadDrawer onCancel={handleClose} lead={selectedRow} setLeads={setLeads} setSelectedRow={setSelectedRow} leads={leads} />
</Drawer>
</>
);
};
export default LeadsTable;
Try creating an object that contains refetchQueries and awaitRefetchQueries: true. Pass that object to useMutation hook as a 2nd parameter. See example below:
const [
createLead,
{ data,
loading: mutationLoading,
error: mutationError },
] = useMutation(GQL_MUTATION_CREATE_LEAD, {
refetchQueries: [{ query: GQL_QUERY_GET_USERS_LEADS, variables: { id: user.id } }],
awaitRefetchQueries: true,
});
Manually updating cache. Example blow is adding a new todo. In your case you can find and update the record before writing the query.
const updateCache = (cache, {data}) => {
// Fetch the todos from the cache
const existingTodos = cache.readQuery({
query: GET_MY_TODOS
});
// Add the new todo to the cache (or find and update an existing record here)
const newTodo = data.insert_todos.returning[0];
cache.writeQuery({
query: GET_MY_TODOS,
data: {todos: [newTodo, ...existingTodos.todos]}
});
};
const [addTodo] = useMutation(ADD_TODO, {update: updateCache});
I have a form component, and the reference of input fields are linked to the useForm reducer with references. I have to set a initial form state after setting the input field references? I have done as below. But it is rendering thrice. How to solve this rendering issue?
import React, { useState } from 'react';
const useForm = () => {
const [ formState, setFormState ] = useState({});
const refs = useRef({});
const register = useCallback(( fieldArgs ) => ref => {
if(fieldArgs) {
const { name, validations, initialValue } = fieldArgs;
refs.current[name] = ref;
}
console.log('Register rendered');
}, []);
useEffect(() => {
console.log('Effect Rendered');
const refsKeys = Object.keys(refs.current);
refsKeys.forEach(refKey => {
if(!formState[refKey]) {
setFormState(prevState => {
return {
...prevState,
[refKey]: {
value: '',
touched: false,
untouched: true,
pristine: true,
dirty: false
}
}
});
}
});
}, [ refs ]);
return [ register ];
}
export { useForm };
And the app component as below
const App = () => {
const [ register ] = useFormsio();
return(
<form>
<input
type = 'email'
placeholder = 'Enter your email'
name = 'userEmail'
ref = { register({ name: 'userEmail' }) } />
<button
type = 'submit'>
Submit
</button>
</form>
)
}
How to solve this multiple rendering issue?
I think the issue in the code above is whenever refs changes you need to loop through all the fields in form and set the state.
Why don't you set the state in register method?
const register = useCallback(( fieldArgs ) => ref => {
if(fieldArgs) {
const { name, validations, initialValue } = fieldArgs;
if(!refs.current[name] ) {
refs.current[name] = ref;
setFormState(prevState => {
return {
...prevState,
[refKey]: {
value: '',
touched: false,
untouched: true,
pristine: true,
dirty: false
}
}
});
}
}
console.log('Register rendered');
}, []);