Template column not rendering with Telerik Ajax ActionLink - telerik-mvc

Template columns are not rendering when any of the below properties are being used.
.Pageable()
.Filterable()
.Sortable()
.Scrollable()
The version of the Telerik controls is: 2011.2.712.340
It is being rendered if I remove these properties, but the header does not accomodate for the template columns and pushes the rest of the columns over to the right (off the grid).What do I need to do in order to use the template columns with the above properties.
What do I need to do in order to use the template columns with the above properties?
Here is my View:
#model Telerik.Web.Mvc.GridModel<YeagerTech.YeagerTechWcfService.Customer>
#{
ViewBag.Title = "Customer Index";
}
<h2>
Customer Index</h2>
<p>
#Ajax.ActionLink("Create New", "Create", "Customer",
new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "createCustomer" })
</p>
#(Html.Telerik().Grid<YeagerTech.YeagerTechWcfService.Customer>(Model.Data)
.Name("Customers")
.DataKeys(dataKeys => dataKeys.Add(o => o.CustomerID))
.Columns(columns =>
{
columns.Template(
#<text>
#Ajax.ActionLink("[ Edit ]", "Edit", "Customer", new { id = item.CustomerID },
new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "editCustomer" })
</text>
);
columns.Template(
#<text>
#Ajax.ActionLink("[ Detail ]", "Details", "Customer", new { id = item.CustomerID },
new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "detailCustomer" })
</text>
);
columns.Bound(o => o.CustomerID).Hidden(true);
columns.Bound(o => o.Email).Width(200);
columns.Bound(o => o.Company).Width(200);
columns.Bound(o => o.FirstName).Width(200);
columns.Bound(o => o.LastName).Width(200);
columns.Bound(o => o.Address1).Width(200);
columns.Bound(o => o.Address2).Width(100);
columns.Bound(o => o.City).Width(200);
columns.Bound(o => o.State).Width(40);
columns.Bound(o => o.Zip).Width(60);
columns.Bound(o => o.HomePhone).Width(120);
columns.Bound(o => o.CellPhone).Width(120);
columns.Bound(o => o.Website).Width(200);
columns.Bound(o => o.IMAddress).Width(200);
columns.Bound(o => o.CreatedDate).Format("{0:MM/dd/yyyy}").ReadOnly(true).Width(120);
columns.Bound(o => o.UpdatedDate).Format("{0:MM/dd/yyyy}").ReadOnly(true).Width(120);
}).DataBinding(dataBinding => dataBinding.Ajax()
.Select("Index", "Customer"))
.Pageable()
.Filterable()
.Sortable()
.Scrollable()
)
Here is my controller for the View:
IEnumerable<YeagerTechWcfService.Customer> customerList = db.GetCustomers(); return View(new GridModel<YeagerTechWcfService.Customer> { Data = customerList });
Here is the HTML of the grid that is rendered for the view using the above properties
<div class="t-widget t-grid" id="Customers"><div class="t-grid-header"><div class="t-grid-header-wrap"><table cellspacing="0"><colgroup><col /><col /><col style="display:none;width:0" /><col style="width:200px" /><col style="width:200px" /><col style="width:200px" /><col style="width:200px" /><col style="width:200px" /><col style="width:100px" /><col style="width:200px" /><col style="width:40px" /><col style="width:60px" /><col style="width:120px" /><col style="width:120px" /><col style="width:200px" /><col style="width:200px" /><col style="width:120px" /><col style="width:120px" /></colgroup><tr><th class="t-header" scope="col"><span class="t-link"> </span></th><th class="t-header" scope="col"><span class="t-link"> </span></th><th class="t-header" scope="col" style="display:none;width:0"><a class="t-link" href="/Customer?Customers-orderBy=CustomerID-asc">Customer ID</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=Email-asc">Email</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=Company-asc">Company</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=FirstName-asc">First Name</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=LastName-asc">Last Name</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=Address1-asc">Address1</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=Address2-asc">Address2</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=City-asc">City</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=State-asc">State</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=Zip-asc">Zip</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=HomePhone-asc">Home Phone</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=CellPhone-asc">Cell Phone</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=Website-asc">Website</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=IMAddress-asc">IM Address</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=CreatedDate-asc">Created Date</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th><th class="t-header t-last-header" scope="col"><a class="t-link" href="/Customer?Customers-orderBy=UpdatedDate-asc">Updated Date</a><div class="t-grid-filter t-state-default"><span class="t-icon t-filter">Filter</span></div></th></tr></table></div></div><div class="t-grid-content" style="height:200px"><table cellspacing="0"><colgroup><col /><col /><col style="display:none;width:0" /><col style="width:200px" /><col style="width:200px" /><col style="width:200px" /><col style="width:200px" /><col style="width:200px" /><col style="width:100px" /><col style="width:200px" /><col style="width:40px" /><col style="width:60px" /><col style="width:120px" /><col style="width:120px" /><col style="width:200px" /><col style="width:200px" /><col style="width:120px" /><col style="width:120px" /></colgroup><tbody><tr><td>
<a data-ajax="true" data-ajax-mode="replace" data-ajax-update="#editCustomer" href="/Customer/Edit/2">[ Edit ]</a>
</td><td>
<a data-ajax="true" data-ajax-mode="replace" data-ajax-update="#detailCustomer" href="/Customer/Details/2">[ Detail ]</a>
</td><td style="display:none;width:0;display:none;width:0;;display:none;width:0">2</td><td>wsyeager36#msn.com</td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td><td>08/13/2011</td><td class="t-last"> </td></tr></tbody></table></div><div class="t-grid-pager t-grid-bottom"><div class="t-status"><a class="t-icon t-refresh" href="/Customer">Refresh</a></div><div class="t-pager t-reset"><a class="t-link t-state-disabled" href="#"><span class="t-icon t-arrow-first">first</span></a><a class="t-link t-state-disabled" href="#"><span class="t-icon t-arrow-prev">prev</span></a><div class="t-numeric"><span class="t-state-active">1</span></div><a class="t-link t-state-disabled" href="#"><span class="t-icon t-arrow-next">next</span></a><a class="t-link t-state-disabled" href="#"><span class="t-icon t-arrow-last">last</span></a></div><div class="t-status-text">Displaying items 1 - 1 of 1</div></div></div>
Through more research, I found out that I need to use the ClientTemplate property for Ajax binding.
If you're using Ajax, why is there an ActionLink property in the columns.Template to begin with? This throws off a lot of the developers.
I modified my new View code below, but the Edit link is not being rendered on my grid.
What am I doing incorrectly?
View
#model Telerik.Web.Mvc.GridModel<YeagerTech.YeagerTechWcfService.Customer>
#{
ViewBag.Title = "Customer Index";
}
<h2>
Customer Index</h2>
<p>
#Ajax.ActionLink("Create New", "Create", "Customer",
new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "createCustomer" })
</p>
#(Html.Telerik().Grid<YeagerTech.YeagerTechWcfService.Customer>()
.Name("Customers")
.DataKeys(dataKeys => dataKeys.Add(o => o.CustomerID))
.Columns(columns =>
{
#*columns.Template(
#<text>
#Ajax.ActionLink("[ Edit ]", "Edit", "Customer", new { id = item.CustomerID },
new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "editCustomer" })
</text>
).ClientTemplate(#"Open");
columns.Template(
#<text>
#Ajax.ActionLink("[ Detail ]", "Details", "Customer", new { id = item.CustomerID },
new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "detailCustomer" })
</text>
);*#
columns.Bound(o => o.CustomerID)
.ClientTemplate("<a href='" + Url.Content("~/Customer/Edit/") + "<#= CustomerID #>'>Edit</a>").Title("Edit");
columns.Bound(o => o.Email).Width(200);
columns.Bound(o => o.Company).Width(200);
columns.Bound(o => o.FirstName).Width(200);
columns.Bound(o => o.LastName).Width(200);
columns.Bound(o => o.Address1).Width(200);
columns.Bound(o => o.Address2).Width(100);
columns.Bound(o => o.City).Width(200);
columns.Bound(o => o.State).Width(40);
columns.Bound(o => o.Zip).Width(60);
columns.Bound(o => o.HomePhone).Width(120);
columns.Bound(o => o.CellPhone).Width(120);
columns.Bound(o => o.Website).Width(200);
columns.Bound(o => o.IMAddress).Width(200);
columns.Bound(o => o.CreatedDate).Format("{0:MM/dd/yyyy}").ReadOnly(true).Width(120);
columns.Bound(o => o.UpdatedDate).Format("{0:MM/dd/yyyy}").ReadOnly(true).Width(120);
}).DataBinding(dataBinding => dataBinding.Ajax()
.Select("Index", "Customer"))
.Pageable()
//.Filterable()
//.Sortable()
.Scrollable()
)
_Layout.cshtml
<link href="#Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<link href="../../Content/2011.2.712/telerik.common.min.css" rel="stylesheet" type="text/css" />
<link href="../../Content/2011.2.712/telerik.windows7.min.css" rel="stylesheet" type="text/css" />
<script src="/Scripts/jquery.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.js" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="../../Scripts/jquery.unobtrusive-ajax.min.js" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
#(Html.Telerik().StyleSheetRegistrar().DefaultGroup(group => group.Add("telerik.common.css").Add("telerik.windows7.css").Combined(true).Compress(true)))#Html.Telerik().ScriptRegistrar().jQuery(false).DefaultGroup(group => group.Combined(true).Compress(true))
Pertinent Rendered output
<th class="t-header" scope="col"><span class="t-link">Edit</span></th>
jQuery(document).ready(function(){ jQuery('#Customers').tGrid({columns:[{"title":"Edit","template":"\u003ca href=\u0027/Customer/Edit/\u003c#= CustomerID #\u003e\u0027\u003eEdit\u003c/a\u003e","member":"CustomerID","type":"Number"},

On my own research, I found out that I can use ServerSide template binding. When picking apart my grid column by column, the Template columns were being rendered, just not being displayed...
If I put, let's say 10 columns in my grid (even though scrolling is enabled), the template columns would not show up.
If I shortened my column list to let's say 5, then the template columns had enough room to show up.
The instance when the grid needs to set scrolling (when there are too many columns to display across the screen) is when the template columns are not displayed.
This defintly sounds like a bug Telerik needs to correct.
Below, is the code for the View:
#model Telerik.Web.Mvc.GridModel<YeagerTech.YeagerTechWcfService.Customer>
#{
ViewBag.Title = "Customer Index";
}
<h2>
Customer Index</h2>
<p>
#Ajax.ActionLink("Create New", "Create", "Customer",
new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "createCustomer" })
</p>
#( Html.Telerik().Grid<YeagerTech.YeagerTechWcfService.Customer>(Model.Data)
.Name("Customers")
.DataKeys(dataKeys => dataKeys.Add(o => o.CustomerID))
.Columns(columns =>
{
columns.Bound(o => o.CustomerID).Hidden(true);
columns.Template(
#<text>
#Ajax.ActionLink("[Edit]", "Edit", "Customer", new { id = item.CustomerID },
new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "editCustomer" })
</text>
).Width(50);
columns.Template(
#<text>
#Ajax.ActionLink("[Detail]", "Details", "Customer", new { id = item.CustomerID },
new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "detailCustomer" })
</text>
).Width(70);
columns.Bound(o => o.Email).Width(200);
columns.Bound(o => o.Company).Width(200);
columns.Bound(o => o.FirstName).Width(100).Title("FName");
columns.Bound(o => o.LastName).Width(100).Title("LName");
columns.Bound(o => o.Address1).Width(200).Title("Addr1");
columns.Bound(o => o.Address2).Width(100).Title("Addr2");
columns.Bound(o => o.City).Width(100);
columns.Bound(o => o.State).Width(40).Title("ST");
columns.Bound(o => o.Zip).Width(60);
columns.Bound(o => o.HomePhone).Width(120);
columns.Bound(o => o.CellPhone).Width(120);
//columns.Bound(o => o.Website).Width(100);
//columns.Bound(o => o.IMAddress).Width(100);
//columns.Bound(o => o.CreatedDate).Format("{0:MM/dd/yyyy}").ReadOnly(true).Width(120);
//columns.Bound(o => o.UpdatedDate).Format("{0:MM/dd/yyyy}").ReadOnly(true).Width(120);
}).DataBinding(dataBinding => dataBinding.Ajax()
.Select("Index", "Customer"))
.Pageable()
.Sortable()
.Scrollable()
)

