How to delete a specific data/object in an object? (ReactJs Reducer) - reactjs

If my context is like this
const [data, dispatch] = useReducer(dataReducer, {
projects:{},
tasks:{},
});
and the data was
{
projects:{
'first':{
name:'first',
taskIds:['task-1'],
},
'second':{
name:'second',
taskIds:['task-2','task-3'],
},
},
tasks:{
'task-1':{
title:'Eat',
time:'1:30pm',
},
'task-2':{
title:'Work',
time:'2:30pm',
},
'task-3':{
title:'Sleep',
time:'9:00pm',
},
},
}
How can i delete 'task-2' inside the projects.second and tasks objects by using a reducer?

In conclusion, you will need to replicate the object without 'task-2' from projects and tasks and pass it as a new state because objects inside an object requires a special action called 'deep copy'. Therefore, I suggest that you should separate projects and tasks since reducer requires a whole object to update the current state to new state.
Then, you can pass each reducer an object you want to pass as such:
tasks:
// tasks
{
tasks:{
'task-1':{
title:'Eat',
time:'1:30pm',
},
'task-3':{
title:'Sleep',
time:'9:00pm',
},
},
}
projects:
// projects
{
projects:{
'first':{
name:'first',
taskIds:['task-1'],
},
'second':{
name:'second',
taskIds:['task-3'],
},
}

let projects = action.projects
let tasks = action.tasks
delete tasks['task-2']

Related

Adding an object to an array contained inside an object without overriding

I am currently trying to loop through an array of objects (each object is a task), where each task contains relevant information such as a name and date. Information from each task is then utilized in creating an object containing arrays, where each array contains objects that correspond to the date, or the array.
My current code is as follows:
contextTasks.forEach((taskItem) => {
taskItem["taskSchedule"].forEach((dateItem) => {
setItems((items) => ({
...items,
[dateItem["date"]]: [
{
name: taskItem["taskName"],
time: new Date(dateItem["time"]).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
}),
type: "Task",
},
],
}));
});
});
However, if there are multiple tasks with the same date, they will override each other and I only end up with one task per date. How would I go about pushing further objects to the array if there are other entries for that specific date?
Finished object:
Object {
"2021-04-21": Array [
Object {
"name": "Test Class v1",
"type": "Class",
},
],
"2021-04-24": Array [
Object {
"name": "Test Task v2",
"type": "Task",
},
//I would like to add another object here without overriding existing contents of the array
],
}
Have you tried using reduce ?
the idea will be to have something like this inside your accumulator:
{"date1": [{val}, {val}, ...] , "date2": [{val}, {val}, ...]}
array.reduce((acc, val) => {
// test if your accumulator has the same date as your date from the val
if(acc[val.date]) {
acc[val.date] = [... acc[val.date], ...your val]
} else {
// no date found in the accumulator so make acc.date = ...acc.date, val
acc[val.date] = [ val ]
}
}, {})
Sorry if the code is not perfect but if you want provide your initial array of data and I will fix the response code
The cause of your issue is the fact you're executing an async method inside a synchronous loop. Also, modifying state forces a re-render, and you're attempting to do it presumably many times at once. It might, and will cause a bottleneck at some point.
The solution: build your new state first, and execute a setState once.

React update values only once

