How to get a single object by using react reducers? - javascript

I found this tutorial https://www.digitalocean.com/community/tutorials/react-crud-context-hooks that describes how to build CRUD app with react hooks and context API.
Provided that state is described as array with object and in appReducer we have operations:
create
edit
remove
is it possible to have also a get method that would return a single object if id is matched with payload.id?
const initialState = {
products: [
{
id: 1,
name: "Milk",
amount: 3,
company: "XXX"
}
]
};
export default function appReducer(state, action) {
switch (action.type) {
case "ADD_PRODUCT":
return {
...state,
products: [...state.product, action.payload],
};
case "EDIT_PRODUCT":
const updatedProduct = action.payload;
const updatedProducts = state.products.map((prod) => {
if (product.id === updatedProduct.id) {
return updatedProduct;
}
return prod;
});
return {
...state,
products: updatedProducts,
};
case "REMOVE_PRODUCT":
return {
...state,
products: state.products.filter(
(prod) => prod.id !== action.payload
),
};
default:
return state;
}
};
What came to my mind is adding selectedProduct property in initialState however I everytime it was required to return products array as well.
Also I was thinking about adding additional reducer however, I have a synchronization problem with this solution and selectedProduct is returned is out of date.

Based on my understanding of the question I believe you are trying to fetch a single product from the list. Well you can add a case for that in your reducer like 'GET_SINGLE_PRODUCT'.
export default function appReducer(state, action) {
switch (action.type) {
case "GET_SINGLE_PRODUCT":
return {
...state,
product: state.products.find(
(prod) => prod.id === action.payload
),
};
default:
return state;
}
};
In State
const initialState = {
products: [
{
id: 1,
name: "Milk",
amount: 3,
company: "XXX"
}
],
product: {}
};

Related

Reverse state in redux

I'm trying to implement sorting functionality in my notes app but have some problems with it. My sort function must just reverse an array of notes but it does not work.
I have a state with notes:
export const initialState = {
notes: [
{
id: nanoid(),
text: '',
date: '',
},
],
}
Action:
export const sortNoteAction = ([notes]) => ({
type: SORT_NOTE,
payload: [notes],
})
Reducer:
export default function notes(state = initialState, { type, payload }) {
switch (type) {
case SORT_NOTE: {
return {
...state,
notes: [payload].reverse(),
}
}
default:
return state
}
}
Action:
export const sortNoteAction = (notes=[]) => ({
type: SORT_NOTE,
payload: notes,
})
Reducer:
export default function notes(state = initialState, action) {
switch (action.type) {
case SORT_NOTE: {
return {
...state,
notes: action.payload.reverse(),
}
}
default:
return state
}
}
I think it doesn't work because you are essentially sorting a list with just one item. If you change [notes] to notes (2 occurrences) and [payload] to payload (1 occurrence) it will probably work

Add Todo in Redux, cannot read property Symbol(Symbol.iterator)

I'm going to add an object to the array, the second time I want to add another object the whole array becomes number one and I end up with an error, my goal is to add a task to program Todo with Redux.
I also get this errors:
TypeError: Cannot read property 'length' of undefined
TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
//todoReducer.js
import {ADD_TODO} from '../Actions/Todo';
const initialState = {
todos:[],
};
const handleAddTodo = (state, action) => {
const {todos} = state;
const newTodo =[...todos, {
id: todos.length + 1,
text: action.title,
isComplete: false,
}]
return (
todos.push(newTodo)
)
}
export default function todoRDS(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return handleAddTodo(state, action)
default:
return state
}
}
Change your return function you return wrong value. You need to return the state
const handleAddTodo = (state, action) => {
const {todos} = state;
return {
...state,
todos: [...todos, {
id: todos.length + 1,
text: action.title,
isComplete: false,
}]
}
}
export default function todoRDS(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return {...state, todos: [...state.todos, { id: state.todos.length +1, title: action.title, isComplete: false }] }
default:
return state
}
}
state is unmutable in react and redux you need to create a new state with old state values and add your new todo inside that new object. If you still want to use handeAddTodo try this:
const handleAddTodo = (state, action) => {
return {...state, todos: [...state.todos, { id: state.todos.length +1, title: action.title, isComplete: false }] }
}

Possible to update multiple reducer by dispatching single redux action?

