I'm following the thinking-in-react tutorial, and I know how to make the filter with displayed text, is there a way to change it so that the text is in a select / dropdown with a text filter?
I've been looking all over the net for a good tutorial but I can't seem to find one, I did find some modules but they are way too complex (I'm new to react).
Here is the javascript:
var ProductCategoryRow = React.createClass({
render: function() {
return (<tr><th colSpan="2">{this.props.category}</th></tr>);
}
});
var ProductRow = React.createClass({
render: function() {
var name = this.props.product.stocked ?
this.props.product.name :
<span style={{color: 'red'}}>
{this.props.product.name}
</span>;
return (
<tr>
<td>{name}</td>
<td>{this.props.product.price}</td>
</tr>
);
}
});
var ProductTable = React.createClass({
render: function() {
var rows = [];
var lastCategory = null;
this.props.products.forEach(function(product) {
if (product.category !== lastCategory) {
rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
}
rows.push(<ProductRow product={product} key={product.name} />);
lastCategory = product.category;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
});
var SearchBar = React.createClass({
render: function() {
return (
<form>
<input type="text" placeholder="Search..." />
<p>
<input type="checkbox" />
{' '}
Only show products in stock
</p>
</form>
);
}
});
var FilterableProductTable = React.createClass({
render: function() {
return (
<div>
<SearchBar />
<ProductTable products={this.props.products} />
</div>
);
}
});
var PRODUCTS = [
{category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
{category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
{category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
{category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
{category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
{category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
];
ReactDOM.render(
<FilterableProductTable products={PRODUCTS} />,
document.getElementById('container')
);
Related
I've got a React App, that scans products via a Scanner(using serialport.io + socket.io) and adds each scanned product into my frontend cart component.
Right now I got it working, but my solution creates a new row in my cart per product scanned as you can see here and I need it to display a new row only the first time a prod is scanned and then if the same product is detected it only updates the quantity and total per product and also the cart total, something like this...
From what I've searched the best way to do this would be by using react useContext and useReducer but I can't get it working.
This is my code on server side index.js:
io.on("connection", (socket) => {
console.log("Socket Connected");
const port = new SerialPort({
path: "COM6",
baudRate: 9600,
autoOpen: false,
});
socket.on("start_scanner", () => {
port.open(function (err) {
if (err) {
console.log("Error opening port: ", err.message);
}
else{
console.log("Scanner Connected");
}
});
port.on("open", function () {
setInterval(function () {
const portReader = port.read();
if (portReader != null) {
const sensorVal = Buffer.from(portReader).toString();
const soap = require("soap");
const url = "http://example.com?wsdl";
soap.createClient(url, function (err, client) {
client.GetProductById(
{
UserId: "1",
ProductId: sensorVal,
},
function (err, result) {
if (err) return console.log(err);
let productScanned = result.GetProductByIdResult;
socket.broadcast.emit("add_product_to_list", productScanned);
}
);
});
}
}, 700);
});
});
This is my Cart component code:
import { useState, useEffect } from "react";
import io from "socket.io-client";
import ProductRow from "./ProductRow";
import "./ProductsList.css";
const socket = io.connect("http://localhost:5000");
const ProductsList = (props) => {
const [scannedData, setScannedData] = useState([]);
useEffect(() => {
socket.on("add_product_to_list", (productScanned) => {
setScannedData((prevProducts) => [...prevProducts, productScanned]);
});
}, [socket]);
return (
<div className="w-9/12 h-full px-20 py-20 flex flex-col ">
<div className="w-full h-auto my-2 px-3 py-3 font-semibold grid grid-cols-3 bg-blue-600 rounded-xl">
<div className="w-[60%] text-left">Product</div>
<div className="w-[20%] text-left">Quant.</div>
<div className="w-[20%] text-left">Price</div>
</div>
<div
id="products-wrapper"
className="w-full h-[95%] flex flex-col overflow-x-hidden overflow-y-scroll"
>
{scannedData.map((productScanned) => (
<ProductRow data={productScanned} />
))}
</div>
<div className="w-full h-[15%] flex flex-row justify-end">
<div className="w-[20%] h-auto px-3 py-3 font-semibold flex flex-col justify-center bg-blue-600 rounded-xl ">
<div className="w-full text-left">
Total: {/* Total price amount */}{" "}
</div>
<div className="w-full text-left">
Qty: {0}
</div>
</div>
</div>
</div>
);
};
export default ProductsList;
This is my Cart Row component:
import "./ProductsList.css";
const ProductRow = ({ data }) => {
return (
<div
className="product-row w-full h-auto my-2 px-3 py-3 text-black text-center grid grid-cols-3 rounded-xl "
key={data._x003C_Id_x003E_k__BackingField}
>
<div className="w-[60%] text-left">
{data._x003C_Name_x003E_k__BackingField}
</div>
<div className="w-[20%] text-left">{1}</div>
<div className="w-[20%] text-left">
{parseFloat(data._x003C_Price_x003E_k__BackingField).toFixed(2)}€
</div>
</div>
);
};
export default ProductRow;
I've also got a cart-context.js and a CartProvider.js files which I was using to achieve my goal but can't get it to work.
/*cart-context.js*/
import React from "react";
const CartContext = React.createContext({
items: [],
totalAmount: 0,
addItem: (item) => {},
removeItem: (id) => {},
});
export default CartContext;
/*CartProvider.js*/
import { useContext, useReducer } from "react";
import CartContext from "./cart-context";
const defaultCartState = {
items: [],
totalAmount: 0,
};
const cartReducer = (state, action) => {
if (action.type === "ADD_ITEM") {
const updatedTotalAmount = state.totalAmount + action.item.price * action.item.amount;
const existingCartItemIndex = state.items.findIndex(
(item) => item.id === action.item.id
)
const existingCartItem = state.items[existingCartItemIndex];
let updatedItems;
if(existingCartItem){
const updatedItem = {
...existingCartItem,
amount: existingCartItem.amount + action.item.amount
}
updatedItems = [...state.items];
updatedItems[existingCartItemIndex] = updatedItem;
} else{
updatedItems = state.items.concat(action.item);
}
return {
items: updatedItems,
totalAmonut: updatedTotalAmount,
};
}
return defaultCartState;
};
const CartProvider = (props) => {
const [cartState, dispatchCartAction] = useReducer(
cartReducer,
defaultCartState
);
const addItemToCartHandler = (item) => {
dispatchCartAction({ type: "ADD_ITEM", item: item });
};
const removeItemFromCartHandler = (id) => {
dispatchCartAction({ type: "REMOVE_ITEM", id: id });
};
const cartContext = {
items: cartState.items,
totalAmonut: cartState.totalAmonut,
addItem: addItemToCartHandler,
removeItem: removeItemFromCartHandler,
};
return (
<CartContext.Provider value={cartContext}>
{props.children}
</CartContext.Provider>
);
};
export default CartProvider;
Could anyone help me out and help me understand my mistakes?
Thanks in advance.
After a search, I am sending the result to frontend in the form of array. But I am not being able to get that array in frontend using fetch. Through postman I am able to get the result from the backend but I am not able to get it in the frontend. In another file, I have set axios.post as well and exported from there and imported in frotend.
I am beginner so I might have written a bad code, any help will mean a lot.
In frontend :
class Hostel extends Component{
constructor (){
super();
this.state = {
country : '',
city : '',
category : '',
errors : {}
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({
errors: nextProps.errors
});
}
}
onChangeAddOptions = e => {
this.setState({ [e.target.id]: e.target.value });
};
addOption = e => {
e.preventDefault();
const newOption = {
country : this.state.country,
city : this.state.city,
category:this.state.category,
}
this.props.saveOptions(newOption,this.props.history);
};
getHostels = async ()=> {
console.log("getHostel function is called");
const response = await fetch('http://localhost:5000/api/users/hostel',{
method : "POST",
// headers:{
// "Content-Type" : "application/json"
// },
})
.then((response)=> {response.json()})
.then((data)=>{
console.log("inside data");
console.log(data);
})
.catch(e=>{
console.error(e.error);
})
console.log("From outside of data");
console.log(response);
}
componentDidMount(){
this.getHostels();
}
render (){
const {errors,country,city,category} = this.state;
return(
<section className="Hosteldashboard">
<div className="left_container">
<h2>Yo che left section</h2>
<div>
<form noValidate onSubmit={this.addOption}>
<div class="form-row">
<label htmlFor="country">Country</label> <br />
<input
type="text"
className="input-control"
placeholder="Country name"
id="country"
value={country}
onChange={this.onChangeAddOptions}
error={errors.country}
className={classnames('', {
invalid: errors.country
})}
/>{' '}
<br />
<span className="text-danger">{errors.country}</span>
</div>
<div class="form-row">
<label htmlFor="city">City</label> <br />
<input
type="text"
className="input-control"
placeholder="City name"
id="city"
value={city}
onChange={this.onChangeAddOptions}
error={errors.city}
className={classnames('', {
invalid: errors.city
})}
/>{' '}
<br />
<span className="text-danger">{errors.city}</span>
</div>
<div class="form-row">
<label htmlFor="category">Category</label> <br />
<input
type="text"
className="input-control"
placeholder="Boys or Girls"
id="category"
value={category}
onChange={this.onChangeAddOptions}
error={errors.category}
className={classnames('', {
invalid: errors.category
})}
/>{' '}
<br />
<span className="text-danger">{errors.category}</span>
</div>
<div>
<button type="submit" className = "searchHostel" onClick={this.getHostels}>
Search
</button>
</div>
</form>
</div>
</div>
In backend :
router.post('/hostel',async (req,res)=>{
try{
console.log(req.body);
const {
errors,
isValid
} = validateSearchHostelInput(req.body);
//Check Validation
// if (!isValid){
// return res.status(400).json(errors);
// }
const page = parseInt(req.query.page) - 1 || 0;
const limit = parseInt(req.query.limit) || 5;
const search = req.query.search || "";
let sort = req.query.sort || "price";
let category = req.query.category || "All";
const categoryOptions = [
req.body.country,
req.body.city,
req.body.category
]
category === "All"
? (category = [...categoryOptions])
: (category = req.query.category.split(","));
req.query.sort ? (sort = req.query.sort.split(",")) : (sort = [sort]);
let sortBy = {};
if(sort[1]) {
sortBy[sort[0]] = sort[1];
} else {
sortBy[sort[0]] = "asc";
}
const hostel = await Hostel.find({title: {$regex: search, $options: "i"}})
.where("category")
.in([...category])
.sort(sortBy)
.skip(page * limit)
.limit(limit);
// const total = await Hostel.countDocuments({
// category: {$in: [...category]},
// title: { $regex: search, $options: "i"},
// });
// const response = {
// error: false,
// total,
// page: page + 1,
// limit,
// categories: categoryOptions,
// hostel
//}
console.log("From Hostel : " + hostel);
res.status(200).json({hostel:hostel});
}catch(err){
console.log(err);
res.status(500).json({error:true,message:"Internal Server Error"});
}
});
module.exports = router;
I got the ngx-pagination module working with all the listings in the GET, but I want the pagination to work server-side too, but I'm unsure how to implement it further than what I have. I'm looking at the documentation for ngx-pagination, but I'm a little bit confused. Here's what I have.
html code
<body [ngClass]="[(this.isOpen && this.mobile) || (this.isOpen && this.tablet) ? 'hideContent' : 'showContent']">
<div class="loading">
<!-- <mat-spinner class="loader" *ngIf="isLoading"></mat-spinner> -->
<ngx-spinner id="loadingIcon" *ngIf="isLoading" type="cog" size="large" color="#3071a9">
<p class="loadingTitle">Loading...</p>
</ngx-spinner>
</div>
<div class="spacing"></div>
<div class="container">
<div class="row no-gutters"
*ngIf="!this.isOpen && this.mobile || this.isOpen && !this.mobile || !this.isOpen && !this.mobile">
<div class="class col-md-7"></div>
</div>
<!-- /|slice:0:show -->
<!--; let i = index-->
<div class="row"
*ngFor="let auction of posts | paginate: { itemsPerPage: 10, currentPage: p, totalItems: this.posts.count }">
<div class="col-md-12 col-centered">
<div class="listingCard" [#simpleFadeAnimation]="'in'">
<div class=container>
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-6">
<div id="title">{{auction.title}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<pagination-controls (pageChange)="p = $event"></pagination-controls>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
</head>
<body [ngClass]="[(this.isOpen && this.mobile) || (this.isOpen && this.tablet) ? 'hideContent' : 'showContent']">
<div class="loading">
<!-- <mat-spinner class="loader" *ngIf="isLoading"></mat-spinner> -->
<ngx-spinner id="loadingIcon" *ngIf="isLoading" type="cog" size="large" color="#3071a9">
<p class="loadingTitle">Loading...</p>
</ngx-spinner>
</div>
<div class="spacing"></div>
<div class="container">
<div class="row no-gutters"
*ngIf="!this.isOpen && this.mobile || this.isOpen && !this.mobile || !this.isOpen && !this.mobile">
<div class="class col-md-7"></div>
</div>
<!-- /|slice:0:show -->
<!--; let i = index-->
<div class="row"
*ngFor="let auction of posts | paginate: { itemsPerPage: 10, currentPage: p, totalItems: this.posts.count }">
<div class="col-md-12 col-centered">
<div class="listingCard" [#simpleFadeAnimation]="'in'">
<div class=container>
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-6">
<div id="title">{{listing.title}}</div>
</div>
</div>
</div>
=
</div>
</div>
</div>
</div>
<pagination-controls (pageChange)="p = $event"></pagination-controls>
</body>
.ts File component
p: number = 1;
ngOnInit(){
this.submitListingService.getListings(this.postsPerPage, this.currentPage);
this.listingService
.getPostUpdateListener()
.pipe(takeUntil(this.destroy))
.subscribe((postData: { listing: Listing[]; postCount: number }) => {
this.isLoading = false;
this.totalPosts = postData.postCount;
this.posts = postData.listing;
this.filteredPosts = postData.listing;
});
}
Angular service
getListings(postsPerPage: number, currentPage: number) {
let listings = "Get Listings";
const params = new HttpParams().set("listings", listings);
const queryParams = `?pagesize=${postsPerPage}&page=${currentPage}`;
this.http
.get<{ message: string; posts: any; maxPosts: number }>(
"http://localhost:3000/api/listings" + queryParams,
{ params }
)
.pipe(
map(postData => {
return {
posts: postData.posts.map(post => {
return {
title: post.title,
id: post._id
};
}),
maxPosts: postData.maxPosts
};
})
)
.pipe(takeUntil(this.destroy))
.subscribe(transformedPostData => {
this.posts = transformedPostData.posts;
this.postsUpdated.next({
listing: [...this.posts],
postCount: transformedPostData.maxPosts
});
});
}
-> Server Code
app.js
app.get("/api/listings", (req, res, next) => {
Post.find({ auctionEndDateTime: { $gte: Date.now() } })
.populate("creator", "username")
.then(documents => {
req.params.Id = mongoose.Types.ObjectId(req.params.Id);
res.status(200).json({
message: "Auction listings retrieved successfully!",
posts: documents
});
});
});
Here is another way to do this.
This might be a better fit for your case.
Post.find({ auctionEndDateTime: { $gte: Date.now() } })
.populate("creator", "username")
.then(documents => {
req.params.Id = mongoose.Types.ObjectId(req.params.Id);
let per_page = req.query.pagesize;
let page = req.query.page || 1;
let offset = (page - 1) * per_page;
res.status(200).json({
message: "Auction listings retrieved successfully!",
posts: documents.slice(offset).slice(0,
per_page)
});
});
and here is one more approach using slice
var skip = req.query.pagesize * (req.query.page - 1)
Post.where('auctionEndDateTime').gte(Date.now()).slice([skip, req.query.pagesize])
.populate("creator", "username")
.then(documents => {
res.status(200).json({
message: "Auction listings retrieved successfully!",
posts: documents
});
})
You will need two values on server side one is page i.e. which page number to show and second is limit i.e. how many results to show on the page.
Then just use:
.skip(limit * (page - 1)).limit(limit)
First operator will skip the unwanted results for eg: if page is 2 and limit is 20 then in first operation the first 20 results will be skipped then in second operation we will limit the result to 20 results so you will get documents 21 to 40 which is desired result.
Your Service File ,
getListings(postsPerPage: number, currentPage: number) {
// let listings = "Get Listings";
// const params = new HttpParams().set("listings", listings);
// const queryParams = `?pagesize=${postsPerPage}&page=${currentPage}`;
const queryParams = postsPerPage+"/"+currentPage; //change this .
this.http
.get<{ message: string; posts: any; maxPosts: number }>(
"http://localhost:3000/api/listings/" + queryParams
)
.pipe(
map(postData => {
return {
posts: postData.posts.map(post => {
return {
title: post.title,
id: post._id
};
}),
maxPosts: postData.maxPosts
};
})
)
.pipe(takeUntil(this.destroy))
.subscribe(transformedPostData => {
this.posts = transformedPostData.posts;
this.postsUpdated.next({
listing: [...this.posts],
postCount: transformedPostData.maxPosts
});
});
}
As you are sending parameters so you need to change the URL of API as below i mentioned in server code ,
Your Server code will be as follow ,
app.get("/api/listings/:postPerPage/:currentPage", (req, res, next) => {
let postPerPage = req.params.postPerPage;
let currentPage = req.params.currentPage;
Post.find({ auctionEndDateTime: { $gte: Date.now() } })
.populate("creator username")
.skip(postPerPage * currentPage)
.limit(postPerPage)
.then(documents => {
req.params.Id = mongoose.Types.ObjectId(req.params.Id);
res.status(200).json({
message: "Auction listings retrieved successfully!",
posts: documents
});
});
});
From the above code you will get the post equals to postPerPage .
Hope this is what you looking for . Thank you
I have created React.js Project with Golden Layout. As you see the image below it's possible to open up three other sub windows by clicking first "View Button", but when I click the second view button the data does not change, I am not able to find where I am going wrong.
First View
Second View
When trying to apply the content to the other three tabs, I get the above error.
App.js File
import React from 'react';
import './App.css';
import Applications from './components/applications';
const $ = window.$;
const App=() =>{
var config = {
content: [{
type: 'row',
content: [{
title: 'Parties',
type:'react-component',
component: 'Applications',
isClosable:false
}
]
}]
};
var myLayout = new window.GoldenLayout( config, $('#layoutContainer') );
myLayout.registerComponent( 'Applications', Applications);
myLayout.init();
return (
<div></div>
);
}
export default App;
Application.js
import React, { Component } from 'react';
import data from '../common'
import titlesparcels from './titlesparcels';
import Services from './Services';
import Document from './document';
import GoldenLayout from 'golden-layout';
let DataValue = [];
class Applications extends Component {
constructor(props){
super(props);
this.state = {
userData: ''
}
}
renderHeader(){
return(
<div >
<table style={{width: 100 + 'em'}} className="table table-striped">
<thead>
<tr>
<th>Application Id</th>
<th>agent</th>
<th>status</th>
<th>partyType</th>
<th>lodgedDate</th>
<th>View</th>
</tr>
</thead>
</table>
</div>
)
}
renderData(){
console.log("in")
DataValue = data.applications;
return DataValue.map((val,key)=>{
return(
<div key={val.id}>
<table className="table table-striped">
<tbody>
<tr>
<td>{val.general.applicationId}</td>
<td>{val.general.agent}</td>
<td>{val.general.status}</td>
<td>{val.general.partyType}</td>
<td>{val.general.lodgedDate}</td>
<td><button onClick={()=> this.showTble(val.id)} >View</button></td>
</tr>
</tbody>
</table>
</div>
)
});
}
showTble=(id)=> {
console.log("User :",this.props,"appId",id)
global.sendId = id;
this.setState({
userData: id
})
this.props.glEventHub._layoutManager.eventHub.emit('params','stateChanged' );
if(this.props.glEventHub._layoutManager){
let myLayout = this.props.glEventHub._layoutManager;
if(myLayout.root.contentItems[0].contentItems.length-1 >1){
this.forceUpdate()
}else{
var titleparcels = {
title: 'Titles & Parcels',
type: 'react-component',
component: 'titlesparcels',
isClosable:false,
props: {"id":id}
};
var services = {
title: 'Services',
type: 'react-component',
component: 'Services',
isClosable:false,
props: {"id":id}
};
try{
let window = 0;
myLayout.registerComponent( 'titlesparcels', titlesparcels);
myLayout.registerComponent( 'Services', Services);
myLayout.registerComponent( 'Document', Document);
myLayout.root.contentItems[0].addChild( titleparcels );
myLayout.root.contentItems[0].addChild( services );
data.applications.map((val,key)=>{
if(val.id === id){
val.documents.forEach(element => {
var document = {
title: 'Documents',
type: 'react-component',
component: 'Document',
isClosable:false,
props: {"doc":element.source}
};
if(window == 0){
console.log("window")
myLayout.root.contentItems[0].addChild( document );
window++;
}else{
window++;
console.log('data')
myLayout.root.contentItems[0].contentItems[3].addChild( document );
}
});
}
});
}catch(e){
alert (e)
console.log(e)
}
}else{
}
}
render() {
if(this.props.data){
let value = this.pro
console.log("value from userData",value)
}
return (
<div>
{this.renderHeader()}
{this.renderData()}
<titlesparcels userId={this.state.userData} />
</div>
);
}
}
export default Applications;
I created a simple app to search video using youtube-api, but when I use npm start it was not give me any errors but give me the warning Warning: Unknown proponItemSearchedon <searchItem> tag. Remove this prop from the element.
in searchItem (created by listItem)
in div (created by listItem)
in listItem
Here is my code:
var React = require('react');
var Item = require('./item.jsx');
var searchItem = React.createClass({
getInitialState : function() {
return {
'queryString' : ''
};
},
handleSearchClicked : function() {
this.props.onItemSearched(this.state);
this.setState({
'queryString' : ''
});
},
handleChangedNameItem : function(e) {
e.preventDefault();
this.setState({
'queryString' : e.target.value
});
},
render : function () {
return (
<div>
<label>
<input id="query" type="text" onChange={this.handleChangedNameItem} value={this.state.queryString} placeholder="Search videos..." />
<button id="search-button" onClick={this.handleSearchClicked}>Search</button>
</label>
</div>
);
}
});
And this is listItem what i show my results
var listItem = React.createClass({
getInitialState : function() {
return {
'results' : []
};
},
handleQuerySearch : function(query) {
var req = gapi.client.youtube.search.list({
'part': 'snippet',
'type': 'video',
'q' : encodeURIComponent(query).replace(/%20/g, "+"),
'order' : 'viewCount',
});
//execute request
req.execute((res) => {
var results = res.result;
this.setState({
'results' : results.items
});
});
},
render : function() {
var listItem = this.state.results.map( item => {
return(
<Item title={item.snippet.title} videoid={item.id.videoId} />
);
});
return (
<div>
<searchItem onItemSearched={this.handleQuerySearch} />
<div className="list-item">
{listItem}
</div>
</div>
);
}
});
module.exports = listItem;
React wants all components to be written in class format. Meaning the names need to be capitalized.
searchItem needs to be SearchItem
You can also define the props that will be received on search item
var SearchItem = React.createClass({
propTypes: {
onItemSearched: React.PropTypes.func
},
....
});