Handle cell click state on React.js - javascript

I have this state defined:
constructor(props){
super(props);
this.state = {
posts:[],
post:{},
openNew:false,
openModify:false
};
}
With the following function which contains a fetch, I recieve an array of objects with responseData:
getPosts(){
fetch(
DOMAIN+'/api/posts/', {
method: 'get',
dataType: 'json',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization':'Bearer '+this.props.token
}
})
.then((response) =>
{
return response.json();
})
.then((responseData) => {
this.setState({posts:responseData});
console.log("Log del responseData de posts");
console.log(responseData);
})
.catch(function() {
console.log("error");
});
}
This function is called in componentDidMount:
componentDidMount(){
this.getPosts()
}
The JSON object obtained from the fetch and kept within this.state.products looks like this:
As shown previously in the fetch, with this line this.setState({posts:responseData}); I can pass posts to the table where I want title, date and hour to be displayed:
<DataTables
height={'auto'}
selectable={false}
showRowHover={true}
columns={CAMPAIGN_TABLE_COLUMNS}
data={this.state.posts}
showCheckboxes={false}
rowSizeLabel="Filas por página"
onCellClick={this.handleOpenModify.bind(this)}
/>
The table called is:
const CAMPAIGN_TABLE_COLUMNS = [
{
key: 'title',
label: 'Título',
style:{width: '40%'}
}, {
key: 'created',
label: 'Fecha',
style:{width: '30%'},
render: (DateToFormat) => {
return moment(DateToFormat).format("DD/MM/YYYY");
}
}, {
key: 'created',
label: 'Hora',
style:{width: '30%'},
render: (DateToFormat) => {
return moment(DateToFormat).format("hh:mm:ss");
}
}
];
With all of this I am able to print the data that I want on the table, looking like this:
What I am not able to do is: When I click on a row of the table to pass the values that were previously printed, such as the title.
This dialog is constructed using the following lines:
<Dialog
title="Modificar Post"
actions={actions}
modal={false}
open={this.state.openModify}
onRequestClose={this.handleClose}
titleClassName="dialog-title"
contentStyle={{width:660}}
autoScrollBodyContent={true}
>
<TextField
fullWidth={true}
floatingLabelText="Título"
errorText="¡Ups! No deberías ver este mensaje."
defaultValue={this.state.posts.title}
/>
</Dialog>
I thought that binding this to handleOpenModify (the function that is called when you click on a row of the table):
handleOpenModify = () => {
this.getPosts();
this.setState({openModify: true});
};
Would allow me to print the title within the TextField as simple as giving to the defaultValue this.state.posts.title, but is not working as you can see on the last picture that I added.
P.D.: I call getPosts() in handleOpenModify in case it had to be called again when a row is clicked, but it hasn't worked either.
Any suggestions?

DataTables provides you the rowNumber and columnIndex as arguments.
For more information, check their docs:
https://github.com/hyojin/material-ui-datatables/blob/master/src/DataTables/DataTablesRow.js#L142
<DataTables
...
onCellClick={(event, rowNumber) => console.log('selectedPost', this.state.posts[rowNumber]) }
/>

Thanks to #EdwardChopuryan and #Semi-Friends I've been able to retrieve the data that I wanted.
First of all I had to change the name of my function handleOpenModify to handleCellClick, since I could pass through the row parameter all I wanted and keep it within post {}, declared before in the sate.
handleCellClick = (y,x,row) => {
this.getPosts();
this.setState({
openModify: true,
newForm:false,
post:{...row, _id:row._id,title:row.title}})
};
Then, on DataTable, bind it on the onCellClick parameter:
<DataTables
height={'auto'}
selectable={false}
showRowHover={true}
columns={CAMPAIGN_TABLE_COLUMNS}
data={this.state.posts}
showCheckboxes={false}
rowSizeLabel="Filas por página"
onCellClick={this.handleCellClick.bind(this)}
/>
And call the value that I wanted on the TextField through the defaultValue:
<TextField
fullWidth={true}
floatingLabelText="Título"
errorText="¡Ups! No deberías ver este mensaje."
defaultValue={this.state.post.title}
/>
And this is the result!

