Searching and Displaying Array with React JS - javascript

I'm currently new in react js and I want to implement a Search in react on array. What I'm trying to do is implement search functionality so that when the user enters in 'E' in "textbox", he/she should see only the names that contain the character 'E' and luckily its working fine till here. Ideally I want that When a user start taking a step back by pressing Backspace or Deleting entered character(s), the list of names should get loaded back with all non matching values and vice versa. Apart, I also I want to disable spacebar in textbox with an alert message when entering spacebar in textbox. And on clicking 'OK' of an alert message the screen that comes after should be same like as it was before entering spacebar(With all names loaded and "textbox" asking user to enter something).
What my code is providing only names matching with the first character even "textbox" is empty and In case of spacebar, its allowing to take next input(just after spacebar) after clicking on alert message's 'OK'. I think its taking spacebar as input.
My code's functionality is neither rolling back of names on Backspace or Deleting a character nor a screen is setting to the same as it was before entering spacebar. I'm getting my desired results by refreshing a tab which I don't want to do. Please help me to resolve the issue
My code is something like as below:
import React from 'react';
import NameItem from "./component/NameItem";
class App extends React.Component {
constructor(props) {
super(props)
this.state = { term: '', names: ["Elon Musk","Bill Gates","Tim Cook","Richard Branson","Jeff Bezos","Warren Buffet","The Zuck","Carlos Slim","Bill Gates","Larry Page","Harold Finch","Sergey Brin","Jack Ma","Steve Ballmer","Phil Knight","Paul Allen","Woz"], filteredData:[] };
}
renderData(filteredData) {
if(filteredData) {
this.setState({names: filteredData});
}
return filteredData.map(item => <NameItem item={item}></NameItem>);
}
filterNames(namePass) {
if(namePass && this.state.names) {
if(namePass === ' ') {
alert('please dont enter space.')
return;
}
let filteredData = this.state.names.filter((item) => {
return item.toLowerCase().startsWith(namePass.toLowerCase())
});
console.log(filteredData);
this.setState({filteredData: filteredData});
if (filteredData) {
this.renderData(filteredData);
}
}
}
render() {
return (
<>
<div>
<label>Search Person: </label>
<input type="text" id="searchEmp"
placeholder="Enter Person's Name"
onChange={(event) => {
this.setState({term: event.target.value});
this.filterNames(event.target.value);
}}/><br/>
</div>
<ul>
<NameItem item={this.state.names}></NameItem>
{
}
</ul>
</>
);
}
}
export default App;

I would recommend that you don't store the filteredData in the state but that's just a preference. You already have the term stored so you can map to the filtered values in your render().
What you absolute cannot do is override this.state.names with just the filtered names, which you are doing here:
if(filteredData) {
this.setState({names: filteredData});
}
When you do that you lose every name which isn't in the filtered array, so you cannot possibly go backwards and show the full list again.
There's no point in setting this.state.name to filteredData because you just render filteredData directly.
class App extends React.Component {
// don't actually need a constructor if there are no props
// can initialize state like this
state = {
term: "",
names: ["Elon Musk","Bill Gates","Tim Cook","Richard Branson","Jeff Bezos","Warren Buffet","The Zuck","Carlos Slim","Larry Page","Harold Finch","Sergey Brin","Jack Ma","Steve Ballmer","Phil Knight","Paul Allen","Woz"]
};
render() {
const filteredData = this.state.names.filter(
(name) =>
// match all names if term is empty
this.state.term === "" ||
// otherwise see if the name starts with the term
name.toLowerCase().startsWith(this.state.term.toLowerCase())
);
return (
<>
<div>
<label>Search Person: </label>
<input
type="text"
id="searchEmp"
placeholder="Enter Person's Name"
value={this.state.term} // make this a controlled input, prevents space from showing
onChange={(event) => {
if ( event.target.value.endsWith(' ')) {
alert('Please don\'t enter space.');
} else {
this.setState({ term: event.target.value });
}
}}
/>
</div>
<div>Showing {filteredData.length} Matches</div>
<ul>
{filteredData.map((name) => (
<li
key={name} // should have a key when using map
>
{name}
</li>
))}
</ul>
</>
);
}
}
Note: I had some strange behavior due to using the name as the key because you had "Bill Gates" in the list twice. I deleted the duplicate item and now it's fine. But keys must be unique, so do not use the name as the key if there is a chance of duplicates.

Related

How do I add input values to an object and display the results in React?

