ES6 includes but case insensitive - javascript

I know I can loop through array and just change value toLowerCase().
I'm curious if there's a reactjs or es6 method that can check if the value is in array.
On addTodo function. You can see that I use includes but this method is case sensitive.
Here's what I have
class Todo extends React.Component {
render() {
return (
<div className="todo">
<input type="checkbox" />
<p>{this.props.children}</p>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.todos = [
'Get Up from bed',
'Eat Breakfast'
];
}
eachTodo(task, i) {
return (
<Todo key={i} index={i}>{task}</Todo>
)
}
addToDo() {
let task_title = this.refs.newTodo.value;
if(task_title !== '') {
let arr = this.todos;
var arr_tlc = this.todos.map((value) => {
return value.toLowerCase();
})
if(arr_tlc.indexOf(task_title.toLowerCase()) === -1) {
arr.push(task_title);
this.setState({
todos: arr
});
}
}
}
render() {
return (
<div className="main-app">
<input ref="newTodo" placeholder="Task"/>
<button onClick={this.addToDo.bind(this)}>Add Todo</button>
<div className="todos">
{this.todos.map(this.eachTodo)}
</div>
</div>
);
}
}
Any help would be appreciated. Thanks!

I would suggest using a Map instead of an array for your todo list. A Map has the advantage that it provides key-based look-up in constant time, and does not store duplicate entries with the same key. You could then use the lower case variant as the key, and the original (mixed-case) string as the value for that key.
You could define todos as a Map instead of an array, and use the lower case string as the key:
constructor(props) {
super(props);
this.todos = new Map();
this.addToDo('Get Up from bed');
this.addToDo('Eat Breakfast');
}
Then, to add the task becomes very straightforward, as a Map overwrites duplicates:
addToDo() {
this.todos.set(task_title.toLowerCase(), task_title);
this.setState({
todos: this.todos
});
}
In rendering you would need to use Array.from(..., <map-function>), as .map is not defined for Map objects:
<div className="todos">
{Array.from(this.todos, this.eachTodo)}
</div>
Which means the eachToDo method will receive a pair of strings (an array), instead of a string, and we want to display the second of the pair, so using index [1]:
eachTodo(task, i) {
return (
<Todo key={i} index={i}>{task[1]}</Todo>
)
}

No, the only built-in methods are case sensitive. You'll need that loop and either toLowerCase or a case-insensitive regular expression (perhaps tucked away in a useful reusable function).

Related

Can't get value of a map index from a child copmonent to pass as function argument in React

This is the first project I'm building in react so i apologise for any errors.
I have three components in React. The method is declared in the top level with two parameters. I pass this method to a child component. In this component I want to call the method inside a map, using the index of the map as an argument. Here is the code:
class App extends Component {
constructor() {
super();
this.state = {
workExperience: [
{ points: [{id: 1, point: "firstPoint"}, {id: 2, point: "secondPoint"}]
]
}
this.captureTask = this.captureTask.bind(this);
}
captureTask(e, index){
//clone state, want to access array using map index in child component below and capture value of the input to update this state
clonedState.points[index] = e.target.value
console.log(index) //undefined
}
render(){
return(
<ChildComponent captureTask={(e, index) => this.captureTask(e, index)} />
)
}
class ChildComponent extends Component {
constructor(props){
super()
}
render(){
return(
<div>
{this.props.points.map((point, index) => {
return <Input key={point.id} handleChange={(e, index) => this.props.captureTask(e, index)} />
})}
</div>
)
}
My issue is in the captureTask method. The index value returns undefined when it gets back to the function. However within the Map I'm able to log index value without any issue.
Is there something I should be doing differently in order to be able to use the map index as an argument in a parent method?
Thanks for any help.
Change the handleChange prop to the following:
handleChange = { (e) => this.props.captureTask(e, index) }
i.e. Remove the index from the arguments list of the arrow function.

React JS search in Array (character by character)

This is my first program in React. I've as below:
import React from 'react';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
name : ''
}
}
render() {
return (
<>
<div>
<label for="searchEmp">Search Person: </label>
<input type="text" value={this.state.name} id="searchEmp"
placeholder="Enter Person's Name"
onChange={event => this.setState({name: event.target.value})}/><br/>
</div>
{["John","Steve","Alen","Stephen",
"Smith","Alex","Jack","Andy","Jacky"].map(item => {
return <div>{item}</div>})}
</>
);
}
}
export default App;
In Output, I've something like as
I want to filter this list character by character. For e.g. When I enter S the list should filtered with names starting from S as below:
Next, If I enter t after S the list should contain only names as:
and so on. How can I get this? Apart, as a newbie to React, Is my code okay? Thanks in advance.
So, first of all I think the good practice would be ti either keep the list as constant one if it's fixed or some variable at class level,
Scenario you are asking for is preety much like you want to filter out the list each time, so you can filter out the list kept in variable using a function & could return the list from the function to use
let namesList = ['ab', 'fg', 'test'];
input = 'a';
let rgxp = new RegExp(input, "g");
function findFilterNames() {
return namesList.filter(x => x.match(rgxp));
}
test = findFilterNames();
console.log(test);
I'll try my best to answer but apologies in advance if this doesn't work, I've just started to learn React as well.
Whenever you use setState, the component will re-render itself, so keeping that in mind, you could use the following:
Create a function that looks for this.state.name and checks to see if its blank or has an actual value. If it does have a value, it will either use filter or map to run through the name array and return div elements with the values placed inside.
See code below:
class App extends React.Component {
constructor(props) {
super(props)
this.state = { name: '' };
}
renderNames() {
this.names =["John","Steve","Alen","Stephen","Smith","Alex","Jack","Andy","Jacky"];
if(this.state.name !== '') {
return this.names.map((name) => {
if (name.includes(this.state.name)){
return <div>{name}</div>;
}
});
} else {
return this.names.map((name) => {
return <div>{name}</div>;
});
}
}
render() {
return (
<>
<div>
<label for="searchEmp">Search Person: </label>
<input type="text" value={this.state.name} id="searchEmp"
placeholder="Enter Person's Name"
onChange={event => this.setState({name: event.target.value})}/><br/>
</div>
{this.renderNames}
</>
);
}
}
I will keep it simple and easy, you must use a function in Javascript called filter() with includes() function.
I will assume the search term that coming from the input called term, and the array that you need to filter it called names
const names = ["John","Steve","Alen","Stephen","Smith","Alex","Jack","Andy","Jacky"];
let term = "s";
const searchResults = names.filter(name => {
return name.toLowerCase().includes(term.toLowerCase());
});
// the result will be array that contain the following ["Steve","Stephen","Smith"]
when you consloe.log(searchResults) you will have ["Steve","Stephen","Smith"] because term value is "s" so you get the all names that have "s" character.
We change the array items to lowercase using toLowerCase() to avoid the case-senstive if there is character upper case
you can test the code here just put the name of the filtered searchResults at the end of the code
I see this way as the fastest way to search by character.
this is a repo as an example using Reactjs :
https://github.com/Tawfeekamr/react-search-in-array-character-by-character.git

Why my setState doesn't accept this object?

I'm working in a to-do list on React and when method deleteItemis called, I get Uncaught Error: Objects are not valid as a React child (found: object with keys {text}) but I don't understand the reason
class Form extends Component{
constructor(props){
super(props);
this.state = {
task: "",
list: []
}
this.saveItem = this.saveItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
saveItem(event){
let state = this.state;
event.preventDefault();
if(this._taskInput.value !== ""){
state.list.push({ text: this._taskInput.value });
this.setState(state);
}
this.setState({task: ""});
}
deleteItem(index){
const {list} = this.state;
this.setState({
list: list.filter((item,itemCurrent) => {
// console.log(item.key, index);
return (itemCurrent !== index);
}),
});
}
render(){
return(
<Fragment>
<form onSubmit={this.saveItem}>
<input value= {this.state.task} id="to-do"
onChange={(e) => this.setState({task: e.target.value})}
ref={event => this._taskInput = event} type="text" className="validate"/>
<label htmlFor="to-do">My tasks</label>
</div>
<button type="submit" name="action">Submit
</button>
</div>
{{this.state.task}}
{this.state.list}
</form>
</div>
<Table list= {this.state.list} deleteItem = {this.deleteItem}/>
first of all there's a big conceptual error here:
if (this._tarefaInput.value !== ""){
state.list.push({ text: this._taskInput.value });
this.setState(state);
}
you are directly editing the state with that push function, you should never do this in react as it will lead to unexpected consequences, this is how you should update the state:
if (this._tarefaInput.value !== ""){
//use the spread operator (...) to create a copy of the list in the state
const newList = [... state.list];
// push the new element tot the new list
newList.push({ text: this._taskInput.value });
// update the state
this.setState({list: newList});
}
Now the error you are getting is likely happening because somewhere in your code (possibly inside <Table/>) you are trying to print each element of the list array as a react component. You haven't shared the part where the list is rendered, but I'm guessing you are doing something like this:
//somewhere inside a render:
{
list.map(Element => <Element />);
}
//Proper way of doing it
{
list.map(element => <p>{element.text}</p>);
}
I can try to help more if you share more of your code and the entitre log with the the error description (with file and line number)
The issue is this line inside your render: {this.state.list}. You can render an array, but you can't render an object. The solution is to map over the array and output some JSX such as the following. Let's assume you have a list of objects with a name and id property:
{this.state.list.map(item => (<div key={item.id}>{item.id}</div>))}

ReactJS: Remove item from list without ID?

I have an issue I can't seem to crack even after checking out a few other posts on here and trying a few things out. I am playing around with React and making a quick todo list. Easy enough as I can add new names and display them on the page as intended. I want to be able to delete items that I choose and for that I was looking around and saw others doing something like this to delete items:
deleteName(id, e) {
const { names } = this.state;
this.setState({
names: names.filter(name => name.id !== id)
});
}
That made sense to me but I wasn't adding any id's to my <li> items so I thought I could just do:
this.setState({
names: names.filter(name => name !== name)
});
But this will just delete the whole list. What am I doing wrong? Should I restructure how I add names to the array to have an id and check that? I'll post the full component code below. Any help I can get is always appreciated. Thanks guys.
class ContactListPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
names: []
};
this.addName = this.addName.bind(this);
this.deleteName = this.deleteName.bind(this);
}
addName(name) {
const { names } = this.state;
if (name === '') {
console.log('add a name first!')
} else {
this.setState({
names: names.concat(name)
});
}
}
deleteName(id, e) {
const { names } = this.state;
this.setState({
names: names.filter(name => name !== name)
});
}
render() {
const { names } = this.state;
const named = names.map((name, i) => (
<Card key={i} className='todo-list'>
<CardText>
<li>{name}
<FloatingActionButton className='fab-delete' mini onClick={this.deleteName}>
<i className="material-icons fab-icon-delete" style={{color: 'white'}}>-</i>
</FloatingActionButton>
</li>
</CardText>
</Card>
));
return (
<div className='contact-list'>
<div className="field-line">
<NewName addName={this.addName} />
<ul className='new-name'>
{named}
</ul>
</div>
</div>
);
}
}
You're comparing each name with itself, that will yield an empty list regardless of what's in it (except I guess NaN?).
names.filter(name => name !== name)
I think you want to pass the name into your delete function from the view. It's been a while since I've done React, but you could probably do this with a lambda in the JSX.
deleteName(nameToDelete) {
const { names } = this.state;
this.setState({
names: names.filter(name => name !== nameToDelete)
});
}
render() {
// Simplified to focus on the onClick change
return names.map(name => <Card>
...
<FloatingActionButton onClick={(e) => this.deleteName(name)} ... />
...
</Card>);
}
If you need to worry about duplicate names, then you can pass the current index into deleteName() rather than the string itself. Up to you if that's necessary or not.
It's hard to tell, but are you referencing name when the passed argument to your callback is actually called id?
Try this:
deleteName(name) {
this.setState((prevState) => ({
names: prevState.names.filter(_name => name !== _name);
}));
}
And change the following:
onClick={this.deleteName.bind(null, name)}
In the callback for the filter, name !== name will always be false since an object is identical to itself. i.e name === name. Therefore the expression names.filter(name => name !== name) will return an empty array and erase your results. You can change your onClick handler to something like this to pass in your id: onClick={(e) => this.deleteName(i, e)} and then use names.filter((name, i) => i !== id) to 'delete' the name, although I imagine you would would probably want to make a copy of the names array and then splice the corresponding name out of it.

React this.props don't update

Can someone help me to understand why this.props doesn't update after i filter it?
Here the slim version of my code
export default class AutoList extends React.Component {
constructor(props) {
super(props);
this.state = {
filterValue: 'all',
isHidden: true,
autoOptValue: ''
}
}
handleOnChangeBrand(evt) {
let selectedValue = evt.target.value;
this.setState({optionValue: selectedValue});
let filtered = this.props.autos.filter((auto) => {
if(auto.brands){
return auto.brands[0] === selectedValue;
}
return false;
});
console.log(this.props.auto) // still same number
console.log(filtered) // less autos. Actual filtered array
}
render() {
let autoDetail = this.props.autos.map(auto => {
return (
<Auto
key={auto.id}
id={auto.id}
name={auto.name}
brands={auto.brands ? auto.brands : false}/>
);
});
return (
<div>
<section>
<select id='autoFilter' className={this.state.isHidden ? 'u-is-hidden' : ''} onChange={this.handleOnChangeBrand.bind(this)} value={this.state.autoOptValue}>
<option value='brand1'> brand1 </option>
<option value='brand2'> brand2 </option>
</select>
</section>
<ul>
{autoDetail}
</ul>
</div>
);
}
So basically i have this.prop.auto is an array of 100 auto, each of them is an object with brand (which is another array) with 2,3 brands each.
I was able to filter, since filtered give me back an array with filtered autos, the correct ones.
But after that, this.props.auto doesn't update, nor does the UI.
I did something similar but sorting the auto by the brands and it works smoothly.
I don't get the difference here
this.props is effectively immutable within a component, so you cannot update the value of this.props.autos. Array#filter is also a pure function, so the array being filtered is not altered, but a new filtered array is returned. This is why when you log filtered in your function you see the filtered array, but this.props.autos is unchanged.
The simple answer to this is to do the filtering within your render method - I have added an initial state for optionValue of false, and within the filter method checked for this and not filtered if it is still false.
export default class AutoList extends React.Component {
constructor(props) {
super(props);
this.state = {
filterValue: 'all',
isHidden: true,
autoOptValue: '',
optionValue: false
}
}
handleOnChangeBrand(evt) {
let selectedValue = evt.target.value;
this.setState({optionValue: selectedValue});
}
render() {
const { optionValue } = this.state;
const autoDetail = this.props.autos
.filter((auto) => {
if (!optionValue) return true;
if(auto.brands){
return auto.brands[0] === optionValue;
}
return false;
})
.map(auto => {
return (
<Auto
key={auto.id}
id={auto.id}
name={auto.name}
brands={auto.brands ? auto.brands : false}/>
);
});
return (
<div>
<section>
<select id='autoFilter' className={this.state.isHidden ? 'u-is-hidden' : ''} onChange={this.handleOnChangeBrand.bind(this)} value={this.state.autoOptValue}>
<option value='brand1'> brand1 </option>
<option value='brand2'> brand2 </option>
</select>
</section>
<ul>
{autoDetail}
</ul>
</div>
);
}
Filter returns a new array. This is so you still have the original on hand. There are so very, very many reasons this is a good thing, and practically 0 reasons it's a bad thing.
Props is meant to be immutable (just like arrays that you call filter on). Don't change the members on props. Nor should you change the members of the members of props, or any descendant data of props in general.
That is why state exists, so if you absolutely must, you can save it there.
Regarding #2, typically you should be pulling that stuff out into higher and higher layers of abstraction, getting away from the view data, completely, and just passing in finished data that's ready for showing.
The Array.prototype.filter() method always returns a new filtered array without changing the old one:
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
In your handleOnChangeBrand event, you're creating a filtered array, without affecting the old one, but then not using that filtered array when React calls render for the second time.
A small example of how you could handle this would be as follows:
1) Have react render your default autos prop
export default class AutoList extends React.Component {
render() {
const autoDetail = this.props.autos.map(auto => {
return (
<Autos ... />
)
});
return (
<ul>
{ autoDetail }
</ul>
);
}
}
2) Add in a click handler and a state value to hold the value what you would like to filter by:
export default class AutoList extends React.Component {
constructor(props) {
super(props);
this.state = {
filter: '' // this is where we will store what we want to filter by
}
};
// All this function will do is update the state which we want to filter by
// this 'arrow function' auto bind 'this' for us, so we don't have to explicitely set .bind as you were doing before
handleOnChangeBrand = (event) => {
this.setState({
filter: event.target.value
})
};
render() {
const autoDetail = this.props.autos.map(auto => {
return (
<Autos ... />
)
});
return (
<ul>
{ autoDetail }
</ul>
);
}
}
3) Finally, we will use the value we are storing in state to filter to the auto brands we would like and use that to build our array
export default class AutoList extends React.Component {
constructor(props) {
super(props);
this.state = {
filter: '' // this is where we will store what we want to filter by
}
};
getFilteredAutos = () => {
// if we have no filter, return everything!
if (this.state.filter === '') {
return this.props.autos;
}
// this is returning the newely filtered array, without affecting the old one
return this.props.autos.filter(auto => {
if(auto.brands){
// we're filtering by our saved value
return auto.brands[0] === this.state.filter;
}
return false;
});
},
handleOnChangeBrand = (event) => {
this.setState({
filter: event.target.value
})
};
render() {
// we're mapping by the filtered results here
const autoDetail = this.getFilteredAutos().map(auto => {
return (
<Autos ... />
)
});
return (
<ul>
{ autoDetail }
</ul>
);
}
}

Categories

Resources