React search with pagination without double setState - javascript

I'm implementing search with pagination in React. So far I found few examples of it, but all they use code with double setState(), before and after AJAX call to backend. For example my current solution is:
import React from "react"
import PropTypes from "prop-types"
import SearchField from "components/SearchField"
import SearchResults from "components/SearchResults"
import Item from "models/Item"
class Search extends React.Component {
constructor() {
super()
this.state = {
query: "",
page: 1,
foundItems: []
}
this.handleSearch = this.handleSearch.bind(this)
this.handlePageChange = this.handlePageChange.bind(this)
}
updateSearchResults() {
const query = this.state.query
const params = {
page: this.state.page
}
Item.search(query, params).then((foundItems) => {
this.setState({ foundItems })
})
}
handleSearch(event) {
this.setState({
query: event.target.value
}, this.updateSearchResults)
}
handlePageChange(data) {
this.setState({
page: data.selected + 1
}, this.updateSearchResults)
}
render() {
return (
<div className="search">
<SearchField onSearch={this.handleSearch} />
<SearchResults
onPageChange={this.handlePageChange}
onSelect={this.props.onSelect}
items={this.state.foundItems}
/>
</div>
)
}
}
Search.propTypes = {
onSelect: PropTypes.func.isRequired
}
export default Search
I know that I can change interface of updateSearchResults to receive query and page as arguments and then I can avoid first setState to pass values there, but it doesn't look like a good solution, because when list of search parameters will grow (sorting order, page size, filters for example) then it'll get a bit clumsy. Plus I don't like idea of manual state pre-management in handleSearch and handlePageChange functions in this way. I'm looking for a better implementation.

I am not fully sure what you are asking, but you can optimise your code a bit by doing the following:
class Search extends React.Component {
constructor() {
super()
this.page = 1;
this.query = "";
this.state = {
foundItems: []
}
this.handlePageChange = this.handlePageChange.bind(this)
}
updateSearchResults(event) {
if(typeof event === "object")
this.query = event.target.value;
const params = {
page: this.page
}
Item.search(this.query, params).then((foundItems) => {
this.setState({ foundItems })
})
}
handlePageChange(data) {
this.page = data.selected + 1;
this.updateSearchResults();
}
render() {
return (
<div className="search">
<SearchField onSearch={this.updateSearchResults} />
<SearchResults
onPageChange={this.handlePageChange}
onSelect={this.props.onSelect}
items={this.state.foundItems}
/>
</div>
)
}
}

Related

JS/React - Live Search Bar With Mapping

After two days of being stuck on this component, I'm asking for any sort of help. I'm trying to search an API based on user input, and then filter that down to a more specific option as the user keeps typing. After solving a dozen or so errors, I'm still left with "Can't find variable 'Query'", and I just can't seem to find or figure out what exactly it's wanting. There was another post on here that led me in the right direction, but didn't provide any sort of answer for the issue I'm having. Any help here would be appreciated.
import axios from "axios";
import axiosRateLimit from "axios-rate-limit";
import React, { Component } from "react";
import SearchBar from "react-native-elements/dist/searchbar/SearchBar-ios";
class CardSearch extends Component {
state = {
data: [],
filteredData: [],
query: "",
};
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 = () => {
axiosRateLimit(
axios.get(`https://api.scryfall.com/cards/autocomplete?q=${query}`),
{ maxRPS: 8 }
)
.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 (
<>
<SearchBar
placeholder='Search For...'
value={this.state.query}
onChange={this.handleInputChange}
/>
<div>
{this.state.filteredData.map((i) => (
<p>{i.name}</p>
))}
</div>
</>
);
}
}
export default CardSearch;
<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>
Have a look at this Link. You are not setting the State in a Constructor. And as already mentioned in the comments you will then have to access the query using this.state.query
https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class
The Code-Sample from the React Documentation:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

I'm trying to implement a Search Filter in React, but it doesn't work, could you help me out?

