React-Redux state of changed value is not refrected on change - javascript

The state of the App is comprised of 2 independent states managed by: modelA, modelB reducers. Ignore modelB. For modelA i have an action - setInput. This action is used in the onChange listener of component Whatever to set inputVal val to whatever.
Whatever listener should also display the inputVal, but that doesn't happen. The changes to inputVal are not reflected.
Debugging shows that the state is indeed modified - whatever is entered in the input element of Whatever, is being save in the state. It's just that the stateToProps mapper is not invoked again.
What am i doing wrong?
var INPUT = 'INPUT';
var setInput = (val = '') => ({
type: INPUT,
payload: val
})
var modelA = (state = {test1: '', test2: false, inputVal: ''}, action) => {
switch (action.type) {
case this.INPUT:
state.inputVal = action.payload;
return state
default:
return state
}
}
var modelB = (state = {test1: '', content: ''}, action) => {
switch (action.type) {
default:
return state
}
}
var stateToProps = state => {
return {
storeValue: state.modelA.inputVal
}
}
var dispatchToProps = dispatch => {
return {
setStoreVal: (val) => {
dispatch(setInput(val))
}
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.store = Redux.createStore(Redux.combineReducers({modelA, modelB}));
}
render() {
return e(
ReactRedux.Provider, {store:this.store},
e('div', null, "App",
e(ReactRedux.connect(stateToProps, dispatchToProps)(Whatever), null)
)
);
}
}
class Whatever extends React.Component {
constructor(props) {
super(props);
this.state = {val:0}
this.listener = this.listener.bind(this);
}
listener(evt) {
this.props.setStoreVal(evt.currentTarget.value);
this.setState({val:evt.currentTarget.value});
}
render() {
return e(
'div', null,
e('p', null, this.props.storeValue),
e('input', {onChange:this.listener, val:this.state.val})
);
}
}

Redux assumes that you never mutate the objects it gives to you in the
reducer. Every single time, you must return the new state object. Even
if you don't use a library like Immutable, you need to completely
avoid mutation.
Never mutate reducer arguments
var modelA = (state = {test1: '', test2: false, inputVal: ''}, action) => {
switch (action.type) {
case this.INPUT:
return {...state, inputVal: action.payload}; // return new one
default:
return state
}
}

Related

React/Redux - Add element instead of replacing state

I am using https://reactflow.dev/ library and in Redux state when I add a new element rather than add it to the array it replaces the previous item with a new one.
Reducer
import * as types from '../actions/types';
const initialState = {
elements: [],
currentElement: undefined,
};
const flow = (state = initialState, action) => {
switch (action.type) {
case types.ADD_ELEMENT:
return {
...initialState,
elements: initialState.elements.concat(action.payload),
// elements: [...initialState.elements, action.payload],
};
}
}
Action
import { ADD_ELEMENT } from './types';
export const addElement = (node) => (dispatch) => {
dispatch({
type: ADD_ELEMENT,
payload: node,
});
};
DndFLow
const onDrop = (event) => {
event.preventDefault();
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
const type = event.dataTransfer.getData('application/reactflow');
const position = reactFlowInstance.project({
x: event.clientX - reactFlowBounds.left,
y: event.clientY - reactFlowBounds.top,
});
const newNode = {
id: getId(),
type,
position,
data: { label: `${type}` },
};
addElement(newNode);
setElements((es) => es.concat(newNode));
};
You're using initialState in the reducer, instead of the state.
Using the state might fix your issue:
const flow = (state = initialState, action) => {
switch (action.type) {
case types.ADD_ELEMENT:
return {
...state,
elements: state.elements.concat(action.payload),
};
}
}
The state = initialState is correct since that means it will use the initialState by default if state doesn't have any value otherwise, but you shouldn't use the initialState beyond that, unless you want to reset your state to it.

React-redux: Update value in array

