redux - action to fill an array and then reset - arrays

I am trying to fill a small array [Should have two elements]. The initial state is
selected: []
Once it fills 2 slots [a, b], it should compare if the numbers are equal (return true or false) and reset the array to the original state [].
export const selectTile = (idx) => {
return {
type: SELECT_TILE,
idx
};
};
const initialState = {
selected: [],
matched: 0,
score: 0
};
export default (state = initialState, action) => {
switch (action.type) {
case SELECT_TILE:
return { ...state, selected: state.selected.push(action.idx) };
default:
return state;
}
};
I have already stumbled upon the first problem. The above reducer returns an error 'state.selected.push is not a function'.
When the first action gets dispatched, it should do:
check if the array's length is smaller than 2
If yes, push the element to the array:
selected: [x]
another action:
check if the array's length is smaller than 2
yes, push the element to the array:
selected: [x, y]
another action:
check if the array's length is smaller than 2
no - compare if x === y, (return something, eg. true or false or any flag)
regardless of whether x===y or not, reset the 'selected' array to [] and wait for another action.
EDIT:
I think my description above was not accurate (btw, an action is dispatched on click of a tile):
action 1: [x]
action 2: [x, y] (if x=y, matched: true, selected: [])
action 3: [x] (the moment this action is dispatched, it should clear
the 'selected' array and a new element should be added to the array.
As it stands with the answer below, the third actions just clears the
array, but no new element is added.
I have adapted the answer below but it gives me an error that state.slice is not an action (error appears when I click on a tile for the second time (action 2 being dispatched).
case SELECT_TILE:
if (state.selected.length < 1) {
return { ...state, selected: [...state.selected, action.idx] };
} else if (state.selected.length === 1) {
const nextState = state.slice();
nextState.selected.concat(action.idx);
if (nextState.selected[0] === nextState.selected[1]) {
return { ...nextState, score: nextState.score + 1, selected: [] };
}
return { ...nextState, selected: [] };
}

You must use concat to push an item to the state array as it retun
export default (state = initialState, action) => {
switch (action.type) {
case SELECT_TILE:
return { ...state, selected: state.selected.concat(action.idx) };
default:
return state;
}
};
or you can do it with the spread operator itself, which i think is a better way
return { ...state, selected: [...state.selected, action.idx] };
As per your requirement is considered
export default (state = initialState, action) => {
switch (action.type) {
case SELECT_TILE:
if(state.selected.length < 2) {
return { ...state, selected: [...state.selected, action.idx] };
} else {
if(state.selected[0] === state.selected[1]) {
return {...state, matched: true, selected: []}
}
}
default:
return state;
}
};

Related

Update the Old and New Value in Redux

I have a problem updating the old value of the array in my redux react app. I have successfully updated the new selected object true. I want to other object to set to false since I have set the another object to true.
const initialState = {
annualPlans: [],
};
const planReducer = (state = initialState, action) => {
switch (action.type) {
case planConstants.UPGRADE_PLAN_SUCCESS:
return {
...state,
annualPlans: state.annualPlans.map((todo) =>
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return original todo
todo
),
};
default:
return state;
}
};
It seems like you want to return current set to false for the others:
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return with current false
{ ...todo, current: false }
Id first create the new todos by looping though a map and then assign to the state
case planConstants.UPGRADE_PLAN_SUCCESS: {
const newTodos = state.annualPlans.map((todo) => {
if (todo.value === action.data.plan_id) {
return { ...todo, current: true }; // if todo.id matched then set the current to true and return;
}
if (todo.current) {
return { ...todo, current: false }; // else if current is already true, then false it and return
}
return todo; // else return original todo
});
return {
...state,
annualPlans: newTodos
};
}
.....
This will optimize the rendering and prevent of looping multiple times.
No need to re create all todo items, if you use pure components then that will mess up this optimisation. You can just add a map to deal with resetting the current value:
const planReducer = (state = initialState, action) => {
switch (action.type) {
case planConstants.UPGRADE_PLAN_SUCCESS:
return {
...state,
annualPlans: state.annualPlans
.map(
(todo) =>
todo.current
? { ...todo, current: false } //reset current to false
: todo // no need to change this one
)
.map((todo) =>
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return original todo
todo
),
};
default:
return state;
}
};

Redux: Action is being called properly but reducer is not updating the state, just keeping the initial state

I have been building an application using React, Redux, Redux-thunk.
While calling action, the payload and type are properly received by the reducer but it is not modifying them.
My Reducer:
import EditorActionTypes from './editor.types';
const INITIAL_STATE = {
editorModified: false,
tabs: [
{
name: 'main.js',
active: true
},
{
name: 'setup.js',
active: false
}
]
};
const editorReducer = ( state = INITIAL_STATE, action ) => {
switch (action.payload) {
case EditorActionTypes.SELECT_TAB:
return {
...state,
tabs: [
...state.tabs.map(
(tab, index) => index === action.payload
? tab.active = true
: tab.active = false
)
]
}
default:
return state;
}
};
export default editorReducer;
Switch condition is wrong,
Reducer function should check for action.type
switch (action.type) {
case EditorActionTypes.SELECT_TAB:
return {
The problematic part, I think, is this:
...state.tabs.map(
(tab, index) => index === action.payload
? tab.active = true
: tab.active = false
)
You are not returning anything from the function inside map. You should return tab objects, instead.
...state.tabs.map(
(tab, index) => ({
...tab,
isActive: index === action.payload
}))
And as #Gangadhar Gandi pointed out, you should switch on the action.type, not the action.payload

Redux adds duplicate array item when loaded from state

Hey everyone probably a simple question, basically I have a button when i click it fires an action and passes down the whole object that I concat to array if its not duplicate but strangely what happens because I save data to local-storage and after I load it from there it does not check for duplicate and duplicates the array item. My reducer code below maybe the error is there?
Searched as much as possible.
const initialState = {
favourites: []
};
const favourites = (state = initialState, action) => {
const { payload } = action;
switch (action.type) {
case actionTypes.ADD_FAVOURITES:
return {
...state,
favourites:
state.favourites.indexOf(payload) === -1
? state.favourites.concat(payload)
: state.favourites
};
default:
return state;
}
};
The issue here seems to be that state.favourites.indexOf(payload) === -1 is always true. This is because the function Array.prototype.findIndex() does not find identical objects.
You should use an alternate method of checking to see if the payload object is already in the favourites array. For example, you could try something like this:
const favourites = (state = initialState, action) => {
const { payload } = action;
switch (action.type) {
case actionTypes.ADD_FAVOURITES:
return {
...state,
favourites:
JSON.stringify(state.favourites).indexOf(JSON.stringify(payload)) === -1
? state.favourites.concat(payload)
: state.favourites
};
default:
return state;
}
};

Redux's Reducer - updating with keys as numbers

In my current implementation, I have an initial state as so:
const state = {
1: {id: 1, status: false},
2: {id: 2, status: false}
}
I'm having a hard time how to implement the reducer correctly if I wanted to update the status on either the item.
I have the following as so:
export default function (state = initialState, action) {
switch (action.type) {
case UPDATE_TO_TRUE: {
const { itemID } = action;
let item = itemID.toString();
return {
...state,
item: {
status: true
}
}
}
default: {
return state;
}
}
}
Essentially I can't figure out how to pinpoint the right key. What's tricky is that the keys are setup as numbers.
Try using a computed property key in the object literal definition:
return {
...state,
[item]: {
id: item,
status: true
}
}
You can access the item you want using bracket notation, which works as long as the key can be represented by a string. So for example:
const state = {
1: {id: 1, status: false},
2: {id: 2, status: false}
};
console.log(state['2']);
returns your second item.

How to update two values in two different reducers at the same time in Redux / Atomic operations

Project description: I've a calendar like Google Calendar, where you have zoomIn and zoomOut buttons, and also a datepicker where you can click any date to move your calendar to that date.
Design of store: I've a filters reducer where I keep the range of the current calendar (for example {start: '01/01/2016', end: '01/01/2017'}), and also a UI reducer where I keep the current zoom level (one of year/month/week)
Scenario: When someone is on zoomLevel='year' and clicks on a date on the datepicker, I need to go to zoomLevel='week' and also modify the range.
Problem: How to update the store with both zoomLevel and range, at the same time? If I don't, my calendar breaks due to inconsistency because when I update the first value the user interface renders everything.
UI reducer as an example of generic reducer:
import * as actions from './action-types'
import { omit } from 'lodash'
import initialState from './initial-state'
export * from './actions'
export * from './selectors'
// TODO: Eval. Tendria mas sentido hacer el switch por dominio?
export default function ui(state = initialState, {type, meta: {domain = 'general'} = {}, payload: {key, value} = {}}) {
switch (type) {
case actions.SET:
return {
...state,
[domain]: {
...state[domain] || {},
[key]: value
}
}
case actions.TOGGLE:
return {
...state,
[domain]: {
...state[domain] || {},
[key]: !!!(state[domain] || {})[key]
}
}
case actions.TOGGLE_IN_ARRAY:
// No sirve para objetos
const index = state[domain] && state[domain][key] ? state[domain][key].indexOf(value) : -1
return index === -1 ?
{
...state,
[domain]: {
...state[domain] || {},
[key]: [
value,
...state[domain][key]
]
}
} : {
...state,
[domain]: {
...state[domain],
[key]: [
...state[domain][key].slice(0, index),
...state[domain][key].slice(index + 1)
]
}
}
case actions.DELETE:
return {
...state,
[domain]: omit(state[domain], key)
}
case actions.CLEAR_DOMAIN:
return {
...state,
[domain]: initialState[domain]
}
case actions.RESET_UI:
return {
...initialState
}
default:
return state
}
}
Dispatched actions will be sent to all reducers in the root reducer which will in turn be passed down to child reducers (depending on reducer implementations).
Hence if your root reducer looks like this:
export default combineReducers({
filters,
ui,
});
You can respond to the same action like this in each reducer:
function filters(state, action) {
case CLICK_ON_DATE:
return {
...state,
start: ...,
end: ...,
};
...
}
function ui(state, action) {
case CLICK_ON_DATE:
return {
...state,
zoomLevel: 'week',
};
...
}

Resources