How do I filter a to do list in React? - javascript

everyone. New to asking questions here, although I use it to find answers pretty frequently. My issue is this:
I have a "to do list" application. I'm able to add, delete, and mark to do items complete. However, what I'm struggling with is the filter for viewing the different items (view all, only active, and only completed).
I have everything built around the buttons and the click event, but what I can't seem to figure out is how to actually display the desired to do items. Clicking the buttons returns an error that todo.filter is not a function, so I'm apparently off in left field somewhere with my current solution.
Here is the code I'm using to try to filter the array and show only the to do items with the boolean completed: true.
filComplete = id => {
this.setState({
todos: this.state.todos.map(todo => {
let comTodo = todo.filter(todo => todo.completed = true);
return comTodo;
})
})
}
From my own understanding, I am mapping all of the todo items where completed is true to a new array for display, but I'm obviously not doing something right.
todo is each individual todo item.
todos is the array of todo items.
id is the key of todo.id.
Thanks in advance.

let comTodo = todo.filter(todo => todo.completed = true);
Using a single = is an assignment, not a comparison. Instead, you can just use
let comTodo = todo.filter(todo => todo.completed);
if completed is a Boolean. You also don't need to combine map with filter if all you want is simple filtering, so the final production would look like
filComplete = id => {
this.setState({
todos: this.state.todos.filter(todo => todo.completed)
})
}

Don't bother with map - that just transforms an array to another of the same length. Just use filter to check the completed property and remove those incomplete todos:
this.setState({
todos: this.state.todos.filter(todo => todo.completed)
});
Although note that doing this means that, once you're only showing the completed todos, you can't recover the full list. You probably want the full list in your props, not state.
One other potential problem with this is that setState is asynchronous, and therefore doesn't always work correctly when you reference this.state inside it - as that won't always have the value you expect. Instead, pass in function which describes how to transform the old state to the new:
this.setState(state => ({
todos: state.todos.filter(todo => todo.completed)
}));

Related

Keeping index of array item in React constant after filtering

I'm building a React component, which currently displays a list of technologies. The user can toggle which of these technologies are 'active' (i.e. selected) by clicking on them. They start as false, change to true, back to false and so on.
When the user does this, they get added to the overall technologies array and displayed on the frontend.
This is done through a 'toggleTechnology' function, which gets two properties passed in to it: item (the technology text) and index (where this item is in the array).
const toggleTechnology = (item, index) => {
console.log(item)
console.log(index)
if (technologies.includes(item)) {
let filteredArray = technologies.filter((currentItem, currentIndex) => currentIndex !== index)
setTechnologies(filteredArray)
} else {
setTechnologies(tech => [...tech, item])
}
}
The list of technologies is kept in a separate array, to be displayed dynamically on the frontend, like so:
const frontEndSklls = ['HTML', 'CSS', 'Javascript', 'React', 'Unit tests', 'Vue.js']
{ frontEndSklls.map((item, index) => {
return <span className={technologies.includes(item) ? "toggle-option active" : "toggle-option"} onClick={() => toggleTechnology(item, index)} key={index}>{item}</span>
}) }
My issue comes in when a user adds multiple skills and then removes some of them.
So for example, if they clicked on the following in this order, each item in the technologies array would have an index like this:
0: HTML
1: Unit tests
2: React
3: CSS
However, if they then clicked on 'React' to remove it, the array would go like this:
0: HTML
1: Unit tests
2: CSS
If they then click on CSS to remove it, I think because the new index of CSS is 2, it isn't removing the property. So what you need to do is click on another element first, say React again, and then CSS can be removed as the new array has CSS at index point 2.
So my question: is there a way for me to adjust my code so that the index remains the same for each of the options, even when you click on and off the elements?
The solution to me seems to be to turn each element in the array into an object, and assign a permanent index number and value to each element in the array - but this doesn't feel as clean as it could be.
Any suggestions would be appreciated.
You have two different arrays in the component:
frontEndSkills - static - used to render all technologies in the UI with activated/deactivated status.
technologies - dynamic - the filtered array used to mark the active technologies in the UI as such.
The problem is that you're supplying the index of an item from the frontEndSkills array to be used in the technologies array, which is not always the same as the former.
#Subham Jain's answer also would work perfectly, but here is my shot at it, without the need for the index at all:
const toggleTechnology = (item) => {
if (technologies.includes(item)) {
let filteredArray = [...technologies].filter(currentItem => currentItem !== item)
setTechnologies(filteredArray)
} else {
setTechnologies(tech => [...tech, item])
}
}
Note: The suggestion to not use the index as a key from the other answers/comments here is sensible and highly recommended. Please consider that seriously. If you do have to use it for any reason at all, make sure that you understand why it is anti-pattern to do so.
I would suggest that change 2 things.
Instead of using "index" for the "key" use "item".
Do not toggle the state based on the index. To achieve this you can modify the "toggletechnology" as below:-
const toggleTechnology = (item) => {
if (technologies.includes(item)) {
let indexOfItem = technologies.indexOf(item);
let filteredArray = [...technologies].splice(indexOfItem,1);
setTechnologies(filteredArray)
} else {
setTechnologies(tech => [...tech, item])
}
}