I'm new to React and I'm trying to build a search filter fetching an API, the console doesn't give me any error, but the filter search bar doesn't work, could someone help me out? Thank you!
So, I think everything should be fine, because in the chrome console, I don't receive any errors, but the SearchBox.js, doesn't seem to work
This is the code:
SearchBox.js:
import React from 'react';
class SearchBox extends React.Component {
constructor(props) {
super(props)
this.state = {
suggestions: [],
text: '',
}
}
componentDidMount() {
fetch('https://api.scryfall.com/catalog/card-names')
.then(response => response.json())
.then(cards => this.setState({ suggestions: cards.data}))
}
onTextChanged = (event) => {
const { items } = this.props;
const value = event.target.value;
let suggestions = [];
if (value.length > 0) {
const regex = new RegExp(`^${value}`, '');
suggestions = Object.keys(items).sort().filter(word => regex.test(word))
}
this.setState(() => ({ suggestions, text: value }))
}
suggestionSelected (value) {
this.setState({
text: value,
suggestions: []
});
}
renderSuggestions () {
const { suggestions } = this.state;
if (suggestions.length === 0) {
return null;
}
return (
<ul>
{suggestions.map((item, index) => <li key={index} onClick={() => this.suggestionSelected(item)}>{item}</li>)}
</ul>
)
}
render () {
const { text } = this.state;
return (
<div>
<input value={text}
onChange={this.onTextChanged} type="text" />
{this.renderSuggestions()}
</div>
);
}
}
export default SearchBox;
App.js:
import React from 'react';
import SearchBox from './components/SearchBox';
class App extends React.Component{
render() {
return(
<div>
<SearchBox items/>
</div>
)
}
};
export default App;
You are missing the functions bindings:
class SearchBox extends React.Component {
constructor(props) {
super(props)
this.state = {
suggestions: [],
text: '',
}
// == Binding ==
this.suggestionSelected = this.suggestionSelected.bind(this);
this.renderSuggestions = this.renderSuggestions.bind(this);
this.onTextChanged = this.onTextChanged.bind(this);
// == ======= ==
}
// [...]
}

How to set timeout on event onChange

