clear search box after onclick - javascript

I have a search box in my header. i can clear my state after searching but the input doesn't get cleared.
I'm purely using the Searchbox to generate a dropdown that contains links to their respective field. So the input field is purely used to mimic a searc
I tried targeting it with refs but when i finally reach the value i can't use the search anymore.
There is a ref for SearchBarHeader, SearchBox and SearchField. But i'm not sure if that is the correct way to do it.
clearSearchBar = () => {
this.searchBarHeader.current.searchBox.current.searchField.current.value = '';
};
and the code for the searchbox.
class Search extends Component {
state = {
organisationNames: [],
errorMessage: null,
};
searchField = React.createRef();
async componentDidMount() {
const organisations = await getSearch();
this.setState({
organisationNames: organisations,
});
}
searchHandler = (e) => {
const searchValue = e.target.value.toLowerCase();
if (!searchValue) {
this.props.clearSearchResult();
} else {
const result = this.state.organisationNames.filter((organisationName) => {
return organisationName.toLowerCase().includes(searchValue);
});
this.props.setSearchResult(result, () => {
if (this.props.searchResult.length === 0) {
this.setState({
errorMessage: "No Results...",
});
} else {
this.setState({
errorMessage: null,
});
}
});
}
};
clearSearchInput = () => {
this.props.clearSearchResult();
};
render() {
return (
<div className="search">
<div className="form-group">
<input
ref={this.searchField}
type="search"
placeholder="Search for company"
onChange={this.searchHandler}
/>
</div>
<div className="search-result-wrapper">
<ul className="search-results">
{this.props.searchResult === undefined ? (
<Skeleton />
) : (
this.props.searchResult.map((res, id) => {
return (
<Link
key={id}
to={"/r/" + res}
onClick={this.clearSearchInput}
>
<li className="search-item">{res || <Skeleton />} </li>
</Link>
);
})
)}
{this.state.errorMessage === null ? (
""
) : (
<li>{this.state.errorMessage}</li>
)}
</ul>
</div>
</div>
);
}
}
export default Search;

It seems to me that you're missing the "value" attribute on your input that makes it reactive to changes in your state. Grabbing one example from react docs, here's the ideal setup:
this.state = {value: ''};
(...)
handleChange(event) {
this.setState({value: event.target.value});
}
(...)
<input type="text" value={this.state.value} onChange={this.handleChange} />
By following the method above, you won't need to use refs to manually clear the input value. Once the form is submitted, you can simply clear your state...
this.setState({value: ''});
... and your input should be cleared.
Here's the link for the docs: https://reactjs.org/docs/forms.html

You are clearing the ref, not the state. There is also not a value attached to your input, so even if the state was cleared, it will not reflect.
You will of course be able to make the form data more dynamic, without having to set and keep companyName constant.
Here is a simple working example is here: https://codesandbox.io/s/flamboyant-voice-oj85u?file=/src/App.js
export default function App() {
const [formData, setFormData] = useState({ companyName: "" });
const handleChange = (e) => {
setFormData({ companyName: e.target.value });
};
const handleClear = () => {
setFormData({ companyName: "" });
};
return (
<div className="search">
<div className="form-group">
<input
type="search"
name="companyName"
value={formData.companyName}
placeholder="Search for company"
onChange={handleChange}
/>
<button onClick={handleClear}>Clear</button>
</div>
<pre>{JSON.stringify(formData, null, 2)}</pre>
</div>
);
}

Related

React: How to do handleChange and handleSubmit for dynamically adding Input Textboxes?

