How to Create a Search Field in ReactJS - reactjs

I'm new to react, and I'm working on a small project that uses a search bar to find data that I've gotten from my database.
The code for this component is below:
import React, { Component } from 'react';
class BodyData extends Component {
state = {
query: '',
data: [],
}
handleInputChange = () => {
this.setState({
query: this.search.value
})
this.filterArray();
}
getData = () => {
fetch(`http://localhost:4000/restaurants`)
.then(response => response.json())
.then(responseData => {
// console.log(responseData)
this.setState({
data:responseData
})
})
}
filterArray = () => {
var searchString = this.state.query;
var responseData = this.state.data
if(searchString.length > 0){
// console.log(responseData[i].name);
responseData = responseData.filter(l => {
console.log( l.name.toLowerCase().match(searchString));
})
}
}
componentWillMount() {
this.getData();
}
render() {
return (
<div className="searchForm">
<form>
<input type="text" id="filter" placeholder="Search for..." ref={input => this.search = input} onChange={this.handleInputChange}/>
</form>
<div>
{
this.state.data.map((i) =>
<p>{i.name}</p>
)
}
</div>
</div>
)
}
}
export default BodyData;
So basically, I want to update the state as I type in the query text, and have the restaurant names I've mapped be reduced till a match is found.
From what I understood, this.state.data will be filtered as I type in my query in the search bar. However when I map out this.state.data, I get the whole list of restaurants instead of what I want to see.
Ive been through a bunch of tutes, and I'm not exactly sure how to go about doing that.
Can anyone help me with this please? Any other comments on the code are also welcome. I'm here to learn :)
Thank you!

You could keep an additional piece of state called e.g. filteredData that contains all elements in data that include your query in the name, and then render that.
Example
class BodyData extends React.Component {
state = {
query: "",
data: [],
filteredData: []
};
handleInputChange = event => {
const query = event.target.value;
this.setState(prevState => {
const filteredData = prevState.data.filter(element => {
return element.name.toLowerCase().includes(query.toLowerCase());
});
return {
query,
filteredData
};
});
};
getData = () => {
fetch(`http://localhost:4000/restaurants`)
.then(response => response.json())
.then(data => {
const { query } = this.state;
const filteredData = data.filter(element => {
return element.name.toLowerCase().includes(query.toLowerCase());
});
this.setState({
data,
filteredData
});
});
};
componentWillMount() {
this.getData();
}
render() {
return (
<div className="searchForm">
<form>
<input
placeholder="Search for..."
value={this.state.query}
onChange={this.handleInputChange}
/>
</form>
<div>{this.state.filteredData.map(i => <p>{i.name}</p>)}</div>
</div>
);
}
}

Here is the code that will work for you
import React, { Component } from 'react';
class BodyData extends Component {
state = {
query: '',
data: [],
searchString:[]
}
handleInputChange = (event) => {
this.setState({
query: event.target.value
},()=>{
this.filterArray();
})
}
getData = () => {
fetch(`http://localhost:4000/restaurants`)
.then(response => response.json())
.then(responseData => {
// console.log(responseData)
this.setState({
data:responseData,
searchString:responseData
})
})
}
filterArray = () => {
let searchString = this.state.query;
let responseData = this.state.data;
if(searchString.length > 0){
// console.log(responseData[i].name);
responseData = responseData.filter(searchString);
this.setState({
responseData
})
}
}
componentWillMount() {
this.getData();
}
render() {
return (
<div className="searchForm">
<form>
<input type="text" id="filter" placeholder="Search for..." onChange={this.handleInputChange}/>
</form>
<div>
{
this.state.responseData.map((i) =>
<p>{i.name}</p>
)
}
</div>
</div>
)
}
}
export default BodyData;
There are few changes which is needed.
Set State is worked asynchronous.SO, to avoid it use arrow function when you need to do something immediately after set state.
there are 2 different keyword in es6 instead of var. Let and Const . use them instead of var.
There is no need of ref in input. you can directly get value of input by event.target.value
Enjoy Coding!!

setState method is asynchronous and it has second parameter - callback that is called when the state is applied.
You need to change handleInputChange methods.
handleInputChange = () => {
this.setState({
query: this.search.value
}, this.filterArray)
}

