Failing to render my search to the browser - javascript

I am trying to filter my todos based on their status i.e; complete and incomplete. I have written down all the code that is required to get it but I get this kind of error:
index.js:29 Uncaught TypeError: Cannot read property 'toLowerCase' of undefined
at index.js:29
at Array.filter (<anonymous>)
at renderTodos (index.js:28)
at index.js:47
Here is my actual code to help me render the filtered todos to the browser:
const todos = [
{
todo: "Pray",
status: "false",
},
{
todo: "Shower",
status: "true",
},
{
todo: "Eat",
status: "true",
},
{
todo: "Run",
status: "false",
},
{
todo: "Work",
status: "false",
},
];
const filters = {
searchText: "",
};
const renderTodos = function (todos, filters) {
const filteredTodos = todos.filter(function (todo) {
return todo.text.toLowerCase().includes(filters.searchText.toLowerCase());
});
const incompleteTodos = filteredTodos.filter(function (todo) {
return !todo.status;
});
document.querySelector("#todos").innerHTML = "";
const summary = document.createElement("h1");
summary.textContent = `You have ${incompleteTodos.length} todos left`;
document.querySelector("#todos").appendChild(summary);
todos.forEach(function (todo) {
const p = document.createElement("p");
p.textContent = todo.text;
document.querySelector("#todos").appendChild(p);
});
};
renderTodos(todos, filters);
document.querySelector("#search").addEventListener("input", function (e) {
filters.searchText = e.target.value;
renderTodos(todos, filters);
});

Your todo object does not have a text property but just status and todo; you probably wanted to filter by todo:
const filteredTodos = todos.filter(function (todo) {
return todo.todo.toLowerCase().includes(filters.searchText.toLowerCase());
});

it looks like the error is in the line :-
return todo.text.toLowerCase().includes(filters.searchText.toLowerCase());
Here you're trying to access the text property of the todo object, which is not available in the objects of the todos array. It only has todo and status property.
so the code will look like this...
return todo.todo.toLowerCase().includes(filters.searchText.toLowerCase());

Related

Mocking a simple function in VueJS, JEST

