I have an application that loads up sizes and stock quantities for particular items, based on user input. Right now, I have two tables loading for each product, one for out of stock items and their sizes and one for in stock items and their sizes. I would like to be able to sort the In Stock table ascending/descending based on the quantity each size has.
So this picture below kinda shows the output as I have it right now. Two tables, one of them should have sorting capabilities.
My approach to this was to create an InStockTable component, where the sorting logic occurs. I'm using this codepen for reference. The difference between that one and mine is that I'm passing the table data as props to the component, rather than having it hardcoded in the component state.
This is the code for the component that handles each product:
class Product extends React.Component {
constructor(props) {
super(props);
}
renderRow(row) { // this function is for the rows of the out of stock table. These need not to be sorted.
return (
<tr key="row.size_id">
<td>{row.quantity}</td>
<td>{row.size_text}</td>
</tr>
);
}
renderSortableRow(row) { // this is for the rows that need to be sorted
var row = {
"quantity": row.quantity,
"size": row.size_text
}
return row;
}
renderProductDetails(product) {
const inStockProdStr = [];
const outStockProdStr = [];
if (product.length) { // product is an array
product.forEach(p => {
if (p.quantity == 0) {
outStockProdStr.push(this.renderRow(p))
}
else {
inStockProdStr.push(this.renderSortableRow(p))
}
});
}
else { // product is an object
if (typeof product == 'object') {
Object.keys(product).forEach(id => {
if (product[id].quantity == '0') {
outStockProdStr.push(this.renderRow(product[id]))
}
else {
inStockProdStr.push(this.renderSortableRow(product[id]))
}
}
);
}
}
return (
<div className={'product-tables'}>
<InStockTable stockArr={inStockProdStr} />
<div className={'out-stock'}>
<h5>Out Of stock</h5>
<table className={'table'}>
<thead className="thead-dark">
<tr>
<th scope="col">Qty</th>
<th scope="col">Size</th>
</tr>
</thead>
<tbody>
{outStockProdStr.map(p => p)}
</tbody>
</table>
</div>
</div >
);
}
render() {
return (
<div id="product-container">
<div id="product-info">
<h2>{this.props.productId}</h2>
<div id="product-line-detail">
{
this.renderProductDetails(this.props.prodStock)
}
</div>
</div>
</div>
);
}
}
This is the code for the In Stock table component:
class InStockTable extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className={'in-stock'}>
<h5>In stock</h5>
<table className={'table'}>
<thead className="thead-dark">
<tr>
<th scope="col">Qty</th>
<th scope="col">Size</th>
</tr>
</thead>
<tbody>
{
this.props.stockArr.map((s) => (
<tr key={s.simple_id}>
<td>{s.quantity}</td>
<td>{s.size_text}</td>
</tr>
))
}
</tbody>
</table>
</div>
);
}
}
If all this worked, I should be able to create a sorting function inside the inStock table component. However, I'm getting the following error:
Error: Objects are not valid as a React child (found: object with keys {quantity, size}). If you meant to render a collection of children, use an array instead.
in tbody (created by Product)
in table (created by Product)
in div (created by Product)
in div (created by Product)
in div (created by Product)
in div (created by Product)
in div (created by Product)
in Product (created by ProductList)
in ProductList (created by RequestForm)
in div (created by RequestForm)
in div (created by RequestForm)
in RequestForm
react-dom.development.js:59:15
The above error occurred in the <tbody> component:
in tbody (created by Product)
in table (created by Product)
in div (created by Product)
in div (created by Product)
in div (created by Product)
in div (created by Product)
in div (created by Product)
in Product (created by ProductList)
in ProductList (created by RequestForm)
in div (created by RequestForm)
in div (created by RequestForm)
in RequestForm
Any ideas? Is this the right way to sort?
The error just means that you are trying to render an object, as the p in {inStockProdStr.map(p => p)} is
{
"quantity": row.quantity,
"size": row.size_text
}
Change it to similar to what you did below (change { to ( so that the map function is actually returning the element):
{inStockProdStr.map((p) => (
<tr>
<td>{p.quantity}</td>
<td>{p.size}</td>
</tr>
))}
Silly mistake here. I was trying to display my InStockArr object in the OutStockArray area. The code now acts as expected and I've fixed the top to reflect.
Related
I have a reactjs project that uses react route version 4 and react16.3 . I can navigate to page http://localhost:8000/#/my-list where it will take you to MyList Component , on my list component there is a link to view a single list which behaves like a SPA by changing the state as below .
import React,{Component} from 'react'
class MyList extends Component{
constructor(props){
super(props)
this.state={
canViewItem:false
}
this.viewItem=this.viewItem.bind(this)
}
viewItem(){
this.setState({canViewItem:true})
}
renderDisplay() {
const {canViewItem}=this.state
if(canViewOrder){
return <canViewItem cancel={this.cancel} item={item} />
}
else {
return this.renderMyList()
}
}
renderMyList(){
return(
<table>
<thead>
<tr>
<th>Iten Name</th>
<th>Quantity</th>
<th>More Details</th>
</tr>
</thead>
<tbody>
<tr>
<td>Item 1</td>
<td>10</td>
<td>onClick={() => this.viewItem(item1Obj)}</td>
</tr>
<tr>
<td>Item 2</td>
<td>30</td>
<td>onClick={() => this.viewItem(item2Obj)}</td>
</tr>
</tbody>
</table>
}
render(){
return this.renderDisplay()
}
}
How do i share a link so as it takes me direct to an item page ?
Inside of componentWillMount() evaluate what is being passed in this.props.location.pathname. You should be able to grab the ID # from your URL and update your state.
I don't see how you are determining in your existing code which item they are viewing.
componentWillMount() {
let id = '';
console.log(this.props.location.pathname);
// write a JS function to parse the ID from the URL here and save to 'id'
// then if you have an ID, save whatever you need to state.
if(id !== '') {
this.setState({
canViewItem: true
});
}
}
As a side note, renderDisplay and renderMyList should be included in your constructor.
tl;dr:
Given a VueJS VNode object, how do I get the HTML element that would be generated if it were rendered?
e.g.:
> temp1
VNode {tag: "h1", data: undefined, children: Array(1), text: undefined, elm: undefined, …}
> temp1.children[0]
VNode {tag: undefined, data: undefined, children: undefined, text: "Test", elm: undefined, …}
> doSomething(temp1)
<h1>Test</h1>
Goal
I'm attempting to build a small VueJS wrapper around the DataTables.net library.
To mimic the behavior of HTML tables in my markup, I want something like the following:
<datatable>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Salary</th>
</tr>
</thead>
<tbody>
<datatable-row v-for="person in people">
<td>{{ person.name }}</td>
<td>{{ person.age }}</td>
<td>{{ person.salary }}</td>
</datatable-row>
</tbody>
</datatable>
What I've done so far
I've started to implement this as follows:
DataTable.vue
<template>
<table ref="table" class="display table table-striped" cellspacing="0" width="100%">
<slot></slot>
</table>
</template>
<script>
/* global $ */
export default {
data: () => ({
instance: null
}),
mounted() {
this.instance = $(this.$refs.table).dataTable();
this.$el.addEventListener("dt.row_added", function(e) {
this.addRow(e.detail);
});
},
methods: {
addRow(row) {
// TODO <-----
console.log(row);
}
}
};
</script>
DataTableRow.vue
<script>
/* global CustomEvent */
export default {
mounted() {
this.$nextTick(() => {
this.$el.dispatchEvent(new CustomEvent("dt.row_added", {
bubbles: true,
detail: this.$slots.default.filter(col => col.tag === "td")
}));
});
},
render() { return ""; }
};
What this currently does:
When the page loads, the DataTable is initialized. So the column headers are properly formatted and I see "Showing 0 to 0 of 0 entries" in the bottom left
The CustomEvent is able to bubble up past the <tbody> and be caught by the DataTable element successfully (circumventing the limitation in VueJS that you can't listen to events on slots)
What this does not do:
Actually add the row
My event is giving me an array of VNode objects. There's one VNode per column in my row. The DataTables API has an addRow function which can be called like so:
this.instance.row.add(["col1", "col2", "col3"]);
In my case, I want the resultant element from the rendering of the VNode to be the elements in this array.
var elems = [];
for (var i = 0; i < row.length; i++)
elems[i] = compile(row[i]);
this.instance.row.add(elems);
Unfortunately this compile method eludes me. I tried skimming the VueJS documentation and I tried Googling it, but no dice. I tried manually passing the createElement function (the parameter passed to the render method) but this threw an error. How can I ask VueJS to render a VNode without injecting the result into the DOM?
I ran into the same issue wanting to do basically the same thing with a row details template for DataTables.net.
One solution could be to create a generic component that renders out a VNode and instantiate that programmatically. Here is how my setup for a dynamic detail row that I insert using datatable's row.child() API.
RenderNode.js
export default {
props: ['node'],
render(h, context) {
return this.node ? this.node : ''
}
}
Datatables.vue
Include the renderer component from above
import Vue from 'vue'
import nodeRenderer from './RenderNode'
Instantiate and mount the renderer to get the compiled HTML
// Assume we have `myVNode` and want its compiled HTML
const DetailConstructor = Vue.extend(nodeRenderer)
const detailRenderer = new DetailConstructor({
propsData: {
node: myVNode
}
})
detailRenderer.$mount()
// detailRenderer.$el is now a compiled DOM element
row.child(detailRenderer.$el).show()
You should define your components like with:
import {createApp} from 'vue';
import {defineAsyncComponent} from "vue";
createApp({
components: {
'top-bar': defineAsyncComponent(() => import('#Partials/top-bar'))
}
}).mount("#app")
This question already has answers here:
React 'cannot read property of undefined' when using map
(4 answers)
Closed 5 years ago.
I've just started looking basics of ReactJs. Following is my component to show list of buses. What I want exactly is I want to perform edit/delete operations over buses. But not able to pass busId of corresponding bus to my edit/delete methods.
Following is component code
import React, {Component} from "react";
import { withRouter } from 'react-router-dom'
import {Table,Badge, Label,FormGroup,Container, Row, Col, CardGroup, Card, CardBlock,CardHeader, Button, Input, InputGroup, InputGroupAddon} from "reactstrap";
import {appSettings} from '../../../../Utils/Util.js';
import Pagination from "react-js-pagination";
var axios = require('axios');
class BusList extends React.Component {
constructor(props) {
super(props);
this.state = {
busList:[]
};
this.loadBuses = this.loadBuses.bind(this);
}
componentWillMount() {
this.loadBuses();
}
loadBuses() {
var url = ‘my-api-complete-url-here’;
axios.get(url)
.then((result) => {
var key = 0;
var buses = result.data.map(function(bus,i){
return <tr key={key++}>
<td key={key++}>{bus.id}</td>
<td key={(key++)}>{bus.number}</td>
<td key={(key++)}>
<Button onClick={(e)=>this.editBus(e, bus.id)}>Edit</Button>
<Button onClick={(e)=>this.deleteBus(e, bus.id)}>Delete</Button>
</td>
</tr>
});
this.setState({busList: buses});
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
<div className="animated fadeIn">
<Table hover responsive striped>
<thead>
<tr>
<th>Sr #</th>
<th>Bus Number</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{this.state.busList}
</tbody>
</Table>
</div>
);
}
editBus(id, e) {
console.log(‘Edit - Bus Id = ' +id);
}
deleteBus(id, e) {
console.log('Delete - Bus Id = ' +id);
}
}
export default BusList;
But when tapped on edit button, I receive this error(
Screenshot)
you're accessing it in wrong order, you're passing (e)=>this.editBus(e, bus.id) and in function you've defined editBus(id, e)
moreover you need to bind(this) at the end of map function
var buses = result.data.map(function(bus,i){
return <tr key={key++}>
<td key={key++}>{bus.id}</td>
<td key={(key++)}>{bus.number}</td>
<td key={(key++)}>
<Button onClick={(e)=>this.editBus(e, bus.id)}>Edit</Button>
<Button onClick={(e)=>this.deleteBus(e, bus.id)}>Delete</Button>
</td>
</tr>
});
Also, you don't need to define key variable. Instead, use second argument i of map function as it is the index of array element.
updated
you need to change your map code with
var buses = result.data.map(function(bus,i){
return <tr key={key++}>
<td key={key++}>{bus.id}</td>
<td key={(key++)}>{bus.number}</td>
<td key={(key++)}>
<Button onClick={(e)=>this.editBus(e, bus.id)}>Edit</Button>
<Button onClick={(e)=>this.deleteBus(e, bus.id)}>Delete</Button>
</td>
</tr>
}.bind(this));
The .bind(this) in the last line does the trick.
I have started to get my hand-on reactjs. I have three components which are placed in separate files. mainPage.js list.js and tableDisplay.jsare the three different files. I have got few doubt after working with reactjs.
The output from mainPage.js is passed as inputs to the list.js, so here
mainPage.js is parent and list.js is the child(if wrong please correct
me).Likewise, list.js output is passed as input to the tableDisplay.js,my doubt here is, does list.js remain as child component or act as parent component for the list.js.
The output fetched in the list.js is a table from the DB. So Im trying to
display the same table in the tableDisplay.js page, for which Im storing the fetched data to be an object. But Im getting an error Objects are not valid as a React child (found: object with keys {Name, Emp.no, Designation, WorkOff}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of tableDispay. So what does this error mean and how to overcome this error ad display the table in the tableDisplay.js
list.js :
class list extends React.Component {
constructor(props) {
super(props);
this.state = {
content: false,
query : '' ,
};
this.clickEvent= this.clickEvent.bind(this);
}
onChange(e) {
this.setState({ query : e.target.value });
}
clickEvent(event) {
event.preventDefault();
this.setState({
content: true,
});
connectDataBase.query(this.state.query, (err, result) => {
var dataFetched = result;
this.setState({
htmlTable: dataFetched,
});
})
}
render() {
return (
<div>
<div id="listClass">
<label> SQL </label>
<br />
<textarea rows="4" cols="50" onChange={this.onChange.bind(this)} value={this.state.query}> Query </textarea>
</div>
<button onClick={this.clickEvent.bind(this)} > Run </button>
<div id="third" >
{this.state.content && <TableDisplay tableData={this.state.htmlTable}/>}
</div>
</div>
)
}
}
export default list;
tableDisplay.js:
class tableDisplay extends React.Component{
constructor(props) {
super(props);
this.state = {
};
}
render(){
return(
<div id="tableClass" >
<label> Result </label>
<br/>
{this.props.tableData.map((x,y)=>
<table key={y}>
{x}
</table>
)}
{this.props.tableData};
</div>
)
}
}
export default tableDisplay;
I think error is in this line:
{this.props.tableData};
tableData is an array (as you are using map on that), and we can't render any array/object directly inside JSX, remove that line it will work.
Note: You can convert the array to string by using join then you can print the data directly.
Use this:
{this.props.tableData.join(' ')};
I think there is a issue with table rendering also, inside table you are directly putting the {x}, but you need to use some td/tr to render data.
You need to define the htmlTable and content in state variable, initial value of content should be false and once you get the data update the content value to true.
Content of tableData is:
[{Name : 'Sam', Emp.No:'12809', Designation:'Engg.', WorkOff :'UK'}]
Use this to render table:
<table>
{tableData.map((obj,y) => {
return <tr key={obj['Emp.No']}>
{Object.keys(obj).map((x,y) => <td key={y}> {obj[x]} </td>)
</tr>
})}
</table>
I am having an issue trying to get a scrolling table to work in React JS. I am also using skeleton. I only mention this, just in case there is some kind of conflict with skeleton and scrolling tables that I don't know about.
I currently have the following React JS component:
HistoryContainer.jsx
import React from 'react';
import HistoryItem from './historyItem';
export default class HistoryContainer extends React.Component {
render(){
var self=this;
return (
<div>
<h6><strong>{'Service History'}</strong></h6>
<table>
<tbody styles={'height: 100px; overflow: scroll;'}>
{
self.props.historyItems.map(function(historyItem)
{
return (
<HistoryItem historyItem={historyItem}/>
)
})
}
</tbody>
</table>
</div>
);
} }
HistoryItem.jsx:
import React from 'react';
export default class HistoryItem extends React.Component{
convertDate(data)
{
var d= new Date(data);
return (d.getMonth()+1)+'\\'+d.getDate()+"\\"+ d.getFullYear();
}
render(){
if(this.props.historyItem.WorkPerformedSummary==='')
{
return null;
}
return(
<div className='row'>
<tr>
<td><strong>{this.props.historyItem.WorkPerformedSummary}</strong></td>
{ this.props.historyItem.CompletedDate ? <td>
{this.convertDate(this.props.historyItem.CompletedDate)}
</td>: <td> {'n/a'} </td> }
</tr>
</div>
);
}
}
So, my issue is, I can't get the table inside of the HistoryContainer.jsx to have a scrollbar, even with the styling in the <tbody>. Any ideas?
Thanks
You need to convert tbody into a block level elements in order for them to be scrollable. Try adding display: block; to tbody and thead.
Got it working. I had to change my code to this
<tbody style={{'height': '300px', 'overflow':'scroll', 'display': 'block'}}>
and also
<thead style={{'display': 'block'}}>