Adding and Removing Data from/to State in Redux/React using Lodash - reactjs

I have a table with check boxes. When a user selects a check box, the id is sent to an action creator.
Component.js
handlePersonSelect({ id }, event) {
const { selectPerson, deselectPerson } = this.props;
event.target.checked ? selectPerson(id) : deselectPerson(id);
}
action.js
export function selectPerson(id) {
console.log("selected")
return {
type: SELECT_PERSON,
payload: id
};
}
export function deselectPerson(id) {
return {
type: DESELECT_PERSON,
payload: id
};
}
I'm able to go all the way up to this step, at which I'm lost:
This is the offending code:
import {
SELECT_PERSON,
DESELECT_PERSON,
FETCH_PEOPLE
} from '../actions/types';
const INITIAL_STATE = { people:[], selectedPeople:[]};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_PEOPLE:
return {...state, people: action.payload.data};
case SELECT_PERSON:
return [action.payload, ...state ];
case DESELECT_PERSON:
return _.without({state, selectedPeople:action.payload});
}
return state;
}
The selectedPeople state should add/subtract the id's based on each case. As of now, everytime I deselect the id, my whole "people" table disappears.

You should return the complete state from the reducer in all cases, so:
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_PEOPLE:
return {...state, people: action.payload.data};
case SELECT_PERSON:
return {...state, selectedPeople: [ ...state.selectedPeople, action.payload]};
case DESELECT_PERSON:
return {...state, selectedPeople: _.without(state.selectedPeople, action.payload)};
default
return state;
}
}
Also note you didn't have a DESELECT_PERSON action, but instead 2 SELECT_PERSON actions. Possibly a typo.

Related

Two Boolean states in the Init State of Reducer, One returns undefined

I've been working with redux for the last couple weeks and was incorporating it into my projects when I ran into this wall. Pretty common reducer for modals being rendered so i can animate them before unmounting them.
const initialState = {
isModalOpen: false,
test: false
}
export default function(state = initialState, action) {
switch (action.type) {
case "modalInteraction":
return {
isModalOpen: action.payload
};
case "testModalInteraction":
return {
test: action.payload
};
default:
return state;
};
}
Sadly, the test property is still returning as undefined despite the fact that the other initial state in the same reducer can be called without a problem. I even removed all the testModalInteraction dispatches in the case that that somehow upset the datatype. I just can't spot the difference that keeps returning undefined.
When you return the new state, make sure to spread the initial state (...state) and then change whatever values you need to change.
const initialState = {
isModalOpen: false,
test: false
}
export default function(state = initialState, action) {
switch (action.type) {
case "modalInteraction":
return {
...state,
isModalOpen: action.payload
};
case "testModalInteraction":
return {
...state,
test: action.payload
};
default:
return state;
};
}
If it is still undefined, make sure the payloads are defined for both actions.
For example, your modalInteraction action could look like
export const modalInteraction = (bool) => ({
type: "modalInteraction",
payload: bool
})
P.S., you can destructure the action object. This allows you to use "type" instead of "action.type" and "payload" instead of "action.payload".
const initialState = {
isModalOpen: false,
test: false
}
export default function(state = initialState, action) {
const {type, payload} = action;
switch (type) {
case "modalInteraction":
return {
...state,
isModalOpen: payload
};
case "testModalInteraction":
return {
...state,
test: payload
};
default:
return state;
};
}

React Redux Reducer does not retain other states