this is a sample on how to bind and retrieve specific data on click of cell
list item creation
var list = CAMPAIGN_TABLE_COLUMNS.map((data, key) =>
<td onClick={this.handleClick.bind(this, key)}>{data.title}</td>
)
onClick handler
handleClick(id) {
let item = CAMPAIGN_TABLE_COLUMNS[id]; // item data
}
as for your current code, you need to modify this part
onCellClick={this.handleOpenModify.bind(this)} // key or the array index
handleOpenModify(e, row, key) { // receive the column number as 3rd param
let item = CAMPAIGN_TABLE_COLUMNS[key]; // now get the respective object
}

Related

How to update a row with contenteditable in Vue?

I'm trying to figure out how to get the current changes in a 'contenteditable' and update it in the row that it was changed.
<tbody>
<!-- Loop through the list get the each data -->
<tr v-for="item in filteredList" :key="item">
<td v-for="field in fields" :key="field">
<p contenteditable="true" >{{ item[field] }}</p>
</td>
<button class="btn btn-info btn-lg" #click="UpdateRow(item)">Update</button>
<button class="btn btn-danger btn-lg" #click="DelteRow(item.id)">Delete</button>
</tr>
</tbody>
Then in the script, I want to essentially update the changes in 'UpdateRow':
setup (props) {
const sort = ref(false)
const updatedList = ref([])
const searchQuery = ref('')
// a function to sort the table
const sortTable = (col) => {
sort.value = true
// Use of _.sortBy() method
updatedList.value = sortBy(props.tableData, col)
}
const sortedList = computed(() => {
if (sort.value) {
return updatedList.value
} else {
return props.tableData
}
})
// Filter Search
const filteredList = computed(() => {
return sortedList.value.filter((product) => {
return (
product.recipient.toLowerCase().indexOf(searchQuery.value.toLowerCase()) != -1
)
})
})
const DelteRow = (rowId) => {
console.log(rowId)
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowId}`, {
method: 'DELETE'
})
.then((response) => {
// Error handeling
if (!response.ok) {
throw new Error('Something went wrong')
} else {
// Alert pop-up
alert('Delete successfull')
console.log(response)
}
})
.then((result) => {
// Do something with the response
if (result === 'fail') {
throw new Error(result.message)
}
})
.catch((err) => {
alert(err)
})
}
const UpdateRow = (rowid) => {
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowid.id}`, {
method: 'PUT',
body: JSON.stringify({
id: rowid.id,
date: rowid.date,
recipient: rowid.recipient,
invoice: rowid.invoice,
total_ex: Number(rowid.total_ex),
total_incl: Number(rowid.total_incl),
duration: rowid.duration
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
})
})
}
return { sortedList, sortTable, searchQuery, filteredList, DelteRow, UpdateRow }
}
The commented lines work when I enter them manually:
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
Each cell has content editable, I'm not sure how to update the changed event
The way these run-time js frontend frameworks work could be summarized as "content is the function of data". What I mean is the html renders the data that you send it. If you want the data to be updated when the user changes it, you need to explicitly tell it to do so. Some frameworks (like react) require you to setup 1-way data binding, so you have to explicitly define the data that is displayed in the template, as well as defining the event. Vue has added some syntactic sugar to abstract this through v-model to achieve 2-way binding. v-model works differently based on whichever input type you chose, since they have slightly different behaviour that needs to be handled differently. If you were to use a text input or a textarea with a v-model="item[field]", then your internal model would get updated and it would work. However, there is no v-model for non-input tags like h1 or p, so you need to setup the interaction in a 1-way databinding setup, meaning you have to define the content/value as well as the event to update the model when the html tag content changes.
have a look at this example:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 contenteditable #input="({target})=>msg=target.innerHTML">{{ msg }}</h1>
<h2 contenteditable>{{ msg }}</h2>
<input v-model="msg">
</template>
If you change the h2 content, the model is not updated because vue is not tracking the changes. If you change through input or h1, the changes are tracked, which will also re-render the h2 and update its content.
TL;DR;
use this:
<p
contenteditable="true"
#input="({target})=>item[field]=target.innerHTML"
>{{ item[field] }}</p>