variable inside Observable subscription gets empty value

I know that Observables take some time to get data while javascript keeps running the others codes and that is troubling me a lot.
I have used ngrx in my angular project. Here, I am trying to fetch some data from the store which is working fine. Then, I convert this data stream into string[] which is also working fine.
To use this string[] me subscribeto this observable. And inside subscription I try to assign the value to other values named filterSizeValues.
Here, the problem comes. If I console.logthis filterSizeValuesinitially I got and empty array. When the observable finishes his job filterSizeValues variable is filled with data.
But I can not effort filterSizeValues variable to be empty array initially. What can I do?
I have already searched the solution in the internet but nothing is working out.
Help me out please. And Many Many Thanks in advance.
Here is my code;
this.sizeTargetingStore$.dispatch(SizeTargetingActions.getSizeTargeting({
campaignId: this.campaignId,
lineItemId: this.lineItemId
}));
Here I am accessing the store to get data.
this.sizeTargeting$
.pipe(switchMap(sizes=>{
let temporary:string[] = [];
sizes.forEach(eachSize=>{
temporary.push(eachSize.name);
})
this.filterSizeValues$ = of(temporary);
return this.filterSizeValues$;
}))
.subscribe(size_name=>{
this.filters.set('size_name', size_name);
})
Here, I am trying to set the filter values.
I also tried this way also.
this.sizeTargeting$
.pipe(switchMap(sizes=>{
let temporary:string[] = [];
sizes.forEach(eachSize=>{
temporary.push(eachSize.name);
})
this.filterSizeValues$ = of(temporary);
return this.filterSizeValues$;
}))
.subscribe(size_name=>{
this.filterSizeValues = size_name
})
this.filters.set('size_name', this.filterSizeValues);
But all ways filters set to an empty array.
Anyone can help me out please?
From my understanding, you have 2 possibilities, either filter out the empty values or skip the first value. You can do so with the filter and skip rxjs operator respectively.
Also I believe that you are misusing the switchMap operator, since you are not using asynchronous operations within your switchMap we can use the map operator instead, so below I have a simplified version of your code with your 2 options to fix your problem.
Option 1:
this.sizeTargeting$.pipe(
filter(sizes => sizes.length > 0), // filter out empty array values
map(sizes => sizes.map(size => size.name)) // perform your remap
).subscribe(sizes => {
this.filterSizeValues = size_name; // Only arrays with values will reach this step
});
Option 2:
this.sizeTargeting$.pipe(
skip(1), // skip the first value
map(sizes => sizes.map(size => size.name)) // perform your remap
).subscribe(sizes => {
this.filterSizeValues = size_name; // Only arrays with values will reach this step
});
Normally when I subscribe to something that I am waiting on to return what I do is I set up a Subject:
private componentDestroyed$ = new Subject<void>();
then in the Observable piping and subscription I do it as:
this.sizeTargeting$
.pipe(takeUntil(this.componentDestroyed$))
.subscribe((sizes: YourTypeHere[]) => {
if(sizes) {
//Do what I need to do with my sizes here, populate what I need,
//dispatch any other actions needed.
}
})