I am struggling to mock the delete function in the lists component.
My test looks like this at the moment
describe("delete a todo", () => {
test("should have todo removed", async () => {
const deleteItem = jest.fn();
const items = [{ id: 1, name: "ana", isComplete: false }];
const wrapper = shallowMount(Todo, items);
console.log(wrapper);
const deleteButton = ".delete";
wrapper.find(deleteButton).trigger("click");
expect(deleteItem).toHaveBeenCalledWith("1");
});
currently, when I run the tests the error reads.
Test Error
The application works fine, but I am not mocking the delete function correctly in my test as a "New Note" is still being passed through. What am I doing wrong?
just in case it helps, here is a part of the file I am testing.
methods: {
addItem() {
if (this.newItem.trim() != "") {
this.items.unshift({
// id: createUID(10),
id: uuid.v4(),
completed: false,
name: this.newItem
});
this.newItem = "";
localStorage.setItem("list", JSON.stringify(this.items));
this.itemsLeft = this.itemsFiltered.length;
}
},
removeItem(item) {
const itemIndex = this.items.indexOf(item);
this.items.splice(itemIndex, 1);
localStorage.setItem("list", JSON.stringify(this.items));
this.itemsLeft = this.itemsFiltered.length;
},
Also for more code, you can get it from the following link :
https://github.com/oliseulean/ToDoApp-VueJS
I think you have to make some changes to your original test case
Change jest.fn() to jest.spyOn(Todo.methods, 'deleteItem') since you have to track calls to methods object in Todo component. Refer: https://jestjs.io/docs/jest-object
Wait for the click event to be triggered with await
Use toHaveBeenCalledTimes not toHaveBeenCalledWith("1")
So your final test case will look like this
describe("delete a todo", () => {
test("should have todo removed", async () => {
const removeItem = jest.spyOn(Todo.methods, 'removeItem')
const items = [{ id: 1, name: "ana", isComplete: false }];
const wrapper = shallowMount(Todo, items)
await wrapper.find('.delete').trigger('click')
expect(removeItem).toHaveBeenCalledTimes(1);
});
});

Why i cannot assign values to destructured object variables?

I have form in my website with onSubmit eventListener, so when user submits the form getCurrencyData function is executed. inside getCurrencyData function im checking whether the user entered value or not, if yes then im making apicall and destructuring generalCurrencyInfo object. The problem is that i cannot assign values to destructured object variables.
class App extends Component {
constructor(props) {
super(props);
this.state = {
generalCurrencyInfo: {
fullName: undefined,
name: undefined,
imageUrl: undefined,
price: undefined,
error: false
}
}
}
getCurrencyData = async (e) => {
e.preventDefault();
const CURRENCYNAME = e.target.elements.currencyName.value.toUpperCase();
//Checks if currency name is not empty
if (CURRENCYNAME) {
const APICALL = await fetch(`url`);
const DATA = await APICALL.json();
let generalCurrencyInfo = {
fullName:undefined,
name: undefined,
imageUrl: undefined,
price: undefined,
error: false
}
//this destructuring doesn't work
let {fullName, name, imageUrl, price, error} =generalCurrencyInfo;
if (DATA.Message === "Success") {
fullName = DATA.Data[0].CoinInfo.FullName;
name = DATA.Data[0].CoinInfo.Name;
imageUrl = `url`;
price = "price";
error = false;
}
this.setState({
generalCurrencyInfo: generalCurrencyInfo
})
}
}
render() {
return (
);
}
}
You have created 5 new variables here:
let {fullName, name, imageUrl, price, error} =generalCurrencyInfo;
Then you have changed this variables, but not generalCurrencyInfo object:
if (DATA.Message === "Success") {
fullName = DATA.Data[0].CoinInfo.FullName;
name = DATA.Data[0].CoinInfo.Name;
imageUrl = `url`;
price = "price";
error = false;
}
Here you set generalCurrencyInfo, what was not changed:
this.setState({
generalCurrencyInfo: generalCurrencyInfo
})
This will be fine:
this.setState({
fullName,
name,
imageUrl,
price,
error,
})
You can just reassign the values to your generalCurrencyInfo object, so no need to destructure:
// reassign values
if (DATA.Message === "Success") {
generalCurrencyInfo.fullName = DATA.Data[0].CoinInfo.FullName;
generalCurrencyInfo.name = DATA.Data[0].CoinInfo.Name;
generalCurrencyInfo.imageUrl = `url`;
generalCurrencyInfo.price = "price";
generalCurrencyInfo.error = false;
}
// or using the spread operator
if (DATA.Message === "Success") {
generalCurrencyInfo = {
...generalCurrencyInfo,
fullName: DATA.Data[0].CoinInfo.FullName,
name: DATA.Data[0].CoinInfo.Name,
imageUrl: `url`,
price: "price",
error: false,
};
}
But if you landed on this page looking to find out how to re-assign a value to a destructured object, you might want to check out this question: Is it possible to destructure onto an existing object? (Javascript ES6)

How to search a single cell with multiple values

I'm trying to search a JSON. Right now it functions with an exact match. I want to add multiple data to one cell - it'll look like this: "data, data2, nope, nope2". If a user searches 'data' it needs to match for data and data2.
this is the json:
[
{"car":"Mercedes, Toyota, KIA", "state":"SA", "groupName":"AHG"},
{"car":"BMW","state":"NSW","groupName":"Brighton BMW"},
{"car":"Tesla","state":"VIC","groupName":"JAMES F"},
{"car":"Audi","state":"WA","groupName":"Audi CBD","groupPhone":"1300 04"},
{"car":"Mercedes","state":"SA","groupName":"AHG","groupPhone":"1300 05"}
]
eg the 1st string of- "car":"Mercedes, Toyota, KIA" I need to return results if a user searches for Toyota. Right now it only works if the string is only "car":"Toyota"
var app = new Vue({
el: '#app',
data: {
addCar: false,
cars: [],
loading: false,
searchCar: "",
searchState: ""
},
methods: {
search: function () {
app.loading = true;
var searchQuery = {
car: encodeURIComponent(app.searchCar)
};
if (app.searchState) {
searchQuery.state = app.searchState;
};
Sheetsu.read("https://sheetsu.com/apis/v1.0su/a4d7192e71fd", {
search: searchQuery
}).then(function (data) {
console.log(data);
app.cars = data;
app.loading = false;
},
function (err) {
console.log(err);
app.cars = [];
app.loading = false;
});
},
}
})
Would be amazing if a user can search for Toyota and be delivered results for any string containing Toyota as a car :)
Simple client-side search can be implemented by passing a regular expression object to the String#match method, where the pattern of the regular expression is your query/search string:
/* Returns non-null result is "query" matches any part of item.car string */
return item.car.match(new RegExp(query, "gi"))
You could combine this with Array#filter() to aquire only items for your JSON where the car field contains the query string:
const json = [
{"car":"Mercedes, Toyota, KIA", "state":"SA", "groupName":"AHG"},
{"car":"BMW","state":"NSW","groupName":"Brighton BMW"},
{"car":"Tesla","state":"VIC","groupName":"JAMES F"},
{"car":"Audi","state":"WA","groupName":"Audi CBD","groupPhone":"1300 04"},
{"car":"Mercedes","state":"SA","groupName":"AHG","groupPhone":"1300 05"}
]
const findItem = (data, query) => {
return data.filter(item => {
return item.car.match(new RegExp(query, "gi"))
});
}
console.log("Search for Toyota", findItem(json, "Toyota"));
console.log("Search for Mercedes", findItem(json, "Mercedes"));
console.log("Search for Tesla", findItem(json, "Tesla"));
console.log("Search for Honda", findItem(json, "Honda"));
Update
To integrate the code snippet shown above with your Vue component, try this:
var app = new Vue({
el: '#app',
data: {
addCar: false,
cars: [],
loading: false,
searchCar: "",
searchState: ""
},
methods: {
search: function() {
app.loading = true;
var searchQuery = {
car: encodeURIComponent(app.searchCar)
};
if (app.searchState) {
searchQuery.state = app.searchState;
};
Sheetsu.read("https://sheetsu.com/apis/v1.0su/a4d7192e71fd", {
search: searchQuery
}).then(function(data) {
/*
Add code here
*/
data = data.filter(item => {
return item.car.match(new RegExp(app.searchCar, "gi"))
});
app.cars = data;
app.loading = false;
},
function(err) {
console.log(err);
app.cars = [];
app.loading = false;
});
},
}
})
You can use filter and includes
let arr = [{"car":"Mercedes, Toyota, KIA", "state":"SA", "groupName":"AHG"},{"car":"BMW","state":"NSW","groupName":"Brighton BMW"},{"car":"Tesla","state":"VIC","groupName":"JAMES F"},{"car":"Audi","state":"WA","groupName":"Audi CBD","groupPhone":"1300 04"},{"car":"Mercedes","state":"SA","groupName":"AHG","groupPhone":"1300 05"},{"car":"Toyota, Testing function", "state":"SA", "groupName":"AHG"}]
let selectedKey = 'car'
let selectedValue = 'Toyota'
let op = arr.filter(({[selectedKey]:key})=> key && key.includes(selectedValue))
console.log(op)

Merge two objects by matching key while following Flow rules

I have created a function as shown below, which should append the array value into the first object.
Let's say we have given data below:
subjects = {
student1: ['Math', 'Science'],
student2: ['Math', 'Physics', 'English'],
};
students = {
student1: {
// other data
subjectsList: [],
},
student2: {
// other data
subjectsList: [],
},
};
Function code below:
const merge = (subjects: Object, students: Object) => {
Object.keys(subjects).forEach((id: Object) => {
const subjectsList = subjects[id];
const student = students[id];
if (student) {
const updatedStudent = {
...student,
subjectsList,
};
students[id] = updatedStudent;
}
});
return students;
};
This would result in a flow error:
Cannot access the computed property using object type [1].
app/reducers/subjects.reducer.js:42:32
42| const student = students[id];
^^
References:
app/reducers/subjects.reducer.js:40:48
40| Object.keys(subjects).forEach((id: Object) => {
^^^^^^ [1]
Object.keys(subjects).forEach((id: Object)
The id in the .forEach((id) => is not an Object, but a String
If you remove the typehinting (or whatever it is called).
const merge = (subjects, students) => {
Object.keys(subjects).forEach((id) => {
const subjectsList = subjects[id];
const student = students[id];
if (stand) {
const updatedStudent = {
...student,
subjectsList,
};
students[id] = updatedStudent;
}
});
return students;
};
I think this will work.

How do I update an array of objects in component state?

I am trying to update the property of an object which is stored in an array.
my state looks something like this:
state = {
todos: [
{
id: '1',
title: 'first item,
completed: false
},
{
id: '2',
title: 'second item,
completed: false
}
],
}
What I am trying to do is access the second element in the 'todos' array and update the completed property to either false -> true or true -> false.
I have a button with the handler for update, and my class method for the update looks like this:
onUpdate = (id) => {
const { todos } = this.state;
let i = todos.findIndex(todo => todo.id === id);
let status = todos[i].completed
let updatedTodo = {
...todos[i],
completed: !status
}
this.setState({
todos: [
...todos.slice(0, i),
updatedTodo,
...todos.slice(i + 1)
]
});
}
While this does work, I want to find out if there is a more concise way of achieving the same result; I tried to use Object.assign(), but that didn't work out because my 'todos' is an array, not an object. Please enlighten me with better code!
It would be best to use update function to make sure you don't work on outdated data:
onUpdate = (id) => {
this.setState(prevState => {
const copy = [...prevState.todos];
const index = copy.findIndex(t => t.id === id);
copy[index].completed = !copy[index].completed;
return { todos: copy }
})
}
You can simply copy your todos from state, then make edits, and after that put it back to the state
onUpdate = (id) => {
var todos = [...this.state.todos]
var target = todos.find(todo => todo.id == id)
if (target) {
target.completed = !target.completed
this.setState({ todos })
}
}

Categories

Resources