I have a newb question :)
I have a modal that opens in React Native with a dropdown select that requires values. I want to calculate the values whenever the modal opens.
let pickupTime; // Set a value that can be overwritten. I'm not using State because I want this value to change whenever I open the modal again.
const pickupTimeOptions = useRef([{ label: "", value: "" }]); // A ref to store the values
useEffect(() => {
const pickup_hours_today = business.pickup_hours_today; // Array of strings I pass to the modal.
console.log("pickup_hours_today", pickup_hours_today);
const options = pickup_hours_today.map((time) => {
return {
label: time,
value: time,
};
});
pickupTimeOptions.current = options;
}, [business.pickup_hours_today]);
console.log("pickupTimeOptions", pickupTimeOptions); // Let's see if we got it
The problem is that the ref never updates. The log prints this:
pickupTimeOptions Object {
"current": Array [
Object {
"label": "",
"value": "",
},
],
}
pickup_hours_today Array [
... // the string array of hours
]
Should be updating the ref
pickupTimeOptions Object {
"current": Array [
Object {
"label": "",
"value": "",
},
],
}
pickup_hours_today Array [
...
]
Should be updating the ref
What am I doing wrong? Should I handle this differently? I don't mind using state, but when I tried, it kept updating it whenever I selected a different values with the dropdown picker.
If you look at the order of console logs, it'll explain what's happening.
This is printed first, meaning calculation in useEffect hasn't happened yet
console.log("pickupTimeOptions", pickupTimeOptions); // Let's see if we got it
According to the documentation useEffect is only called after the render. You need to do the calculation before or during the render cycle.
You can use useMemo which is executed during rendering. Refer to the documentation for more details
Your updated code should look something like this
let pickupTime; // Set a value that can be overwritten. I'm not using State because I want this value to change whenever I open the modal again.
const pickupTimeOptions = useMemo(() => {
const pickup_hours_today = business.pickup_hours_today; // Array of strings I pass to the modal.
console.log("pickup_hours_today", pickup_hours_today);
const options = pickup_hours_today.map((time) => {
return {
label: time,
value: time,
};
});
return options;
}, [business.pickup_hours_today]);
console.log("pickupTimeOptions", pickupTimeOptions); // Let's see if we got it

findIndex problem with Immer and Redux Starter Kit

I have a redux state that I need to update, it looks like this:
[
{
date: moment("2019-06-15").toObject(),
attendance: [
{
name: "Ruben Hensen",
membershipNumber: "2084700",
attending: true,
present: false
},
{
name: "Peter Petersen",
membershipNumber: "2084701",
attending: true,
present: false
},
{
name: "Karel Kootjes",
membershipNumber: "2084702",
attending: true,
present: false
},
{
name: "Niedaar Mennekes",
membershipNumber: "2084703",
attending: true,
present: false
},
]
},
...
...
...
]
My reducer tries to find the correct week so it can update it later but is unable to find the correct week inside the array.
TOGGLE_PRESENCE: (state, action) => {
console.log(state);
console.log(action);
const index = state.findIndex(week => week.date === action.payload.date);
console.log(index);
}
The problem is that I use Redux Starter Kit and it uses Immer inside reducers. If I run my reducer this is the result of the console logs.
It's not able to find the correct week.
Any tips or pointers would be appreciated. I don't really understand Immer or the Proxy objects it uses.
Edit:
#markerikson solved the problem. I changed this:
const index = state.findIndex(week => week.date === action.payload.date);
to this:
const index = state.findIndex(week =>moment(week.date).isSame(action.payload.date));
And now it finds the correct index.
The moment instances are not going to be === equal to each other. You probably need to use Moment's APIs for comparing them, like:
const index = state.findIndex(week => week.date.isSame(action.payload.date));
Note that this part doesn't have anything to do with Immer or Redux Starter Kit specifically - it's that different object instances are going to have different references.

React Redux Store Layout: How to handle "pending" state of "add item" request?