Related

Message prints in every Dynamic Accordion in ReactJs

I have a dynamic Accordion in ReactJs. I am getting the message from my backend. but it's printing in every Accordion. I'm sharing the code
import React, { useState, useEffect } from "react";
import ApplicantDash from "./ApplicantDash";
import {
Accordion,
AccordionSummary,
AccordionDetails,
Typography,
} from "#material-ui/core";
import * as FcIcons from "react-icons/fc";
import ApplicantService from "../services/ApplicantService";
export default function AvailJobs() {
const [aplcntEmail, setAplcntEmail] = useState("aman#gmail.com"); //change to aplcntemail
const [isShow, setIsShow] = useState(false);
const [msg, setMsg] = useState([""]);
const [job, setJob] = useState([
{
jobTitle: "",
dateOfPosting: Date,
lastDateToApply: new Date().toLocaleDateString([], {
year: "numeric",
month: "long",
day: "numeric",
}),
preferableSkills: [],
requiredExp: 0,
recruiterEmail: "",
companyName: "",
companyAddress: "",
},
]);
useEffect(() => {
const data = ApplicantService.getAllJobs()
.then((response) => {
console.log(response.data);
setJob(response.data);
})
.catch((error) => {
alert(error.response.data);
});
}, []);
const onApplyButton = (item,key) => {
const data2 = ApplicantService.applyForJob(aplcntEmail, item)
.then((response) => {
console.log(response.data);
setIsShow(true);
setMsg(response.data)
})
.catch((error) => {
setIsShow(true);
setMsg(error.response.data);
});
};
return (
<div>
<ApplicantDash />
<div className="container bg-light">
<div className="card-bodies">
<section className="mb-4">
<h2 className="h1-responsive font-weight-bold text-center my-4">
All Available jobs
</h2>
</section>
{job.map((item, key) => (
<>
<Accordion key={key}>
<AccordionSummary
expandIcon={<FcIcons.FcExpand />}
aria-controls="panel1a-content"
id="panel1a-header"
className="Accordian"
>
<Typography>
<div className="d-flex p-1 justify-content-evenly">
<div className="p-1">
<b> Job: </b> {item.jobTitle}
</div>
<div className="p-2"></div>
<div className="p-1">
<b> Company: </b> {item.companyName}
</div>
<div className="p-2"></div>
<div className="p-1">
<b> Last Date: </b> {item.lastDateToApply}
</div>
</div>
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
<div className="container">
<table class="table table-borderless">
<tbody>
<tr>
<td>JOB TITLE</td>
<td>:</td>
<td>
<b>{item.jobTitle}</b>
</td>
</tr>
<tr>
<td>Company</td>
<td>:</td>
<td>
<b>{item.companyName}</b>
</td>
</tr>
<tr>
<td>Address</td>
<td>:</td>
<td>
<b>{item.companyAddress}</b>
</td>
</tr>
<tr>
<td>Last Date to Apply</td>
<td>:</td>
<td>
<b>{item.lastDateToApply}</b>
</td>
</tr>
<tr>
<td>Experience</td>
<td>:</td>
<td>
<b>{item.requiredExp}</b>
</td>
</tr>
<tr>
<td> Skills </td>
<td>:</td>
<td>
<table className="table table-condensed w-auto table-borderless table-hover">
{item.preferableSkills.map((S, index1) => {
return (
<tbody key={index1}>
<td scope="col">
{index1 + 1}.<b>{S}</b>
</td>
</tbody>
);
})}
</table>
</td>
</tr>
<tr>
<td></td>
<td></td>
<td>
<button
type="button"
class="btn btn-primary"
onClick={() => onApplyButton(item,key)}
>
Apply for the job{" "}
</button>
</td>
</tr>
</tbody>
{isShow && <>
{msg}
</>}
</table>
</div>
</Typography>
</AccordionDetails>
</Accordion>
</>
))}
</div>
</div>
</div>
);
}
Now when I click on Apply for this job button. The message I get from backend prints only to Active accordion
Here some pictures which might help.
enter image description here
As you can see the response from backend is prints in the both of the accordion
Issue
The issue here is that you've a single boolean isShow state and a single msg state, and all the accordion detail sections use the same single isShow state to conditionally render the msg state.
Solution
A simple solution would be to store the id, or title, or index, of the accordion to show the message of.
Example:
export default function AvailJobs() {
...
const [isShow, setIsShow] = useState({}); // <-- initially empty object
...
const onApplyButton = (item, key) => {
ApplicantService.applyForJob(aplcntEmail, item)
.then((response) => {
console.log(response.data);
setMsg(response.data);
})
.catch((error) => {
setMsg(error.response.data);
})
.finally(() => {
setIsShow(show => ({
...show,
[key]: true // <-- set true the specific key
}));
});
};
return (
<div>
...
{job.map((item, key) => (
<Accordion key={key}>
...
<AccordionDetails>
<Typography>
<div className="container">
<table class="table table-borderless">
<tbody>
...
<tr>
...
<td>
<button
type="button"
class="btn btn-primary"
onClick={() => onApplyButton(item, key)}
>
Apply for the job
</button>
</td>
</tr>
</tbody>
{isShow[key] && <>{msg}</>} // <-- check if isShow[key] is truthy
</table>
</div>
</Typography>
</AccordionDetails>
</Accordion>
))}
...
</div>
);
}