As an initial state I use array of objects:
export default{
items: [
{
Date: 1,
Operation: 'revenue',
}
]
}
In component I dispatch the action, which must update one element in object of array: "Operation"
const mapDispatchToProps = (dispatch) => {
return {
selectOperation: (input) => dispatch({type: app.SELECT, payload: input})
}
};
class OperationSelect extends React.Component {
// constructor
handleChange(event) {
this.props.selectOperation({
key: 'Operation',
value: event.target.value
});
};
// render() logic
}
export default connect(null, mapDispatchToProps)(OperationSelect)
Reducer:
import initialState from '../constants/initialState';
import { app } from '../constants/types';
export default function update(state = initialState, action) {
switch (action.type) {
case app.SELECT:
return {
...state,
[action.payload.key]: state.items.map(
(item, i)=> i===0 ? {...item, Operation: action.payload.value}
: item
)
};
default:
return state;
}
}
But, when I select new value and application run handleChange, dispatch the action, run reducer, the state in store keeps the old value of "Operation".
What am I doing wrong?
This is I think what you need to do:
first add an id property to your items and then do something like this:
export default {
items: [
{
id: 0,
Date: 1,
Operation: "revenue",
},
],
};
class OperationSelect extends React.Component {
// constructor
handleChange(event) {
this.props.selectOperation({
key: "Operation", // I think you need to check this and try to findout that you need this or not
value: event.target.value,
id: 0 // 0 is just an example you need to decide how you would implement the id
});
}
// render() logic
}
export default function update(state = initialState, action) {
switch (action.type) {
case app.SELECT:
return {
...state,
items: state.items.map((item, i) =>
i === action.payload.id ? { ...item, Operation: action.payload.value } : item
),
};
default:
return state;
}
}
The problem was in that I did not update in reducer the state of array.
This is a working code:
export default function update(state = initialState, action) {
switch (action.type) {
case app.SELECT:
return {
...state,
items: state.items.map(
(item, i)=> i===0 ? {...item, [action.payload.key]: action.payload.value}
: item
)
};
default:
return state;
}
}
Earlier in return-block I used [action.payload.key] in place, where "items" should have used. So I updated "Operation" in place, where "items" updated.

React redux state change does not cause update even after deep copy of all state data