I am displaying a list. Each item in the list is having a textbox. Textbox is showing DisplayOrder. Please find the sandbox: https://codesandbox.io/s/solitary-butterfly-4tg2w0
In Post API call, how to pass changed textbox values with corresponding description-id as a collection. StoredProcedure is taking description-id and display-sequence as parameters to save changed data in the database.
Please help on form submit how to do this? Thanks
import "./styles.css";
import React from "react";
import XMLParser from "react-xml-parser";
const data = `<?xml version="1.0"?>
<Category>
<description description-id="11" display-sequence="2">testing</description>
<description description-id="15" display-sequence="5">Guide</description>
<description description-id="20" display-sequence="7">test</description>
<description description-id="25" display-sequence="10">Guide</description>
<description description-id="30" display-sequence="12">test</description>
</Category>
</xml>`;
const REQUEST_URL = "";
const axios = {
get: () =>
new Promise((resolve) => {
setTimeout(resolve, 1000, { data });
})
};
class Sort_Descr extends React.Component {
constructor(props) {
super(props);
this.state = {
proddescriptions: [],
proddescription_id: "",
display_sequence: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {}
componentDidMount() {
this.getlistofdescriptions();
}
getlistofdescriptions() {
axios
.get(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then((response) => {
const jsonDataFromXml = new XMLParser().parseFromString(data);
const descriptions = jsonDataFromXml.getElementsByTagName(
"description"
);
console.log(descriptions);
this.setState({
proddescriptions: jsonDataFromXml.getElementsByTagName("description")
});
});
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<div>
<ul style={{ listStyle: "none" }}>
{this.state.proddescriptions.map((item, index) => {
return (
<li key={item.attributes["description-id"]}>
{item.attributes["description-id"]}
<input
type="text"
size="5"
maxlength="3"
value={item.attributes["display-sequence"]}
onChange={this.handleChange}
/>
{item.value}
</li>
);
})}
</ul>
</div>
<input type="submit" name="submit" value="Submit" id="btnsubmit" />
</form>
</div>
);
}
}
export default function App() {
return (
<div className="App">
<h4>Sort list by updating the number in the textbox</h4>
<Sort_Descr />
</div>
);
}
Instead of storing and updating JSON/XML data in state I'd suggest mapping it to a simpler object that is easier to identify and update. React state should be the minimal amount of data necessary to represent your information, store only what you need.
const proddescriptions = descriptions.map(({ attributes, value }) => ({
id: attributes["description-id"],
sequence: attributes["display-sequence"],
value
}));
this.setState({ proddescriptions });
When mapping the state.proddescriptions to the inputs, use the id as the inputs name attribute value for identification when updating.
{this.state.proddescriptions.map((item) => (
<li key={item.id}>
<label>
{item.id}
<input
type="text"
name={item.id} // <-- name attribute
size="5"
maxLength="3"
value={item.sequence}
onChange={this.handleChange}
/>
</label>
{item.value}
</li>
))}
Implement the handleChange to shallow copy the previous state and update the matching array element by id.
handleChange = (event) => {
const { name, value } = event.target;
this.setState((prevState) => ({
proddescriptions: prevState.proddescriptions.map((el) =>
el.id === name
? {
...el,
sequence: value
}
: el
)
}));
};
In the handleSubmit callback use the onSubmit event object to prevent the default form action and access the current state.proddescriptions and map it back to any format your APIs are expecting.
handleSubmit = (event) => {
event.preventDefault();
console.log(this.state.proddescriptions);
// manipulate `state.proddescriptions` into a request payload
};

How to add input validation in react?

I am having a simple form that has firstName and lastName.
<label htmlFor="firstName">First Name: </label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={basicDetails.firstName}
onChange={(event) => handleInputChange(event)}
/>
<label htmlFor="lastName">Last Name: </label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={basicDetails.lastName}
onChange={(event) => handleInputChange(event)}
/>
For this I am trying to add validation.
The validation rules are,
Both fields should accept only text
First name is required and should have at least 4 characters.
If Last name field has value, then it needs to be at least 3 characters.
Things I have tried to achieve this,
components/utils.js
export function isLettersOnly(string) {
return /^[a-zA-Z]+$/.test(string);
}
components/basic_details.js
const handleInputChange = (event) => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
return;
}
setValue((prev) => {
const basicDetails = { ...prev.basicDetails, [name]: value };
return { ...prev, basicDetails };
});
};
On handle input field, I am making the validation to check whether the input has value but I am unable to get the point how to catch the actual validation error and display below respective input box.
Kindly please help me to display the validation message on the respective fields.
Working example:
I suggest adding an errors property to the form data in form_context:
const [formValue, setFormValue] = useState({
basicDetails: {
firstName: '',
lastName: '',
profileSummary: '',
errors: {},
},
...
});
Add the validation to basic_details subform:
const ErrorText = ({ children }) => (
<div style={{ color: 'red' }}>{children}</div>
);
const BasicDetails = () => {
const [value, setValue] = React.useContext(FormContext);
const { basicDetails } = value;
const handleInputChange = (event) => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: 'Can have only letters.',
},
},
}));
return;
}
switch (name) {
case 'firstName': {
const error = value.length < 4 ? 'Length must be at least 4.' : null;
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: error,
},
},
}));
break;
}
case 'lastName': {
const error = value.length < 3 ? 'Length must be at least 3.' : null;
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: error,
},
},
}));
break;
}
default:
// ignore
}
setValue((prev) => {
const basicDetails = { ...prev.basicDetails, [name]: value };
return { ...prev, basicDetails };
});
};
return (
<>
<br />
<br />
<div className="form-group col-sm-6">
<label htmlFor="firstName">First Name: </label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={basicDetails.firstName}
onChange={(event) => handleInputChange(event)}
/>
</div>
<br />
{basicDetails.errors.firstName && (
<ErrorText>{basicDetails.errors.firstName}</ErrorText>
)}
<br />
<br />
<div className="form-group col-sm-4">
<label htmlFor="lastName">Last Name: </label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={basicDetails.lastName}
onChange={(event) => handleInputChange(event)}
/>
</div>
<br />
{basicDetails.errors.lastName && (
<ErrorText>{basicDetails.errors.lastName}</ErrorText>
)}
<br />
</>
);
};
Lastly, check the field values and errors to set the disabled attribute on the next button in index.js. The first !(value.basicDetails.firstName && value.basicDetails.lastName) condition handles the initial/empty values state while the second condition handles the error values.
{currentPage === 1 && (
<>
<BasicDetails />
<button
disabled={
!(
value.basicDetails.firstName && value.basicDetails.lastName
) ||
Object.values(value.basicDetails.errors).filter(Boolean).length
}
onClick={next}
>
Next
</button>
</>
)}
This pattern can be repeated for the following steps.
First you must be getting converting controlled component to uncontrolled component error in your console. For controlled component it is always preferred to use state to set value for the input. And with onChange handler you set the state. I will try to put into a single component so you would get the idea and apply your case
import React, {useState} from 'react';
import {isLettersOnly} from './components/utils'; // not sure where it is in your folder structure
const MyInputComponent = ({value, ...props}) => {
const [inputValue, setInputValue] = useState(value || ''); // input value should be empty string or undefined. null will not be accepted.
const [error, setError] = useState(null);
const handleChange = event => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
setError('Invalid Input');
}
setInputValue(value);
}
return (
<>
<input
value={inputValue}
onChange={handleChange}
{...props}
/>
{error && (
<span className={"error"}>{error}</span>
)}
</>
)
}
export default MyInputComponent;
This is a very rudimentary component. just to show the concept. You can then import this component as your input field and pass necessary props like name, className etc from parent.
import React from 'react';
import MyInputComponent from 'components/MyInputComponent';
const MyForm = (props) => {
return props.data && props.data.map(data=> (
<MyInputComponent
name="lastName"
className="form-control"
value={data.lastName}
));
}