I'm working on a CV Application in React and part of the process is adding your previous Education and displaying it so a potential employer can see it. I have an input set up so users can enter information and upon hitting save, the values are sent and displayed in a separate div.
This form also only shows up when I click the +Add Education button at the bottom.
<form>
<label htmlFor="school">School</label>
<input type="text" name="school" onChange={(e) => this.setSchool(e.target.value)} />
<label htmlFor="study">Field of Study</label>
<input type="text" name="study" onChange={(e) => this.setStudy(e.target.value)} />
<button onClick={this.onSubmit} className="save">Save</button>
</form>
<button onClick={this.toggleForm} className="form-btn">
+ Add Education
</button>
I also have this onChange event that's attached to each input which takes the values from each input and displays them to a div.
onChange={(e) => this.setSchool(e.target.value)}
Here is the code I'm using for both setSchool and setStudy.
class Education extends Component {
constructor(props) {
super(props);
this.state = {
school: "",
study: "",
showOutput: false,
};
setSchool = (value) => {
this.setState({ school: value });
};
setStudy = (value) => {
this.setState({ study: value });
};
I also have this code which is placed above my other jsx code.
render() {
return (
<>
<div>
{this.state.showOutput && (
<div className="experience">
<p>{`School: ${this.state.school}`}</p>
<p>{`Field of Study: ${this.state.study}`}</p>
</div>
)}
This code exists for the purpose of displaying only when the setSchool and setStudy values aren't empty. When the user fills them out and clicks save, it displays in a div above the form.
Now here's where I need help
Everything about this code works as intended. However, when I go to click +Add Education for the second time, the values are simply being overriden. So instead of having two separate divs like this:
Div 1
School: University of Test
Field of Study: Comp. Sci
Div 2
School: University of Test 2
Field of Study: Comp. Sci 2
I'm only getting one because the second div of inputted information is overriding the first.
School: University of Test 2
Field of Study: Comp. Sci 2
I've been trying so solve this issue and the only thing I can think of is to add each input value to an object rather than just adding it to setState:
const study = [
{
school: school,
fieldofStudy: fieldofStudy,
},
];
But I can't seem to figure out how to add the values to this object and then display the results. I know if I displayed the results by looping through the array instead of e.target.value I could get them to show up but everything I've tried thus far hasn't worked.
Add a state array to keep track of your school/study pairs (this.state.education, for example) and then add them with the button is pressed like:
setEducation = () =>{
this.setState((state)=>({education: [...state.education, {study: this.state.study, school: this.state.school}]}))
And then just loop through this.state.education in your render

how to set usestate value in ternery operator pass a callback functoin setState()

<label htmlFor="rank" className="font-bbold">Rank:
</label>
<InputNumber
id="rank"
value={singlepoints.rank}
onValueChange={(e) =>
onRankChange(e, index)}
required>
</InputNumber>
{
// (singlepoints.rank === 0 || singlepoints.rank === null ) ? () => console.log('fjdkfhdfhd') : null
// ||
(singlepoints.rank === 0 ||
singlepoints.rank === null ) ?
**(() => setInvalid(true))** &&
<small className="p-error ml-6">
Rank is required.</small>
: null
}
Hi this is not how you handle state change.
First to validate something you usually have onBlur event (it fires when the input looses focus)
Second instead of trying to running code in ternary you have to do it in the useEffect hook:
useEffect(() => {
if (singlepoints.rank === 0 ||
singlepoints.rank === null )
setInvalid(true)
}, [singlepoints])
However I can recommend use formik and yup to do the validation, once you figure it out it will make your life much and much easier in terms of form validation and change handling
You're off to a good start here.
I see that you have a label and an InputName component, and it looks like the "rank" must be required and not zero.
I want to start by making a reference to React "controlled component" which essentially is "an input form element (for example <input />) whose value is controlled by React".
The code below will give you an idea of how to rewrite your code (please note that I added a submit button to handle the conditional statement):
import { useState } from "react";
function App() {
const [singlePointsRank, setSinglePointsRank] = useState("");
const [invalid, setInvalid] = useState(false);
function handleSubmit(e) {
e.preventDefault();
if (singlePointsRank == 0) {
setInvalid(true);
} else {
//do something else if the user provides a valid answer
}
}
return (
<div className="App">
<form>
<label>Rank:</label>
<input
required
name="rank"
value={singlePointsRank}
onChange={(e) => setSinglePointsRank(e.target.value)}
/>
<button onClick={handleSubmit} type="submit">
Submit
</button>
</form>
{/*The error message below will only be displayed if invalid is set to true */}
{invalid && <p>Please provide a valid rank number.</p>}
</div>
);
}
export default App;
Note that the required property in the input element prevents the form from being submitted if it is left blank. Therefore, you do not really need to check for singlePointsRank === null.
This way React will update the state variable singlePointsRank as the user types something in it. Then, when the user clicks on the submit button, React will call the handleSubmit function to which you can add your conditional statement set your second state variable invalid to false.
Regarding ternary operator, I do not recommend using it in this case since you might want to add some extra code to your conditional statement, such as re-setting invalid back to false, removing the error message and clearing the input field to allow the user to submit another rank number.
See the example below just to give you an idea:
import { useState } from "react";
function Random2() {
const [singlePointsRank, setSinglePointsRank] = useState("");
const [invalid, setInvalid] = useState(false);
function handleSubmit(e) {
e.preventDefault();
if(singlePointsRank == 0) {
setInvalid(true);
setSinglePointsRank("");
} else {
// do something else and clear the input field and reset invalid back to false
setInvalid(false);
setSinglePointsRank("");
}
}
function handleInputChange(e) {
if(setSinglePointsRank === "") {
setInvalid(false);
}
setSinglePointsRank(e.target.value)
}
return (
<div className="App">
<form>
<label>Rank:</label>
<input
required
name="rank"
value={singlePointsRank}
onChange={handleInputChange}
/>
<button onClick={handleSubmit} type="submit">
Submit
</button>
</form>
{/*The error message below will only be displayed if invalid is set to true */}
{invalid && <p>Please provide a valid rank number.</p>}
</div>
);
}
export default Random2;
Again, since the main question here is regarding ternary operators and setting state, although I do not recommend using it in this case, you could rewrite the initial handleSubmit function as follows just to play around with your code:
function handleSubmit(e) {
e.preventDefault();
singlePointsRank == 0 ? setInvalid(true) : console.log("do something else");
}
To get a little more practice, you could rewrite
{invalid && <p>Please provide a valid rank number.</p>}
as
{invalid ? <p>Please provide a valid rank number.</p> : ""}

Hande input onChange event in dynamic react js form

I have a Form component with a title input and a dynamic number of ingredient inputs (the user can add and remove them with corresponding buttons). Most of the form functions properly. However, I receive a warning that I have passed a value prop to a form field without an onChange handler for the ingredient input element(s).
In render() for my Form component, this part should dynamically generate inputs (EDIT: by mapping from an array of ingredients in my Form's state):
{this.state.ingredients.map((element, i) => {
return (
<div key={i}>
<input
type='text'
placeholder='new ingredient'
value={element || ''}
onChange={this.handleIngredientChange.bind(this)}
/>
</div>
);
})}
I thought these inputs would connect to my handleIngredientChange method to update the state of the ith ingredient name when the user changes the input value:
handleIngredientChange(i, event) {
let ingredients = [...this.state.ingredients];
ingredients[i] = event.target.value;
this.setState({ingredients});
}
And that this method would appropriately allow react to control each input element.
I seem to be misunderstanding this and/or .bind(), because I clearly assigned onChange={this.handleIngredientChange.bind(this)} to the ingredient input element in my map function. When the user types in one of these inputs, we get `TypeError: undefined is not an object (evaluating 'event.target').
Perhaps the root of the problem is that when the user types in the input, handleIngredientChange is not correctly set up to get the user's desired input value, but I cannot see where the error is.
I have looked at a lot of similar questions and have tried to implement their answers to no avail. Could anyone point me in the right direction on why I have not handled the onChange event properly? I'd be more than happy to explain my intentions further if you need me to. Many thanks in advance.
Full code:
export default class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
title: '',
ingredients: [],
};
}
handleTitleChange(event) {
let title = this.state.title;
title = event.target.value;
this.setState({title});
}
handleIngredientChange(i, event) {
let ingredients = [...this.state.ingredients];
ingredients[i] = event.target.value;
this.setState({ingredients});
}
removeClick(i) {
let ingredients = [...this.state.ingredients];
ingredients.splice(i, 1);
this.setState({ingredients});
}
addClick() {
let ingredients = [...this.state.ingredients];
ingredients.push('');
this.setState({ingredients});
}
handleSubmit(event) {
console.log('submit request logged')
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
placeholder='recipe title'
value={this.state.title || ''}
onChange={this.handleTitleChange.bind(this)}
/>
{this.state.ingredients.map((element, i) => {
return (
<div key={i}>
<input
type='text'
placeholder='new ingredient'
value={element || ''}
onChange={this.handleIngredientChange.bind(this)}
/>
</div>
);
})}
<input
type='button'
value='add'
onClick={this.addClick.bind(this)}
/>
<input
type='Submit'
value='save'
onClick={this.handleSubmit.bind(this)}
/>
</form>
)
}
}
I'm not sure if this helps
try change
<input
type="text"
placeholder="new ingredient"
value={element || ""}
id={i}
onChange={this.handleIngredientChange.bind(this)}
/>
and then alter
handleIngredientChange(event) {
let ingredients = [...this.state.ingredients];
ingredients[event.target.id] = event.target.value;
this.setState({ ingredients });
}
https://codesandbox.io/s/react-playground-forked-yf7rpm

can't edit input with prop value

I have a string , in certain places I need to insert input tags with values. Everything displays fine , but I can't delete or edit values in input. What is wrong with that input?
editModalText() {
let modalMessage="Hello, my name is /# Ann #/. I'm working for /# IStaff #/, could you please call me back"
return (
<div>
{modalMessage
.split("/")
.map((text, idx) =>
text.includes("#") ? this.replaceCharacter(idx, text) : text,
)}
</div>
)
}
replaceCharacter(idx, text) {
let formattedText = text.replace(/#/g, " ")
return (
<input
key={idx}
value={formattedText}
onChange={e => this.setState({input:e.target.value})}
/>
)
}
replace value={formattedText} with defaultValue={formattedText}
this way input will be editable. it will show default value on first render and as you type you'll store that value in your state.
you can read more about controlled and uncontrolled components in the docs
I think you need to bind the input value and the state together. I am not sure how you're currently calling replaceCharacter but I would do something like this :
replaceCharacter(idx) {
return (
<input
key={idx}
value={this.state.input.replace(/#/g, " ")}
onChange={e => this.setState({input:e.target.value})}
/>
)
}
This way when you update your state with the onChange event the value of the state will be populated in the input.

How can I render out to page no results when my search field is empty, but still render asynchronously when searching?

I'm rendering to page a bunch of buttons based on what a user is typing. Basically the user starts typing and with each letter pressed a new set of buttons is rendered to the page where an obj.content contains the string being typed. This is all working fine, but I have one small problem. When a user first enters the program all of the buttons are rendered out to the page showing all options. I would like to show zero buttons if nothing is being searched for.
As of right now the normal state is looking for any matches of '', which every string that is searched contains so every button is rendered out to the screen.
Is there a way to render out zero buttons if my search fields are empty?
I have tried...
const RenderSearchResults = () => {
<div>
{renderResults}
</div>
}
const renderResults = this.props.data.filter(obj => {
return obj.content.includes(this.state.keyToFind);
}).map((obj, idx) => {
return (
<button name={obj.name} value={this.state.btnToFind} key={idx}>{obj.title} </button>
)
});
// FROM THE MAIN COMPONENT RETURN
return (
<input type="text name="keyToFind" placeholder="SEARCH" value={this.state.keyToFind} onChange={this.handleChange.bind(this} /> <br />
{this.state.keyToFind !== '' ? <RenderSearchResults /> : console.log("no input")}
)
// THIS WORKS BUT STARTS WITH ALL BUTTONS RENDERED OUT
return (
<input type="text name="keyToFind" placeholder="SEARCH" value={this.state.keyToFind} onChange={this.handleChange.bind(this} /> <br />
{renderResults}
)
However the above method doesn't update when a user starts typing. The only way I can get the search / render to work asynchronously is if I just place {renderResults} into the main component return without the if statement checking to see if the search field is empty. However, this results in all possible buttons being rendered out to page as the normal state.
Anyway to start with nothing being rendered out to the page?
I created a small example similar to what you are describing but much more simplified. Here I am checking if keyToFind is empty string and returning an empty array directly from the method that does the rendering.
class RenderButtons extends React.PureComponent {
state = {
keyToFind: ''
}
renderResults = () => {
if (this.state.keyToFind === '') return [];
const renderResults = ['a', 'b', 'c', 'aa']
.filter(obj => {
return obj.indexOf(this.state.keyToFind) >= 0;
})
.map((obj, idx) => {
return (<button name={obj} value={obj} key={idx}>{obj}</button>)
});
return renderResults;
}
handleChange = (event) => {
this.setState({ keyToFind: event.target.value });
}
render () {
const renderResults = this.renderResults();
return (
<div>
<input type="text" value={this.state.keyToFind} onChange={this.handleChange} />
{renderResults}
</div>
);
}
}
Here is a working example on codesandbox.

Categories