React component is re-rendering items removed from state

This is a difficult one to explain so I will do my best!
My Goal
I have been learning React and decided to try build a Todo List App from scratch. I wanted to implement a "push notification" system, which when you say mark a todo as complete it will pop up in the bottom left corner saying for example "walk the dog has been updated". Then after a few seconds or so it will be removed from the UI.
Fairly simple Goal, and for the most part I have got it working... BUT... if you quickly mark a few todos as complete they will get removed from the UI and then get re-rendered back in!
I have tried as many different ways of removing items from state as I can think of and even changing where the component is pulled in etc.
This is probably a noobie question, but I am still learning!
Here is a link to a code sandbox, best way I could think of to show where I am at:
Alert Component State/Parent
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/layout/PageContainer.js
Alert Component
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/parts/Alert.js
Any help much appreciated!
When you call a set function to update state, it will update from the last rendered value. If you want it to update from the last set value, you need to pass the update function instead of just the new values.
For instance, you can change your setTodos in your markComplete function to something like this.
setTodos(todos => todos.map((todo) => {
if (id === todo.id) {
todo = {
...todo,
complete: !todo.complete,
};
}
return todo;
}));
https://codesandbox.io/s/jovial-yalow-yd0jz
If asynchronous events are happening, the value in the scope of the executed event handler might be out of date.
When updating lists of values, use the updating method which receives the previous state, for example
setAlerts(previousAlerts => {
const newAlerts = (build new alerts from prev alerts);
return newAlerts;
});
instead of directly using the alerts you got from useState.
In the PageContainer.js, modify this function
const removeAlert = (id) => {
setAlerts(alerts.filter((alert) => alert.id !== id));
};
to this
const removeAlert = (id) => {
setAlerts(prev => prev.filter((alert) => alert.id !== id));
};
This will also fix the issue when unchecking completed todos at high speed

React 1000 checkboxes - clicking/re-render takes several seconds

So I am trying to learn React, and have a quite simple application I am trying to build.
So I have a backend API returning a list of say 1000 items, each item has a name and a code.
I want to put out all the names of this list into check boxes.
And then do X with all selected items - say print the name, with a selectable font to a pdf document.
With this I also want some easy features like "select all" and "deselect all".
So since I am trying to learn react I am investigating a few different options how to do this.
None seems good.
So I tried making a child component for each checkbox, felt clean and "react".
This seems to be really bad performance, like take 2-3 seconds for each onChange callback so I skipped that.
I tried making a Set in the class with excluded ones. This too seems to be really slow, and a bad solution since things like "select all" and "deselect all" will be really ugly to implement. Like looping through the original list and adding all of them to the excluded set.
Another solution I didn't get working is modifying the original data array. Like making the data model include a checked boolean to get a default value, and then modify that. But then the data needs to be a map instead of an array. But I have a feeling this solution will be really slow too whenever clicking the checkbox. I dont quite understand why it is so slow to just do a simple checkbox.
So please tell me how to approach this problem in react.
A few direction questions:
How do I modify an array when I fetch it, say add a checked: true variable to each item in the array?
async componentDidMount() {
const url = "randombackendapi";
const response = await fetch(url);
const data = await response.json();
this.setState({ data: data.data, loading: false });
}
Why is this so extremely slow? (Like take 3 seconds each click and give me a [Violation] 'click' handler took 3547ms) warning. And my version of each item being a sub function with callback being equally slow. How can I make this faster? - Edit this is the only question that remains.
{this.state.data.map((item, key) => (
<FormControlLabel
key={item.code}
label={item.name}
control={
<Checkbox
onChange={this.handleChange.bind(this, item.code)}
checked={!this.state.excludedSets.has(item.code)}
code={item.code}
/>
}
/>
))}
handleChange = (code, event) => {
this.setState({
excludedSets: event.target.checked
? this.state.excludedSets.delete(code)
: this.state.excludedSets.add(code)
});
};
I guess I don't understand how to design my react components in a good way.
How do I modify an array when I fetch it, say add a checked: true variable to each item in the array?
Once you have the array you can use a map to add a checked key, which will just make the process much easier by utilizing map on the main array to check and an easier implementation for the select-deselect all feature
let data = [{code: 1},{code: 2},{code: 3}]
let modifiedData = data.map(item => {
return {...item, checked: false}
})
//modifiedData = [ { code: 1, checked: false }, { code: 2, checked: false }, { code: 3, checked: false } ]
I would recommend to save the modified data inside the state instead of the data you fetched since you can always modify that array to send it back to the api in the desired format
now that you have the modified array with the new checked key you can use map to select and deselect like so
const handleChange = (code) => {
modifiedData = modifiedData.map(item => item.code === code ? {...item, checked: !item.checked}: item)
}
And as of the deselect all | select all you can use another map method to do this like so
const selectAllHandler = () => {
modifiedData = modifiedData.map(item => {
return {...item, checked: true}})
}
and vice-versa
const deselectAllHandler = () => {
modifiedData = modifiedData.map(item => {
return {...item, checked: false}})
}
It's a common rendering issue React will have, you can use virtualize technique to reduce the amount of DOM elements to boost the re-rendering time.
There're several packages you can choose, like react-virtuoso, react-window etc.
The main concept is to only render the elements inside your viewport and display others when you're scrolling.
So I was unable to get the React checkbox component performant (each click taking 2-3 seconds), so I decided to just go with html checkboxes and pure javascript selectors for my needs, works great.