React-Bootstrap-TypeAhead giving error when trying to change already selected option

So I'm using react-bootstrap-typeahead and it's working completely fine when I'm typing something in the search box. It gives me the relevant options when I type something in the search box like this:
However, when I select one of options and then try to re-change the text it throws an error. This is how it looks like when I select 1 option.
And this is the error it throws: TypeError: 'Cannot read property 'match' of undefined'
Here is the state of the Search component which has the Typeahead:
class Search extends Component {
state = {
hcpName: [],
hcps: [],
searchName: '',
isLoading: false,
hcp_id: 101,
searchSelectedOption: ''
}
And here is the Typeahead I'm using:
<div className='col-md-3'>
<div class="form-group">
<Typeahead
id="basic-example"
options={this.state.hcpName}
placeholder="Search by Name..."
emptyLabel={this.state.isLoading ?
<>
<span>Searching...
<Loader
style={{ paddingLeft: '5px', display: 'inline' }}
type="Circles"
color="#0caf8d"
height={15}
width={20}
radius={30}
/>
</span>
</>
: ''}
isLoading={this.state.isLoading}
onInputChange={(searchName) => this.setState({ searchName }, () => {
{
let nameValue = this.state.searchName;
this.setState({ isLoading: true })
axios.post('/get-hcp-data', {
nameValue: nameValue
})
.then((res) => {
const hcps = res.data;
this.setState({ hcps: hcps, hcpName: Object.values(hcps.hcp_details_concat) })
this.setState({ isLoading: false })
}, (error) => {
console.log(error);
});
}
})}
onChange={(selectedOption) => {
console.log('selected option: ', selectedOption[0]);
console.log('npi id selected', selectedOption[0].replace(/(^.*\[|\].*$)/g, ''));
console.log('parsed npi id selected', parseInt(selectedOption[0].replace(/(^.*\[|\].*$)/g, '')[0]));
this.setState({hcp_id: parseInt(selectedOption[0].match(/[^[\]]+(?=])/g)[0])});
}}
/>
</div>
</div>
Inside 'onInputChange' inside Typeahead, I'm basically making an api call after every keystroke that a user enters. So that's why you can see an axios request over there. And inside 'onChange', I extract the number inside the square brackets of the user selection.
As I mentioned, I face an error when I try to change the text of the already selected option. For example, suppose I clicked on [101]Anna, I see that text in the search bar. And when I try to modify it again, I immediately see an error. What's the possible reason for this?
Here is the console log for onInput change:
I solved the problem by identifying that the match/replace function can't be used inside the onChange of Typeahead so I instead directly used it while fetching the api data. For that, I first set the state according to what the user has selected like this:
onChange={(selectedOption) => this.setState({ searchName: selectedOption }, () => {
console.log('selected option: ', selectedOption);
})}
And then while fetching the data, I made use of the searchName state to run the replace function.
dataFetch = () => {
this.setState({ receivedData: [], loading: true });
let page_id = this.state.page_id;
let hcp_id = parseInt(this.state.searchName[0].replace(/(^.*\[|\].*$)/g, ''));
axios.post('/test-json', {
page_id: page_id,
hcp_id: hcp_id
})
.then((res) => {
this.setState({ receivedData: res.data, loading: false });
console.log('State after loading data: ', this.state);
}, (error) => {
this.setState({ error: true });
console.log(error);
});
}

How to properly render new elements after a POST request?

