ReactJS Search input by multiple values - javascript

I have a search and select filters on my page. The issue that I am having is that I can't seem to make the search work with multiple json values.
Example value is { "id": "1", "role": "teacher", "subject": "mathematics", "name": "Jonathan Kovinski" } and I want to be able to use key and values.
I've tried using some other question about combining json key and value into a single array and passing it to the search filter but it didn't work.
text = data.filter(info => {
return Object.keys(info).map(function(key) {
var singleOne = JSON.stringify(info[key]);
console.log(info, "This is the json one")
}).toLowerCase().match(searchString);
});
Here is a link to a JS Fiddle that I've created with all of my code.
I am trying to set my search bar to use all keys and values for searching and sorting data.

i would suggest you put the filtered data in a seperate key in the state in case you want to revert to the original result,
use the Obeject.values instead of Object.keys and filter the data in the handleChange function,
here's a working code :
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
data: [],
searchString: "",
filtered: []
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.fetchData();
}
handleChange(e) {
var value = e.target.value;
this.setState({
searchString: value,
filtered: this.state.data.filter(e =>
Object.values(e)
.join(" ")
.toLowerCase()
.match(value)
)
});
}
fetchData() {
fetch("https://api.myjson.com/bins/lo3ls")
.then(response => response.json())
.then(json => {
this.setState({
isLoaded: true,
data: json,
filtered: json
});
})
.catch(error => console.log("parsing failed", error));
}
render() {
var { isLoaded, data } = this.state;
const searchString = this.state.searchString.trim().toLowerCase();
let text = this.state.data;
console.log(text);
if (searchString.length > 0) {
text = text.filter(info => {
return info.role.toLowerCase().match(searchString);
});
}
return (
<div>
<input
type="text"
id="searchbar"
value={this.state.searchString}
onChange={this.handleChange}
placeholder="Search"
name="device"
/>
<select className="category-select" name="categories" onChange={this.handleChange}>
{data.map(info => (
<option value={info.role}>{info.role}</option>
))}
</select>
{/* map through the filtered ones*/}
{this.state.filtered.map(info => (
<div className="display">
<span className="role">Role: {info.role}</span>
<span> Name: {info.name}</span>
<span>, Subject: {info.subject}</span>
</div>
))}
</div>
);
}
}
ReactDOM.render(<Hello name="World" />, document.getElementById("container"));

Actually, I read all of your code in Fiddle, But I proffer Fuse to you. Use it inside your code in componentDidMount and implement your search. it is very easy and handy.
const options = {
shouldSort: true,
threshold: 0.6,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [
"title",
"author.firstName"
]
};
const fuse = new Fuse(list, options); // "list" is the item array
const result = fuse.search(""); // put your string inside double quotation
The result is your answer.

Related

Question on adding a input value to an array using setState

I'm trying to use setState to update a state array in React using a basic HTML input.
The state I'm trying to update looks like:
"businessName": "",
"grossAnnualSales": 0,
"contactEmail": "",
"numEmployees": 0,
"annualPayroll": 0,
"locations": [{"zip": ""}],
"industryId": "",
I need to add a zip code inputted by the user in a React component to this object in the array.
So far I've tried this and it's not working, it just updates to a string and not an array:
updateZip(){
return e => this.setState({"locations": [{"zip": e.currentTarget.value}]})
}
The React component:
<input onChange={this.updateZip()} type="text" className="zip-input"/>
How can I update the state succesfully?
Try replacing your updateZip function with an arrow function.
updateZip = (e) => {
return e => this.setState({"locations": [{"zip": e.currentTarget.value}]}) }
Also
<input onChange={(e) => this.updateZip(e)} type="text" className="zip-input"/>
use e.target.value and pass onChange={this.updateZip}
class App extends Component {
state = {
locations: [{ zip: "" }]
};
updateZip = (e) => {
this.setState({ locations: [{ zip: e.target.value }] });
};
render() {
return (
<div>
<input onChange={this.updateZip} type="text" className="zip-input" />
<p>{this.state.locations[0].zip}</p>
</div>
);
}
}
export default App;
CodeSandBox

Mapping through state: (TypeError): this.state.[something].map is not a function