AutoComplete ReactJS / Redux NOT changing the search value

im trying to make the title of the movie clicked to be the in search input value.
exapmle: searched for "batman", clicked on result "batman ninja", search input becomes "batman ninja"
Search Component:
handleSelect is the function im passing to each search result
class MoviesSearch extends Component {
state = {
display: false,
title: ''
};
handleChange = e => {
this.props.movieSearch(e.target.value);
this.setState({
display: false
});
};
handleSubmit = e => {
e.preventDefault();
const { searchInput } = this.props;
this.props.fetchMovie(searchInput);
this.setState({
display: true
});
};
handleSelect = e => {
// this.setState({title: e.target.value})
// this.setState({title: e.target.value})
// this.setState({display: false, title: this.props.movies.Title})
// this.props.movieSearch(this.state.title)
}
render() {
//////////////////////////////////////////
////STATE:
const { movies } = this.props;
//////////////////////////////////////////
//// BUTTONS:
const btnDisabled = (
<button type="submit" disabled>
Search
</button>
);
const btnEnabled = <button type="submit">Search</button>;
///////////////////////////////////////////
//// DISPLAY CONTENT:
const display = (
<div className="dropdown-content">
<MovieList select={this.handleSelect}/>
</div>
);
///////////////////////////////////////////
console.log(this.state.title)
return (
<div className="movieSearch">
<form className="searchForm" onSubmit={this.handleSubmit}>
<div
className={movies.length === 0 ? "dropdown" : "dropdown is-active"}
>
<input
type="text"
placeholder="Enter Movie Name"
onChange={this.handleChange}
value={this.props.movies.Title}
/>
<div className="dropdown-menu">
{this.state.display ? display : null}
</div>
{this.props.searchInput.length <= 0 ? btnDisabled : btnEnabled}
</div>
</form>
</div>
);
}
}
const mapStateToProps = state => ({
searchInput: state.movies.searchInput,
movies: state.movies.movies
});
export default connect(mapStateToProps, { movieSearch, fetchMovie })(
MoviesSearch
);
MovieItem Component:
selectedItem is the function that was passed (handleSelect)
const MovieItem = ({ movie, selectedItem }) => {
return (
<NavLink className="dropdown-item" to="#" onClick={(e)=>selectedItem(e.movie.Title)}>
<img
className="poster"
src={movie.Poster === "N/A" ? "" : movie.Poster}
alt=""
/>
<span className="title">{movie.Title}</span>
</NavLink>
);
};
export default MovieItem;
In MovieItem component, use onClick={(e)=>selectedItem(movie.Title)}
In your parent component, use
handleSelect = value => {
this.setState({title: value})
}