I have a react page that looks like this:
and right now when creating a new category the post request goes through to the database but the categories is not rendered again to display the new category unless you refresh the page (GET request for all categories on page start up).
SideBar.js
createNewCategory = async (input) => {
console.log("CREATING NEW: ", input);
var response = await fetch("http://localhost:8081/api/categories", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Token": 1234,
Accept: "application/json"
},
body: JSON.stringify({
title: input
})
})
let resp = await response.json();
this.setState({
categories: [...this.state.categories, resp]
})
}
CreateCategory.js
handleNewCategory = (event) => {
event.preventDefault()
this.props.createNewCategory(this.state.input)
this.setState({
input: ''
})
}
render(){
return (
<form onSubmit={this.handleNewCategory} className="new-category-form">
<h4>Create Category</h4>
<input onChange={this.handleInput} className="new-category-input" type="text" value={this.state.input} />
<input className="new-category-input" type="submit" value="Create" />
</form>
)
}
CategoriesContainer.js
function CategoriesContainer(props) {
function renderCategories(){
console.log("PROPS: ", props)
return props.categories.map(category => {
console.log("CATEACH: ", category)
return <Category key={category.id} category={category} />
})
}
return(
<div>
{renderCategories()}
</div>
)
}
At the moment if I create a new category with a name of letters I get the err
Uncaught (in promise) SyntaxError: Unexpected token a in JSON at position 0 sidebar.js:46
and if I create it with numbers I get
Warning: Each child in a list should have a unique "key" prop.
Im still new to react so hopefully Im not completely off the mark here, any ideas?
Fixed it. First off I was using response instead of resp to update the state and I was returning just the name rather than the whole object to the POST request.

Event Data is undefined after confirmation modal has been clicked

I have a dropdown from the semantic-ui package, which works fine when I don't include the confirmation modal code in my project. This is what I currently have:
<Dropdown
loading={settingConfig}
disabled={!configEditable}
options={configs && configs.length > 0 ? configs.map(formatConfig) : teltonikaConfigs.map(formatTeltonikaConfig)}
onChange={this.handleConfigChange} />
<EditButton editingEnabled={configEditable} onClick={this.toggleConfigEdit} />
The options are formatted in the following way:
const formatConfig = conf => ( { key: conf.id, text: conf.name + '-' + conf.scriptVersionId + '.' + conf.configVersionId, value: conf.id, image: getRisk(conf.risk)} );
const formatTeltonikaConfig = conf => ({key: conf.id, text: conf.name, value: conf.id});
When an option is selected, the following function is called:
handleConfigChange = ( e, data ) => {
const forceUpdate = true;
// Not an Atom B Device
if (this.props.device.type !== "HARDWIRED-BM") {
const configId = data.value;
this.setState( { settingConfig: true, configEditable: false } );
updateConfiguration(this.props.device.imei, configId, forceUpdate)
.then( this.props.handleDeviceUpdate )
.catch( error => toast.error( "There has been an error whilst updating the device.. This will need to be updated manually. " + error.message ) )
.then(() => this.setState( { settingConfig: false } ) )
} else {
const configId = data.value;
console.log(" Id " + configId);
this.setState( { settingConfig: true, configEditable: false } );
updateTeltonikaDeviceConfig(this.props.device.imei, configId, forceUpdate)
.then(this.props.handleDeviceUpdate)
.catch(error => toast.error("There has been an error whilst updating the device.. This will need to be updated manually. " + error.message))
.then(() => this.setState({settingConfig: false}))
}
};
This will then get the configId from data.value as per the formatted configs and works fine...
However, when I add a confirmation dialog/modal so when the user selects an option, they have to confirm yes or no, configId/data.value is undefined. The code for this is as follows:
The dropdown changes to this (onChange method changes):
<Dropdown
loading={settingConfig}
disabled={!configEditable}
options={configs && configs.length > 0 ? configs.map(formatConfig) : teltonikaConfigs.map(formatTeltonikaConfig)}
onChange={this.show} />
<EditButton editingEnabled={configEditable} onClick={this.toggleConfigEdit} />
I also have this code to show the dropdown:
show = () => this.setState({ open: true })
handleConfirm = (e, data) => {
this.handleConfigChange(e, data);
this.setState({ open: false })
}
handleCancel = () => this.setState({ open: false })
Then the confirmation dialog code is as follows:
<div>
<Confirm
open={this.state.open}
cancelButton='No'
confirmButton="Yes"
onCancel={this.handleCancel}
onConfirm={this.handleConfirm}
/>
</div>
So from this, when an option is selected, the dialog window opens and when the user clicks YES e,data is sent into the handleConfirmMethod.. Now my problem is that data contains the following values, and not the configId which is passed through without the confirmation dialog:
The data passed through is that of what is in the confirmation dialog code.. Can someone please help me with this, as I need to pass through the configId as data?
Hope this makes sense!
You cannot get configId(data.value) from handleConfirm because, handleConfirm is a callback function which is fired when you click the "yes" button on <Confirm/>, it has no idea of which option you just selected. The only place you can get that information is the onChange callback on that <Dropdown/>.
To solve your problem, we need to pass the configId(data.value) from Dropdown's onChange callback to Confirm's onConfirm callback. There are several solutions.
I personally would suggest to save configId(data.value) into a state like:
<Dropdown
...
onChange={this.show} /* better to rename it to something else cuz it's not just "show" any more */
/>
// save the configId(data.value) to state
show = (event, data) => this.setState({ open: true, selectedConfigId: data.value });
// get the value from state. You can also get "this.state.selectedConfigId" in this.handleConfigChange directly
handleConfirm = (e, data) => {
this.handleConfigChange(e, this.state.selectedConfigId);
this.setState({ open: false })
}