Getting Error TypeError: Cannot read property 'rows' of null

I am fairly new to react and I am trying add a row in my react app on the click of a button. I followed this link How to add and remove table rows Dynamically in React.js
to do so but I am not able to translate it to my code.
My code here:
KPIDetails.js
Here I am rendering the view in KPI Details.js file.
<MuiThemeProvider>
<React.Fragment>
<Grid container>
<Grid item xs={6} direction="row" alignItems="center">
<table
className="table table-bordered table-hover"
id="tab_logic"
>
<thead>
<tr>
<th className="text-center"> KPI</th>
<th className="text-center"> UOM</th>
<th className="text-center"> Base</th>
<th className="text-center"> Target</th>
<th className="text-center"> Target Date</th>
</tr>
</thead>
<tbody>
{this.state.rows.map((item, idx) => (
<tr id="addr0" key={idx}>
<td>{idx}</td>
<td>
<input
type="text"
name="Kpi_Before"
value={this.state.rows[idx].Kpi_Before}
onChange={this.handleChangeRows(idx)}
className="form-control"
/>
</td>
<td>
<input
type="text"
name="UOM_Before"
value={this.state.rows[idx].UOM_Before}
onChange={this.handleChangeRows(idx)}
className="form-control"
/>
</td>
<td>
<input
type="text"
name="Base_Before"
value={this.state.rows[idx].Base_Before}
onChange={this.handleChangeRows(idx)}
className="form-control"
/>
</td>
<td>
<input
type="text"
name="Target_Before"
value={this.state.rows[idx].Target_Before}
onChange={this.handleChangeRows(idx)}
className="form-control"
/>
</td>
<td>
<input
type="text"
name="Target_Before"
value={this.state.rows[idx].dateTime}
onChange={this.handleChangeRows(idx)}
className="form-control"
/>
</td>
</tr>
))}
</tbody>
</table>
<button
onClick={this.handleRemoveRow}
className="pull-right btn btn-default"
>
Delete Row
</button>
<Button
variant="outlined"
color="primary"
onClick={this.handleAddRow}
size="small"
style={styles.button}
>
+
</Button>
</Grid>
</Grid>
< /React.Fragment>
</MuiThemeProvider>
This is the js file where I call all the functions
User Form.js
export class UserForm extends Component {
state = {
step: 1,
Title: "",
Details: "",
What: "",
Why: "",
How: "",
Status: "",
Cost: "",
Benefits: "",
Kpi_Before: "",
Kpi_After: "",
Time: "",
UOM_Before: "",
Base_Before: "",
Target_Before: "",
dateTime: null,
rows: ["row1"]
};
//1
handleChangeRows = idx => e => {
const {Kpi_Before, value} = e.target;
const rows = [...this.state.rows];
rows[idx] = {
[Kpi_Before]: value
};
this.setState({
rows
});
};
//2
handleAddRow = () => {
const item = {
KPI_Before: "",
UOM_Before: "",
Base_Before: "",
Target_Before: "",
dateTime: ""
};
this.setState({
rows: [...this.state.rows, item]
});
};
//3
handleRemoveRow = () => {
this.setState({
rows: this.state.rows.slice(0, -1)
});
};
}
What am I doing wrong. Is there any other way to do it?
I have added:
state = {
rows: []
};
in the component and it solved the issue