Few pointers I'll like to show-
setState({}) function is asynchronous, you'll have to either use functional setState and call filteredArray method as a callback. Or, call filteredArray at render, which is where your values will be updated and be reflected.
Your filterArray method is not returning / setting the filtered list of data. So what you type, even though it is getting filtered, it is not getting set / returned anywhere.

Related

React state not updating accordingly

I have a search component where the search result updates according to the search input, where if there is data returned from the API, it is rendered as a book grid, if there is no data, a message is displayed, and if the search input is empty, nothing is rendered.
My problem is that when query state updates the searchResult state does update but when I delete the search input so fast (make the search input empty), query becomes updates as an empty string but searchResult does not update according to it. What could be the problem?
Here is the code to the search component: (Note: I tried the componentDidUpdate() method and the setState() callback function but nothing worked)
import React, { Component } from "react";
// import "React Router" components
import { Link } from "react-router-dom";
// import custom components
import Book from "./Book";
// import required API
import * as BooksAPI from "../BooksAPI";
export default class BookSearch extends Component {
state = {
query: "",
searchResult: [],
};
handleInputChange = (query) => {
this.setState(
{
query: query.trim(),
},
() => {
if (query) {
console.log(query);
BooksAPI.search(query).then((books) => {
this.setState({ searchResult: books });
});
} else {
this.setState({ searchResult: [] });
}
}
);
};
// componentDidUpdate(currentProps, currentState) {
// if (currentState.query !== this.state.query && this.state.query) {
// BooksAPI.search(this.state.query).then((books) => {
// this.setState({ searchResult: books });
// });
// } else if (currentState.query !== this.state.query && !this.state.query) {
// this.setState({ searchResult: [] });
// }
// }
render() {
const { query, searchResult } = this.state;
const { updateBookShelves } = this.props;
return (
<div className="search-books">
<div className="search-books-bar">
<Link to="/" className="close-search">
Close
</Link>
<div className="search-books-input-wrapper">
<input
type="text"
placeholder="Search by title or author"
value={query}
onChange={(event) => this.handleInputChange(event.target.value)}
/>
</div>
</div>
<div className="search-books-results">
<ol className="books-grid">
{ searchResult.error ?
<p>No results matching your search</p>
: searchResult.map((book) => (
<Book
key={book.id}
book={book}
updateBookShelves={updateBookShelves}
/>
))
)
) )}
</ol>
</div>
</div>
);
}
}
I am not 100% sure about this solution since it is using setState inside callback of other setState but you can give it a try.
I think you can probably need to use setTimeout before calling api for data and before checking if query exist or not we can set timeout to null so it will not call unwanted api calls.
handleInputChange = query => {
this.setState(
{
query: query.trim()
},
() => {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
if (query) {
this.timeout = setTimeout(() => {
BooksAPI.search(query).then(books => {
this.setState({ searchResult: books });
});
}, 800);
} else {
this.setState({ searchResult: [] });
}
}
);
};

How to Create a Search Field in React Typescript