I am trying to use stackoverflow api to make my first react redux project. I need to maintain a state like the following:
{
selectedTag: reactjs,
selectedSortOrder: activity,
items:[]
}
My reducer is given below:
const initialState = {
selectedTag: 'C#',
selectedSortOrder: 'activity', items: []
}
function SelectTag(state = initialState, action) {
switch (action.type) {
case SELECTTAG:
// console.log(state);
return Object.assign({}, state, { selectedTag: action.selectedTag });
default:
return state;
}
}
function SelectSortOrder(state = initialState, action) {
switch (action.type) {
case SELECTSORTORDER:
//console.log(state);
return Object.assign({}, state, { selectedSortOrder: action.selectedSortOrder });
default:
return state;
}
}
function ReceivePosts(state = { items: [] }, action) {
switch
(action.type) {
case RECEIVESORTEDPOSTS:
case RECEIVEPOST:
console.log(state);
return Object.assign({}, state, { items: action.items })
default:
return state
}
}
const rootReducer = combineReducers({ ReceivePosts, SelectTag, SelectSortOrder })
And mapStateToProps is:
const mapStateToProps = (state) => {
const selectedTag = state.SelectTag.selectedTag;
const items = (state.ReceivePosts.items);
const tags = (state.ReceiveTags.tags);
const selectedSortOrder = state.SelectSortOrder.selectedSortOrder;
return {selectedTag, items, tags, selectedSortOrder};
}
I have 2 problems here:
a. State does not remember all the data. For eg. suppose I select the tag first and then get items, my state has only items. SelectedTag is not set in the state.
b. I am not sure why mapStateToProps needs the reducer name. Eg: const selectedTag = state.SelectTag.selectedTag;
Actually it should be state.selectedTag. But my code expects the reducer name "SelectTag" to fetch the state value.
What am I doing wrong?
You haven't configured your reducers correctly. The initialState is assigned to all of your reducers which isn't required
const initialState={
selectedTag:'C#',
selectedSortOrder:'activity',
items:[]
}
function SelectTag(state = initialState.selectedTag, action){
switch(action.type){
case SELECTTAG:
return action.selectedTag
default:
return state;
}
}
function SelectSortOrder(state = initialState.selectedSortOrder, action){
switch(action.type){
case SELECTSORTORDER:
return action.selectedSortOrder
default:
return state;
}
}
function ReceivePosts(state = {items:[]}, action){
switch(action.type){
case RECEIVESORTEDPOSTS:
case RECEIVEPOST:
console.log(state);
return Object.assign({}, state, {items:action.items})
default:
return state
}
}
const rootReducer = combineReducers({ReceivePosts, SelectTag, SelectSortOrder})
And in mapStateToProps you would use it like
const mapStateToProps = (state) => {
const selectedTag = state.SelectTag;
const items = (state.ReceivePosts.items);
const tags = (state.ReceiveTags.tags);
const selectedSortOrder = state.SelectSortOrder;
return {selectedTag, items, tags, selectedSortOrder};
}
1. Try this code change
const initialState = {
selectedTag: 'C#',
selectedSortOrder: 'activity',
items: []
}
function SelectTag(state = initialState.selectedTag, action) {
switch (action.type) {
case SELECT TAG:
return {
...state,
selectedTag: action.selectedTag
}
default:
return state;
}
}
function SelectSortOrder(state = initialState.selectedSortOrder, action) {
switch (action.type) {
case SELECTSORTORDER:
return {
...state,
selectedSortOrder: action.selectedSortOrder
}
default:
return state;
}
}
function ReceivePosts(state = { items: [] }, action) {
switch (action.type) {
case RECEIVESORTEDPOSTS:
case RECEIVEPOST:
return {
...state,
items: action.items
}
default:
return state
}
}
const rootReducer = combineReducers({ ReceivePosts, SelectTag, SelectSortOrder });
2. I am not sure why mapStateToProps needs the reducer name. Eg: const selectedTag = state.SelectTag.selectedTag;
Its because when you use combinereducers, you are combining multiple slices of data, then you need to specify the slice from which you want to fetch the data.
const rootReducer = combineReducers({
receivePosts = ReceivePosts,
selectTag = SelectTag,
selectSortOrder = SelectSortOrder
});
Issue: You have not configured your initialstate properly, you are using the same initialstate in SelectTag and also in SelectSortOrder, if the initial state is same then why do you need two reducers?

React redux action resetting non attached state