Disclaimer: this question is targeting specific package reduxsauce
Takes classic redux action, by dispatching a single action, it will flow thru all the reducer and if we want to update the state, we catch the type in each and every reducer as we see fit
loginPage.js
this.props.memberLogin({ name: 'john' }); //{ type: MEMBER_LOGIN, payload: { name: 'john' } }
LoginAction.js
const memberLogin = member => {
return { type: MEMBER_LOGIN, payload: member }
}
authReducer.js
const INITIAL_STATE = { isLoggedIn: false }
switch(state = INITIAL_STATE, action) {
case MEMBER_LOGIN: return { ...state, isLoggedIn: true };
default: return state;
}
memberReducer.js
const INITIAL_STATE = { member: null }
switch(state = INITIAL_STATE, action) {
case MEMBER_LOGIN: return { ...state, member: action.payload };
default: return state;
}
Wondering by using reduxsauce, can we still achieve something similar as demonstrated above? (dispatch single action and update both reducer)
Yes, you can.
I created this Snack example to help you, but the gist is that you have to configure your reducers to listen to the same action.
Kinda like so:
const reduceA = (state, action) => ({
...state,
a: action.value,
});
const reduceB = (state, action) => ({
...state,
b: action.value,
});
const { Types, Creators: actionCreators } = createActions({
testAction: ['value'],
});
const HANDLERS_A = {
[Types.TEST_ACTION]: reduceA,
};
const HANDLERS_B = {
[Types.TEST_ACTION]: reduceB,
};
In the example both reducers A and B have their state values altered by the same action testAction.

How can i add item in redux with nested data structure like this? I am adding new items to choice array

I want to push new items dynamically to choice array which is nested deeply so that then i can render react native TextInput component dynamically using the map function. Can anyone please help me out. Please find the datastructure and reducer design along with other code below.
Use case is like this:
UI:
Question: _________
Choice: _________ Add more choices
Right Choice: _________ Dropdown which gets data from choice array
Next Add New Question
On pressing add new question, new object from dynamicMCQGenerationDataFormat[0] is added and then using the map function i than render two questions each having question, choice, right choice properties. I hope this is enough for people to understand.
(On clicking add more choices will be rendered using map function. I have to not only push new items in choice array but later i have to even change the content inside them.)
I have a datastructure dynamicMCQGenerationDataFormat:
[
{
"question": "What",
"choice": ["choice"],
"rightChoice": ""
}
]
Reducer:
import {
EXAM_UPDATE,
REFRESH_EXAM,
INSERT_NEW_QUESTION,
INSERT_NEW_CHOICE,
UPDATE_QUESTION
} from '../actions/types';
import dynamicMCQGenerationDataFormat from '../assets/MCQGenerationFormat/dynamicMCQGeneration.json';
const INITIAL_STATE = {
description: "",
level: "",
status: "",
mcq: dynamicMCQGenerationDataFormat
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case EXAM_UPDATE:
return { ...state, [action.payload.prop]: action.payload.value }
case INSERT_NEW_QUESTION:
console.log(action.payload);
return { ...state, mcq: [ ...state.mcq, action.payload] }
case UPDATE_QUESTION:
return {
...state, mcq: state.mcq.map((mcq, i) => i === action.payload.idx ? {...mcq, question: action.payload.value} : mcq)
}
case INSERT_NEW_CHOICE:
return { ...state, mcq: [...state.mcq, choice: [...state.mcq.choice]] }
case REFRESH_EXAM:
return { INITIAL_STATE }
default:
console.log('Enroll Guard Form Reducer');
return state;
}
};
ActionCreator:
import { Actions } from 'react-native-router-flux';
import {
EXAM_UPDATE,
EXAMS_FETCH_SUCCESS,
INSERT_NEW_QUESTION,
UPDATE_QUESTION,
INSERT_NEW_CHOICE
} from './types';
import firebase from 'firebase';
import { Alert } from 'react-native';
export const examUpdate = ({ prop, value }) => {
return {
type: EXAM_UPDATE,
payload: { prop, value }
};
};
export const insertNewQuestion = (value) => {
return {
type: INSERT_NEW_QUESTION,
payload: value
};
};
export const insertNewChoice = (value) => {
return {
type: INSERT_NEW_CHOICE,
payload: value
};
};
export const questionUpdate = ({ idx, value }) => {
return {
type: UPDATE_QUESTION,
payload: { idx, value }
};
}
Calling the action creator from one of my react native component:
pushNewQuestion() {
this.props.insertNewQuestion(dynamicMCQGenerationDataFormat[0]);
}
Here dynamicMCQGenerationDataFormat is again the same:
[
{
"question": "What",
"choice": ["choice"],
"rightChoice": ""
}
]
Okay. I solved it. For those who face the same problem while updating something deeply nested and cannot denormalize data please refer this.
case INSERT_NEW_CHOICE:
return {
...state, mcq: state.mcq.map((mcq, i) => i === action.payload.idx ? {...mcq, choice: [ ...mcq.choice, action.payload.value]} : mcq)
}
case UPDATE_CHOICE:
return {
...state, mcq: state.mcq.map((mcq, i) => i === action.payload.idx ? {...mcq, choice: [ ...mcq.choice.map((choice, i) => i === action.payload.sIdx ? action.payload.value : choice ) ] } : mcq)
}