Weird behavior on array map after state update

Hope you're all keeping well in the current climate!
I'm working with Firebase and ReactJS at the moment and have encountered an odd behavior with the state update in react and the array map functionality in JavaScript. The code below shows my logic for listening to updates from the firestore database:
listenForContentChanges = async () => {
let content = [];
await db.collection('clients').doc(this.props.client)
.collection('projects').doc(this.props.id)
.collection('content').orderBy('created').onSnapshot(querySnapshot => {
querySnapshot.docChanges().forEach(async change => {
if (change.type === 'added') {
content.push({
id: change.doc.id,
uploaded: change.doc.data().created.seconds
});
}
if (change.type === 'removed') {
content = content.filter(arrayItem => arrayItem.id !== change.doc.id);
}
});
this.setState({
content: content,
addingContent: false
});
});
}
As you can see, an array is populated with 'content' information just the ID of the document and a field containing the time in seconds of when that document was created. This gives me an array back:
0: {id: "SZ4f0Z27rN2MKgXAlglhZVKDsNpKO6", uploaded: 1586323802}
I need this array sorted so the most recent document comes first and because Firebase doesn't offer this as a query parameter (you can only sortBy and not change the direction) I copy the array to a new array and then loop over that instead to get everything in the correct order:
const sortedArr = [...this.state.content];
sortedArr.reverse();
/// IN THE RETURN: ///
{sortedArr.map((content, index) => (
<Content key={index} />
))}
This works okay with no issues. My problem is that now when a new element is added/one is taken from the state array this.state.content and the component is re-rendered, I am seeing a weird behavior where the last elements, instead of showing the new data, seem to duplicate themselves. Please see an example timeline below:
As you can see, when there is a new document added in firestore, the code shown above fires correctly, pushing a new array element onto the state and re-rendering the component. For those who are interested, yes the state is being correctly updated, this is what is being logged inside the render function after the state update:
0: {id: "x07yULTiB8MhR6egT7BW6ghmZ59AZY", uploaded: 1586323587}
1: {id: "SZ4f0Z27rN2MKgXAlglhZVKDsNpKO6", uploaded: 1586323802}
You can see there index 1 is new which is the document that has just been added. This is then, reversed and mapped in the return() function of the render() but causes the weird behavior shown in the image above. Any help on what may be causing this would be a great help, I've been banging my head against the wall for a while now!
Using array index as react key fails when the rendered array is mutated (i.e. unstable). React sees the new length, but the key for the first element is still the same so it bails on rerendering (even though it's the new element).
Try instead to always use unique keys within your dataset, like that id property.
{sortedArr.map((content, index) => (
<Content key={content.id} />
))}

Categories

Resources