I'm new to react-typescript, and I'm working on a project that uses a search bar to find data that I've gotten from my database.
I find the result in stackoverflow some one write the answer using React Js. But I want the answer in React-Typescript:
React Code below:
class BodyData extends React.Component {
state = {
query: "",
data: [],
filteredData: []
};
handleInputChange = event => {
const query = event.target.value;
this.setState(prevState => {
const filteredData = prevState.data.filter(element => {
return element.name.toLowerCase().includes(query.toLowerCase());
});
return {
query,
filteredData
};
});
};
getData = () => {
fetch(`http://localhost:4000/restaurants`)
.then(response => response.json())
.then(data => {
const { query } = this.state;
const filteredData = data.filter(element => {
return element.name.toLowerCase().includes(query.toLowerCase());
});
this.setState({
data,
filteredData
});
});
};
componentWillMount() {
this.getData();
}
render() {
return (
<div className="searchForm">
<form>
<input
placeholder="Search for..."
value={this.state.query}
onChange={this.handleInputChange}
/>
</form>
<div>{this.state.filteredData.map(i => <p>{i.name}</p>)}</div>
</div>
);
}
}
> I want the sample autocomplete textbox code in React-Typescript
In typescript, React.Component is a generic. You can use that to define the types of your state and props. This will automatically apply the appropriate type definitions to both the state initialization and setState.
interface BodyDataProps {
// if you have any props, put them here
}
interface BodyDataState {
query: string;
data: WhateverTheDataTypeIs[];
filteredData: WhateverTheDataTypeIs[];
}
class BodyData extends React.Component<BodyDataProps, BodyDataState> {
// rest of the class is the same
I don't know what your data looks like, so replace WhateverTheDataTypeIs with a type describing your data.

How to get select option value React

I need a little help with my project. I think it is almost done, but I don't know how to finish...
So, I want to build app with input, select and button. Into input u can write for example, mettalica and after click on button app renders list with all songs, titles and tabTypes(guitar tabs). The problem is that i want to get info from select and render only that songs which includes for example player tabs.
Sandbox Code: https://codesandbox.io/s/react-example-ys6py?fontsize=14&hidenavigation=1&theme=dark
class Search extends React.Component {
state = {
searchValue: "",
songs: [],
musicTabs: [
'Dowolne',
'Bass',
'Player',
'Chords',
'Guitar'
],
result: ''
};
handleOnChange = event => {
this.setState({ searchValue: event.target.value });
};
handleSelectChange = (event) => {
this.setState({
result: event.target.value
})
console.log(this.state.result)
}
handleSearch = () => {
this.makeApiCall(this.state.searchValue);
};
makeApiCall = async searchInput => {
let api_url = `https://www.songsterr.com/a/ra/songs/byartists.json?artists=${searchInput}`;
const response = await fetch(api_url);
const songs = await response.json();
this.setState({ songs });
};
render() {
return (
<div>
<Header />
<input
name="text"
type="search"
placeholder="Search..."
onChange={event => this.handleOnChange(event)}
value={this.state.SearchValue}
/>
<Select optionValue={ this.state.musicTabs } change={ this.handleSelectChange } value={ this.state.result } />
<br />
<button onClick={this.handleSearch}>Search</button>
{this.state.songs ? (
<div>
{
this.state.songs.map((song, index) => (
<div key={index} className="lists">
<h1>Artist: <span>{song.artist.name}</span></h1>
<h2>Song title: <span>{song.title}</span></h2>
<ol>
<b>Available tabs:</b>
{song.tabTypes.map((tab, index) =>
<li key={index}> {song.tabTypes[index]} </li>
)}
</ol>
</div>
))
}
</div>
) : (
<p>Something</p>
)}
</div>
);
}
}
const Select = (props) => {
const { optionValue, change } = props;
const valueMusicTabs = optionValue.map((musicTab, index) => {
return <option name={ optionValue[index] } key={ index }> { optionValue[index] } </option>
})
return (
<>
<select onChange={ change }>
{ valueMusicTabs }
</select>
</>
)
};
Thanks for help guys!
I think you did everything right, just used the wrong prop
<Select optionValue={ this.state.musicTabs } onChange={ this.handleSelectChange } value={ this.state.result } />
the change prop on the Select component should just be changed to onChange since it's a default event it will be passed with the event to your handleChange method
I checked the codesandbox, everything was working right. this.setState is an asynchronous function. So, if you will console.log after this.setState chances are your will not log updated value. You can do it like this with a callback function.
handleSelectChange = (event) => {
this.setState({
result: event.target.value
}, () => console.log(this.state.result))
}
If you want to filter you can do that by making a function like:
filterSongs = selected => {
return songs.filter(song => song.tabTypes === selected);
}
and After that modify your handleSelectChange as:
handleSelectChange = (event) => {
let songs = filterSongs(event.target.value);
this.setState({
result: event.target.value,
toDisplay: songs
}, () => console.log(this.state.result))
}
and finally in your JSX:
return (
<>
{toDisplay.map((song, index) => {
return <p key={index}>{song.toString()}</p>
})}
</>
);
If I understand correctly. you want to get the results from API based on selected values of input and select.
as I can see you are only passing the param from input nothing from select.
handleSearch = () => {
this.makeApiCall(this.state.searchValue);
};
makeApiCall = async searchInput => {
let api_url = `https://www.songsterr.com/a/ra/songs/byartists.json?artists=${searchInput}`;
const response = await fetch(api_url);
const songs = await response.json();
this.setState({ songs });
};
The new call will be
let api_url = `https://www.songsterr.com/a/ra/songs/byartists.json?artists=${searchInput}&tabTypes=${selectValue}`;
I do not know how what are the parameters this API accepts.
Thanks for answers! I have last question, where I have to use method to filter that rendered list. If I select "Player" I want to render only that songs, which inlcudes "Player" tab in tabTypes. I still can't get it. I can't do it by changing API link.

Why, after I removed the extra code, the list of names is not displayed?

I have a react application, the result of which is: a list of names that are displayed using the fetch method, which in turn takes data from a json file - which button is pressed from that array the data is taken(first or second) + there is a search filter by firstname.
App.js:
import React from "react";
import TableData from "./TableData";
import TableSearch from "./TableSearch";
import "./styles.css";
class App extends React.Component {
state = {
data: [],
filteredData: [],
search: "",
shift: "first"
};
async componentDidMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch("/data/today.json");
const data = (await response.json()).group;
this.setState(
{
data,
shift: Object.keys(data)[0]
},
this.filter
);
};
updateSearch = e => {
this.setState({
search: e.target.value
});
};
filter = () => {
this.setState(({ search, data, shift }) => {
const s = search.toLowerCase();
return {
filteredData: data[shift].filter(n =>
n.firstName.toLowerCase().includes(s)
)
};
});
};
onClick = ({
target: {
dataset: { shift }
}
}) => {
this.setState(() => ({ shift }), this.filter);
};
render() {
const { search, shift, data, filteredData } = this.state;
return (
<div>
<TableSearch
value={search}
onChange={this.updateSearch}
onSearch={this.filter}
/>
{Object.keys(data).map(n => (
<button
data-shift={n}
onClick={this.onClick}
className={n === shift ? "active" : ""}
>
{n} shift
</button>
))}
<TableData data={filteredData} />
</div>
);
}
}
export default App;
But at the moment I need to simplify and reduce my application, since something needs to be change in the logic, so just the same, I need go to the initial version of the application.
I just need to leave a list of names on the screen, and remove the buttons and filter.
Well, I removed:
App.js:
import React from "react";
import TableData from "./TableData";
class App extends React.Component {
state = {
data: []
};
async componentDidMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch("/data/today.json");
const data = (await response.json()).group;
console.log(data);
this.setState({
data
});
};
render() {
return (
<div>
<TableData data={this.state.data} />
</div>
);
}
}
export default App;
And the list is not displayed ...
I think maybe this has something to do with the filter, but I'm not sure.
How then can I write the code so that list name only is displayed without buttons and filters? But at the same time, I cannot modify the TableData.js file since in this tasks it is necessary for further manipulations with the code.Well, at least not much to changeTableData.js
Given the code filteredData: data[shift] and shift: Object.keys(data)[0]:
filteredData is an array
data is an object like {first: [], second: []}
So, in the shortened version, you have to use an array, e.g.:
<TableData data={Object.values(this.state.data)[0] || []} />
And fix your initial state to match the type:
state = {
data: {}
}