When triggering an action updateLog, it seems it resets other state items. In my case updateLog should manipulate log and that works just fine. The thing is it also resets tasks to the default values. What am I doing wrong here?
Component:
class Generator extends Component {
render() {
return (
<div className="generator">
<Inputs />
<button onClick={this.generate.bind(this)}>Go!</button>
<Log />
</div>
);
}
generate() {
this.props.updateLog("ANYTHING!");
}
}
function mapStateToProps(state) {
return {
tasks: state.tasks
};
}
function matchDispatchToProps(dispatch){
return bindActionCreators({updateLog: updateLog}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(Generator);
Action:
export const updateLog = (message) => {
return {
type: 'LOG_UPDATED',
payload: message
}
};
Logreducer:
const initialLog = "";
export default function (state = initialLog, action) {
switch (action.type) {
case 'LOG_UPDATED':
return state + "\n" + action.payload
break;
}
return state;
}
All reducers:
const allReducers = combineReducers({
tasks: taskReducer,
log: logReducer
});
export default allReducers
taskReducer:
export default function (state = null, action) {
switch (action.type) {
case 'TASK_UPDATED':
var tasks = Object.assign({}, action.payload);
return tasks;
break;
}
// Default task properties
return {
CreateDatabaseTask: {
enabled: false,
type: "sqlite"
}
}
}
The problem lies in your task reducer. If the action type matches none of the ones defined in the switch statement, you should return the current state. Instead, you are returning the initial state.
Try changing it to return the current state instead:
const initialState = {
CreateDatabaseTask: {
enabled: false,
type: "sqlite"
}
}
export default function (state = initialState, action) {
switch (action.type) {
case 'TASK_UPDATED':
var tasks = Object.assign({}, action.payload);
return tasks;
break;
default:
return state;
}
}

How to add data to redux state array without any id in a reducer?

I've got this reducer :
import { GET_CHAT, ADD_MESSAGE } from '../actions';
export default function (state = null, action) {
switch (action.type) {
case GET_CHAT:
return { ...state, [action.meta.id]: action.payload.data };
case ADD_MESSAGE:
console.log(state[action.meta.convId].chat.messages)
return {
...state,
[action.meta.convId]: {
...state[action.meta.convId],
chat: {
...state[action.meta.convId].chat,
messages: {
...state[action.meta.convId].chat.messages,
action.payload
}
}
}
};
default:
return state;
}
}
Where I want to add the action.payload to the end of the messages array. The issue is : when I try to put a key to the payload like so :
[action.meta._id]:action.payload
Messages is not an array anymore but becomes an object. So i can't loop over it anymore with map(). And redux won't let me push the payload directly to the array, it seems like i must put a key ...
Any help would be appreciated, thank's :)
If you want your messages to always be an array then your reducer should be like below:
import { GET_CHAT, ADD_MESSAGE } from '../actions';
export default function (state = null, action) {
switch (action.type) {
case GET_CHAT:
return { ...state, [action.meta.id]: action.payload.data };
case ADD_MESSAGE:
console.log(state[action.meta.convId].chat.messages)
return {
...state,
[action.meta.convId]: {
...state[action.meta.convId],
chat: {
...state[action.meta.convId].chat,
messages: [ //USE SQUARE BRACKETS INSTEAD
...state[action.meta.convId].chat.messages,
action.payload
]
}
}
};
default:
return state;
}
}

How to assign values to a reducer with an initialState with two objects?

I have the following reducer below for user's submitting a rating and getting the user's ratings (your_ratings)...
When LOAD_YOURRATINGS_SUCCESS occurs, the two levels of initalState are being remove, and your_ratings is becoming the value of store.rating, when I want it to be store.rating.your_ratings.
What am I doing wrong below? Thanks
import * as types from '../actions/actionTypes';
const initialState = {
rating: {},
your_ratings: {}
}
export default function ratingReducer(state = initialState, action) {
switch (action.type) {
case types.CREATE_RATING_SUCCESS:
return action.rating
case types.LOAD_YOURRATINGS_SUCCESS:
return action.your_ratings
default:
return state;
}
}
You need to update your state and not overwrite it, you can do it with the help of spread operator like
export default function ratingReducer(state = initialState, action) {
switch (action.type) {
case types.CREATE_RATING_SUCCESS:
return {...state, rating: action.rating}
case types.LOAD_YOURRATINGS_SUCCESS:
return {...state, your_ratings: action.your_ratings}
default:
return state;
}
}

Resources