Search Filter input ReactJS - reactjs

I have class Comopnent :
state = {
names: ['first', 'second']
};
updateSearch = (event) => {
let updatedList = this.state.names;
updatedList = updatedList.filter(name=> {
return name.toLowerCase().search(
event.target.value.toLowerCase()) !== -1;
})
this.setState({names: updatedList});
}
render() {
const names = this.state.names.map((name) => { return (
<div key={name}>
<span>{name}</span>
</div>
)})
return (
<div>
<input
placeholder="Search"
type="text"
onChange={this.updateSearch} />
{names}
</div>
)
}
When I type some text that agrees with the name, search is working, and only that name is showing, but when i remove text from input all names should show back, but they don't (only the previously searched name is displayed). Why?
Thanks for answers in advance!

Add one more val in state as initialName and in updateSearch set updateSearch value
with initalNames.
Try This.
state = {
names: ['first', 'second'],
intiailNames:['first','second']
};
updateSearch = (event) => {
let updatedList = this.state.intiailNames;
updatedList = updatedList.filter(name=> {
return name.toLowerCase().search(
event.target.value.toLowerCase()) !== -1;
})
this.setState({names: updatedList});
}

Related

how to update input defaultvalue using useRef (after the initial render?)

const Component = ()=>{
const [list, setList] = useState(getLocalStorage());
const [isEditing, setIsEditing] = useState(false);
const [itemToEdit, setItemToEdit] = useState();
const refContainer = useRef(null);
const putLocalStorage = () => {
localStorage.setItem("list", JSON.stringify(list));
};
const editItem = (id) => {
refContainer.current.focus();
setItemToEdit(() => {
return list.find((item) => item.id === id);
});
setIsEditing(true);
};
const handleSubmit = (e)=>{
e.preventDefault();
let nameValue = refContainer.current.value;
if (isEditing){
setList(list.map((item)=>{
if (item.id === itemToEdit.id){
return {...item, name: nameValue};
}
else {
return item;
}
);
}
else {
let newItem = {
id: new Date().getItem().toString(),
name: nameValue,
}
setList([...list, newItem])
}
nameValue="";
setIsEditing(false);
}
useEffect(() => {
putLocalStorage();
}, [list]);
return (
<div>
<form onSubmit={handleSubmit}>
<input type="text" ref={refContainer} defaultValue={isEditing ? itemToEdit.name : ""}/>
<button type="submit">submit</button>
</form>
<div>
{list.map((item) => {
const { id, name } = item;
return (
<div>
<h2>{name}</h2>
<button onClick={() => editItem(id)}>edit</button>
<button onClick={() => deleteItem(id)}>
delete
</button>
</div>
);
})}
</div>
</div>
)
}
So this part:
<input type="text" ref={refContainer} defaultValue={isEditing ? itemToEdit.name : ""} />
I want to show to users what they are editing by displaying the itemToEdit on the input.
It works on the first time when the user clicks edit button
But after that, the defaultValue does not change to itemToEdit
Do you guys have any idea for the solution?
(i could use controlled input instead, but i want to try it with useRef only)
Otherwise, placeholder will be the only solution...
The defaultValue property only works for inicial rendering, that is the reason that your desired behavior works one time and then stops. See a similar question here: React input defaultValue doesn't update with state
One possible solution still using refs is to set the itemToEdit name directly into the input value using ref.current.value.
const editItem = (id) => {
refContainer.current.focus();
setItemToEdit(() => {
const item = list.find((item) => item.id === id);
refContainer.current.value = item.name;
return item;
});
setIsEditing(true);
};

React button to show an element

I'm pulling countries from the Restcountries API and if the current state of the array has more than one or less than or equal to ten countries, I want to list the country names along with a 'show' button next to each one. The show button should display what's in the return (render) of my Country function. In the App function, I wrote a handler for the button named handleViewButton. I'm confused on how to filter the element in the Countries function in the else conditional statement in order to display the Country. I tried passing handleViewButton to the Button function, but I get an error 'Uncaught TypeError: newSearch.toLowerCase is not a function'. I really just want to fire the Country function to display the country button that was pressed.
App.js
import React, { useState, useEffect } from 'react';
import './App.css';
import axios from 'axios';
const Country = ({country}) => {
return (
<>
<h2>{country.name}</h2>
<p>capital {country.capital}</p>
<p>population {country.population}</p>
<br/>
<h3>languages</h3>
{country.languages.map(language => <li key={language.name}>{language.name}</li>)}
<br/>
<img src={country.flag} alt="country flag" style={{ width: '250px'}}/>
</>
);
}
const Countries = ({countries, handleViewButton}) => {
const countriesLen = countries.length;
console.log(countriesLen)
if (countriesLen === 0) {
return (
<p>Please try another search...</p>
);
} else if (countriesLen === 1) {
return (
<ul>
{countries.map((country, i) => <Country key={i} countriesLen={countriesLen} country={country}/>)}
</ul>
);
} else if (countriesLen > 10) {
return (
<div>
<p>Too many matches, specify another filter</p>
</div>
);
} else {
return (
<ul>
{countries.map((country, i) => <li key={i}>{country.name}<Button handleViewButton={handleViewButton}/></li>)}
</ul>
)
};
};
const Button = ({handleViewButton}) => {
return (
<button onClick={handleViewButton}>Show</button>
);
};
const Input = ({newSearch, handleSearch}) => {
return (
<div>
find countries <input value={newSearch} onChange={handleSearch}/>
</div>
);
};
function App() {
const [countries, setCountries] = useState([]);
const [newSearch, setNewSearch] = useState('');
const handleSearch = (event) => {
const search = event.target.value;
setNewSearch(search);
};
const handleViewButton = (event) => {
const search = event.target.value;
setNewSearch(countries.filter(country => country === search));
};
const showCountrySearch = newSearch
? countries.filter(country => country.name.toLowerCase().includes(newSearch.toLowerCase()))
: countries;
useEffect(() => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(res => {
setCountries(res.data);
console.log('Countries array loaded');
})
.catch(error => {
console.log('Error: ', error);
})
}, []);
return (
<div>
<Input newSearch={newSearch} handleSearch={handleSearch}/>
<Countries countries={showCountrySearch} handleViewButton={handleViewButton}/>
</div>
);
};
export default App;
you can use a displayCountry to handle the country that should be displayed. Most often you would use an id, but I'm using here country.name since it should be unique.
Then you would use matchedCountry to find against your list of countries.
After that, a onHandleSelectCountry to select a given country. if it's already selected then you could set to null to unselect.
Finally, you would render conditionally your matchedCountry:
const Countries = ({countries}) => {
const [displayCountry, setDisplayCountry] = useState(null);
const countriesLen = countries.length;
const matchedCountry = countries.find(({ name }) => name === displayCountry);
const onHandleSelectCountry = (country) => {
setDisplayCountry(selected => {
return selected !== country.name ? country.name : null
})
}
if (countriesLen === 0) {
return (
<p>Please try another search...</p>
);
} else if (countriesLen === 1) {
return (
<ul>
{countries.map((country, i) => <Country key={i} countriesLen={countriesLen} country={country}/>)}
</ul>
);
} else if (countriesLen > 10) {
return (
<div>
<p>Too many matches, specify another filter</p>
</div>
);
} else {
return (
<>
<ul>
{countries.map((country, i) => <li key={i}>{country.name}<Button handleViewButton={() => onHandleSelectCountry(country)}/></li>)}
</ul>
{ matchedCountry && <Country countriesLen={countriesLen} country={matchedCountry}/> }
</>
)
};
};
I can only help to point out some guidelines.
First: The button does not have value attribute. Hence what you will get from event.target.value is always blank.
const Button = ({handleViewButton}) => {
return (
<button onClick={handleViewButton}>Show</button>
);};
First->Suggestion: Add value to the button, of course you need to pass the value in.
const Button = ({handleViewButton, value}) => {
return (
<button onClick={handleViewButton} value={value}>Show</button>
);};
Second: To your problem 'Uncaught TypeError: newSearch.toLowerCase is not a function'. Filter always returns an array, not a single value. if you do with console or some sandbox [1,2,3].filter(x=>x===2) you will get [2] not 2.
const handleViewButton = (event) => {
const search = event.target.value;
setNewSearch(countries.filter(country => country === search));
};
Second->Suggestion: To change it to get the first element in array, since country(logically) is unique.
const result = countries.filter(country => country === search)
setNewSearch(result.length>0?result[0]:"");
A better approach for array is find, which always return first result and as a value. E.g. [1,2,2,3].find(x=>x===2) you will get 2 not [2,2] or [2].
countries.find(country => country === search)

Map and If statement unexpected behaviour

The results I want to achieve are:
If the entered name is already on the array "persons", show an alert "Name already exist".
The code is not working, because I keep on getting the alert message all the time.
I think the condition comparison variable (personObject.name ) is wrong.
Can someone explain how this should be done?
Without the condition, the names ad added propperly into the array.
//Condition
if (persons.map((person) => person.name === personObject.name) )
{
alert("Name already exist");
}
else
{
//*/
setPersons(persons.concat(personObject))
setNewName('')
console.log(persons)
}
Code sandbox
Full code:
const App = () => {
//Reminder: current state, function that updates it, initial state.
const [persons, setPersons] = useState([
//The array persons is empty at start
]);
const [newName, setNewName] = useState('');
//adding new persons
const addPerson = (event) => {
event.preventDefault();
/* complete the addPerson function for creating new persons */
const personObject = {
name: newName,
id: persons.length + 1,
};
//Condition
if (persons.map((person) => person.name === personObject.name)) {
alert('Name already exist');
} else {
//*/
setPersons(persons.concat(personObject));
setNewName('');
console.log(persons);
}
};
const handlePersonChange = (event) => {
console.log(event.target.value);
setNewName(event.target.value);
};
return (
<div>
<h2>Phonebook</h2>
<form onSubmit={addPerson}>
<div>
name:
<input value={newName} onChange={handlePersonChange} />
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
{console.log(persons)}
<ul>
{persons.map((person) => (
<li key={person.id}>{person.name}</li>
))}
</ul>
</div>
);
};
export default App;
You are checking condition with using map and map will return new array so if condition will be always true.
So instead of map you should use some like below:-
persons.some((person) => person.name === personObject.name);
You can use the filter method that filters an array and returns you an array with items matching the condition, if this new array is empty it means no items matches it :
let alreadyExists = persons.filter(person => person.name === personObject.name)
if (alreadyExists.length > 0) {
alert("Name already exist")
} else {
setPersons(persons.concat(personObject))
setNewName('')
console.log(persons)
}

React: Search box and filter map

Expected results:
Type keywords in the input to search, and filter the list of posts.
Below is my code in React, when I type in the search box, searchSpace did get called, console.log(search_keywords) and console.log('filter', data) show up in Console. But the filteredData did not update and my list of posts are still the same. What am I missing here?
const Blog = () => {
const [posts] = useState([]);
let filteredData = posts;
let search_keywords = "";
const searchSpace = (event) => {
search_keywords = event.target.value;
console.log(search_keywords)
filteredData = posts.filter((data)=>{
if(data.title.toLowerCase().includes(search_keywords)){
console.log('filter', data)
return data
}
});
}
return (
<>
<input placeholder="search..." id="search" type="text" onChange={(e)=>searchSpace(e)} />
{
filteredData.map((v, i) => {
return <Card post={v} key={i} />;
})
}
</>
);
}
omitted some codes that are not relevant
You need to maintain a filteredData state as well. Setting it will trigger a re-render. Think of posts as read-only data.
const Blog = () => {
const [posts,setPosts] = useState([]);
let [filteredData,setFilteredData] = useState([]);
let search_keywords = "";
const searchSpace = (event) => {
search_keywords = event.target.value;
console.log(search_keywords)
filteredData = posts.filter((data)=>{
if(data.title.toLowerCase().includes(search_keywords)){
console.log('filter', data)
return data
}
});
setFilteredData(filteredData);
}
return (
<>
<input placeholder="search..." id="search" type="text" onChange={(e)=>searchSpace(e)} />
{
filteredData.map((v, i) => {
return <Card post={v} key={i} />;
})
}
</>
);

Group checkbox in Antd by using array object

Here is the original example of group checkbox of antd that I need and its fine:
const plainOptions = ['Apple', 'Pear', 'Orange'];
const defaultCheckedList = ['Apple', 'Orange'];
class App extends React.Component {
state = {
checkedList: defaultCheckedList,
indeterminate: true,
checkAll: false,
};
onChange = checkedList => {
this.setState({
checkedList,
indeterminate: !!checkedList.length && checkedList.length < plainOptions.length,
checkAll: checkedList.length === plainOptions.length,
});
};
onCheckAllChange = e => {
this.setState({
checkedList: e.target.checked ? plainOptions : [],
indeterminate: false,
checkAll: e.target.checked,
});
};
render() {
return (
<div>
<div style={{ borderBottom: '1px solid #E9E9E9' }}>
<Checkbox
indeterminate={this.state.indeterminate}
onChange={this.onCheckAllChange}
checked={this.state.checkAll}
>
Check all
</Checkbox>
</div>
<br />
<CheckboxGroup
options={plainOptions}
value={this.state.checkedList}
onChange={this.onChange}
/>
</div>
);
}
}
My question is how can I replace the plainOptions and defaultCheckedList by object array instead of simple array and using attribute name for this check boxes?
For example this object:
const plainOptions = [
{name:'alex', id:1},
{name:'milo', id:2},
{name:'saimon', id:3}
];
const defaultCheckedList = [
{name:'alex', id:1},
{name:'milo', id:2}
];
I want to use attribute name as the key in this example.
Problem solved. I should use "Use with grid" type of group checkbox. It accepts object array. The only think I could do was creating a function that inject "label" and "value" to my object. It makes some duplicates but no problem.
function groupeCheckboxify(obj, labelFrom) {
for (var i = 0; i < obj.length; i++) {
if (obj[i][labelFrom]) {
obj[i]['label'] = obj[i][labelFrom];
obj[i]['value'] = obj[i][labelFrom];
}
if (i == obj.length - 1) {
return obj;
}
}
}
// for calling it:
groupeCheckboxify( myObject , 'name');
I'd this same problem and couldn't find any answer on the entire web. But I tried to find a good way to handle it manually.
You can use this code:
import { Checkbox, Dropdown } from 'antd';
const CheckboxGroup = Checkbox.Group;
function CheckboxSelect({
title,
items,
initSelectedItems,
hasCheckAllAction,
}) {
const [checkedList, setCheckedList] = useState(initSelectedItems || []);
const [indeterminate, setIndeterminate] = useState(true);
const [checkAll, setCheckAll] = useState(false);
const onCheckAllChange = (e) => {
setCheckedList(e.target.checked ? items : []);
setIndeterminate(false);
setCheckAll(e.target.checked);
};
const onChangeGroup = (list) => {
if (hasCheckAllAction) {
setIndeterminate(!!list.length && list.length < items.length);
setCheckAll(list.length === items.length);
}
};
const updateItems = (el) => {
let newList = [];
if (el.target.checked) {
newList = [...checkedList, el.target.value];
} else {
newList = checkedList.filter(
(listItem) => listItem.id !== el.target.value.id,
);
}
setCheckedList(newList);
};
useEffect(() => {
setCheckedList(initSelectedItems);
}, []);
const renderItems = () => {
return (
<div classname="items-wrapper">
{hasCheckAllAction ? (
<Checkbox
indeterminate={indeterminate}
onChange={onCheckAllChange}
checked={checkAll}
>
All
</Checkbox>
) : null}
<CheckboxGroup onChange={onChangeGroup} value={checkedList}>
<>
{items.map((item) => (
<Checkbox
key={item.id}
value={item}
onChange={($event) => updateItems($event)}
>
{item.name}
</Checkbox>
))}
</>
</CheckboxGroup>
</div>
);
};
return (
<Dropdown overlay={renderItems()} trigger={['click']}>
<div>
<span className="icon icon-arrow-down" />
<span className="title">{title}</span>
</div>
</Dropdown>
);
}
It looks like the only difference you are talking about making is using an array of objects instead of strings? If that's the case, when looping through the array to create the checkboxes, you access the object attributes using dot notation. It should look something like this if I understand the problem correctly.
From CheckboxGroup component:
this.props.options.forEach(el => {
let name = el.name;
let id = el.id;
//rest of code to create checkboxes
or to show an example in creating components
let checkboxMarkup = [];
checkboxMarkup.push(
<input type="checkbox" id={el.id} name={el.name} key={`${el.id} - ${el.name}`}/>
);
}
'el' in this case refers to each individual object when looping through the array. It's not necessary to assign it to a variable, I just used that to show an example of how to access the properties.

Resources