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.
Related
I wanted to know how I can pass the state into an action so that it can use the state to make an API call. the state I want to pass is the input because it's the image URL that gets sent to Clarifai's servers to predict the celebrity. The handle search is responsible for updating state to the URL.
I've tried to use get state with no luck
This is my action
export const requestPrediction = () => {
return function (dispatch, getState) {
dispatch(fetchCelebrequest)
let input = getState().input
app.models.predict(Clarifai.CELEBRITY_MODEL,
input)
.then(res => {
const data = res.outputs[0]['data']['regions'][0]['data'].concepts[0]
dispatch(fetchCelebSuccess(data))
})
.catch(err => {
const error = err.message
dispatch(fetchCelebFailed(error))
})
}
}
This is my reducer.js
import {
CHANGE_SEARCHFIELD,
REQUEST_PREDICTION_PENDING,
REQUEST_PREDICTION_SUCESS,
REQUEST_PREDICTION_FAILED
} from './constants'
const initialState = {
input: '',
imageUrl: '',
box: {},
isSignedIn: false,
isPending: false,
celeb: {},
error: '',
celebConfidence: [],
user: {
id: '',
name: '',
email: '',
entries: 0,
joined: ''
}
}
export const handleSearch = (state=initialState, action={}) => {
switch (action.type) {
case CHANGE_SEARCHFIELD:
return { ...state, input: action.payload }
default:
return state
}
}
export const requestPrediction = (state=initialState, action={}) => {
switch(action.type) {
case REQUEST_PREDICTION_PENDING:
return {...state, isPending: true}
case REQUEST_PREDICTION_SUCESS:
return {...state, celebName: action.payload, isPending: false}
case REQUEST_PREDICTION_FAILED:
return {...state, error: action.payload, isPending: false}
default:
return state
}
}
The proper way to do it is via setState since the updates my be asynchronous.
You may check out this link. How can I pass state to an action in React.js?
I need to increase property in the state, but I have no idea how I can do it
help please, I have tried two days but got many troubles
export const initialState = {
ids: [],
basket: []
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_BASKET':
if (state.ids.indexOf(action.item.id) === -1) {
// if new item add to basket
return {
ids: [...state.ids, action.item.id],
basket: [...state.basket, action.item]
}
}
else {
if (state.basket.length > 0) {
// state.basket[state.basket.length - 1].quantity += 1
}
return {
...state,
// IT'S WRONG
basket: [...state.basket, [state.basket.length - 1].quantity += 1]
}
}
default:
return state;
}
}
You mutate state (basket.quantity += 1) when it should treated as immutable.
I believe you need to rethink how you structure your state if it gets so hard to immutably update a state.
export const initialState = {
ids: [],
basket: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_TO_BASKET":
if (state.ids.indexOf(action.item.id) === -1) {
...
} else {
...
const basketsShallowCopy = [...state.basket];
const basketToUpdate = basketsShallowCopy.pop();
const updatedBasked = {
...baskedToUpdate,
quantity: basketToUpdate.quantity + 1,
};
basketsShallowCopy.push(updatedBasked);
return {
...state,
basket: basketsShallowCopy,
};
}
...
}
};
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
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),
}
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
}
}