I have a component which when using CTRL+Z should trigger an undo action. Tracing the code through it is obvious that the state is updated correctly and that the arrays in it are not being mutated. However the component is not rerendered until I click on it, which causes a highlight to occur. At this point the component jumps to its previous location. I have attempted using forceUpdate() after the undo action is dispatched but that did not succeed either.
My reducer is a single line returning state and the new object as the action.payload. My action creator reads the original data, clones everything (some of them multiple times in a 'swing wildly' attempt to solve this) and then dispatches the undo action and data to the reducer.
Stepping through the code and comparing values shows me that everything seems correct so I cannot see where the issue is.
Here is my action creator:
export const Layout_Undo_Change = (callback) => (dispatch, getState) => {
const state = getState();
const desks = state.layout_moveData.desks;
//if no past to undo to
if (desks.past.length === 0) return;
const previous = clone(desks.past)[desks.past.length - 1];
const undoPast = clone(desks.past.slice(0, desks.past.length - 1));
const undoFuture = clone([desks.present, ...clone(desks.future)])
const undoDesks = { past: undoPast, future: undoFuture, present: previous };
dispatch({ type: ActionTypes.LAYOUT_UNDO_MOVES, payload: undoDesks });
// callback();
}
and here is the reducer:
export const layout_moveData = (state = {
desks: {
past: [],
present: null,
future: []
}
}, action) => {
switch (action.type) {
case ActionTypes.LAYOUT_DESKS_LOADING:
return { ...state, desks: { present: [], past: [], future: [] } };
case ActionTypes.LAYOUT_DESKS_LOADED:
return { ...state, desks: { present: action.payload, past: [], future: [] } };
case ActionTypes.LAYOUT_DESK_DELETED:
return { ...state, desks: action.payload };
case ActionTypes.LAYOUT_RESTORE_ALL:
return { ...state, desks: { present: [], past: [], future: [] } };
case ActionTypes.LAYOUT_SET_MOVES:
return { ...state, desks: action.payload };
case ActionTypes.LAYOUT_UNDO_MOVES:
return { ...state, desks: action.payload };
case ActionTypes.LAYOUT_REDO_MOVES:
return { ...state, desks: action.payload };
default:
return state
}
}
and finally here is the calling line from the component:
handleKeyPress = (e) => {
console.log("Layout.handleKeyPress");
if (this.state.edit) {
switch (e.code) {
case 'KeyZ':
if (e.ctrlKey) {
this.props.Layout_Undo_Change(this.forceUpdate);
e.cancelBubble = true;
this.forceUpdate();
}
break;
case 'KeyY':
if (e.ctrlKey) {
//this.props.Layout_Redo_Change();
UndoMove.redo();
e.cancelBubble = true;
}
break;
default:
break;
}
}
}
Edit - adding mapState code
mapDispatchToProps code:
const mapDispatchToProps = (dispatch) => {
//add action creators here - by reference?
return {
Layout_Set_Current_Site: (siteId) => { dispatch(Layout_Set_Current_Site(siteId)) },
Layout_Get_Sites: () => { dispatch(Layout_Get_Sites()) },
Layout_Get_Map_Background: (siteId, callback) => { dispatch(Layout_Get_Map_Background(siteId, callback)) },
Layout_Get_Desk_Types: () => { dispatch(Layout_Get_Desk_Types()) },
Layout_Fetch_Desks: (siteId) => { dispatch(Layout_Fetch_Desks(siteId)) },
Layout_Undo_Change: (callback) => { dispatch(Layout_Undo_Change(callback)) },
Layout_Redo_Change: () => { dispatch(Layout_Redo_Change()) },
Layout_Clear_Desk: (deskId) => { dispatch(Layout_Clear_Desk(deskId)) },
Layout_Delete_Desk: (deskId) => { dispatch(Layout_Delete_Desk(deskId)) },
Layout_Update_Desk_Data: (desk, deskId) => { dispatch(Layout_Update_Desk_Data(desk, deskId)) },
Layout_Get_UserImages: (deskId) => { dispatch(Layout_Get_UserImages(deskId)) },
Layout_Create_Desk: (type, siteId, height, width) => { dispatch(Layout_Create_Desk(type, siteId, height, width)) },
Layout_Restore_All: () => { dispatch(Layout_Restore_All()) },
Layout_Set_Current_Desk: (deskId) => { dispatch(Layout_Set_Current_Desk(deskId)) }
};
}
mapStateToProps code:
const mapStateToProps = (state) => {
return {
layout: state.layout,
layout_moveData: state.layout_moveData,
roles: state.siteMap.siteMapData.userRoles
}
}
Any help to point me in the correct direction would be awesome.
Extrapolated all components and items to separate classes to better handle individual state changes rather than dealing with everything from top down

How to handle adding new item or delete existing one in React using Redux

I have a list of products called work items stored on my Redux store and I want to add an action that adds new work item or remove existing one when user picks up a a work item from the ui.
What I have so far is this workItemReducer:
import {
FETCH_WORKITEMS_BEGIN,
FETCH_WORKITEMS_SUCCESS,
FETCH_WORKITEMS_FAILURE,
SELECTED_WORKITEM
} from '../actions/workItemAction';
const initialState = {
workItems: [{"name":'work 1'}, {"name":'work 2'}, {"name":'work 3'}],
workItemsSelected: {},
loading: false,
error: null
};
export default function workItemReducer(state = initialState, action) {
switch(action.type) {
case FETCH_WORKITEMS_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_WORKITEMS_SUCCESS:
return {
...state,
loading: false,
workItems: action.payload.workItems
};
case FETCH_WORKITEMS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
workItems: []
};
case SELECTED_WORKITEM:
return {
...state,
workItemsSelected: action.payload.workItem
};
default:
return state;
}
}
and the actions looks as below:
export const FETCH_WORKITEMS_BEGIN = 'FETCH_WORKITEMS_BEGIN';
export const FETCH_WORKITEMS_SUCCESS = 'FETCH_WORKITEMS_SUCCESS';
export const FETCH_WORKITEMS_FAILURE = 'FETCH_WORKITEMS_FAILURE';
export const SELECTED_WORKITEM = 'SELECTED_WORKITEM';
export const fetchWorkItemsBegin = () => ({
type: FETCH_WORKITEMS_BEGIN
});
export const fetchWorkItemsSuccess = workItems => ({
type: FETCH_WORKITEMS_SUCCESS,
payload: { workItems }
});
export const fetchWorkItemsFailure = error => ({
type: FETCH_WORKITEMS_FAILURE,
payload: { error }
});
export const selectedWorkItem = workItem => ({
type: SELECTED_WORKITEM,
payload: { workItem }
});
I have a container component that disptach or call these actions which I am a bit confused where the logic of adding a new one or removing existing one happens, either on the container/smart component or directly in the reducer.
Container component has this method:
onWorkItemSelect = (workItem) => {
this.props.dispatch(selectedWorkItem(workItem));
};
Anyone can help on writing the logic of adding new or remove existing one and where that code should live?
adding this to reducer works thou im not sure if all this code should remain into the reducer:
case SELECTED_WORKITEM:
let arr = [];
if (containsObject(action.payload.workItem, state.workItemsSelected)) {
arr = remove(state.workItemsSelected, action.payload.workItem);
} else {
arr = [...state.workItemsSelected, action.payload.workItem];
}
return {
...state,
workItemsSelected: arr
};
It should be done in the reducer
when adding one you could just spread the current array which you can get from the reducer state
const { workItems } = state;
const { workItem } = action.payload;
return {
// ...other stuff to return
workItems: [...workItems, workItem],
}
to delete one
const { workItems } = state;
const { workItem } = action.payload;
return {
// ...other stuff to return
workItems: workItems.filter(x => x.name === workItem.name),
}

