I have a feeling I'm so close to this... I have a to do list application I'm building.
A user can click on a button labeled "complete" to mark that specific item as complete.
My thinking on this is, when the user clicks that button to only update "completed" state to true.
For some reason the text within the state changed to "undefined" from the selected item and another item adds to the state when clicking "complete"
Here is my action:
export function completeTodo() {
return {
type: "COMPLETE_TODO",
completed: true
}
}
Here is my reducer:
case "COMPLETE_TODO": {
return Object.assign({}, state, {
todos: [{
text: action.text,
completed: action.completed,
id: getId(state)
}, ...state.todos]
});
}
The following code creates new array with one new todo object and all the previous todos (so basically you are adding one todo to the beginning of the array from previous state):
[
{
text: action.text,
completed: action.completed,
id: getId(state)
},
...state
]
You should filter out old todo object:
[
{
text: action.text,
completed: action.completed,
id: getId(state)
},
...state.todos.filter(todo => todo.id !== getId(state))
]
Your COMPLETE_TODO action doesn't have a text field so the reducer assigns undefined to the state.
As far as I understand, you dont want to change the items' text property when completed. You can remove the text assignment from the reducer.
Related
in the reactjs I am not understand the below code please help me in this
...todo, complete: !todo.complete
according to me first ...todo is the spread array, and after comma the condition has been implemented on it.
It's vanilla JavaScript, nothing related to react here
This is not a condition
!false -> true
!true -> false
Doing this:
const todo = {
name: 'Something to do',
complete: false
}
const newTodo = {
...todo,
complete: !todo.complete
}
Results in:
{
name: 'Something to do',
complete: true
}
this is spread operator.
The JavaScript spread operator (...) allows us to quickly copy all or
part of an existing array or object into another array or object.
you can read more in this link
This is the Spread Operator, just plain JS, nothing related to React.
When you use the spread operator:
const todo = {
completed: false,
title: "Test"
};
const sameTodo = { ...todo };
The sameTodo variable will be the same as the todo variable.
// sameTodo
{
completed: false,
title: "Test"
}
In the example you gave in the question, it will first create a new object and then add the values of the todo object to it
Eg:
const todo = {
completed: false,
title: "Something"
}
/*
Value of `changedTodo`:
{
completed: false,
title: "Something"
}
*/
const changedTodo = {
...todo
}
And then it says
complete: !todo.complete
The ! Mark is a logical 'Not' operator, it will change a value to its opposite.
Eg:
// True will become false & false will become true
!true --> false
!false --> true
So when we get back to changedTodo, we can add the operator to the completed property:
const changedTodo = {
...todo,
completed: !todo.completed
}
And when you assign a property that already exists in an object that you are spreading, it will overwrite it.
So, here the todo object already has a completed property, so when we use
completed: !todo.completed, it will overwrite what was on the todo object and change it to it's opposite.
Since here the todo object has the completed property as false, it will turn to true
So in the end, we get this:
const changedTodo = {
title: "Something",
completed: true
}
By the way, this has nothing to do with React or useReducer. This is plain vanilla Javascript.
Hope this helps.
I have the following object which is my initial state in my reducer:
const INITIAL_STATE = {
campaign_dates: {
dt_start: '',
dt_end: '',
},
campaign_target: {
target_number: '',
gender: '',
age_level: {
age_start: '',
age_end: '',
},
interest_area: [],
geolocation: {},
},
campaign_products: {
survey: {
name: 'Survey',
id_product: 1,
quantity: 0,
price: 125.0,
surveys: {},
},
reward: {
name: 'Reward',
id_product: 2,
quantity: 0,
price: 125.0,
rewards: {},
},
},
}
And my reducer is listening for an action to add a reward to my object of rewards:
case ADD_REWARD:
return {
...state, campaign_products: {
...state.campaign_products,
reward: {
...state.campaign_products.reward,
rewards: {
...state.campaign_products.reward.rewards,
question: action.payload
}
}
}
}
So far so good (despite the fact that every object added is named "question")... its working but its quite messy. I've tried to replace the reducer above using the immutability-helper, to something like this but the newObh is being added to the root of my state
case ADD_REWARD:
const newObj = update(state.campaign_products.reward.rewards, { $merge: action.payload });
return { ...state, newObj }
return { ...state, newObj }
First, you must understand how the object shorthand works. If you're familiar with the syntax before ES2015, the above code translates to:
return Object.assign({}, state, {
newObj: newObj
});
Note how the newObj becomes a key and a value at the same time, which is probably not what you want.
I assume the mentioned immutability-helper is this library: https://www.npmjs.com/package/immutability-helper. Given the documentation, it returns a copy of the state with updated property based on the second argument.
You're using it on a deep property so that it will return a new value for that deep property. Therefore you still have to merge it in the state, so you have to keep the approach you've labelled as messy.
What you want instead is something like:
const nextState = update(state, {
$merge: {
campaign_products: {
reward: {
rewards: action.payload
}
}
}
});
return nextState;
Note how the first argument is the current state object, and $merge object is a whole object structure where you want to update the property. The return value of update is state with updated values based on the second argument, i.e. the next state.
Side note: Working with deep state structure is difficult, as you've discovered. I suggest you look into normalizing the state shape. If applicable, you can also split the reducers into sub-trees which are responsible only for the part of the state, so the state updates are smaller.
So, I have this function called !HandleDetail that gets the item once clicked and loads a detail screen. When this reloads, it shows only one object no matter what object I clicked on. So if I clicked on a iPhone, and I reload the browser, it reloads with the only item (a T-shirt) object showing. I am sure this has something to do with state as I fixed other bugs like this using state. I am thinking I have to map over the created array to gather the information and set the state of it. Here is a video showing what I mean: https://tyriquedaniel14-gmail.tinytake.com/tt/MzYwMDcwN18xMDg4NTEzMQ. To make things clear, I am trying to have the detail state show the corresponding item with the same id.
state = {
products: [],
clothing: [],
gadget: [],
smoke:[],
detailProduct: detailProduct,
cart: [],
modalOpen: false,
modalProduct: detailProduct,
cartSubTotal: 0,
cartTax: 0,
cartTotal: 0
};
getItem = id => {
const product = this.state.products.find(item => item.id === id);
return product;
};
handleDetail = id => {
const product = this.getItem(id);
this.setState(() => {
return { detailProduct: product };
});
};
export const detailProduct = {
id: 1,
title: "COMME DES GARCONS TEE",
img: "img/product-1.png",
img2: "img/product-1-1.png",
img3: "img/product-1-2.png",
img4: "img/product-1-3.png",
luxury: "All Luxuryitems are inspected to verify authenticity",
price: 200,
info: " COMME DES GARCONS PLAY BASIC LOGO TEE",
inCart: false,
count: 0,
total: 0,
size1: "Small",
size2: "Medium",
size3: "Large",
size4: "Extra Large",
fabric: "100% Cotton",
category: "Mens Fashion"
};
This is My router link
<Link to="/details">
I am trying to turn this into an array of all products.
I have tried adding the map method and setting detailProduct to product.
It is the normal behaviour in the react state, if you refresh the page the state becomes the initial state as you set it to your const with the detailProduct of your t-shirt, if you want to show a specific item based on the click you need to add the id to the url, because everytime you refresh the page your state is reseted so no record of the actions you did clicking.
Use a url with the id like details/id that way your app can remember the item you visited
I am building a small application for adopting pets that has a form to add and delete pets from the adoption list.
API
[
{category: 'dog', pets: [{breed: 'chihuahua', name: 'bar' }, ....]},
{category: 'cat', pets: [{breed: 'tobby', name: 'foo'}, ....]},
....
]
Function that creates the form
export default function petFieldset({
petGroups, addPet, deletePet, deleteCategory,
nameInputChange, breedInputChange, categoryInputChange
}) {
const categoryField = Object.keys(petGroups).map((keys) => (
<Fieldset
title={'Pet Category'}
button={<Delete onClick={deleteCategory.bind(this, { keys })} />}
key={keys}
>
<CategoryComponent
petGroup={petGroups[keys]}
deletePet={deletePet}
categoryKey={keys}
nameInputChange={nameInputChange}
breedInputChange={breedInputChange}
categoryInputChange={categoryInputChange}
/>
<br />
<AddPet onClick={addPet.bind(this, { keys })} />
</Fieldset>
));
return (<div>{ categoryField }</div>);
}
My reducer addPet looks like this
[ADD_PET]: (state, { payload }) => {
state[payload.keys].pets.push({ breed: '', name '' });
return { ...state };
},
The AddPet component is a button that dispatches the action ADD_PET that creates a new form field with the inputs name and breed at the bottom of the form. This is done by pushing an empty { breed: '', name: '' } to the pets array for the selected category.
The problem is when I try to add a pet more than once.
When a pet is added the first time, it creates an empty form with the breed and name with empty fields. Now if new pets are added, the form will contain the same name and breed fields of the field added previously.
example of the unwanted behaviour:
1) form gets loaded with the data from the API as place holder. [expected]
2) user clicks on AddPet button which creates a new empty form with name and breed without placeholders. [expected]
3) user writes the pet name and breed on the newly created form and clicks the AddPet component. [expected]
4) a new form with same name and breed as in step 3). [unexpected]
Something is happening with the state[payload.keys].faqs.push({ breed: '', name '' }) from the reducer.
push is not an immutable operation. you are changing the array itself. So it has the same state and previous state.
You have to create a copy of previous array, and push into this new copy the new item.
For example:
[ADD_PET]: (state, { payload }) => {
let pets = [...state[payload.keys].pets];
pets.push({ breed: '', name '' });
return { ...state, payload.keys: pets };
},
When writing a react-redux application, I need to keep both application and UI state in the global state tree. What is the best approach to design it's shape?
Lets say I have a list of todo items:
{
items: [
{ id: 1, text: 'Chew bubblegum' },
{ id: 2, text: 'Kick ass' }
]
}
Now I want to let the users select and expand the items. There are (at least) two options how to model the state shape:
{
items: [
{ id: 1, text: 'Chew bubblegum', selected: true, expanded: false },
{ id: 2, text: 'Kick ass', selected: false, expanded: false }
]
}
But this is mixing the UI state (selected and expanded) with the application state. When I save the todo list to the server, I want to save just the application state, not the UI state (in real-world app, UI state can contain state of modal dialogs, error messages, validation status etc).
Another approach is to keep another array for the UI state of the items:
{
items: [
{ id: 1, text: 'Chew bubblegum' },
{ id: 2, text: 'Kick ass' }
],
itemsState: [
{ selected: true, expanded: false },
{ selected: false, expanded: false }
]
}
Then you have to combine those two states when rendering an item. I can imagine that you can zip those two arrays in the connect function to make rendering easy:
const TodoItem = ([item, itemState]) => ...;
const TodoList = items => items.map(item => (<TodoItem item={item} />));
export default connect(state => _.zip(state.items, state.itemsState))(TodoList);
But updates to state can be painful, because items and itemsState must be kept in sync:
When removing an item, corresponding itemState must be removed.
When reordering items, itemsState must be reordered too.
When the list of todo items is updated from the server, it is necessary to keep ids in the UI state and do some reconciliation.
Is there any other option? Or is there any library that helps keeping the app state and UI state in sync?
Another approach inspired by normalizr:
{
ids: [12,11], // registry and ordering
data: {
11: {text: 'Chew bubblegum'},
12: {text: 'Kick ass'}
},
ui: {
11: { selected: true, expanded: false },
12: { selected: false, expanded: false }
}
}
I'm currently looking at this myself for a side project. I'm going to approach it similar to Rick's method above. The data{} serves as the source of truth and you use that to push local ui changes into (reflecting the most current state). You do need to merge the data and ui together before render, and I myself have tried that in a few places. I will say, as far as keeping in sync, it shouldn't be too bad. Basically, whenever you save/fetch data, you're updating data{} and clearing out ui{} to prepare for the next use case.