How to assign new key value pair without overwriting top level keys? - reactjs

How do I add a key/value pair to a nested object without overwriting the top level keys in my Redux reducer?
notes state before:
notes: {
-KQpqwDTyLOzd-8UXi-z: {
created: 1473035305252,
text: 'stuff',
}
-KQqe4xiwV4-5WIs2Gpg: {
created: 1473017044898,
text: 'more stuff',
}
}
notes state after:
notes: {
0: {
created: 1473035305252,
text: 'stuff',
new: 'new value',
}
1: {
created: 1473017044898,
text: 'more stuff',
}
}
here is my reducer that is producing the above results:
import _ from 'lodash'
const notes = (state = [], action) => {
switch (action.type) {
case 'FETCH_NOTES':
return action.payload;
case 'CLEAR_NOTES':
return state = [];
case 'UPDATE_NOTE':
console.log(state)
return _.map(state, (note, index) => {
if (index === action.id) {
return _.assign({}, note, {
new: action.new
})
}
return note
})
default:
return state
}
}
export default notes

Please use mapValues function instead of map function. Updated code below.
return _.mapValues(state, (note, index) => {
if (index === action.id) {
return _.assign({}, note, {
new: action.new
})
}
return note
})

Related

How do I check if an item exists and modify it or return the original object