Tables don't render after first click in react

When i click on nav button then table data loads but on second time click table data doesn't load but data in loads on every click in react js
I have put the code in componentdidMount() {} to load it every time when it loads. i made async function which loads table and also set header.
{this.state.numofbugs.map((data)=> {
return (
<li key={data}>
<Link to={'/applogs/' + data}
className="waves-effect" className={this.props.location.pathname === '/applogs/' + data ? 'active': ''}>
<span className="font-size waves-effect" >{data}</span>
</Link>
</li>
)
})}
in file where it renders
componentDidMount() {
if (this.props.location.pathname.includes('app_')) {
this.showTable(this.props.location.pathname.split('/')[2]);
}
}
async showTable(col_name) {
// empty data
this.state.logtable = []
let conf = { headers: { Authorization: 'Bearer ' + localStorage.getItem('session') }}
Axios.get(links.baseURL + 'sample?collection=' + col_name, conf).then((response) => {
// get headers
this.state.tableHeaders = [
Object.keys(response.data.result[0])[0],
Object.keys(response.data.result[0])[1],
Object.keys(response.data.result[0])[2],
Object.keys(response.data.result[0])[3],
]
Object.keys(response.data.result).map((key) => {
this.state.logtable[key] = response.data.result[key];
this.state.logtable[key]['key'] = key.toString()
});
this.setState({'logtable': this.state.logtable});
});
}
I expected to get data in my table on every button click in table but after one time click on tab of collapse data laods in table then on second time data loads in tag not table
First thing, you don't need to empty your state like,
this.state.logtable = []
If you want to replace your existing state with new one, you can simply set the state with new data.
Another issue is, you are directly mutating your state, like this
this.state.tableHeaders = [
Object.keys(response.data.result[0])[0],
Object.keys(response.data.result[0])[1],
Object.keys(response.data.result[0])[2],
Object.keys(response.data.result[0])[3],
]
And this
Object.keys(response.data.result).map((key) => {
this.state.logtable[key] = response.data.result[key];
this.state.logtable[key]['key'] = key.toString()
});
You should make use of local variables to create temporary array, and then need to set them in your state like,
async showTable(col_name) {
let conf = { headers: { Authorization: 'Bearer ' + localStorage.getItem('session') }}
Axios.get(links.baseURL + 'sample?collection=' + col_name, conf).then((response) => {
// get headers
let tableHeaders = Object.keys(response.data.result[0]).map((key,index) => index <=3 && key.toString())
let logtable = Object.keys(response.data.result).map((key) => ({
logtable[key] : response.data.result[key];
logtable[key]['key'] : key.toString()
}));
this.setState({logtable, tableHeaders});
});
}

Categories

Resources