I want to add a search field in my dashboard and I have get api in this api have array and in the array I have objects so how I can do it in react what logic I should apply on it .
Here is the logic I apply on it but it returns error that
This is error:
TypeError: item.item_name is null
and this is logic i apply on it :
handleChange = event => {
const lowercasedFilter = this.state.SearchResult.toLowerCase();
this.state.all_items.filter(item => {
// console.log(item)
return (item.item_name.toLowerCase().indexOf(lowercasedFilter) !== -1)
});
this.setState({ SearchResult: event.target.value });
};
and here is input field :
<input placeholder="Type Keyword here ....." onChange={this.handleChange} value={SearchResult} />
Two things need to fix.
check if item_name is not null before calling toLowerCase()
filter() doesnot modify the original array so you need to store the result in a variable and then change the state.
Here is code
handleChange = event => {
const lowercasedFilter = this.state.SearchResult.toLowerCase();
let res = this.state.all_items.filter(item => {
// console.log(item)
return (item.item_name && item.item_name.toLowerCase().indexOf(lowercasedFilter) !== -1)
});
this.setState({all_items:res, SearchResult: event.target.value });
};
If you try this, does it fix your issue?
this.state.all_items.filter(item => item.item_name.toLowerCase() === lowercasedFilter);
I've updated it slightly as you only need to check direct comparison rather than using indexOf.
as per the comments in other answer and in your question desciption i think you want to implement search suggetion component,which fetch list of item from api and on keypress of seach box filter the list of item based on user input.
to do it you will need to maintan two list of data one is provided by API and another one is filttered on user input so you component state should be like
//a constructor of your component
constructor(props){
super(props);
this.state={
//i assume all_items will be initialied at the componentDidmount event by api call
all_items :
[{item_name: 'hello'},{item_name: 'humty'},{item_name: 'dumpty'},{item_name: 'world'}] ,
//use this to display suggestions by filtering the all_items
filtered_items:[],
SearchResult:'',//you should name it searchQuery not SearchResult as it hold input from search box
}
}
your event handle should be like this,if you check your code you are using searchResult from state before updating it
handleChange = event => {
/***
* your old code ,which is wrong initially this.state.SearchResult will be ''
* const lowercasedFilter = this.state.SearchResult.toLowerCase();
*/
const lowercasedFilter = event.target.value;
//filter the all_items to make suggestion
let filtered_items =this.state.all_items.filter(item => {
return (item.item_name.toLowerCase().indexOf(lowercasedFilter) !== -1)
});
if(filtered_items.length<=0){
filtered_items=this.state.all_items;
}
//now update both filtered items and searchResult,previosuly you are just filtering all items but not updating them
this.setState({ SearchResult:event.target.value,filtered_items:filtered_items });
}
your code render suggetions
get_suggestion=()=>{
if(this.state.SearchResult.length>0){
return <ul>
{this.state.filtered_items.map((item)=>{
return <li>{item.item_name}</li>
})}
</ul>
}
return null;
}
your render method should be like this
render() {
let suggestions=this.get_suggestion();
return (
<div className="App">
<input type='text' onKeyUp={this.handleChange} />
{suggestions}
</div>
);
}
Related
In a React project, I'm displaying certain records in a table which also has input text boxes for changing the values when needed. To process those changed records it needs to be added into an array, but, getting undefined when changed the values. Although each record is associated with unique id, unable to add in new array. Please refer to the code below.
const textChange = (data) => {
const { id, value } = data;
setDataNew((prevInfo) => {
// Here the records are getting undefined and not getting added into array
const dataIndex = id - 1;
prevInfo[dataIndex] = value;
return [...prevInfo];
});
};
Any suggestions and solution highly appreciated.
Please refer to code sandbox link for better clarity --> https://codesandbox.io/s/elated-varahamihira-xpjtdb?file=/src/Table.js:757-959
If I understood it correctly here is what you need to do if you need all the records which got updated :
const textChange = (data) => {
const { id, value } = data;
setDataNew((prevInfo) => {
const newList = [...prevInfo];
const index = newList.findIndex((datum) => datum.id === id);
if (index !== -1) {
newList[index] = { id, value };
} else {
newList.push({ id, value });
}
return [...newList];
});
};
Mistake in your code
You were getting undefined because you were calculating index like :
const dataIndex = id - 1;
if the changed object id was 6708 , you were adding element at 6707th index. Hence all first 6706 element were showing up as undefined.
Link : working demo
I'm trying to add data from a single input field to an array which i want to store in localstorage but when i submit input button, the item is stored at first but if i try to store a second item, the array previous item is replaced with the newly typed item data instead of adding to it like i'd expect an array to behave. i don't understand this behaviour. i will really really appreciate a detailed explanation since i'm using react to do this.
This is my code below
input field
import React from "react";
import "./addoption.css";
function AddOption({ validateOption }) {
const handleAddoption = (e) => {
e.preventDefault();
const inputValue = e.target.elements[0].value.trim();
validateOption(inputValue);
e.target.elements[0].value = "";
};
return (
<div className="addoption">
<form onSubmit={handleAddoption}>
<input type="text" name="list" />
<button>Add Option</button>
</form>
</div>
);
}
export default AddOption;
*this is my code to add the input data to the localstorage *
const handleAddoption = (option) => {
if (!option) {
return setErrorhandler("Enter valid value to add item");
} else if (listItems.options.indexOf(option) > -1) {
return setErrorhandler("This option already exists!");
}
const array = localStorage.getItem("Options");
let items = [];
if (array) {
items = JSON.parse(array);
}
let storedArray = JSON.stringify(items.push(option));
localStorage.setItem("options", storedArray);
setListItems({ options: items });
};
``
Array.prototype.push certainly mutates the array, but it's return value isn't the array, it's the new length of the array. You may want to do the mutation separate from the JSON serializing.
The reason for the overwriting is because you're using two different storage keys for getting and setting. You are not getting what was stored so you are only appending new data to an empty array. Make sure you also use the same key to both retrieve and set the localStorage.
const handleAddoption = (option) => {
if (!option) {
return setErrorhandler("Enter valid value to add item");
} else if (listItems.options.indexOf(option) > -1) {
return setErrorhandler("This option already exists!");
}
const array = localStorage.getItem("options");
let items = [];
if (array) {
items = JSON.parse(array);
}
items.push(option);
localStorage.setItem("options", JSON.stringify(items));
setListItems({ options: items });
};
A more optimal solution would be to read in and initialize the options state from localStorage, and use an useEffect hook to just persist state updates back to localStorage. This way is a little easier to manage.
Example:
const initializeState = () => ({
// ... other listItems initial state
options: JSON.parse(localStorage.getItem("options")) || [],
});
const [listItems, setListItems] = useState(initializeState());
useEffect(() => {
localStorage.setItem("options", JSON.stringify(listItems.options));
}, [listItems.options]);
const handleAddoption = (option) => {
if (!option) {
return setErrorhandler("Enter valid value to add item");
} else if (listItems.options.indexOf(option) > -1) {
return setErrorhandler("This option already exists!");
}
setListItems(prevState => ({
...prevState
options: prevState.options.concat(option),
}));
};
I'm teaching myself React and one of my exercises is using axios to fetch a list of countries from an API
const fetchCountries = () => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
};
React.useEffect(fetchCountries, []);
Then as a user types into an input the list of countries filters down.
const handleInputChange = event => {
const filter = event.target.value; // current input value
let matchingCountries = query !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(query.toLowerCase()) !== -1)
: countries;
setQuery(filter);
setMatches(matchingCountries)
console.log('matches', matches)
console.log('query', query)
};
My goal is that when a single country is matched, a new API request is triggered (to fetch the weather, but the what isn't my problem, the timing is). When a single country is matched, I will then render some data about the country, then fetch and render the weather details for the single country's capital city.
One of the problems I'm having is that when I set the state, the value always seems to be one step behind. For example, in this Codepen when you enter FRA you should get "France". However, I have to enter "FRAN" to get the match. This doesn't happen when I don't use a state variable for the matches (just let matches). This becomes a problem because I need to run the next API call when the number of matches = 1, but the length of the matches state is always wrong.
So I would like to know 1. how to get the correct state of the matched countries. And 2. when I should run the second API call without getting into an infinite loop.
useEffect solution using separation of concern
1 function should do 1 thing
handleInputChange updates state
useEffect updates state
But they are not coupled.
Later you might have a new function called handleDropdownChange which updates state
It that case you don't need to modify useEffect
At the end of the day, we (developers) don't like to rewrite things
const [countries, setCountries] = React.useState([]);
const [query, setQuery] = React.useState("");
const [matches, setMatches] = React.useState([]);
React.useEffect(() => {
let matchingCountries = query !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(query.toLowerCase()) !== -1)
: countries;
setMatches(matchingCountries)
}, [query]); // called whenever state.query updated
const handleInputChange = event => {
setQuery(event.target.value); // update state
};
const fetchCountries = () => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
};
React.useEffect(fetchCountries, []);
And there is also solution (not recommended) by directly using event.target.value provided by #Joseph D.
The only problem is you are using an old query value in handleInputChange().
Remember setting the state is asynchronous (i.e. doesn't take effect immediately)
Here's an updated version:
const handleInputChange = event => {
const filter = event.target.value; // current input value
let matchingCountries = filter ? <code here>
// ...
setQuery(filter);
};
UPDATE:
To call the weather api if there's a single country match is to have matches as dependency in useEffect().
useEffect(
() => {
async function queryWeatherApi() {
// const data = await fetch(...)
// setData(data)
}
if (matches.length === 1) {
queryWeatherApi();
}
},
[matches]
)
1) The reason for your problem is in this line:
let matchingCountries = filter !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(query.toLowerCase()) !== -1)
: countries;
you use query instead of filter variable, your handler function should look like this:
const handleInputChange = event => {
const filter = event.target.value; // current input value
let matchingCountries = filter !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(filter.toLowerCase()) !== -1)
: countries;
setQuery(filter);
setMatches(matchingCountries)
};
2) Where to run your next API call:
For studying purpose I do not want to recommend you using some application state management lib like redux.Just calling it right after setFilter and setQuery. It will run as expected. Because calling an API is asynchronous too so it will be executed after setQuery and setFilter what does not happen with console.log, a synchronous function.
i have an array in my component and based on the searchString i am filtering the array of items. and it is working fine.
if user removes the characters from the search field i want to show all the records again. but i am unable to show all records again when clearing the items from the search field.
please see below code.
this.filterServ.filterData.subscribe(searchData => {
if (Object.keys(searchData).length != 0) {
console.log('component', searchData);
this.cardData = this.cardData.filter((project) => {
let name = project.Name.toLowerCase();
if (name.includes(searchData.searchString.toLowerCase())) {
return true;
}
});
console.log('filterd data', this.cardData);
}
});
You already mutated the cardData. Therefore, you can't revert it back.
The solution is to create another property, for example you can name it displayData.
Then you can do like:
this.displayData = this.cardData.filter((project) => {
and instead of using cardData on the template, use displayData instead
Declare field filterData and bind this field in the template.
this.filterServ.filterData.subscribe(searchData => {
if (Object.keys(searchData).length != 0) {
console.log('component', searchData);
this.filterData= this.cardData.filter((project) => {
let name = project.Name.toLowerCase();
if (name.includes(searchData.searchString.toLowerCase())) {
return true;
}
});
console.log('filterd data', this.cardData);
}
});
Hope this help!
You should take copy of array data before filtering, and return the original array if input text is empty.
private originalData;
this.originalData = this.cardData.slice();
this.filterServ.filterData.subscribe(searchData => {
if (Object.keys(searchData).length != 0) {
console.log('component', searchData);
this.cardData = this.originalData.filter((project) => {
let name = project.Name.toLowerCase();
if (name.includes(searchData.searchString.toLowerCase()))
{
return true;
}
});
console.log('filterd data', this.cardData);
}
});
So basically, I have a web application that retrieves data from Firebase using rxjs observables.
here's my code,
initializeItems(){
this.travelList$ = this.plsdala.getTravelList()
.snapshotChanges()
.map(
changes => {
return changes.map(c=>({
key: c.payload.key, ...c.payload.val()
})).slice().reverse();//to reverse order
})
this.travelList$.subscribe(res => {
for(let i=0;i<res.length;i++){
this.ListOfitems.push (res[i].toAddress);
}
})
}
this is called from the constructor. problem here is that i cannot check if it is push successfully and if try to print in console , it wont print. why?
the element pushed is needed for filtering. heres is the code for filtter. but when i print the this.ListOfitems in console it is undefined and im wondering unto why? when the elements are initialized first
getItems(ev: any) {
console.log("awdaw");
console.log(this.ListOfitems);
if (this.ListOfitems.length>1){
console.log("otin");
let val = ev.target.value;
if (val && val.trim() != '') {
this.ListOfitems = this.ListOfitems.filter((ListOfitems) => {
return (ListOfitems.toLowerCase().indexOf(val.toLowerCase()) > -1);
})
}
}
}
when you are declaring list of items if you want to push into the array you need to declare it empty first like this:
ListOfitems: string[] = [];