reducers updating state directly

I am trying to learn react in proper way.
what I learnt in react is we should not update state directly and so we need too use setState.
But in my reducers they are updating state directly.
Can you tell me how to fix it.
Providing my code snippet below.
Is this the correct way of updating reducers.
import { isBlank, filterFromArrayByKey, filterFromArrayByValue } from 'components/Helpers';
const INITIAL_STATE = {
tab: 'operational',
search: '',
region: '',
county: '',
SPORT: '',
SPORTCounties: [],
loading: false,
error: '',
operation: {},
lookup: {},
specialty: [],
SPORTs: [],
ballsRanker: [],
playersRanker: [],
ballsNonRanker: [],
playersNonRanker: [],
includeplayers: false,
includeBorderingCounties: false,
SPORTAdequacy: []
};
export default function (state = INITIAL_STATE, action) {
console.log("state.zoomDefault--->", state.zoomDefault);
delete state.zoomDefault;
console.log("state.zoomDefault--->", state.zoomDefault);
// console.log("state.errorMessage--->", state.errorMessage);
delete state.errorMessage;
// console.log("after delete state.errorMessage--->", state.errorMessage);
switch (action.type) {
case SET_SPORTS:
//console.log('action.Rankeyload-->', action.Rankeyload);
state.ballsRanker = state.copyballsRanker;
state.ballsNonRanker = state.copyballsNonRanker;
state.playersRanker = state.copyplayersRanker;
state.playersNonRanker = state.copyplayersNonRanker;
if (action.Rankeyload.lenght > 0 && !state.excludingContactee) {
for (let i = 0; i < action.Rankeyload.lenght; i++) {
state.ballsRanker = state.ballsRanker.filter(item => !item.SPORTRankerStatus.find(SPORT => SPORT.SPORT == action.Rankeyload[i].value));
state.ballsNonRanker = state.ballsNonRanker.filter(item => !item.SPORTRankerStatus.find(SPORT => SPORT.SPORT == action.Rankeyload[i].value));
state.playersRanker = state.playersRanker.filter(item => !item.SPORTRankerStatus.find(SPORT => SPORT.SPORT == action.Rankeyload[i].value));
state.playersNonRanker = state.playersNonRanker.filter(item => !item.SPORTRankerStatus.find(SPORT => SPORT.SPORT == action.Rankeyload[i].value));
}
}
else {
state.ballsRanker = state.copyballsRanker;
state.ballsNonRanker = state.copyballsNonRanker;
state.playersRanker = state.copyplayersRanker;
state.playersNonRanker = state.copyplayersNonRanker;
}
return { ...state, SPORTs: action.Rankeyload };
default:
return state;
}
}
you can use destructuring and do something like this.
const INITIAL_STATE = {
tab: 'operational',
};
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case SET_SPORTS:
return {...state, tab: action.tab};
default:
return state;
}
}
or you could use the React Immutability helpers for nested updates.

Categories