I'm trying to map through my state, where I have collected data from an external API.
However, when I do i get this error here:
TypeError: this.state.stocks.map is not a function
I want to render the results to the frontend, through a function so that the site is dynamic to the state.favorites.
Though the console.log(), I can see that the data is stored in the state.
I have found others with a similar issue, but the answers did not work out.
UPDATE:
I have changed the componentDidMount() and it now produces an array. The issue is that I get no render from the renderTableData() functions.
console.log shows this array:
0: {symbol: "ARVL", companyName: "Arrival", primaryExchange: "AESMSBCDA)LNKOS/ TLTE(N GAEQLGAR ", calculationPrice: "tops", open: 0, …}
1: {symbol: "TSLA", companyName: "Tesla Inc", primaryExchange: " RNK EAASGTDACLN)LE/OGMELAQSTB (S", calculationPrice: "tops", open: 0, …}
2: {symbol: "AAPL", companyName: "Apple Inc", primaryExchange: "AMTGS/C) AALGDRSTNLEOEL(S BAE NQK", calculationPrice: "tops", open: 0, …}
length: 3
__proto__: Array(0)
This is my code:
import React, { Component } from 'react'
import './Table.css';
class Table extends Component {
constructor (props) {
super(props);
this.state = {
favorites: ['aapl', 'arvl', 'tsla'],
stocks: []
};
}
componentDidMount() {
this.state.favorites.map((favorites, index) => {
fetch(`API`)
.then((response) => response.json())
.then(stockList => {
const stocksState = this.state.stocks;
const stockListValObj = stockList;
console.log(stocksState)
console.log(stockListValObj)
this.setState({
stocks: [
... stocksState.concat(stockListValObj)
]
}, () => { console.log(this.state.stocks);});
})
})
}
renderTableData() {
this.state.stocks.map((stocks, index) => {
const { companyName, symbol, latestPrice, changePercent, marketCap } = stocks //destructuring
return (
<div key={symbol} className='headers'>
<div className='first-value'>
<h4>{companyName}</h4>
<h4 className='symbol'>{symbol}</h4>
</div>
<div className='align-right'>
<h4>{latestPrice}</h4>
</div>
<div className='align-right'>
<h4 className='changePercent'>{changePercent}</h4>
</div>
<div className='align-right'>
<h4>{marketCap}</h4>
</div>
</div>
);
})
}
render() {
return (
<div className='table'>
<h1 id='title'>Companies</h1>
<div className='headers'>
<h4 className='align-right'></h4>
<h4 className='align-right'>Price</h4>
<h4 className='align-right'>+/-</h4>
<h4 className='align-right'>Market Cap</h4>
</div>
<div>
{this.renderTableData()}
</div>
</div>
)
}
}
export default Table;
You should always aim in making single state update, try reducing the state update to single update.
I suggest 2 solution:
Move the data fetch section into a separate function, update a temporary array variable return the variable at the end of the execution.
async dataFetch() {
const sampleData = this.state.stocks || [];
await this.state.favorites.forEach((favorites, index) => {
fetch(`API`)
.then((response) => response.json())
.then((stockList) => {
sampleData.push(stockList);
// const stocksState = this.state.stocks;
// const stockListValObj = stockList;
// console.log(stocksState);
// console.log(stockListValObj);
// this.setState({
// stocks: [
// ... stocksState.concat(stockListValObj)
// ]
// }, () => { console.log(this.state.stocks);});
});
});
return Promise.resolve(sampleData);
}
componentDidMount() {
this.dataFetch().then((stockValues) => {
this.setState({ stocks: stockValues });
});
}
Use Promise.all() this return a single array value which would be easier to update on to the stock state.
Suggestion : When not returning any values from the array try using Array.forEach instead of Array.map
Return keyword is missing in renderTableData
renderTableData() {
this.state.stocks.map((stocks, index) => { ...});
to
renderTableData() {
return this.state.stocks.map((stocks, index) => { ...});
I would say that this is happening because on the first render, the map looks into state.stock and it's an empty array. After the first render, the componentDidMount method is called fetching the data.
I would suggest to just wrap the map into a condition. If stock doesn't have any object, then return whatever you wish (null or a loader/spinner for example).
It's enough to add it like this for not returning anything in case the array is empty (it will be filled after the first render, but this is useful as well to return error message in case the fetch fails):
this.state.stocks.length > 0 && this.state.stocks.map((stocks, index) => {

Type the value into input when clicked on with React

I have a list and it returns the transaction ID's. Currently this list is not useful since you need to copy the transaction ID and paste it into the input.
How this list is rendered:
{this.state.all_transactions.map(tx => (
<li key={tx.key}>{tx}</li>
))}
componentDidMount() {
if (window.sessionStorage.getItem("wallet")) {
(async () => {
const arweave = Arweave.init();
var address = window.sessionStorage.getItem("WalletAddress");
const txids = await arweave.arql({
op: "and",
expr1: {
op: "equals",
expr1: "from",
expr2: address
},
expr2: {
op: "equals",
expr1: "App-Name",
expr2: "arshard"
}
});
console.log(txids);
this.setState({ all_transactions: txids });
})();
}
}
For example, when I clicked on the one transaction it should take it's place on the "Enter TX" input below. So users won't have to be copy and paste.
I tried adding onClick before the list item however it didn't work.
More code:
state = {
all_transactions: [],
value: ''
};
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({value: e.target.value});
window.sessionStorage.setItem("transactionID", e.target.value);
}
You could do something like
{this.state.all_transactions.map(tx => (
<li key={tx.key} onClick={() => this.setState({ value: tx })}>{tx}</li>
))}
Just guessing here what you want to set it too since you didn't share the whole code. You basically need to use the same state what you use for your text input.

Filter state in React without removing data

I'm trying to make a react component that can filter a list based on value chosen from a drop-down box. Since the setState removes all data from the array I can only filter once. How can I filter data and still keep the original state? I want to be able to do more then one search.
Array list:
state = {
tree: [
{
id: '1',
fileType: 'Document',
files: [
{
name: 'test1',
size: '64kb'
},
{
name: 'test2',
size: '94kb'
}
]
}, ..... and so on
I have 2 ways that I'm able to filter the component once with:
filterDoc = (selectedType) => {
//way #1
this.setState({ tree: this.state.tree.filter(item => item.fileType === selectedType) })
//way#2
const myItems = this.state.tree;
const newArray = myItems.filter(item => item.fileType === selectedType)
this.setState({
tree: newArray
})
}
Search component:
class SearchBar extends Component {
change = (e) => {
this.props.filterTree(e.target.value);
}
render() {
return (
<div className="col-sm-12" style={style}>
<input
className="col-sm-8"
type="text"
placeholder="Search..."
style={inputs}
/>
<select
className="col-sm-4"
style={inputs}
onChange={this.change}
>
<option value="All">All</option>
{this.props.docTypes.map((type) =>
<option
value={type.fileType}
key={type.fileType}>{type.fileType}
</option>)}
</select>
</div>
)
}
}
And some images just to get a visual on the problem.
Before filter:
After filter, everything that didn't match was removed from the state:
Do not replace original data
Instead, change what filter is used and do the filtering in the render() function.
In the example below, the original data (called data) is never changed. Only the filter used is changed.
const data = [
{
id: 1,
text: 'one',
},
{
id: 2,
text: 'two',
},
{
id: 3,
text: 'three',
},
]
class Example extends React.Component {
constructor() {
super()
this.state = {
filter: null,
}
}
render() {
const filter = this.state.filter
const dataToShow = filter
? data.filter(d => d.id === filter)
: data
return (
<div>
{dataToShow.map(d => <span key={d.id}> {d.text}, </span>)}
<button
onClick={() =>
this.setState({
filter: 2,
})
}
>
{' '}
Filter{' '}
</button>
</div>
)
}
}
ReactDOM.render(<Example />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<body>
<div id='root' />
</body>
Don't mutate local state to reflect the current state of the filter. That state should reflect the complete available list, which should only change when the list of options changes. Use your filtered array strictly for the view. Something like this should be all you need to change what's presented to the user.
change = (e) => {
return this.state.tree.filter(item => item.fileType === e.target.value)
}

Why the code stops working when I start using localStorage?

The code below is only working when I remove the componentWillMount that uses localStorage. With usage localStorage it gives a mistake
this.state.interests.map is not a function
I tried to move usage of localStorage out of component but it won't help. I suppose that using local storage somehow changes this.state.interests that they stop being an array.
let interests = ["Музыка", "Компьютеры", "Радио"]
let ListOfInterest = React.createClass({
getInitialState: function() {
return {value: '', interests: interests};
},
componentWillMount() {
let local = localStorage.getItem('interests')
if (local) {
this.setState({interests: local});
} else {
localStorage.setItem('interests', this.state.interests)}
},
deleteInterest(key) {
delete interests[key]
this.setState(this.state) // without this line the page will not re-render
},
addInterest() {
interests.unshift(this.state.value)
this.setState({value: ''})
},
handleChange(event) {
this.setState({value: event.target.value})
},
render() {
return <div className="interests">
<b>Интересы</b>
<br/>
{this.state.interests.map((int, index) => {
return <button onClick={() => {
this.deleteInterest(index)
}} key={index} className="btn-interest">{int}</button>
})}
<input type='text' placeholder="Add" value={this.state.value} onChange={(e) => this.handleChange(e)}/>
<button onClick={() => {
this.addInterest()
}} className="add">Add interest</button>
</div>
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
You have several issues in your example
in localStorage.setItem second argument have to be a String, you can not store Array(when you do it, in storage will be string separated by coma because called method toString - [1, 2, 3].toString() ), you need stringify array before set to Storage
keyValue A DOMString containing the value you want to give the
key you are creating/updating.
localStorage.setItem(
'interests', JSON.stringify(this.state.interests)
)
and parse when get value
let local = JSON.parse(localStorage.getItem('interests'));
this.setState(this.state) this is not good way to update state, you need update state like so
deleteInterest(key) {
this.setState({
interests: this.state.interests.filter((el, i) => i !== key)
})
},
addInterest() {
this.setState({
value: '',
interests: this.state.interests.concat(this.state.value)
});
},
Example

Categories