Implementing combineReducers using ```for...in```?

In lesson 16 of the egghead.io series on Redux, I tried to implement my own combineReducers function before looking at how Dan did it. I got as far as the following. I tried to use for ... in on the sub-reducers (todos, visibilityFilter) passed in like so
const combineReducers = (reducers) => {
return (state,action) => {
let temp = {};
for (let i in reducers) {
temp[i] = reducers[i](state,action)
}
return temp;
}
}
This does not work. When I test it with the expect library, I receive the following error in the console. Strangely, if I'm not mistaken, it looks like the state from the call to the todos reducer has been nested in to the call of the visibilityFilter reducer. This is very odd as my code shows them being distinctly separate fields in the object returned.
Uncaught Error: Expected { todos: [ { completed: false, id: 1, text:
'Go shopping' } ], visibilityFilter: { todos: [ { completed: false,
id: 0, text: 'Learn Redux' } ], visibilityFilter: 'SHOW_ALL' } } to
equal { todos: [ { completed: false, id: 0, text: 'Learn Redux' }, {
completed: false, id: 1, text: 'Go shopping' } ], visibilityFilter:
'SHOW_ALL' }
My test code is
const testTodoApp = () => {
const stateBefore = {
todos: [{id: 0, text:'Learn Redux', completed: false}],
visibilityFilter: 'SHOW_ALL',
};
// action is an object. with a defined type property.
const action = {
type: 'ADD_TODO',
id: 1,
text: 'Go shopping',
};
const stateAfter = {
todos: [{id: 0, text:'Learn Redux', completed: false},
{id: 1, text:'Go shopping', completed: false},
],
visibilityFilter: 'SHOW_ALL',
};
deepFreeze(stateBefore);
deepFreeze(action);
expect(
todoApp(stateBefore, action)
).toEqual(stateAfter);
console.log("Test passed: todoApp")
}
testTodoApp();
This test will pass if I use the built-in combineReducers.
The sub-reducers and call to combineReducers are as follows:
const todo = (state = {} ,action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id, text: action.text, completed: false,
};
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state;
}
return {
...state, completed: !state.completed,
};
default:
return state;
}
}
const todos = (state=[], action) =>{
switch (action.type) {
case 'ADD_TODO':
console.log('ADD_TODO switch selected')
return [
...state,
todo(undefined,action),
];
case 'TOGGLE_TODO':
console.log('TOGGLE_TODO switch selected')
return state.map( t => todo(t, action))
default:
console.log('default switch selected')
return state;
}
}
const visibilityFilter = (
state = 'SHOW_ALL',
action
) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
}
const todoApp = combineReducers({
todos,
visibilityFilter,
})
My questions are:
What was it in my code that caused this nesting of one reducer within the other?
I realize Dan used reduce instead, but for pedagogical reasons, how can I go about using the for ... in pattern to implement combineReducers?
After that, can you please comment on the appropriateness of using for ... in for such applications, and if it is a bad pattern, what is it that makes it so?
I just realized that the todos reducer and visibilityFilter reducers have to be passed the part of the combined state that corresponds to their key, not the entire combined state. So the working code should look like this, where I have added an object accessor to the corresponding part of the state in the 5th line.
const combineReducers = (reducers) => {
return (state,action) => {
let temp = {};
for (let i in reducers) {
temp[i] = reducers[i](state[i],action)
}
return temp;
}
}

Categories

Resources