Too many re-renders. React limits the number of renders to prevent an infinite loop. Updating state of a functional component inside the render method

Every time I display the cart items I want to update the total price and count of the items using the useState function inside the render method. But, immediately after the UI is rendered I get a react error mentioned above.
Is there a better way of doing what I'm trying to achieve without getting the error?
const Cart = () => {
const cartItems = useItems()
const firebase = useFirebase()
//Items count
//Total amount of the items
let [total, updateTotal] = useState(0)
let [count, updateCount] = useState(1)
//Method to add items to the cart
useEffect(() => {
if (!firebase) return
}, [firebase, cartItems])
return (
<Layout>
<SEO title="Cart" />
<Navbar count={count} />
<MDBContainer>
<MDBCol lg="12" className="">
<MDBTable responsive className="mt-5 z-depth-1">
<MDBTableHead>
<tr className="bg-light">
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Product
</div>
</th>
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Price
</div>
</th>
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Quantity
</div>
</th>
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Remove
</div>
</th>
</tr>
</MDBTableHead>
<MDBTableBody id="products-list">
{cartItems.map(product => {
updateTotal((total += product.price))
updateCount((count += 1))
return (
<tr>
<td class="px-3 font-weight-normal">
{product.name} <span class="d-none">{product.id}</span>{" "}
</td>
<td width="10%" class="text-center font-weight-normal">
{product.price}
<span>/kg </span>{" "}
</td>
<td width="10%" class="text-center font-weight-normal">
{product.count}
</td>
<td width="10%" class="text-center">
<div class="px-3 font-weight-normal">
{" "}
<button
class="bg-transparent border-0"
id="delete-button"
>
<i class="fa fa-trash-alt delete-icon"></i>
</button>
</div>
</td>
</tr>
)
})}
</MDBTableBody>
<MDBTableFoot>
<tr>
<td className="px-3 text-uppercase font-weight-bold">Total</td>
<td className="font-weight-bold px-5">₹{total}</td>
<td className="font-weight-bold pr-2 text-center">{count}</td>
</tr>
</MDBTableFoot>
</MDBTable>
</MDBCol>
</MDBContainer>
</Layout>
)
}
export default Cart
use blank [] instead of [firebase, cartItems]
The below code should work for you:
const cartItems = useItems();
// I assume this gives you cartItems.
let [total, updateTotal] = useState(() => {
if (cartItems) {
// item object {id: "2", name: "Cucumber", category: "vegetable", price: 50, // //count: 0}
return cartItems.reduce((acc, elem) => {
acc += elem.price * elem.count;
return acc;
}, 0);
}
return 0;
});
let [count, updateCount] = useState(cartItems.length);
React.useEffect(() => {
if (cartItems) {
updateCount(cartItems.length);
// item object {id: "2", name: "Cucumber", category: "vegetable", price: 50, // //count: 0}
const total = cartItems.reduce((acc, elem) => {
acc += elem.price * elem.count;
return acc;
}, 0);
updateTotal(total);
}
}, [cartItems]);
So, basically you need to initiate the state once you get some value from the useItems and then also need to update it when cartItems reference changes
Putting [firebase, cartItems] tells react to always re-render if any change is made to these two parametres. So that is why there are too many re-renders.