export const basketReducer = (state = { total:0, items:[]}, action) => {
switch (action.type) {
case "ADD_ITEM":
const item = [...state.items, action.payload]
const updateBasket = state.items.map(el => {
if (el._id === action.payload._id) {
return {
...el,
quantity: el.quantity + action.payload.quantity
}
}
return item
})
It seems your code is close.
First check if state.items array already contains some element with a matching _id property.
If there is a match then shallow copy the state and shallow copy the items array and update the matching element.
If there is no match then shallow copy the state and append the new data to the items array.
Reducer case logic:
case "ADD_ITEM":
const hasItem = state.items.some(el => el._id === action.payload._id);
if (hasItem) {
// update item
return {
...state:
items: state.items.map(el => {
if (el._id === action.payload._id) {
return {
...el,
quantity: el.quantity + action.payload.quantity
}
}
return el; // <-- return current mapped element if no change
}),
};
} else {
// add item
return {
...state,
items: state.items.concat(action.payload),
};
}

Reducer doesn't update state

I am trying to create a reducer to update a property in object, but i cant be able to update and store the new state information
Reducer
export default function hideCardNumber(state = INITIAL_STATE, action: Action) {
if (action.type === 'HIDE_CARDNUMBER') {
return {
...state,
data: {...state.data, action }}
}
else
return state
}
Action
export const toggleViewNumberCard = (cardId: number, hideCardNumber: boolean) => {
return {
type: 'HIDE_CARDNUMBER',
cardId,
hideCardNumber,
}
}
dispatch to action
function handleToggleViewCardNumber() {
cards.map((card: Card) => {
if (card.cardId === props.activeCard ) {
dispatch(toggleViewNumberCard(
card.cardId,
!card.hideCardNumber,
))
}
})
}
Initial State
export const INITIAL_STATE = {
activeCard: 0,
data: [
{
cardId: 0,
cardName: 'Card',
cardUsername: 'Name',
cardNumber: '1234 1234 1234 1234',
hideCardNumber: false, <-- Trying to replace this property when reducer update
},
]
}
You need to update reducer like this:
const {hideCardNumber, cardId} = action;
return {
...state,
data: state.data.map(item => item.cardId === cardId ? {...item, hideCardNumber} : item )
}
In real-world scenarios, cardID will be a hash. Also to easily maintain the store data when the application grows INITIAL_STATE should be like this.
export const INITIAL_STATE = {
activeCard: 0,
data: {
123456: {
cardId: 123456,
cardName: 'Card',
cardUsername: 'Name',
cardNumber: '1234 1234 1234 1234',
hideCardNumber: false,
},
}
}
Then the Reducer will be like this.
export default function hideCardNumber(state = INITIAL_STATE, action) {
if (action.type === 'HIDE_CARDNUMBER') {
return {
...state,
data: {
...state.data,
[action.cardId]: {
...state.data[action.cardId],
hideCardNumber: action.hideCardNumber
}
}
}
}
else
return state
}
If the activeCard matches one of the IDs in the cards, the code will work perfectly.
first of all it's better to put you data in payload like:
export const toggleViewNumberCard = (cardId: number, hideCardNumber: boolean) => {
return {
type: 'HIDE_CARDNUMBER',
payload : {
cardId,
hideCardNumber,
}
}
}
looks like you have array of cards ,first of all you must have find your current card that you wanna replace using cardid like this:
const index = state.data.findIndex(
(card) => cardId === action.payload.cardId
);
then copy your old array :
const newArray = [...state.data];
then replace that index of newarray with your new hideCardNumber value like this:
newArray[index] = {
...newArray[index],
action.payload.hideCardNumber
};
return {
...state,
data: newArray,
};
i hope it would help you

Composing a redux reducer for nested objects in an array

I'm new to Redux and am having some difficulty composing a working reducer for my situation.
My current state looks like this
export const userData = {
userID: '12345678',
userDetails: {
firstName: 'Joe',
surname: 'Bloggs'
},
currentGames: [
{
gameId: 'G-00000001',
gameSelections: [
{
subgameId: '',
selection: ''
}
]
}
]
};
My action looks like this
function selectWinner (gameId, subgameId, selection) {
return {
type: SELECT_WINNER,
gameId,
subgameId,
selection
}
}
The aim is to be able to add/update the objects in the gameSelections array.
There may be more than one Object in the currentGames array also.
I've heard I should use .map but I'm not really sure how.
You're on the right track for using .map() to iterate over the array of objects. It also looks like your action-creator has all the necessary parameters to update your reducer state.
Your reducer can look something like this:
const userReducer = (state=userData, action) => {
switch(action.type){
case SELECT_WINNER:
return {
...state,
currentGames: [...state.currentGames].map((game) => {
if(game.gameId == action.gameId){
return {
...game,
gameSelections: [...game.gameSelections].map((gameSelection) => {
if(gameSelection.subgameId == action.subgameId){
return {
...gameSelection,
selection: action.selection
}
} else {
return gameSelection
}
})
}
} else {
return game
}
})
}
default:
return state
}
}
Kind of messy, but would get the job-done with a deeply nested state.
Add item to array:
case'ADD_ITEM':
return {
...state,
some_arr: [...state.some_arr, action.payload]
}
update spicific item in array:
case 'UPDATE_ITEM':
return {
...state,
some_arr: state. some_arr.map(
(item, index) => index === specific_index
? {...item, ...action.payload}
: content
)
}
Deep cloning of the state is required.
useful link-https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns
const reducer = (state = userData, action) => {
switch (action.type) {
case CASENAME:
return {
userID: state.userID,
userDetails: {
...state.userdetails
},
currentGames: [
{
gameId: action.gameId,
gameSelections: [
{
subgameId: action.subgameId,
selection: action.selection
}
]
}
]
};
}
}

Toggle state of object key value in an array

I need to create a reducer that toggles the state of done using the id or index of the todo
state = {
todos: [
{
title: "eat rice",
done: false,
id: 1
},
{
title: "go fishing",
done: true,
id: 2
},
{
title: "drink coffee",
done: false,
id: 3
}
]
}
I tried this but it mutates the state, the payload being the index of the object in the array.
case "DONE":
const todos = [...state.todos];
todos[action.payload].done = !todos[action.payload].done;
return {
...state,
todos: todos
};
You could use a map function instead. The function will generate a new array which you can use to replaces todos with.
case "DONE":
const newTodos = state.todos.map((todo, index) => {
// Based on your code, I assume action.payload is the index of the todo in the array of todos
if (index === action.payload) {
const newTodo = {...todo};
todo.done = !todo.done;
return todo;
}
return todo;
});
return {
...state,
todos: newTodos,
};
If you don't want to iterate over every todo, you could do something else such as using slice to create a copy of the array and then change the one value:
case "DONE":
const newTodos = todos.slice();
const updatedTodo = {...newTodos[action.payload]};
updatedTodo.done = !updatedTodo.done;
newTodos[action.payload] = updatedTodo;
return {
...state,
todos: newTodos,
};
Found the answer. Thanks for the contributions.
case "DONE":
const newTodos = state.todos.map((todo, index) => {
if (index === action.payload) {
const newTodo = { ...todo };
newTodo.done = !newTodo.done;
return newTodo;
}
return todo;
});
return {
...state,
todos: newTodos
};
Using the spread operator or map will create a new array but will not automatically clone the contained objects, as JavaScript follows "pass by reference". You'd have to clone the object as well. So maybe something like
case "DONE":
const todos = state.todos.map((todo, index) => {
const newTodo = {...todo};
if (action.payload === index) {
newTodo.done = !todo.done;
}
return newTodo;
});
return {
...state,
todos,
};
Of course you could also use a clone utility or something like Immutable.js.

Target object based on a value of a key of that object which is an element in an array

How do I target what object based on a value of a key of that object which is an element in an array.
Reducer:
const initialState = {
posts: []
}
export default function newData (state = initialState, action) {
switch (action.type) {
case "updateNumber":
return {
...state,
/* How to determine which object's number I should update? */
/* The payload is the id */
number: this.state.number ++ // <== need help here.
}
default:
return state
}
}
Data returned from an API call will be appended to posts:
posts:
[
{
id: '1',
name: 'Name One',
number: 11
},
{
id: '2',
name: 'Name Two',
number: 22
},
{
id: '3',
name: 'Name Three',
number: 33
}
]
In the component I'm rendering it :
class Posts extends Component {
renData () {
console.log("ThePosts: ", this.props.posts);
return posts.map((post, postIndex) => {
return (
<View key = {postIndex}>
<TouchableOpacity
onPress={() => updateNumber(post.id)}
>
<Text> {post.number} </Text>
</TouchableOpacity>
</View>
)
});
}
render(){
return(
<View style={style.container}>
{this.renData}
</View>
)
}
}
function mapDispatchToProps(dispatch) {
return {
updateNumber: (id) => {
dispatch(updateNumber(id))
}
}
}
function mapStateToProps(state) {
return {
posts: state.posts,
}
}
export default connect( mapStateToProps, mapDispatchToProps ) ( Posts );
How to determine which object's number is to be updated?
Note: I tried this approach but I'm finding it very difficult to append API call's data to an object. It's easier with arrays.
Please assist.
Assuming that updateNumber is actually just incrementing the number, you must first find the index of the object in the array that you want to update in the reducer using the id. You can then create a new array that replaces the object with an incremented object:
export default function newData (state = initialState, action) {
switch (action.type) {
case "updateNumber": {
const { posts } = state;
// find object to update
const index = posts.findIndex(({ id }) => id === action.id);
if (index > -1 ) {
// create new object with existing data and increment number
const updatedData = {...posts[index]};
updatedData.number++;
// return new posts array that replaces old object with incremented object at index
return { posts: [...posts.slice(0, index), updatedData, ...posts.slice(index + 1)]};
}
// return state if no object is found
return state;
}
default:
return state
}
}

Resources