Using multiple filters re-renders component

I use filters in an application which limit the output of a list of users. See the example of the same concept in my codesandbox
The idea is that the list accepts multiple filter values to narrow down the search as much as needed. The first filter works fine when typing in any characters, but switching over and then also typing something inside the second filter input rerenders the list and overwrites the search findings of the first filter.
Main Component:
<div className="App">
<Filters onChange={this.handleFilter} />
<div className="list">
<List users={filteredUsers} />
</div>
</div>
Filter Inputs:
<div className="filters">
<input
name="name"
type="text"
placeholder="Search by name"
onChange={props.onChange}
/>
<input
name="email"
type="text"
placeholder="Search by email"
onChange={props.onChange}
/>
</div>
filter handler:
handleFilter = event => {
const target = event.target;
let updateUsers = this.state.users;
updateUsers = updateUsers.filter(user => {
let type;
if (target.name === "name") {
type = user.name;
} else if (target.name === "email") {
type = user.email;
}
return type.toLowerCase().search(target.value.toLowerCase()) !== -1;
});
this.setState({ filteredUsers: updateUsers });
};
I do plan on using several more filters and the list should not re-render.
What is a way to prevent this or work out a better solution?
How to big websites apply their filters?
You could set filter values of email and name both to state, then trigger a filter function that uses both of those values for the final result. You should also store the values of filter in state, either that of component or a parent. This way you shouldn't have your search values overwritten.
import React, { Component } from "react";
const Filters = ({ onChange, emailFilterValue, nameFilterValue }) => (
<div className="filters">
<input
name="name"
type="text"
placeholder="Search by name"
onChange={onChange}
value={nameFilterValue}
/>
<input
name="email"
type="text"
placeholder="Search by email"
onChange={onChange}
value={emailFilterValue}
/>
</div>
);
class Main extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
nameFilter: "",
emailFilter: ""
};
}
handleFilter = e => {
const { name, value } = e.target;
if (name === "email") {
this.setState({ emailFilter: value });
} else if (name === "name") {
this.setState({ nameFilter: value });
}
this.filterUsers();
};
filterUsers = () => {
const { users, nameFilter, emailFilter } = this.state;
let updateUsers = users.slice();
if (nameFilter.length > "") {
// do your search based on name
}
if (emailFilter.length > "") {
// then apply email filter
}
this.setState({ users: updateUsers });
};
render() {
return (
<div className="App">
<Filters
onChange={this.handleFilter}
emailFilterValue={this.state.emailFilter}
nameFilterValue={this.state.nameFilter}
/>
<div className="list">
<List users={filteredUsers} />
</div>
</div>
);
}
}
Here is a sample of my solution. Generally "big websites" reduce the amount of time filtering as much as possible if it's unnecessary. I would equally add a setTimeout() to not filter on EVERY input immediately, and clear said timeout if the user types within 400ms (for example).
In your filter component I would keep track of the filtered words:
class Filters extends React.Component {
constructor(props) {
super(props);
this.state = {
email: "",
name: ""
};
}
handleChange = event => {
const { onChange } = this.props;
let key = "email";
if (event.target.name === "name") {
key = "name";
}
this.setState({ [key]: event.target.value });
onChange({ email: this.state.email, name: this.state.name });
};
render() {
const { onChange } = this.props;
return (
<div className="filters">
<input
name="name"
type="text"
placeholder="Search by name"
onInput={this.handleChange}
/>
<input
name="email"
type="text"
placeholder="Search by email"
onInput={this.handleChange}
/>
</div>
);
}
}
And in your App component I would create two different filters, this way it puts a priority on your "name" property if it's filled out:
handleFilter = data => {
let updateUsers = this.state.users;
console.log(data);
if (data.name.length > 0) {
updateUsers = updateUsers.filter(user => {
return user.name.toLowerCase().includes(data.name);
});
}
if (data.email.length > 0) {
updateUsers = updateUsers.filter(user => {
return user.email.toLowerCase().includes(data.email);
});
}
this.setState({ filteredUsers: updateUsers });
};

React: Add/Change text input base on a selected option

