React Native Redux - How to delete one item at a time - react-native

I'm creating a react native application for e-commerce purposes and am trying to get the cart functionality working. I'm using redux and I am able to add one item at a time to the cart but when I try to delete an item from the cart, all of the same items get deleted. For example, if I have 2 pencils, 3 pens and 4 notebooks in my cart and I click the delete button on one of the notebooks, all 4 notebooks get removed from my cart. I want to be able to only remove one item at a time.
const cartItems = (state = [], action) => {
switch (action.type) {
case 'ADD_TO_CART':
return [...state, action.payload]
case 'REMOVE_FROM_CART':
return state.filter(cartItem => cartItem.id !== action.payload.id)
//return state.filter(cartItem => cartItem !== action.payload.id
//const filteredItems = state.filter((cartItem) => cartItem.id !== action.payload.id);
}
return state
}
export default cartItems
The two lines that are commented out are the other ways I've tried, but ultimately didnt work.

This line
return state.filter(cartItem => cartItem.id !== action.payload.id)
Means "return a new array consisting of all state elements except those which have id property matching action.payload.id".
So if there are multiple items with the same id, all f them will be filtered out.
Since this is just plain JavaScript, you can have any logic here, you just need to return a new array. This, for example, will delete only the first matching element:
let removed = false;
return state.filter(cartItem => {
if (cartItem.id === action.payload.id && !removed) {
removed = true;
return false;
}
return true;
});
A better solution would be to create another identifier for the items in cart, so that the action does not remove the first matching item in the list, but the one the user requested to delete by tapping the delete button.

Related

What is the proper way to trigger different functions on command click of status bar item in vs code extension api

