I am pretty new guy to redux.I am little bit confused that how to change this data without mutating it.
my data structure like this
{
postOwnwerName:"",
postTags:[],
photos:[
{
id:dc5c5d7c5s,
caption:null
},
{
id:dccccccc5s,
caption:null
}
],
}
Whent this function fires
this.props.updateCaption(id,caption)
my Reducer should set data according to the id in the data structure without mutating it
import * as types from '../Actions/actionTypes';
const initialState = {
postOwnwerName:"",
postTags:[],
photos:[],
};
export default function uploadReducer(state=initialState,action) {
switch (action.type){
case types.SETPHOTOSTATE:
return Object.assign({},state,{ photos:state.photos.concat(action.payload) });
case types.SETCAPTIONTEXT:
//this method is not working
return (
Object.assign({},state,state.photos.map((data) => {
if(data.id == action.payload.id){
return Object.assign({},data,{caption:action.payload.caption})
}else{
return data
}
}))
)
default:
return state
}
}
if the state object has many level it will be difficult to change state without mutating. In those scenario I first copy the state deeply and then make changes to the new state and return new state. See here Cloning object deeply. You can try this once.
export default function uploadReducer(state=initialState,action) {
let newState = JSON.parse(JSON.stringify(state));
switch (action.type){
case types.SETPHOTOSTATE:
newState.photos.push(action.payload);
return newState;
case types.SETCAPTIONTEXT:
newState.photos.map((data) => {
if(data.id == action.payload.id){
data.caption = action.payload.caption;
}
});
return newState;
default:
return newState;
}
}
Related
I can't get my reducer to return updated state.
The action (confirmed with debugger) is an array of objects - something like: [{name: "test"}, {name: "second_test"}]
I think something must be wrong with my spread operator, although I've also tried Object.assign() in the debugger and that seems to return the expected result.
My components is apparently just getting the default state. Here's my reducer code. Any help would be appreciated. Thank you!
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK':
return {...state, all: [...state.all, action.payload]}
case 'FETCH_DECKS':
debugger;
return {...state, all: action.payload}
default: return state
}
}
I had this very issue recently (exactly as you describe), and I resolved the problem by using the immutability-helper package which is listed here on the ReactJS docs.
This approach allows you to delegate the heavy lifting of state immutability and nested state updates. You should find this resolves your issue:
import update from 'immutability-helper';
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK': {
/* Add payload to nested state.all list */
return update(state, { all : { $push : [action.payload] }});
}
case 'FETCH_DECKS': {
/* Replace state.all with incoming payload */
return update(state, { all : { $set : action.payload }});
}
default: return state
}
}
I have an object that trying too deeply clone it before mutate in Redux but the nested objects become empty after deep cloning it with lodash or json
const initial = {
infamy: {a: 1}
}
export const playerReducer = (state = initial, action) => {
switch (action.type) {
case SET_DATA:
console.log("III", state);
state = cloneDeep(state); //Why the neseted obj becomes empty?
console.log("JJJ", state);
break;
}
};
Edit:
looks look the issue was the condition i had for checking if the object was empty wasn't working so the empty data from the api was replacing the initial values but im wounding why the console.log was showing the post made mutation rather than pre made mutation
case SET_DATA:
console.log("III", state);
const nextState = cloneDeep(state);
console.log("JJJ", nextState); //why the log shows the change in line 10 made? shouldn't it log the values then change happen?
nextState.membershipId = action.payload.membershipId;
nextState.membershipType = action.payload.membershipType;
nextState.displayName = action.payload.displayName;
console.log(action.payload.gambitStats);
if (action.payload.gambitStats.allTime !== "undefined") { //the bug is here
nextState.gambitStats = cloneDeep(action.payload.gambitStats);
nextState.infamy = cloneDeep(action.payload.infamy);
}
return nextState;
You are checking for undefined as a string "undefined" instead of:
if (action.payload.gambitStats.allTime !== undefined) { ...
or just:
if (!!action.payload.gambitStats.allTime)
In principal I would say that the state would not be emptied by the use of cloneDeep alone.
In the other hand, I see that you are using a Redux pattern and you should not directly manipulate the state.
Instead you should return the next state and also return the current state by default.
const initial = {
infamy: {a: 1}
}
export const playerReducer = (state = initial, action) => {
switch (action.type) {
case SET_DATA:
const nextState = cloneDeep(state);
// Modify nextState according to the intent of your action
return nextState;
default:
return state;
}
};
Hope it helps. :)
If an item is already in an array, I want to remove it, else I'd like to add it to the array. How should I go about this? I have attached incomplete code:
import { SELECTED_ITEM } from '../actions/type'
const INITIAL_STATE = []
export default(state = INITIAL_STATE ,action) => {
switch(action.type) {
case SELECTED_ITEM:
state.map(function(value) {
if(value === action.payload) {
?????
}
})
return [...state,action.payload]
default : return state;
}
}
Do I have to loop through all values to find the item? And how can I return the new state with removed or added item?
You can do an Array#includes check, then filter as needed (assuming primitive values):
if(state.includes(value)) {
return state.filter(value => value !== action.payload);
}
return [...state, action.payload];
I managed using lodash, which is best for array iteration. For using normal loops or every i find some issue in some cases. Thus its not reliable.
var e = _.findIndex(state.itemList, function(o) { return o.UserID== action.payload.UserID; });
if(e!==-1){
state.itemList.splice(e, 1)
return { ...state, itemList: [...state.itemList] }
}
I have difficulties understanding how the global store should be dispatched with actions from my react components. I'm very new to the whole concept and I don't get my component to re-render on dispatch(). I invested deeply and found that although the reducer returns the updated global state the values are not mapped back to the component props. But a proper function (mapStateToProps) is defined.
Minimal example: Please have a look at this plunkr (or minimal example code below).
Explanation:
I have a component Controls with a method switchActivities. The component is connected to the global store and my global state is available in the component props.
var PullFromStoreControls = function (state) {
return {
concrete: state.me.bool,
nested: state.me.nested.later
}
}
var PushToStoreControls = function (dispatch) {
return {
switchFilter: function (type, value) {
dispatch({
type: 'SET_VAL',
value: value
})
}
}
}
Controls = connect(
PullFromStoreControls,
PushToStoreControls
)(Controls)
I wired the variable state.me.bool to props.conrete to avoid side-effects of a deep state tree. I also connected a dispatcher to update the global state via the reducer. However, if the dispatcher is invoked by 'switchActivities' the new value of the checkbox makes it up to the reducer correctly and then gets lost. The global state seems never updated correctly.
What am I missing?
index.html
<!DOCTYPE html>
<html>
<head>
<script data-require="react#*" data-semver="15.5.0" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.0/react.min.js"></script>
<script data-require="react#*" data-semver="15.5.0" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.0/react-dom.min.js"></script>
<script data-require="redux#*" data-semver="3.2.1" src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.2.1/redux.js"></script>
<script data-require="react-redux#*" data-semver="4.4.5" src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.4.5/react-redux.js"></script>
<!-- support for jsx on my localhost, on Plunkr jsx will be automatically transpiled to js -->
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script type = "text/babel" src="minimal.jsx"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
minimal.jsx
function d(x){
console.log(x);
}
const AppState = {
me: {
bool: false,
nested: {
later: "I also want to change values deeper in the tree."
}
}
}
function reducer(state, action) {
if (state === undefined) {
return AppState;
}
switch (action.type) {
case 'SET_VAL':
state.me.bool = action.value;
break;
}
console.log("The reducer returns the changed state");
console.log(state);
return state;
}
// create global store with initial configuration `AppState`
const store = Redux.createStore(reducer, AppState);
// create provider and connect function not having webpack available
var Provider = ReactRedux.Provider;
var connect = ReactRedux.connect;
class Controls extends React.Component {
switchActivities() {
console.log("------------------ clicked ------------------");
console.log("set value from:");
console.log(this.props.concrete);
// inverse current state
const state = !this.props.concrete;
// output
console.log("to:");
console.log(state);
// call dispatcher
this.props.switchFilter("show_act", state);
}
render() {
console.log("I would like to re-render if this.props.concrete has updated!");
const switchActivities = <MapSwitch name="switch_act" label="Show something" checked={this.props.concrete} onChange = {() => this.switchActivities()} />;
return <div id="map-controls">
{switchActivities}
</div>
}
}
var PullFromStoreControls = function (state) {
return {
concrete: state.me.bool,
nested: state.me.nested.later
}
}
var PushToStoreControls = function (dispatch) {
return {
switchFilter: function (type, value) {
dispatch({
type: 'SET_VAL',
value: value
})
}
}
}
Controls = connect(PullFromStoreControls, PushToStoreControls)(Controls)
const MapSwitch = ({name, label, checked, onChange}) => (
<label for={name}>{label}
<input type="checkbox" className="switch" data-toggle="switch"
name={name}
onChange={onChange}
checked={checked}
/>
</label>
)
ReactDOM.render(
<Provider store={store}>
<Controls/>
</Provider>,
document.getElementById('app')
);
Solution (update)
It is a difference whether I alter the state object within the reducer and return that or if I create a new object and return that. Although both returned objects are the same the former is a reference while the latter is a real new variable. I learned that the hard way.
Good explanation:
https://github.com/reactjs/redux/blob/master/docs/recipes/reducers/ImmutableUpdatePatterns.md
function reducer(state, action) {
switch (action.type) {
case 'SET_VAL':
return {
...state,
me : {
...state.me,
bool: action.value
}
}
}
return state;
}
Your problem is that you are mutating state. The second principle of Redux is that the state should never be mutated directly - rather, your reducer is a pure function which should return a new state: https://redux.js.org/docs/introduction/ThreePrinciples.html#changes-are-made-with-pure-functions
Your issue is here:
switch (action.type) {
case 'SET_VAL':
// you are attempting to mutate state.me.bool - this is an antipattern!
state.me.bool = action.value;
break;
}
Instead, write your reducer in a way that returns a new copy of state.
function reducer(state, action) {
switch (action.type) {
case 'SET_VAL':
return {
...state,
me : {
...state.me,
bool: action.value
}
};
default:
return state;
}
}
Notice that you need to copy each level of state for nested structures. I'm using the Object spread operator here, but Object.assign() with all work. Hope this helps!
I have a string in my action that I want to set as text for a notification box in my component. I am having trouble figuring out how to move that string to the component using react/redux/ES6.
I have a reducer
`export default function changeNotificationText(state = initialState.notifyMessage, action, newMessage) {
let newState;
switch(action.type){
case types.DELETE_PROCESS_NOTIF_MESSAGE:
state = newMessage;
return state;
case types.DELETE_PROCESS_NOTIF_MESSAGE_FAILURE:
state = newMessage;
return state;
}
return state;
}
`
The state item is called notifyMessage.
Here is the call to the reducer from the action
export function changeNotificationTextSuccess(newMessage) {
return {type: types.DELETE_PROCESS_NOTIF_MESSAGE, notifyMessage: newMessage};
}
your reducer should look like this:
export default function changeNotificationText(state = initialState.notifyMessage, { type, notifyMessage }) {
switch(type){
case types.DELETE_PROCESS_NOTIF_MESSAGE:
return notifyMessage;
case types.DELETE_PROCESS_NOTIF_MESSAGE_FAILURE:
return notifyMessage;
}
return state;
}
notice that notifyMessage is part of the action you dispatch