Creating multiple page application using react

I have created SPA using react and hence my address bar does not change when I move to another view.Kindly help in changing the address bar. News feed Code is
import React from 'react';
import axios from 'axios';
import Stories from './Stories';
export default class NewsFeed extends React.Component {
constructor(props) {
super(props);
this.state = { feed: [], showPopUp: false, showStoryPopUp: false, readArr: [], importantArr: [], counterArr: [], deleteArr: [] };
this.handleClose = this.handleClose.bind(this);
this.handleCreateFeed = this.handleCreateFeed.bind(this);
this.handlePost = this.handlePost.bind(this);
this.handleCreateStories = this.handleCreateStories.bind(this);
}
componentWillMount() {
let self = this;
axios.get('src/rest/feed.json')
.then(function (response) {
let counterArr = self.state.counterArr;
let readArr = self.state.readArr;
let deleteArr = self.state.deleteArr;
for (let item of response.data) {
counterArr.push(0);
readArr.push(false);
deleteArr.push(false);
}
self.setState({ feed: response.data });
})
.catch(function (error) {
console.log(error);
});
}
changeImportant(index) {
let arr = this.state.importantArr;
arr[parseInt(index)] = !arr[parseInt(index)];
this.setState({ importantArr: arr });
}
changeReadFlag(index) {
let arr = this.state.readArr;
arr[parseInt(index)] = !arr[parseInt(index)];
this.setState({ readArr: arr });
}
decrement(index) {
let arr = this.state.counterArr;
arr[parseInt(index)] = arr[parseInt(index)] - 1;
this.setState({ counterArr: arr });
}
handleDelete(index) {
let arr = this.state.deleteArr;
arr[parseInt(index)] = !arr[parseInt(index)];
this.setState({ deleteArr: arr });
}
increment(index) {
let arr = this.state.counterArr;
arr[parseInt(index)] = arr[parseInt(index)] + 1;
this.setState({ counterArr: arr });
}
handlePost(header, description,broker) {
// console.log(document.querySelector("#title-input").value,document.querySelector("#description-input").value)
let tempObj = { imgsrc: "images1.jpg" };
// tempObj.header = document.querySelector("#title-input").value;
// tempObj.description = document.querySelector("#description-input").value;
tempObj.header = header;
tempObj.description = description;
tempObj.broker = broker;
let tempArr = this.state.feed;
tempArr.push(tempObj);
let counterArr = this.state.counterArr.push(0);
this.setState({ showStoryPopUp: false });
}
handleClose() {
this.setState({ showPopUp: false, showStoryPopUp: false })
}
handleCreateFeed() {
this.setState({ showPopUp: true });
}
handleCreateStories() {
this.setState({ showStoryPopUp: true });
}
render() {
return (
<div className="email-class lis-cls">
{this.state.showStoryPopUp ? <Stories handlePost={this.handlePost} handleClose={this.handleClose} /> : null}
{this.props.userType === 'admin' ? <button id="story-btn" type="button" className="btn btn-primary fixed-cls fa fa-pencil" style={{ display: this.state.showPopUp || this.state.showStoryPopUp ? 'none' : 'inline-block' }} onClick={this.handleCreateStories} > Create Stories</button> : null}
{this.state.feed.map((feed, index) => {
return (<div key={index} id={index} className={this.state.showPopUp || this.state.showStoryPopUp ? 'row row-feed hide-cls' : this.state.deleteArr[index] ? 'row row-feed hide-cls' : 'row row-feed'} >
<h4 className="list-header">{feed.header}</h4>
<img src={"src/img/" + feed.imgsrc} alt="Smiley face" height="100" width="100" />
<span className="feed-text">{feed.description}</span>
<div className="row">
<div className="col-md-1">
<span className={this.state.readArr[index] ? "fa fa-check-circle pull-right" : ""}></span>
</div>
<div className="col-md-1">
<div className="pull-right">
<div className="fa fa-arrow-up display-block-cls" onClick={this.increment.bind(this, index)}></div>
<div className={this.state.counterArr[index] == 0 ? "vote-cls" : this.state.counterArr[index] > 0 ? "vote-cls upvote" : "vote-cls downvote"}>
{this.state.counterArr[index]}
</div>
<div className="fa fa-arrow-down display-block-cls" onClick={this.decrement.bind(this, index)}> </div>
</div>
</div>
<div className="col-md-8">
<button id="read" className="btn btn-success fa fa-pencil" onClick={this.changeReadFlag.bind(this, index)}> Read </button>
<button id="delete" className="btn btn-danger fa fa-trash-o" onClick={this.handleDelete.bind(this, index)}> Delete </button>
<button className="btn btn-primary fa fa-exclamation" onClick={this.changeImportant.bind(this, index)}> Important </button>
</div>
</div>
<hr className={this.state.importantArr[index] ? 'imp-cls' : 'hr-cls'} />
</div>)
})}
</div>
);
}
}
stoies code is
import React from 'react';
export default class Stories extends React.Component {
constructor(props) {
super(props);
this.handleClose = this.handleClose.bind(this);
this.handlePost = this.handlePost.bind(this);
}
handleClose() {
this.props.handleClose();
}
handlePost(){
let header = document.querySelector("#title-input").value;
let description = document.querySelector("#description-story-input").value;
let broker = document.querySelector("#broker-input").value;
this.props.handlePost(header,description,broker);
}
render() {
return (<div className={'popup-cls add-story-top'}>
<div className={'add-story email-class'}>
<img onClick={this.handleClose} className="cross-cls" src="src/img/cross.png" alt="Smiley face" height="35" width="35" />
<h4 className="list-header">Create Stories</h4>
<table>
<tbody>
<tr>
<td>
Title
</td>
<td>
<input type="text" id="title-input" />
</td>
</tr>
<tr>
<td>
Description </td>
<td> <textarea rows="4" cols="50" id="description-story-input"></textarea>
</td>
</tr>
<tr>
<td>
Broker </td>
<td> <input type="text" id="broker-input" />
</td>
</tr>
<tr>
<td>
Ticker </td>
<td> <input type="text" id="ticker-input" />
</td>
</tr>
<tr>
<td>
Category </td>
<td> <input type="text" id="category-input" />
</td>
</tr>
<tr>
<td>
Direction </td>
<td> <input type="number" min="-2" max="2" defaultValue="0" id="direction-input" />
</td>
</tr>
<tr>
<td>
Rating </td>
<td> <input type="number" min="-5" max="5" defaultValue="0" id="rating-input" />
</td>
</tr>
<tr>
<td>
Score </td>
<td> <input type="number" min="-4" max="4" defaultValue="0" id="score-input" />
</td>
</tr>
<tr>
<td>
Trade Price </td>
<td> <input type="text" id="trade-price-input" />
</td>
</tr>
<tr>
<td>
Pre Trade Price </td>
<td> <input type="text" id="pre-trade-input" />
</td>
</tr>
<tr>
<td>
Attachment </td>
<td> <input type="file" id="attachment-input" />
</td>
</tr>
<tr>
<td>
Links </td>
<td> <input type="text" id="links-input" />
</td>
</tr>
<tr>
<td>
Percentage Change </td>
<td> <input type="text" id="percentage-change-input" />
</td>
</tr>
<tr>
<td>
Tags </td>
<td> <input type="text" id="tag-input" />
</td>
</tr>
<tr>
<td></td>
<td><button onClick={this.handlePost} id="post-btn" type="button" className="btn btn-primary fa fa-envelope" > Post</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>);
}
}
login code is
import React from 'react';
import axios from 'axios';
export default class Login extends React.Component {
constructor(props) {
super(props);
this.state = { login: false, userName: '', password: '' ,isValid : true};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangeUserName = this.handleChangeUserName.bind(this);
this.handleChangePassword = this.handleChangePassword.bind(this);
}
handleSubmit() {
let self = this;
axios.get('src/rest/login.json')
.then(function (response) {
response.data.map((user)=>{
if(user.userName === self.state.userName && user.password === self.state.password ){
self.props.setLogin( true,user.role);
self.setState({isValid :true })
}else{
self.setState({isValid :false })
}
});
})
.catch(function (error) {
console.log(error);
});
}
handleChangeUserName(event){
this.setState({userName : event.target.value});
}
handleChangePassword(event){
this.setState({password : event.target.value});
}
render() {
return (
<div className="email-class email-class-div login-cls">
<div className="row row-login header-class">
<h3 className="float-class" >Login</h3>
<img src="src/img/star.png" className="float-class img-class" alt="Smiley face" height="35" width="35" />
</div>
<div className="error-div" style={{display : !this.state.isValid?'block':'none'}}>Invalid username or password</div>
<div className="row row-login androidTextbox">
<input className="col-md-6" type="text" placeholder="User ID/Email" onChange={this.handleChangeUserName}/>
</div>
<div className="row row-login androidTextbox">
<input className="col-md-6" type="password" placeholder="Password" onChange={this.handleChangePassword} />
</div>
<div className="row row-login submit-row" onClick={this.handleSubmit}>
<div id="button" >SUBMIT</div>
</div>
</div>
);
}
}
All three have same address.
I'm not sure how you are navigating without routes unless you are showing/hiding components. You could look into implementing the hash router

