I've create an autocomplete search input bar using AWS Cloudscape. When I attempt to use the onSelect to save state, it does not save state. When I select a second item from the search bar, the first item then shows up. I assume this is a state async issue. I need the most recent selection, how do I get that?
import React from "react";
import Regions from "./trailforks_regions.json"
import RegionNames from "./region_names.json"
import * as dfd from "danfojs";
import { Autosuggest } from '#cloudscape-design/components';
export class AutoCompleteInputComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
df: new dfd.DataFrame(Regions),
value: null,
final_value: "",
options: [],
status: "loading",
regionContext: [],
uri_value: "",
loaded: false
};
}
lookupRegionUriName() {
console.log("Checking for uri region name on:" + this.state.final_value)
let region_name_split = this.state.final_value.split(",")[0]
let query_df = this.state.df.query(this.state.df["name"].eq(region_name_split))
let uri_name = query_df["region_uri_name"].values
console.log("found:" + uri_name)
return uri_name
}
handleChange = event => {
this.setState({ value: event.detail.value });
};
handleSelect = event => {
console.log(event) // This shows the correct data! But the set state does not set this data.
this.setState({ final_value: event.detail.value });
this.lookupRegionUriName(this.state.final_value)
};
handleLoadItems = ({ detail: { filteringText, firstPage, samePage } }) => {
this.filteringText = filteringText;
var my_options = RegionNames.filter(regions => regions.toLowerCase().startsWith(this.filteringText.toLowerCase()))
var region_values = []
for (var i = 0; i <= my_options.length; i++) {
region_values.push({ value: my_options[i] })
}
this.setState({
options: region_values.slice(0, 25),
status: 'loading',
});
};
enteredTextLabel = value => `Use: "${value}"`;
renderInput() {
if (this.state.df != null) {
const { status, value, options, final_value, uri_value } = this.state;
return (
<>
<Autosuggest
onChange={this.handleChange}
onSelect={this.handleSelect}
onLoadItems={this.handleLoadItems}
value={value}
options={options}
enteredTextLabel={this.enteredTextLabel}
ariaLabel="Autosuggest example with suggestions"
placeholder="Enter Trailforks Region Name"
empty="No matches found"
finishedText={this.filteringText ? `End of "${this.filteringText}" results` : 'End of all results'}
//status={status}
loadingText="searching"
filteringType="manual"
/>
</>
)
} else {
return (<></>)
}
}
render() {
return (
<>
{this.renderInput()}
</>
)
}
}
Figured it out. It was a state issue, here are the changes for future people:
import React from "react";
import Regions from "./regions.json"
import RegionNames from "./region_names.json"
import * as dfd from "danfojs";
import { Autosuggest } from '#cloudscape-design/components';
export class AutoCompleteInputComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
df: new dfd.DataFrame(Regions),
value: null,
final_value: "",
options: [],
status: "loading",
regionContext: [],
uri_value: "",
loaded: false
};
}
updateRegion(state_object) {
this.lookupRegionUriName()
return state_object.final_value
}
lookupRegionUriName() {
console.log("Checking for uri region name on:" + this.state.final_value)
let region_name_split = this.state.final_value.split(",")[0]
let query_df = this.state.df.query(this.state.df["name"].eq(region_name_split))
let uri_name = query_df["region_uri_name"].values
this.setState({uri_value: uri_name})
console.log("found:" + uri_name)
}
handleChange = event => {
this.setState({ value: event.detail.value });
};
handleSelect = event => {
console.log(event) // This shows the correct data! But the set state does not set this data.
this.setState({ final_value: event.detail.value });
this.lookupRegionUriName(this.state.final_value)
};
handleLoadItems = ({ detail: { filteringText, firstPage, samePage } }) => {
this.filteringText = filteringText;
var my_options = RegionNames.filter(regions => regions.toLowerCase().startsWith(this.filteringText.toLowerCase()))
var region_values = []
for (var i = 0; i <= my_options.length; i++) {
region_values.push({ value: my_options[i] })
}
this.setState({
options: region_values.slice(0, 25),
status: 'loading',
});
};
enteredTextLabel = value => `Use: "${value}"`;
renderInput() {
if (this.state.df != null) {
const { status, value, options, final_value, uri_value } = this.state;
return (
<>
<Autosuggest
onChange={this.handleChange}
onSelect={event => {this.setState({final_value: event.detail.value}, () => {this.updateRegion(this.state)})}}
//onSelect={this.handleSelect}
onLoadItems={this.handleLoadItems}
value={value}
options={options}
enteredTextLabel={this.enteredTextLabel}
ariaLabel="Autosuggest example with suggestions"
placeholder="Enter Trailforks Region Name"
empty="No matches found"
finishedText={this.filteringText ? `End of "${this.filteringText}" results` : 'End of all results'}
status={status}
loadingText="searching"
filteringType="manual"
/>
</>
)
} else {
return (<></>)
}
}
render() {
return (
<>
{this.renderInput()}
</>
)
}
}
Related
I am loading a child component on parent component in React.js. With a click on the button, data will be pass to child component through props, and child component will map through that data and render on screen. I am getting data from localstorage and processing its data structure to child component.
But, the issue is when I click on the button and data is being passed and rendered, the button is shown and after the child component is rendered that shows up. I need the loading spinner when I click on the button and it disappears and shows the actual component.
I have tried methods like loading: false in the state but to no avail.
Thanks for your help
import ShowPatientAppointments from './ShowPatientAppointments.component';
class PatientAppointmnent extends Component {
state = {
doctorSlots: null,
timingSlot: null,
bookDay: null,
bookTime: null,
hasTiming: false,
}
getSlots = () => {
let slot = [];
let time = [];
for (let i=0; i< localStorage.length; i++) {
let key = localStorage.key(i);
let value = JSON.parse(localStorage[key]);
slot.push(key);
time.push(value);
this.setState({doctorSlots: slot, timingSlot: time});
}
console.log(this.state.doctorSlots, this.state.timingSlot);
}
render() {
const { doctorSlots, timingSlot, hasTiming } = this.state;
return(
<div>
<button onClick={this.getSlots} className='button'>Show me dates</button>
{doctorSlots === null ? <p></p> : <PatientSelectDay props={doctorSlots} timing={timingSlot} getTimings={this.getTiming} />}
</div>
)
}
}
export default PatientAppointmnent;
class PatientSelectDay extends Component {
state = {
options: [...this.props.props].map(obj => {
return {value: `${obj}`, label: `${obj}`}
}),
timingOptions: [...this.props.timing],
open_id: [],
part_id: '',
doctorDay: 'day',
doctorTiming: 'timing',
}
changeSingleHandler = e => {
this.setState({ part_id: e ? e.value : '' });
};
changeHandler = e => {
let add = this.state.open_id;
add.push(e.map(x => x.value));
this.setState({ open_id: e ? add : [] });
};
saveState = (option) => {
...save selected options
}
render() {
const {options, timingOptions} = this.state;
return (
<div>
<div className='carousel'>
{options.map((option, index) => {
const timing = timingOptions[index].map(obj => {
return {value: `${obj}`, label: `${obj}`}});
return(
<div key={index}>
<Select
name="open_id"
value={option}
onChange={this.changeSingleHandler}
options={option}
className='select'
/>
<Select
name="open_id"
value={this.state.open_id}
onChange={this.changeHandler}
options={timing}
className='select'
/>
<button onClick={() => this.saveState(option)} className='button-left'>Select Days</button>
</div>
)
})}
</div>
</div>
)
}
}
export default PatientSelectDay;
You need to update your code adding a loading state variable.
class PatientAppointmnent extends Component {
state = {
doctorSlots: null,
timingSlot: null,
bookDay: null,
bookTime: null,
hasTiming: false,
loading: false
}
getSlots = () => {
let slot = [];
let time = [];
this.setState({
loading: true
})
for (let i=0; i< localStorage.length; i++) {
let key = localStorage.key(i);
let value = JSON.parse(localStorage[key]);
slot.push(key);
time.push(value);
this.setState({doctorSlots: slot, timingSlot: time, loading: false});
}
console.log(this.state.doctorSlots, this.state.timingSlot);
}
renderButton = () => {
const { doctorSlots, timingSlot, loading } = this.state;
if(doctorSlots === null && timingSlot === null) {
return <div>
{loading ? <p>Loading...</p> : <button onClick={this.getSlots} className='button'>Show me dates</button>}
</div>
}
return null;
}
render() {
const { doctorSlots, timingSlot, hasTiming, loading } = this.state;
return(
<div>
{this.renderButton()}
{doctorSlots === null ? <p></p> : <PatientSelectDay props={doctorSlots} timing={timingSlot} getTimings={this.getTiming} />}
</div>
)
}
}
export default PatientAppointmnent;
class PatientSelectDay extends Component {
state = {
options: [...this.props.props].map(obj => {
return {value: `${obj}`, label: `${obj}`}
}),
timingOptions: [...this.props.timing],
open_id: [],
part_id: '',
doctorDay: 'day',
doctorTiming: 'timing',
}
changeSingleHandler = e => {
this.setState({ part_id: e ? e.value : '' });
};
changeHandler = e => {
let add = this.state.open_id;
add.push(e.map(x => x.value));
this.setState({ open_id: e ? add : [] });
};
saveState = (option) => {
...save selected options
}
render() {
const {options, timingOptions} = this.state;
return (
<div>
<div className='carousel'>
{options.map((option, index) => {
const timing = timingOptions[index].map(obj => {
return {value: `${obj}`, label: `${obj}`}});
return(
<div key={index}>
<Select
name="open_id"
value={option}
onChange={this.changeSingleHandler}
options={option}
className='select'
/>
<Select
name="open_id"
value={this.state.open_id}
onChange={this.changeHandler}
options={timing}
className='select'
/>
<button onClick={() => this.saveState(option)} className='button-left'>Select Days</button>
</div>
)
})}
</div>
</div>
)
}
}
export default PatientSelectDay;
I am trying to conditionally render a component based on toggling of flag inside state. It looks like the state is getting updated but the component is not getting rendered. Can some one tell what is wring here. renderTree function updates the state, but render is not called then.
import React from "react";
import CheckboxTree from "react-checkbox-tree";
import "react-checkbox-tree/lib/react-checkbox-tree.css";
import { build } from "../data";
import { Input, Dropdown } from "semantic-ui-react";
import _ from "lodash";
class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
nodes: build(),
checked: [],
expanded: [],
isDropdownExpanded: false,
keyword: ""
};
}
onCheck = checked => {
this.setState({ checked }, () => {
console.log(this.state.checked);
});
};
onExpand = expanded => {
this.setState({ expanded }, () => {
console.log(this.state.expanded);
});
};
renderTree = () => {
this.setState(
prevState => {
return {
...prevState,
isDropdownExpanded: !prevState.isDropdownExpanded
};
},
() => {
console.log(this.state);
}
);
};
onSearchInputChange = (event, data, searchedNodes) => {
this.setState(prevState => {
if (prevState.keyword.trim() && !data.value.trim()) {
return {
expanded: [],
keyword: data.value
};
}
return {
expanded: this.getAllValuesFromNodes(searchedNodes, true),
keyword: data.value
};
});
};
shouldComponentUpdate(nextProps, nextState) {
if (this.state.keyword !== nextState.keyword) {
return true;
}
if (!_.isEqual(this.state.checked, nextState.checked)) {
return true;
}
if (_.isEqual(this.state.expanded, nextState.expanded)) {
return false;
}
return true;
}
getAllValuesFromNodes = (nodes, firstLevel) => {
if (firstLevel) {
const values = [];
for (let n of nodes) {
values.push(n.value);
if (n.children) {
values.push(...this.getAllValuesFromNodes(n.children, false));
}
}
return values;
} else {
const values = [];
for (let n of nodes) {
values.push(n.value);
if (n.children) {
values.push(...this.getAllValuesFromNodes(n.children, false));
}
}
return values;
}
};
keywordFilter = (nodes, keyword) => {
let newNodes = [];
for (let n of nodes) {
if (n.children) {
const nextNodes = this.keywordFilter(n.children, keyword);
if (nextNodes.length > 0) {
n.children = nextNodes;
} else if (n.label.toLowerCase().includes(keyword.toLowerCase())) {
n.children = nextNodes.length > 0 ? nextNodes : [];
}
if (
nextNodes.length > 0 ||
n.label.toLowerCase().includes(keyword.toLowerCase())
) {
n.label = this.getHighlightText(n.label, keyword);
newNodes.push(n);
}
} else {
if (n.label.toLowerCase().includes(keyword.toLowerCase())) {
n.label = this.getHighlightText(n.label, keyword);
newNodes.push(n);
}
}
}
return newNodes;
};
getHighlightText = (text, keyword) => {
const startIndex = text.indexOf(keyword);
return startIndex !== -1 ? (
<span>
{text.substring(0, startIndex)}
<span style={{ color: "red" }}>
{text.substring(startIndex, startIndex + keyword.length)}
</span>
{text.substring(startIndex + keyword.length)}
</span>
) : (
<span>{text}</span>
);
};
render() {
const { checked, expanded, nodes, isDropdownExpanded } = this.state;
let searchedNodes = this.state.keyword.trim()
? this.keywordFilter(_.cloneDeep(nodes), this.state.keyword)
: nodes;
return (
<div>
<Dropdown fluid selection options={[]} onClick={this.renderTree} />
{isDropdownExpanded && (
<div>
<Input
style={{ marginBottom: "20px" }}
fluid
icon="search"
placeholder="Search"
iconPosition="left"
onChange={(event, data) => {
this.onSearchInputChange(event, data, searchedNodes);
}}
/>
<CheckboxTree
nodes={searchedNodes}
checked={checked}
expanded={expanded}
onCheck={this.onCheck}
onExpand={this.onExpand}
showNodeIcon={true}
/>
</div>
)}
</div>
);
}
}
export default Widget;
Problem is in your shouldComponentUpdate method:
shouldComponentUpdate(nextProps, nextState) {
if (this.state.keyword !== nextState.keyword) {
return true;
}
if (!_.isEqual(this.state.checked, nextState.checked)) {
return true;
}
if (_.isEqual(this.state.expanded, nextState.expanded)) {
return false;
}
return true;
}
Since renderTree only changes isDropdownExpanded value, shouldComponentUpdate always returns false
If shouldComponenetUpdate returns true then your component re-renders, otherwise it dosen't.
In your code sandbox, it can be seen that every time you click on the dropdown, the shouldComponenetUpdate returns false for this condition
if (_.isEqual(this.state.expanded, nextState.expanded)) {
return false;
}
Either you need to change the state of this variable in your renderTree function or you need to re-write this condition as
if (_.isEqual(this.state.isDropdownExpanded, nextState.isDropdownExpanded)) {
return false;
}
Ciao, to force a re-render in React you have to use shouldComponentUpdate(nextProps, nextState) function. Something like:
shouldComponentUpdate(nextProps, nextState) {
return this.state.isDropdownExpanded !== nextState.isDropdownExpanded;
}
When you change isDropdownExpanded value, shouldComponentUpdate will be triggered and in case return is equal to true, component will be re-rendered. Here working example (based on your codesandbox).
I am trying to make live search for name in table but i can't make live search i don't know how to do this i wrote my code like this as i mentioned please help me how to make live search on name field foe table and in Search Page i used onSubmit={this.props.loaddata like this thanks
import React, { Component } from "react";
import Search from "../../views/Cars/Search";
class Search1 extends Component {
constructor(props) {
super(props);
this.state = {
query: []
};
}
// Get Data from filter date
getData = async e => {
try {
const search = e.target.elements.search.value;
e.preventDefault();
const res = await fetch(`https://swapi.co/api/people/?search=${search}`);
const query = await res.json();
console.log(query);
this.setState({
query: query.results
});
} catch (e) {
console.log(e);
}
};
async componentDidMount() {
// let authToken = localStorage.getItem("Token");
try {
const res = await fetch(`https://swapi.co/api/people/`);
const query = await res.json();
// console.log(movie);
this.setState({
query: query.results
});
} catch (e) {
console.log(e);
}
}
render() {
const options = this.state.query.map(r => <li key={r.id}>{r.name}</li>);
return (
<div>
<Search loaddata={this.getData} />
{options}
</div>
);
}
}
export default Search1;
Genrally You can try React-Search
import Search from 'react-search'
import ReactDOM from 'react-dom'
import React, { Component, PropTypes } from 'react'
class TestComponent extends Component {
HiItems(items) {
console.log(items)
}
render () {
let items = [
{ id: 0, value: 'ruby' },
{ id: 1, value: 'javascript' },
{ id: 2, value: 'lua' },
{ id: 3, value: 'go' },
{ id: 4, value: 'julia' }
]
return (
<div>
<Search items={items} />
<Search items={items}
placeholder='Pick your language'
maxSelected={3}
multiple={true}
onItemsChanged={this.HiItems.bind(this)} />
</div>
)
}
}
Made few changes to your component. Send e.target.value from your child component
class Search1 extends Component {
constructor(props) {
super(props);
this.state = {
query: []
};
}
// Get Data from filter date
getData = search => {
const url = `https://swapi.co/api/people${search ? `/?search=${search}` : ``}`;
// e.preventDefault();
fetch(url)
.then(res => res.json())
.then(data =>
this.setState({
query: data.results || []
})).catch(e => console.log(e));
};
async componentDidMount() {
// let authToken = localStorage.getItem("Token");
this.getData();
}
render() {
const options = this.state.query.map(r => <li key={r.id}>{r.name}</li>);
return (
<div>
<Search loaddata={this.getData} />
{options}
</div>
);
}
}
export default Search1;
For Gettind Data from Api you can follow this code of react-search
import Search from 'react-search'
import ReactDOM from 'react-dom'
import React, { Component, PropTypes } from 'react'
class TestComponent extends Component {
constructor (props) {
super(props)
this.state = { repos: [] }
}
getItemsAsync(searchValue, cb) {
let url = `https://api.github.com/search/repositories?q=${searchValue}&language=javascript`
fetch(url).then( (response) => {
return response.json();
}).then((results) => {
if(results.items != undefined){
let items = results.items.map( (res, i) => { return { id: i, value: res.full_name } })
this.setState({ repos: items })
cb(searchValue)
}
});
}
render () {
return (
<div>
<Search items={this.state.repos}
multiple={true}
getItemsAsync={this.getItemsAsync.bind(this)}
onItemsChanged={this.HiItems.bind(this)} />
</div>
)
}
I know the state is updating because 1. the 'Loading...' is going away, I can console log this.state.images to see the array. However when the state updates and the loading goes the searchbar shows up but the Card's within CardList do not.
They do show up when I search for a correct string, but not before.
If I pass this.state.images to CardList they show up perfectly. However when I move to the filteredImages they only show up when filtered.
Any ideas? Thanks in advance.
class App extends Component {
constructor() {
super();
this.state = {
images:[],
searchfield: ''
}
}
getLabels = (image) => {
const AuthKey = key.key;
const res = axios.post(`https://vision.googleapis.com/v1/images:annotate?key=${AuthKey}`, {
requests: [
{
image:{
source:{
imageUri: `http://storage.googleapis.com/${image}`
}
},
features:[
{
type:"LABEL_DETECTION",
maxResults:10
}
]
}
]
});
res.then(function (response) {
const results = response.data.responses[0].labelAnnotations;
const ex = results.map(result => {
return result.description;
});
return ex;
});
return res;
};
componentDidMount() {
imageFiles.imageFiles.forEach(img => {
this.getLabels(img).then(result => {
const results = result.data.responses[0].labelAnnotations;
const labels = results.map(result => {
return result.description;
});
//Add new values to the state
this.setState({images:[...this.state.images, {img, labels}]});
});
})
}
onSearchChange = (event) => {
this.setState({searchfield: event.target.value});
}
render() {
const filteredImages = this.state.images.filter(image => {
return image.labels.includes(this.state.searchfield.toLowerCase());
});
// Create an array of objects to store the image path and the labels detected from Google Vision
if (this.state.images.length === 0) {
return <h1>Loading...</h1>
} else {
return (
<Grid className="App">
<SearchBox searchChange={this.onSearchChange}/>
<CardList images={filteredImages} />
</Grid>
)}
}
}
export default App;
class App extends Component {
constructor() {
super();
this.state = {
images:[],
searchfield: '',
filteredImages:[]
}
}
getLabels = (image) => {
const AuthKey = key.key;
const res = axios.post(`https://vision.googleapis.com/v1/images:annotate?key=${AuthKey}`, {
requests: [
{
image:{
source:{
imageUri: `http://storage.googleapis.com/${image}`
}
},
features:[
{
type:"LABEL_DETECTION",
maxResults:10
}
]
}
]
});
res.then(function (response) {
const results = response.data.responses[0].labelAnnotations;
const ex = results.map(result => {
return result.description;
});
return ex;
});
return res;
};
componentDidMount() {
imageFiles.imageFiles.forEach(img => {
this.getLabels(img).then(result => {
const results = result.data.responses[0].labelAnnotations;
const labels = results.map(result => {
return result.description;
});
//Add new values to the state
this.setState({images:[...this.state.images, {img, labels}]});
this.setState({filteredImages:[...this.state.images, {img, labels}]});
});
})
}
onSearchChange = (event) => {
this.setState({searchfield: event.target.value});
let filteredImages = this.state.images.filter(image => {
return image.labels.includes(this.state.searchfield.toLowerCase());
});
this.setState({filteredImages});
}
render() {
// Create an array of objects to store the image path and the labels detected from Google Vision
if (this.state.images.length === 0) {
return <h1>Loading...</h1>
} else {
return (
<Grid className="App">
<SearchBox searchChange={this.onSearchChange}/>
<CardList images={this.state.filteredImages} />
</Grid>
)}
}
}
export default App;
I am using the react-autosuggest component for a search bar in my application. My interpretation of the component is the following:
import Autosuggest from 'react-autosuggest';
import React from 'react'
export class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
suggestions: this.props.tickers
};
}
onChange = (event, { newValue, method }) => {
this.setState({
value: newValue
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: this.getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: this.props.tickers
});
};
escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
getSuggestions(value) {
const escapedValue = this.escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return this.state.suggestions.filter(language => regex.test(language));
}
getSuggestionValue(suggestion) {
return suggestion;
}
renderSuggestion(suggestion) {
return (
<span>{suggestion}</span>
);
}
render() {
const { value, suggestions } = this.state;
const inputProps = {
placeholder: "Search for stocks...",
value,
onChange: this.onChange
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={this.renderSuggestion}
inputProps={inputProps} />
);
}
}
This current implementation works fine when initially searching, for example, providing the letter 'A' gives all the options for A. When providing 'AL', it then further filters the list to provide options for only 'AL'. When I delete the character 'L' however, the list doesn't refresh and only still shows suggestions for 'AL' and not A.
Entering A
Entering AB
Deleting the B from AB.
It should work
getSuggestions(value) {
const escapedValue = this.escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return this.props.tickers.filter(language => regex.test(language));
}