How to setState to answer from APi and use map

Im trying to create recipes searcher. In App.js I receive query from search input from another component and I want to setState to answer from APi. Console.log from callback in setState shows updated state but the state is not updated. I need setState updaed so I can use map on it and display list of recipes in render. It gives me error map is not a function because this.state.recipesList is still empty. Anyone can help me ?
class App extends Component {
state = {
query: "",
recipesList: []
};
getQuery = query => {
const key = "2889f0d3f51281eea62fa6726e16991e";
const URL = `https://www.food2fork.com/api/search?key=${key}&q=${query}`;
fetch(URL)
.then(res => res.json())
.then(res => {
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
});
console.log(this.state.recipesList);
};
render() {
const test = this.state.recipesList.map(item => {
return (
<div className="recispesList">
<h1>{item.title}</h1>
</div>
);
});
return (
<div className="App">
<Search query={this.getQuery} />
<div className="contentWrapper">{}</div>
</div>
);
}
}
Search component:
class Search extends Component {
state = {
searchValue: ""
};
handleChange = val => {
let searchValue = val.target.value;
this.setState({
searchValue
});
};
handleSubmit = e => {
e.preventDefault();
this.setState({
searchValue: ""
});
this.props.query(this.state.searchValue);
};
render() {
return (
<div className="searchWrapper">
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.searchValue} />
<button />
</form>
</div>
);
}
}
export default Search;
It seems that instead of directly assigning the whole response to recipesList:
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
you need to get recipes array first via res.recipes:
this.setState(
{
recipesList: res.recipes
},
() => {
console.log(this.state.recipesList);
}
);

Resources