Redux | Why this store's parameter type changes after second click? - javascript

I'm trying to push a new value in the store's state. It works fine the first time I click on the button "Add item", but the second time I got the following error: "state.basket.push is not a function". I configure the action to console log the state and got the following results:
1st click: {...}{basketItems: Array [ "44" ]}
2nd click: Object {basketItems: 0 }
Why the variable type is changing from array to an int?
Here is the code for the rendered component:
function Counter({ basketItems,additem }) {
return (
<div>
<button onClick={additem}>Add item</button>
</div>
);
}
const mapStateToProps = state => ({
basketItems: state.counterReducer.basketItems,
});
const mapDispatchToProps = dispatch => {
return {
additem: ()=>dispatch({type: actionType.ADDITEM, itemName:'Dummy text' }),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
And the reducer looks like this:
import {ADDITEM} from "../actions/types";
const initialState = { basket: [], };
export default function reducer(state = initialState, action) {
switch (action.type) {
case ADDITEM:
console.log(state);
// let newBasket = state.basket.push('44');
return {
...state,
basket: state.basket.push('44')
};
default:
return state;
}
}
I'm copying the state before updating the basket to prevent weird behaviors.

There's two problems here:
state.basket.push() mutates the existing state.basket array, which is not allowed in Redux
It also returns the new size of the array, not an actual array
So, you're not doing a correct immutable update, and you're returning a value that is not an array.
A correct immutable update here would look like:
return {
...state,
basket: state.basket.concat("44")
}
Having said that, you should really be using our official Redux Toolkit package, which will let you drastically simplify your reducer logic and catch mistakes like this.

Related

Redux - action gets dispatched, but doesn't work with the reducer

I've got a weird problem where I dispatch an action correctly, but the reducer doesn't get called. I'm using redux-thunk and everything is set up exactly the same how it's set up in one of my other projects. The action type matches, but nothing happens.
Also, if I put a console.log on the default case of the reducer, it gets logged 4 times every time I refresh the page, so I suppose the reducer gets loaded correctly.
export const submitForm = names => {
console.log(names); //This logs what it's supposed to log every time
// I press the submit button
return dispatch => {
dispatch({
type: "SUBMIT_FORM",
names
});
};
};
export default function form(state = {}, action) {
switch (action.type) {
case "SUBMIT_FORM":
console.log(action.names);
return Object.assign({}, state, {
names: action.names
});
default:
return state;
}
}
//My component is connected like this:
const mapStateToProps = state => ({});
const mapDispatchToProps = dispatch => ({
formActions: bindActionCreators(formActions, dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
It seems a little strange to me that an action creator returns a function. Action creatorsnormally return an action, that is, an object. Async action creators returns functions. So, you can try refactor your action creator that will return a objetct, and then you pass the action creator to the dispatch as an argument.
export const submitForm = names => {
console.log(names); //This logs what it's supposed to log every time
// I press the submit button
return {
type: "SUBMIT_FORM",
names
}
};
dispatch(submitForm(names));
Besides that, according to react-redux.js.org/mapdispatch to use bindActionCreators in mapDispatchToProps, mapDispatchToProps does not return an object, but does return bindActionCreators.
const mapDispatchToProps = dispatch =>
bindActionCreators(formActions, dispatch);
another point, if you need to pass an argument to your action creator I would do like this:
const mapDispatchToProps = dispatch => ({
submitForm: (names) => dispatch(submitForm(names))
});
Unfortunately, I can't see all of your code. I hope I helped you with something.

Reducer not returning updated state

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
}
}

Retrieve a single element from reducer array

I have a company reducer that I'd want to contain an array of companies. Since each object is fairly large when user goes on /company/name I only want to fetch that one object from my API and add it to this array, so if user visits the same company page multiple times I won't have to call again the api.
My reducer:
const initialState = {
companies: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_COMPANIES:
return {
...state
};
case GET_COMPANY:
return {
...state,
companies: [action.payload, ...state.companies]
};
default:
return state;
}
};
Is there a way for me to update the state in reducer and just return 1 company? Like shown above this would return all companies in this state, though logically it does not make sense with 'GET_COMPANY'
My action:
export const getCompany = name => dispatch => {
axios.get("/companies/" + name).then(res =>
dispatch({
type: GET_COMPANY,
payload: res.data
})
);
};
You need some 'id' or 'name' or other property of the company object you want to retrieve,
and than you can use find method to do this:
action.payload.find(company=>company.id===id)
According to the MDN:
The find() method returns the value of the first element in the array that satisfies the provided testing function. Otherwise undefined is returned.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

Redux initial state gets mutated even when using Object.assign

This is a simple replication of a problem i encounter in an actual app.
https://jsfiddle.net/zqb7mf61/
Basically, if you clicked on 'Update Todo" button, the text will change from "Clean Room" to "Get Milk". "Clean Room" is a value in the initial State of the reducer. Then in my React Component, I actually try to clone the state and mutate the clone to change the value to "Get Milk" (Line 35/36). Surprisingly, the initial State itself is also mutated even though I try not to mutate it (as seen in line 13 too).
I am wondering why Object.assign does not work for redux.
Here are the codes from the jsFiddle.
REDUX
const initState = {
task: {id: 1, text: 'Clean Room'}
}
// REDUCER
function todoReducer (state = initState, action) {
switch (action.type) {
case 'UPDATE_TODO':
console.log(state)
let newTodo = Object.assign({}, state) // here i'm trying to not make any changes. But i am surpise that state is already mutated.
return newTodo
default:
return state;
}
}
// ACTION CREATORS:
function updateTodo () {
return {type: 'UPDATE_TODO'};
}
// Create Store
var todoStore = Redux.createStore(todoReducer);
REACT COMPONENT
//REACT COMPONENT
class App extends React.Component{
_onSubmit = (e)=> {
e.preventDefault();
let newTodos = Object.assign({}, this.props.todos) // here i clone the redux state so that it will not be mutated, but i am surprise that it is mutated and affected the reducer.
newTodos.task.text = 'Get Milk'
console.log(this.props.todos)
this.props.updateTodo();
}
render(){
return (
<div>
<h3>Todo List:</h3>
<p> {this.props.todos.task.text} </p>
<form onSubmit={this._onSubmit} ref='form'>
<input type='submit' value='Update Todo' />
</form>
</div>
);
}
}
// Map state and dispatch to props
function mapStateToProps (state) {
return {
todos: state
};
}
function mapDispatchToProps (dispatch) {
return Redux.bindActionCreators({
updateTodo: updateTodo
}, dispatch);
}
// CONNECT TO REDUX STORE
var AppContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(App);
You use Object.assign in both the reducer as in the component. This function only copies the first level of variables within the object. You will get a new main object, but the references to the objects on the 2nd depth are still the same.
E.g. you just copy the reference to the task object around instead of actually creating a new task object.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Deep_Clone
Apart from that it would be better to not load the whole state into your component and handle actions differently. Lets just solve this for now. You will have to create a new task object in your onSubmit instead of assigning a new text to the object reference. This would look like this:
newTodos.task = Object.assign({}, newTodos.task, {text: 'Get Milk'})
Furthermore to actually update the store, you will have to edit your reducer as you now assign the current state to the new state. This new line would look like this:
let newTodo = Object.assign({}, action.todos)

Redux state not updating with javascript object

I have this container and component and the object yeast that Im trying to put in my store. However, its not working whenever I try and save it. The object looks like this
{ yeastType : value, temp: value}
Container.js
const mapDispatchToProps = (dispatch) => {
return {
handleYeastChange: (yeast) => {
dispatch(actions.updateYeast(yeast))
}
}
};
const RecipeYeastContainer = connect(
mapStateToProps,
mapDispatchToProps
)(RecipeYeast);
Component.js
updateYeastState = (updatedYeast) => {
this.props.handleYeastChange(updatedYeast)
};
I have no errors in the console or anything. When I open my redux dev tools its telling me the state has already been updated by the time the action is called. And thus only ever saving the first letter thats input into my field. It never persists it. Its really weird. Its also never showing up in the UI. I can type as much as I want and see the action firing and the state keeping the first letter but its not showing up in the input.
Whats weird is that when I change the code to pass in both yeastType and temp to the property function and construct the object in there it works. (See below)
This works: Container.js
const mapDispatchToProps = (dispatch) => {
return {
handleYeastChange: (yeastType, temp) => {
const yeast = {yeastType, temp}
dispatch(actions.updateYeast(yeast))
}
}
};
Component.js
updateYeastState = (updatedYeast) => {
this.props.handleYeastChange(updatedYeast.yeastType, updatedYeast.temp)
};
I cannot figure out why this is happening. I thought I could just pass the object all the way through and not have to reconstruct it.
Do you dispatch your action correctly? And in using redux, you are not updating the state of the component, you're updating the store and then the value in component is from your mapStateToProps function that get from the store. Say it you're updating your store with the object named yourReducer store. ex:
Container.js:
const mapStateToProps = (state) => ({
inputValue: state.yourReducer.value
})
const mapDispatchToProps = (dispatch) => ({
inputHandler: (e) => {
dispatch(yourAction({type: 'CHANGE_INPUT', value: e.target.value}))
// The store catches the action and pass to the reducer that can read the action's type
// in `yourReducer` will catch the 'CHANGE_INPUT' type and
// return a new value as same as the value that passed from the action
}
})
export default connect(mapStateToProps)(Component)
Component.js
export default class Component extends React.Component {
{ your custom function here ... }
render() {
const { inputValue, inputHandler } = this.props
return (
<div>
{/* The input will be the same as the inputValue changed */}
<input value={inputValue} onChange={inputHandler} />
</div>
)
}
}
For debugging redux you can try this redux-devtool.

Categories

Resources