I have a gallery that show images, and i have a search textbox
Im Trying to use Timeout on Input event to prevent the api call on every letter im typing :
I try to handle the event with doSearch function onChange: but now I cant write anything on the textbox and it cause many errors
Attached to this session the app and gallery components
Thanks in advance
class App extends React.Component {
static propTypes = {
};
constructor() {
super();
this.timeout = 0;
this.state = {
tag: 'art'
};
}
doSearch(event){
var searchText = event.target.value; // this is the search text
if(this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(function(){this.setState({tag: event.target.value})} , 500);
}
render() {
return (
<div className="app-root">
<div className="app-header">
<h2>Gallery</h2>
<input className="input" onChange={event => this.doSearch(event)} value={this.state.tag}/>
</div>
<Gallery tag={this.state.tag}/>
</div>
);
}
}
export default App;
This is the Gallery class:
import React from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
import Image from '../Image';
import './Gallery.scss';
class Gallery extends React.Component {
static propTypes = {
tag: PropTypes.string
};
constructor(props) {
super(props);
this.state = {
images: [],
galleryWidth: this.getGalleryWidth()
};
}
getGalleryWidth(){
try {
return document.body.clientWidth;
} catch (e) {
return 1000;
}
}
getImages(tag) {
const getImagesUrl = `services/rest/?method=flickr.photos.search&api_key=522c1f9009ca3609bcbaf08545f067ad&tags=${tag}&tag_mode=any&per_page=100&format=json&safe_search=1&nojsoncallback=1`;
const baseUrl = 'https://api.flickr.com/';
axios({
url: getImagesUrl,
baseURL: baseUrl,
method: 'GET'
})
.then(res => res.data)
.then(res => {
if (
res &&
res.photos &&
res.photos.photo &&
res.photos.photo.length > 0
) {
this.setState({images: res.photos.photo});
}
});
}
componentDidMount() {
this.getImages(this.props.tag);
this.setState({
galleryWidth: document.body.clientWidth
});
}
componentWillReceiveProps(props) {
this.getImages(props.tag);
}
render() {
return (
<div className="gallery-root">
{this.state.images.map((dto , i) => {
return <Image key={'image-' + dto.id+ i.toString()} dto={dto} galleryWidth={this.state.galleryWidth}/>;
})}
</div>
);
}
}
First of all why do you need to use setTimeout to set value that is entered by user. I don't see any use using setTimeout in doSearch function.
The reason your doSearch function won't work because you are not binding it.
You can directly set value to tag using setState in doSearch function in following ways.
ES5 way
constructor(props){
super(props);
this.doSearch = this.doSearch.bind(this);
}
doSearch(event){
this.setState({
tag: event.target.value
});
}
ES6 way
doSearch = (event) => {
this.setState({
tag: event.target.value
});
}
Doing setState inside setTimeout in doSearch function won't work because
input tag has value assigned.
ES5 way
constructor(props){
super(props);
this.doSearch = this.doSearch.bind(this);
}
doSearch(event){
if(this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(function(){
this.setState({
tag: event.target.value
})
}.bind(this),500);
}
setTimeout in ES6 way
doSearch = (event) => {
if(this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.setState({
tag: event.target.value
})
},500);
}
Gallery component:
Check current props changes with previous change in componentWillRecieveProps to avoid extra renderings.
Try with below updated code
import React from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
import Image from '../Image';
import './Gallery.scss';
class Gallery extends React.Component {
static propTypes = {
tag: PropTypes.string
};
constructor(props) {
super(props);
this.state = {
images: [],
galleryWidth: this.getGalleryWidth()
};
}
getGalleryWidth(){
try {
return document.body.clientWidth;
} catch (e) {
return 1000;
}
}
getImages(tag) {
const getImagesUrl = `services/rest/?method=flickr.photos.search&api_key=522c1f9009ca3609bcbaf08545f067ad&tags=${tag}&tag_mode=any&per_page=100&format=json&safe_search=1&nojsoncallback=1`;
const baseUrl = 'https://api.flickr.com/';
axios({
url: getImagesUrl,
baseURL: baseUrl,
method: 'GET'
})
.then(res => res.data)
.then(res => {
if (
res &&
res.photos &&
res.photos.photo &&
res.photos.photo.length > 0
) {
this.setState({images: res.photos.photo});
}
});
}
componentDidMount() {
this.getImages(this.props.tag);
this.setState({
galleryWidth: document.body.clientWidth
});
}
componentWillReceiveProps(nextProps) {
if(nextProps.tag != this.props.tag){
this.getImages(props.tag);
}
}
shouldComponentUpdate(nextProps, nextState) {
if(this.props.tag == nextProps.tag){
return false;
}else{
return true;
}
}
render() {
return (
<div className="gallery-root">
{this.state.images.map((dto , i) => {
return <Image key={'image-' + dto.id+ i.toString()} dto={dto} galleryWidth={this.state.galleryWidth}/>;
})}
</div>
);
}
}
I am keeping tag initial value to empty as you are not doing anything with value art.
Please try with below code
class App extends React.Component {
static propTypes = {
};
constructor() {
super();
this.timeout = 0;
this.state = {
tag: '',
callGallery: false
};
}
doSearch = (event) => {
this.setState({tag: event.target.value, callGallery: false});
}
handleSearch = () => {
this.setState({
callGallery: true
});
}
render() {
return (
<div className="app-root">
<div className="app-header">
<h2>Gallery</h2>
<input className="input" onChange={event => this.doSearch(event)} value={this.state.tag}/>
<input type="button" value="Search" onClick={this.handleSearch} />
</div>
{this.state.callGallery && <Gallery tag={this.state.tag}/>}
</div>
);
}
}
export default App;
This is because you haven't bound this to your method.
Add the following to your constructor:
this.doSearch = this.doSearch.bind(this);
Also, you don't need the fat arrow notation for onChange. Just do:
onChange={this.doSearch}
onChange handler is just fine but you need to bind the setTimeout to render context.Currently,it is referring to window context.And the code as follows
doSearch(event){
var searchText = event.target.value; // this is the search text
if(this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(function(){
this.setState({
tag: event.target.value
})
}.bind(this),500);
}

How to handle two similar components in react

I am very new to React. I have two components: TimePickerComponent and the TimeDurationPickerComponent.
The TimePickerComponent gets passed a TimeString(string) via props(only if initial data exists) and displays it like "08:00". Code:
class TimePickerComponent extends React.Component {
_placeholder;
_defaultTimeString;
_timeString;
_errorStatus;
_classes;
constructor({ placeholder, defaultTimeString, timeString, errorStatus, classes }) {
super();
this._placeholder = placeholder;
this._defaultTimeString = defaultTimeString;
this._timeString = timeString;
this._errorStatus = errorStatus;
this._classes = classes;
}
get Placeholder() {
return this._placeholder;
}
get DefaultTimeString() {
return this._defaultTimeString ? this._defaultTimeString : CONTROLS_CONSTANTS.DEFAULT_TIME_STRING;
}
get TimeString() {
return this._timeString;
}
get ErrorStatus() {
return this._errorStatus;
}
get Classes() {
return this._classes;
}
render() {
return <FormControl>
<TextField error={this.ErrorStatus}
label={this.Placeholder}
defaultValue={this.TimeString ? this.TimeString : this.DefaultTimeString}
className={this.Classes.layout}
type="time"
InputLabelProps={{
shrink: true
}}
/>
</FormControl>
}
}
TimePickerComponent.propTypes = {
placeholder: PropTypes.string.isRequired,
defaultTimeString: PropTypes.string,
timeString: PropTypes.string,
errorStatus: PropTypes.bool
}
export default withStyles(styles)(TimePickerComponent);
The TimeDurationPickerComponent gets passed a TimeInMinutes(number) via props. But the display is the same as of the TimePickerComponent("08:00"). Code:
class TimeDurationPickerComponent extends React.Component {
_placeholder;
_timeInMinutes;
_classes;
constructor({ placeholder, timeInMinutes, classes }) {
super();
this._placeholder = placeholder;
this._timeInMinutes = timeInMinutes;
this._classes = classes;
}
get Placeholder() {
return this._placeholder;
}
get TimeInMinutes() {
return this._timeInMinutes;
}
get Classes() {
return this._classes;
}
get TimeString() {
let timeFormat = CONTROLS_CONSTANTS.TIME_FORMATS.HOURS_MINUTES_COLON_SEPARATED;
let duration = moment.duration({ minutes: this.TimeInMinutes });
//https://github.com/moment/moment/issues/463
return moment.utc(duration.asMilliseconds()).format(timeFormat);
}
render() {
return <TimePickerComponent
placeholder={this.Placeholder}
timeString={this.TimeString}
classes={this.Classes}
/>
}
}
TimeDurationPickerComponent.propTypes = {
placeholder: PropTypes.string.isRequired,
timeInMinutes: PropTypes.number
}
export default TimeDurationPickerComponent;
To avoid code redundancy I reused my TimePickerComponent in the TimeDurationPickerComponent and just convert the TimeInMinutes in a TimeString and pass it down to the TimePickerComponent via props.
My question now: Is this a good practice how I solved this or should I use a HigherOrderComponent for that? Or should I use an inheritance approach for that? Which solution would be the best and why?
Thank you in advance.
What you've done here is probably fine. It could be done with a higher order component as well but a composition based approach like what you have won't have any performance issues and to be honest it's probably more readable than using an HOC.
On another note you should be using this.props and this.state to represent your class properties. They are build into React components and are what will cause your component to automatically re-render upon change.
It also makes your code significantly more concise so for example you could reduce your second component down to something like this:
class TimeDurationPickerComponent extends React.Component {
constructor(props) {
super(props);
}
createTimeString() {
let timeFormat = CONTROLS_CONSTANTS.TIME_FORMATS.HOURS_MINUTES_COLON_SEPARATED;
let duration = moment.duration({ minutes: this.props.TimeInMinutes });
//https://github.com/moment/moment/issues/463
return moment.utc(duration.asMilliseconds()).format(timeFormat);
}
render() {
return <TimePickerComponent
placeholder={this.props.Placeholder}
timeString={this.createTimeString()}
classes={this.props.Classes}
/>
}
}
Example of a component that uses flow:
// #flow
import React from 'react';
import './css/ToggleButton.css';
type Props = {
handleClick: Function;
label: string;
};
type LocalState = {
active: bool,
};
class ToggleButton extends React.Component<Props, LocalState> {
clickHandler: () => void;
constructor(props: Props) {
super(props);
this.state = {
active: true,
};
this.clickHandler = this.clickHandler.bind(this);
}
clickHandler() {
this.setState({ active: !this.state.active });
this.props.handleClick();
}
render() {
const buttonStyle = this.state.active ? 'toggle-btn-active' : 'toggle-btn-inactive';
return (
<button
className={`toggle-btn ${buttonStyle}`}
onClick={this.clickHandler}
>{this.props.label}
</button>
);
}
}
export default ToggleButton;

Search result pagination with React JS

How would one implement pagination for search using React?
Here's my code for returning users.
export default class SearchPanel extends Component {
static propTypes = {
isLoading: PropTypes.bool,
users: PropTypes.array,
}
static contextTypes = {
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
}
static defaultProps = {
isLoading: false,
users: [],
}
constructor(props, context) {
super(props, context);
}
render() {
const searchResults = (this.props.isLoading)
? <h1>LOADING USERS</h1>
: this.props.users.map((user) => <SearchResultUser key={user.username} {...user} />);
return (
<div className="ibox-content">
{this.props.users}
</div>
)
}
}
Note: I've kept most of the html out of the render to keep the code looking simple for this question.
So in a nutshell, this.props.users returns an array of users, I just need to be able to paginate the result by lets say 5 per page.
Use this function:
getUsers(page, amount) {
return this.props.users.filter(function(item, i) {
return i >= amount*(page-1) && i < page*amount
});
}
E.g {() => getUsers(1, 5)} will return users between 1-5, where {() => getUsers(2,5)} will return users between 6-10.
Example: http://codepen.io/zvona/pen/GpEdqN?editors=001

Categories

Resources