I am using the VScode extensions api. I have an item like so.
const item = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right
);
it's command is set to the following
item.command = "codevids.record";
Which calls the record() function onClick
vscode.commands.registerCommand("codevids.record", () => record());
All of this makes sense to me for the most part. Now I am handling logic in the record function so that when it is clicked again it has a different effect, it determines this by what is in the status bar string.
Like so
const record = () => {
if (item.text === `$(record) codevid` || item.text === `$(stop) codevid`) {
item.text = `$(pass) codevid`;
clearInterval(intervalID);
} else {
item.text = `$(record) codevid`;
There must be a more proper way to handle the status bar getting clicked in a boolean manner. I actually want a click to play, click to pause, and click done and run different functions.
What are my options in this regard.
Thanks ahead of time, and please let me know if you need more details.
I ended up doing a switch statement, maybe there is a more vscode way of doing it instead of checking against the string in the status bar, but works for now.
const record = async () => {
console.log(item.text);
switch (item.text) {
case `$(device-camera-video) codevid`:
console.log("recording");
item.text = `$(record) codevid`;
break;
case `$(record) codevid`:
console.log("recording options");
const go = vscode.window
.showErrorMessage("Pause or Finish", "Pause", "Finish")
.then((selection) => {
if (selection === "Pause") {
item.text = `$(debug-pause) codevid`;
} else if (selection === "Finish") {
item.text = `$(pass) codevid`;
}
return selection;
});
console.log("go", await go);
break;
case `$(debug-pause) codevid`:
console.log("paused");
case `$(pass) codevid`:
console.log("finished");
default:
console.log("default");
}
```

Update state more efficiently in React native?

I'm building a checklist app with multiple tabs. It works but when the list grows larger, it's not performing very snappy when I want to check 1 item for instance. I have the feeling this is because the entire state (consisting of all items in all tabs) is updated, when I just want to update 1 item. The tabs and items are generated dynamically (ie, at compile-time I don't know how many tabs there will be). Any idea how this could be done more efficiently?
This is the (stripped down) state provider:
export default class InpakStateProvider extends React.Component {
state = {projectName: " ", tabs: [{name: " ", items: [{checked: false, name: " "}]}]};
DeleteItem = (categoryname: string, itemname: string) => {
let stateTabs = this.state.tabs;
var tab = stateTabs.find((tab) => tab.name == categoryname);
if(tab){
let index = tab.items.findIndex(el => el.name === itemname);
tab.items.splice(index, 1);
}
this.setState({projectName: this.state.projectName, tabs: stateTabs})
};
CheckItem = (categoryname: string, itemname: string) => {
var tab = this.state.tabs.find((tab) => tab.name == categoryname);
if(tab){
let index = tab.items.findIndex(el => el.name === itemname);
tab.items[index] = { ...tab.items[index], checked: !tab.items[index].checked };
}
this.setState({projectName: this.state.projectName, tabs: this.state.tabs});
};
ClearChecks = () => {
let stateTabs = this.state.tabs;
stateTabs.forEach((tab) => {
let tabItems = [...tab.items];
tabItems.forEach((item) => item.checked = false);
});
this.setState({projectName: this.state.projectName, tabs: stateTabs})
}
render(){
return (
<Context.Provider
value={{
projectName: this.state.projectName,
tabs: this.state.tabs,
DeleteItem: this.DeleteItem,
CheckItem: this.CheckItem,
ClearChecks: this.ClearChecks,
}}
>
{this.props.children}
</Context.Provider>
);
}
}
The issue here is that all list components are being re-rendered upon updating the state. My advice is to move the state of checked inside of the list item component. Or if you don't want to do that, I advise you to read about React memoization.
If you go for the memoziation approach if you update the state, and the props of the list item didn't change, this will not re-render the unchanged components, it will only trigger the re-render for the components with the prop checked that has changed.
Here's the documentation for memoization if it helps: https://reactjs.org/docs/react-api.html.
Also, on another note, always go for FlatLists instead of using map. You won't notice a big difference with a small dataset, but performance takes a big hit with mid-large datasets.

Always first filtered selected on Quasar Select

I am using Quasar Framework and using the Q-Select with filter.
I would like to the first filtered option always be already marked, because then if I hit enter, the first will selected.
After some research I found out how to achieve this in a generic way.
The second parameter on the function update received at filterFn is the instance of QSelect itself.
Hence, we can use
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
To keep the focus on the first filtered element, regardless of multiselect or not.
The final code is something like
filterFn(val, update) {
update(
() => {
const needle = val.toLocaleLowerCase();
this.selectOptions = this.qSelectOptions.filter(v => v.toLocaleLowerCase().indexOf(needle) > -1);
},
ref => {
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
});
}
There is one option to achieve this is set model value in the filter method if filtered options length is >0.
filterFn (val, update, abort) {
update(() => {
const needle = val.toLowerCase()
this.options = stringOptions.filter(v => v.toLowerCase().indexOf(needle) > -1)
if(this.options.length>0 && this.model!=''){
this.model = this.options[0];
}
})
}
Codepen - https://codepen.io/Pratik__007/pen/QWjYoNo

How to programmatically switch a switch in React Native?

I make us several Switch Components in one view. When I switch on one switch, I want all others to switch off. Currently, I set the boolean value property via the state. This results in changes happen abruptly because the switch is just re-rendered and not transitioned.
So how would you switch them programmatically?
EDIT 2: I just discovered that it runs smoothly on Android so it looks like an iOS-specific problem.
EDIT: part of the code
_onSwitch = (id, switched) => {
let newFilter = { status: null };
if (!switched) {
newFilter = { status: id };
}
this.props.changeFilter(newFilter); // calls the action creator
};
_renderItem = ({ item }) => {
const switched = this.props.currentFilter === item.id; // the state mapped to a prop
return (
<ListItem
switchButton
switched={switched}
onSwitch={() => this._onSwitch(item.id, switched)}
/>
);
};

Lists and Components not updating after data change - (VueJS + VueX)

A question about best practice (or even a go-to practice)
I have a list (ex. To-do list). My actual approach is:
On my parent component, I populate my 'store.todos' array. Using a
getter, I get all the To-do's and iterate on a list using a v-for
loop.
Every item is a Component, and I send the to-do item as a prop.
Inside this component, I have logic to update the "done" flag. And this element display a checkbox based on the "state" of the flag. When it does that, it do an action to the db and updates the store state.
Should I instead:
Have each list-item to have a getter, and only send the ID down the child-component?
Everything works fine, but if I add a new item to the to-do list, this item is not updated when I mark it as completed. I wonder if this issue is because I use a prop and not a getter inside the child component
Code:
store:
const state = {
tasks: []
}
const mutations = {
CLEAR_TASKS (state) {
state.tasks = [];
},
SET_TASKS (state, tasks) {
state.tasks = tasks;
},
ADD_TASK (state, payload) {
// if the payload has an index, it replaces that object, if not, pushes a new task to the array
if(payload.index){
state.currentSpaceTasks[payload.index] = payload.task;
// (1) Without this two lines, the item doesn't update
state.tasks.push('');
state.tasks.pop();
}
else{
state.tasks.push(payload.task);
}
},
SET_TASK_COMPLETION (state, task){
let index = state.tasks.findIndex(obj => obj.id == task.id);
state.tasks[index].completed_at = task.completed_at;
}
}
const getters = {
(...)
getTasks: (state) => (parentId) => {
if (parentId) {
return state.tasks.filter(task => task.parent_id == parentId );
} else {
return state.tasks.filter(task => !task.parent_id );
}
}
(...)
}
const actions = {
(...)
/*
* Add a new Task
* 1st commit add a Temp Task, second updates the first one with real information (Optimistic UI - or a wannabe version of it)
*/
addTask({ commit, state }, task ) {
commit('ADD_TASK',{
task
});
let iNewTask = state.currentSpaceTasks.length - 1;
axios.post('/spaces/'+state.route.params.spaceId+'/tasks',task).then(
response => {
let newTask = response.data;
commit('ADD_TASK',{
task: newTask,
index: iNewTask
});
},
error => {
alert(error.response.data);
});
},
markTaskCompleted({ commit, dispatch, state }, task ){
console.log(task.completed_at);
commit('SET_TASK_COMPLETION', task);
dispatch('updateTask', { id: task.id, field: 'completed', value: task.completed_at } ).then(
response => {
commit('SET_TASK_COMPLETION', response.data);
},
error => {
task.completed_at = !task.completed_at;
commit('SET_TASK_COMPLETION', task);
});
},
updateTask({ commit, state }, data ) {
return new Promise((resolve, reject) => {
axios.patch('/spaces/'+state.route.params.spaceId+'/tasks/'+ data.id, data).then(
response => {
resolve(response.data);
},
error => {
reject(error);
});
})
}
}
And basically this is my Parent and Child Components:
Task List component (it loads the tasks from the Getters)
(...)
<task :task = 'item' v-for = "(item, index) in tasks(parentId)" :key = 'item.id"></task>
(...)
The task component display a "checkbox"(using Fontawesome). And changes between checked/unchecked depending on the completed_at being set/true.
This procedure works fine:
Access Task list
Mark one existing item as done - checkbox is checked
This procedure fails
Add a new task (It fires the add task, which firstly adds a 'temporary' item, and after the return of the ajax, updates it with real information (id, etc..). While it doesn't have the id, the task displays a loading instead of the checkbox, and after it updates it shows the checkbox - this works!
Check the newly added task - it does send the request, it updates the item and DB. But checkbox is not updated :(
After digging between Vue.js docs I could fix it.
Vue.js and Vuex does not extend reactivity to properties that were not on the original object.
To add new items in an array for example, you have to do this:
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
More info here:
https://v2.vuejs.org/v2/guide/reactivity.html
and here: https://v2.vuejs.org/v2/guide/list.html#Caveats
At first it only solved part of the issue. I do not need the "hack" used after pushing an item into the array (push and pop an empty object to force the list to reload)
But having this in mind now, I checked the object returned by the server, and although on the getTasks, the list has all the fields, including the completed_at, after saving a new item, it was only returning the fields that were set (completed_at is null when created). That means that Vue.js was not tracking this property.
I added the property to be returned by the server side (Laravel, btw), and now everything works fine!
If anybody has a point about my code other than this, feel free to add :)
Thanks guys