ngTable filter customization to a single inputText above the table

How do I use ngTable with a custom filter inputText, that is common to all the columns ? I know ngTable filter can be turned off to not show the inputText boxes above the column, but I want to have a single inputText placed above the table or outside the table, when any value entered, it will be filtered across all the columns.
Appreciate any help.
TIA.
Here is my code, I use a hidden column that consist of all the values across the row. This is not perfect.
$scope.ngTableData =[
{"colAB": "testA1testB1", "colA": "testA1", "colB": "testB1"},
{"colAB": "testA2testB2", "colA": "testA2", "colB": "testB2"},
{"colAB": "testA3testB3", "colA": "testA3", "colB": "testB3"},
{"colAB": "testA4testB4", "colA": "testA4", "colB": "testB4"},
{"colAB": "testA5testB5", "colA": "testA5", "colB": "testB5"},
{"colAB": "testA12testB12", "colA": "testA12", "colB": "testB12"},
{"colAB": "testA13testB13", "colA": "testA13", "colB": "testB13"},
{"colAB": "testA14testB14", "colA": "testA14", "colB": "testB14"}
] ;
$scope.filter= {
colAB: undefined
};
var dataArr = $scope.ngTableData.length;
$scope.userTable = new NgTableParams({
page: 1,
count: 5,
filter: $scope.filter
}, {
total: $scope.ngTableData.length,
getData: function ($defer, params) {
var orderedData = params.sorting() ?
$filter('orderBy')($scope.ngTableData, params.orderBy()) : $scope.ngTableData;
orderedData =
$filter('filter')(orderedData, params.filter());
params.total(orderedData.length);
defer.resolve(
orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count())
);
}
});
<label>Search: </label>
<input ng-model="filter.colAB">
<table ng-table="userTable" show-filter="false" class="table table-striped">
<tr ng-repeat="user in $data ">
<td data-title="'Col A'" sortable="'colA'" filter='{ "colA": "text" }'>
{{user.colA}}
</td>
<td data-title="'Col B'" sortable="'colB'" filter="{ 'colB': 'text' }">
{{user.colB}}
</td>
<td filter='{ "colAB": "text" }' ng-if="false">
{{user.colAB}}
</td>
</tr>
</table>
You can supply an object with a property named $ with the value of your global filter to the method filter() of the instance of NgTableParams. Look at the example snippet below:
var app = angular.module("myApp", ["ngTable"]);
app.controller('DemoCtrl', function($scope, NgTableParams) {
var data = [
{name:'Ana', age:12, money:38945, country:'pan'},
{name:'Ric', age:34, money:34945, country:'cr'},
{name:'Rob', age:76, money:34845, country:'col'},
{name:'Stu', age:23, money:34895, country:'ven'},
{name:'Amy', age:22, money:34894, country:'usa'},
{name:'Jay', age:77, money:34895, country:'mex'}
];
$scope.tableParams = new NgTableParams({}, {dataset: data});
// For use with global search button
$scope.makeGlobalSearch = function() {
var term = $scope.globalSearchTerm;
/** L##K HERE **/
$scope.tableParams.filter({ $: term });
};
// Watcher for global search without button
$scope.$watch('globalSearchTermAuto', function(newTerm, oldTerm) {
/** L##K HERE **/
$scope.tableParams.filter({ $: newTerm });
}, true);
});
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://rawgit.com/esvit/ng-table/master/dist/ng-table.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://rawgit.com/esvit/ng-table/master/dist/ng-table.min.js"></script>
<div ng-app="myApp" class="container-fluid">
<div class="row" ng-controller="DemoCtrl">
<div class="col-xs-12">
<!-- GLOBAL FILTER -->
<form name="searchForm" novalidate>
<h3>Global search with button</h3>
<div class="input-group">
<input ng-model="globalSearchTerm"
type="text"
class="form-control"
placeholder="Search term"
name="searchTerm"
required />
<span class="input-group-btn">
<button ng-click="makeGlobalSearch()"
class="btn btn-default"
ng-disabled="demo.searchForm.$invalid">
<span class="glyphicon glyphicon-search"></span>
</button>
</span>
</div>
<h3>Global search without button</h3>
<div>
<input ng-model="globalSearchTermAuto"
type="text"
class="form-control"
placeholder="Search term"
name="searchTerm"
required />
</div>
</form>
<!-- NG TABLE -->
<h3>ngTable directive</h3>
<table ng-table="tableParams" class="table table-condensed table-bordered table-striped">
<tr ng-repeat="row in $data">
<td data-title="'Name'" filter="{name: 'text'}">{{row.name}}</td>
<td data-title="'Age'" filter="{age: 'number'}">{{row.age}}</td>
<td data-title="'Money'" filter="{money: 'number'}">{{row.money}}</td>
<td data-title="'Country'" filter="{country: 'text'}">{{row.country}}</td>
</tr>
</table>
</div>
</div>
</div>

Resources