I am trying to display a new text input based on the selected option. I am able to do that as below but the old value entered is always present no matter what I change the new select option to.
What might be a better way to achieve this? Appreciate any suggestions.
class loadComponent extends React.Component {
static propTypes = {
......
};
static defaultProps = {
....
};
constructor() {
super();
this.state = {
value: ""
};
}
state = {
...
};
reset = (selected) => {
this.setState({
selectedInputName: selected.target[selected.target.selectedIndex].text,
selectedInputId: selected.target.value
});
};
makeTextInput = () => {
return (
<TextInput
label={this.state.selectedInputName}
placeholder={`Please enter ${this.state.selectedInputName} here!`}
onBlur={event => this.setState({[this.state.selectedInputId]: event.target.value})}
showClear
value={this.state.value}
/>
);
};
render() {
let newInputText = '';
if (this.state.selectedInputId !== '') {
newInputText = this.makeTextInput();
}
return (
<Select
label="What would you like to search with?"
options={this.props.searchOptions}
onChange={selected => this.reset(selected)}
/>
<div className="search margin_bottom_large">
{newInputText}
);
makeTextInput function creates a new object, but from react's perspective it's the same component because react distinguishes them by looking at their type and key. To make react recreate an element, you have to change one of those values.
This code changes type of NewInputText element each time it renders (because NewInputText always refers to a new function):
reset = (selected) => {
this.setState({
selectedInputName: selected.target[selected.target.selectedIndex].text,
selectedInputId: selected.target.value
});
};
makeTextInput = () => {
return (
<TextInput
label={this.state.selectedInputName}
placeholder={`Please enter ${this.state.selectedInputName} here!`}
onBlur={event => this.setState({[this.state.selectedInputId]: event.target.value})}
showClear
/>
);
};
render() {
let NewInputText = () => '';
if (this.state.selectedInputId !== '') {
NewInputText = () => this.makeTextInput();
}
return (
<Select
label="What would you like to search with?"
options={this.props.searchOptions}
onChange={selected => this.reset(selected)}
/>
<div className="search margin_bottom_large">
<NewInputText />
);
This code assigns different key to TextInput each time:
reset = (selected) => {
this.setState({
selectedInputName: selected.target[selected.target.selectedIndex].text,
selectedInputId: selected.target.value
});
};
makeTextInput = () => {
return (
<TextInput
key={Math.random()}
label={this.state.selectedInputName}
placeholder={`Please enter ${this.state.selectedInputName} here!`}
onBlur={event => this.setState({[this.state.selectedInputId]: event.target.value})}
showClear
/>
);
};
render() {
let newInputText = '';
if (this.state.selectedInputId !== '') {
newInputText = this.makeTextInput();
}
return (
<Select
label="What would you like to search with?"
options={this.props.searchOptions}
onChange={selected => this.reset(selected)}
/>
<div className="search margin_bottom_large">
{newInputText}
);
Is there a better way to do this?
I think using the controlled component design pattern would be ideal in this situation.
class SomeInput extends Component {
constructor() {
super();
this.state = {
value: "" //Keep value state here
};
}
render() {
/* Doing something like the following will allow you to clear
the input value simply by doing the following..
this.setState({ value: '' });
*/
return (
<Input
type="text"
onChange={e => this.setState({ value: e.target.value })} // set value state to entered text
value={this.state.value} // set value of input to value piece of state
/>
);
}
}
This will give you full access to the current value of the input, thereby allowing you to set it to anything or clear it at anytime or for any event simply by doing the following this.setState({ value: '' }).
Don't know the rest of your code which could be handy but you can try:
makeTextInput = () => (
<TextInput
label={this.state.selectedInputName}
placeholder={`Please enter ${this.state.selectedInputName} here!`}
onBlur={event => this.setState({[this.state.selectedInputId]: event.target.value})}
showClear
/>
);
change = (event) => {
this.setState({
selectedInputName: event.target.value
});
}
render() {
return (
<Select
label="What would you like to search with?"
options={this.props.searchOptions}
onChange={this.change}
/>
<div className="search margin_bottom_large">
{this.makeTextInput()}
);
What you need to do is only setState properly. Each time you change a state the component will be re-rendered which means that the makeTextInput method will be triggered.
EDIT:
by the way, it is good idea to use getter for returning component in render method, in this case:
get textInput() {
return (
<TextInput
label={this.state.selectedInputName}
placeholder={`Please enter ${this.state.selectedInputName} here!`}
onBlur={event => this.setState({[this.state.selectedInputId]: event.target.value})}
showClear
/>
);
}
and then in render method, just use {this.textInput}

Categories

Resources