Example store:
{
todos: {
byId: {
"1": { id: "1", title: "foo" },
"2": { id: "2", title: "bar" }
},
allIds: ["2", "1"] // ordered by `title` property
}
}
Now the user wants to add a new Todo Entry:
dispatch({
type: 'ADD_TODO_REQUEST',
payload: { title: "baz" }
})
This triggers some API request: POST /todos. The state of the request is pending as long as there's no response (success or error). This also means, that I have no id yet for the newly created Todo Entry.
Now I already want to add it to the store (and display it). But of course I can't add it to byId and allIds, because it has no id yet.
Question 1: How should I change the layout of my store to make this possible?
After the response arrives, there are two possibilities:
success: Update the store and set the id property of the new Todo Entry. Using dispatch({type:'ADD_TODO_SUCCESS', payload: response.id}).
error: Remove the new Todo Entry from the store. Using dispatch({type:'ADD_TODO_ERROR', payload: ???})
Now the reducer for those two actions has to somehow find the corresponding element in the store. But it has no identifier.
Question 2: How do I find the item in the store if it has no id?
Additional information:
I'm using react with redux-saga
It should be possible to have multiple concurrent ADD_TODO_REQUEST running at the same time. Though it must be possible to have multiple pending Todo Entries within the store. (For example if the network connection is really slow and the user just enters "title1" and hits the "add" button, then "title2" and "add", "title3" and "add".) Though it's not possible to disable the AddTodo component while a request is pending.
How do you solve these kind of problems within your applications?
EDIT: There's even more:
The same functionality should be available for "updating" and "deleting" Todo Entries:
When the user edits a Todo Entry and then hits the "save" button, the item should be in the pending state, too, until the response arrives. If it's an error, the old version of the data must be put back into the store (without requesting it from the server).
When the user clicks "delete", then the item will disappear immediately. But if the server response is an error, then the item should be put back into the list.
Both actions should restore the previous data, if there's an error respsonse.
I found a simple solution. But I'm sure that there are other possibilities and even better solutions.
Keep the Todo Entries in 2 separate collections:
{
todos: {
byId: {
"1": { id: "1", title: "foo" },
"2": { id: "2", title: "bar" }
},
allIds: ["2", "1"],
pendingItems: [
{ title: "baz" },
{ title: "42" }
]
}
}
Now I can find them in the store "by reference".
// handle 'ADD_TODO_REQUEST':
const newTodoEntry = { title: action.payload.title };
yield put({ type: 'ADD_TODO_PENDING', payload: newTodoEntry });
try {
const response = yield api.addTodoEntry(newTodoEntry);
yield put({ type: 'ADD_TODO_SUCCESS', payload: { id: response.id, ref: newTodoEntry } });
} catch(error) {
yield put({ type: 'ADD_TODO_ERROR', payload: newTodoEntry });
}
The reducer will look like this:
case 'ADD_TODO_PENDING':
return {
..state,
pendingItems: // add action.payload to this array
}
case 'ADD_TODO_SUCCESS':
const newTodoEntry = { ...action.payload.ref, id: action.payload.id };
return {
..state,
byId: // add newTodoEntry
allByIds: // add newTodoEntry.id
pendingItems: // remove action.payload.ref from this array
}
case 'ADD_TODO_ERROR':
return {
..state,
pendingItems: // remove action.payload.ref from this array
}
There are 2 problems:
The reducer must use the object reference. The reducer is not allowed to create an own object from the action payload of ADD_TODO_PENDING.
The Todo Entries cannot be sorted easily within the store, because there are two distinct collections.
There are 2 workarounds:
Use client side generated uuids which only exist while the items are within the pending state. This way, the client can easily keep track of everything.
2.
a) Add some kind of insertAtIndex property to the pending items. Then the React component code can merge those two collections and display the mixed data with a custom order.
b) Just keep the items separate. For example the list of pending items on top and below that the list of already persisted items from the server database.

Redux updating nested immutable data

I have an issue with updating the immutable redux and quite nested data. Here's an example of my data structure and what I want to change. If anyone could show me the pattern of accessing this update using ES6 and spread operator I would be thankful.
My whole state is an object with projects (key/value pairs - here as an example only one project) that are objects with its own key (and the keys are ids as well), arrays of procedures and inside the tasks:
{ 1503658959473:
{ projectName: "Golden Gate",
projectLocation": "San Francisco",
start:"22/09/1937",
id:1503658959473,
procedures:[
{ title: "Procedure No. 1",
tasks:[
{name: "task1", isDone: false},
{name: "task2", isDone: false},
{name: "task3", isDone: false}
]
}
]
}
}
What I'm willing to do is to update one single task 'isDone' property to 'true'. It's some kind of toggling the tasks. How can I return this state with that information updated?
The action creator pass this information to reducer:
export function toggleTask(activeProject, task, taskIndex) {
return {
type: TOGGLE_TASK,
payload: {
activeProject,
task,
taskIndex
}
};
}
You've run into a common issue with Redux. The docs recommend that you flatten your data structure to make it easier to work with, but if that's not what you want to do, I'd refer to this part of their docs.
Because both Object.assign() and the ...spread operator create shallow copies, you must go through each level of nest in your object and re-copy it.
Your code might look something like this...
function updateVeryNestedField(state, action) {
return {
...state,
procedures : {
...state.procedures,
tasks : {
return tasks.map((task, index) => {
if (index !== action.taskIndex) {
return task
}
return {
...task,
task.isDone: !task.isDone
}
}
}
}
}
}
I myself would create a new class called ProjectModel, which has a public method toggleTask that is able to update its task's status. The reducer state would be an object whose keys are project IDs and values are ProjectModel instances.

Resources