How to fix updating the same info twice in redux? - javascript

I'm trying to make a refactoring of my reducer's, actions' code. The problem is that one state depends on another, so one state is updated and then another one must receive the same info but insted it updates it.
Here is the demo.
The folder is src/reducer.
I've tried to separate the states but it gave me more troubles.
F.e. I have two states: boards and currentBoard
What happens in boards:
case 'ADD_CARD':
const { list } = action.payload
return [
...boards.slice(0, list.boardId),
updateCards(currentBoard, action.payload),
...boards.slice(list.boardId + 1)
]
What happens in currentBoard:
case 'ADD_CARD':
return updateCards(currentBoard, action.payload)
How can I rewrite my code to make it better? I expect it at least to be updated once.

The action 'ADD_CARD' in boards already calls updateCards(currentBoard, action.payload), so just remove the ADD_CARD from the currentBoard.
Also you might want to change the code of ADD_CARD action inside boards (it's not clean):
case 'ADD_CARD':
const { list } = action.payload;
const newBoard = updateCards(currentBoard, action.payload);
list.splice(list.boardId, 0, newBoard);
return list;
}

Related

ReactJS/Redux - Pure vs Impure Javascript functions?

I have gone through the definitions of the Pure and Impure Javascript functions in the ReactJs Official Docs.
Pure functions are ones that do not attempt to change their inputs, and always return the same result for the same inputs.
Example
function sum(a, b) {
return a + b;
}
Impure function is one that changes its own input.
Example
function withdraw(account, amount) {
account.total -= amount;
}
Now, can somebody tell me, how can I mistakenly make functions impure in React/Redux, where pure functions are required?
React and Redux both need pure functions coupled with immutability to run in a predictable fashion.
If you don't follow these two things, your app will have bugs, the most common being React/Redux not able to track changes and unable to re-render when your state/prop changes.
In terms of React, consider the following example:
let state = {
add: 0,
}
function render() {
//...
}
//pure function
function effects(state,action) {
//following immutability while updating state, not directly mutating the state.
if(action == 'addTen') {
return {...state, add: state.add + 10}
}
return state;
}
function shouldUpdate(s) {
if(s === state){
return false
}
return true
}
state = effects(state, 'addTen')if(shouldUpdate(state)) {
render();
}
The state is held by the state object which has only added property. This app renders the app property. It shouldn't always render the state when anything happens but should check whether a change occurred in the state object.
Like so, we have an effects function, a pure function which we use to affect our state. You see that it returns a new state when the state is to be changed and returns the same state when no modification is required.
We also have a shouldUpdate function which checks using the === operator whether the old state and the new state is the same.
To make mistakes in terms of React, you can actually do the following :
function effects(state,action) {
doRandom(); // effects should only be called for updating state.
// Doing any other stuff here would make effects impure.
if(action == 'addTen') {
return {...state, add: state.add + 10}
}
return state;
}
You can also make mistakes by setting the state directly and not using effects function.
function doMistake(newValue) {
this.state = newValue
}
The above should not be done and only effects function should be used to update the state.
In terms of React, we call effects as setState.
For Redux:
Redux's combineReducers utility checks for reference changes.
React-Redux's connect method generates components that check reference changes for both the root state and the return values from mapState functions to see if the wrapped component actually needs to re-render.
Time-travel debugging requires that reducer be pure functions with no side effects so that you can correctly jump between different states.
You can easily violate the above three by using impure functions as reducers.
Following is taken directly from redux docs:
It's called a reducer because it's the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue).
It's very important that the reducer stays pure. Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.
Simply said the state cannot be mutated. A new instance of the state should be returned every time there is a change so
This code is not correct :
const initialStates = {
items: ['item1']
}
export const ItemMaster = (state = initialStates, action) => {
switch (action.type) {
case TYPES.ADD_ITEM:
{
state.items.push(action.item)
return state
}
default:
return state
}
}
This code when written as a pure function below, This returns a new instance of the array it does not modify the actual array itself. This is the reason you should use a library like immer to handle immutability
const initialStates = {
items: ['item1']
}
export const ItemMaster = (state = initialStates, action) => {
switch (action.type) {
case TYPES.ADD_ITEM:
{
state = {...state,items:state.items.concat(action.item)}
return state
}
default:
return state
}
}
You could make pure functions impure by adding API calls or writing codes that result in side effects.
Pure functions should always be on point and self-explanatory, and should not require you to refer 3 or 4 other functions to understand what's going on.
// Pure Function
function USDtoEUR(USD, todayRate) {
return USD * todayRate;
}
// Impure Function
function USDtoEUR(USD) {
const todayRate = getTodayRate();
return USD * todayRate;
}
In case of React / Redux
const mapState = async state => {
const { data } = await whatDoINeed()
let mappedState = {}
if (data.needDolphin) {
mappedState.dolphin = state.dolphin
}
if (data.needShark) {
mappedState.shark= state.shark
}
return mappedState;
}
// Or for Redux Reducer
// Bad
{
setData: (state, payload) => {
const set = whatToSet()
return {
...state,
set.dolphin ? ...{ dolphin: payload.dolphin } : ...{},
set.shark ? ...{ shark : payload.shark } : ...{},
}
}
}
// Good
{
setData: (state, payload) => {
return {
...state,
// Just send only the things need
// to be sent
...payload
}
}
}
This should not be done. Everything a connect function or reducer function needs must be supplied through argument or written within its function. It should never get from outside.

React says I'm trying to render an object as a React child but I can't determine where

I'm trying to render an element by mapping over a redux store, and for some reason react thinks I'm trying to return an object (error: Objects are not valid as a React child (found: [object HTMLButtonElement]). I've looked over my entire code and several other answers to people with similar problems but nothing seems to be solving the problem.
I've followed the problem all the way back and not found a single object. I've also read several other posts on the same problem to see if the fixes there were applicable but none of the fixes for other people worked in this code. I'm pretty new at this so I could be missing something obvious but I've been at this for a few hours and haven't made any meaningful headway.
The code that is causing the problem is the onClick within the li tag in this block.
<ul id = 'currentTasks'>
{this.props.tasks.map( (task, idx) => {
return (
<li onClick = {this.completeHandler(event.target)} key={idx}>{task}</li>
)
})
}
</ul>
The handler it's referencing is just
completeHandler(target) {
this.props.completeTask(target)
Which references this block in my redux store
const compTask = (task) => {
return {
type: COMP,
task
}
}
Also I don't know if the problem is here but here is my two piece of mapToProps code, I've seen problems where the error was there
const mapStateToProps = (state) => {
return {
tasks: state[0],
compTasks: state[1]
}
};
const mapDispatchToProps = (dispatch) => {
return {
submitNewTask: (nTask) => {
dispatch(addTask(nTask))
},
completeTask: (cTask) => {
dispatch(compTask(cTask))
}
}
};
and here is my redux store, just in case the problem is in how I'm storing tasks
const taskReducer = (state = [[],[]], action) => {
switch(action.type) {
case ADD:
return [state[0].concat(action.task), [...state[1]]];
case COMP:
let idx = state[0].indexOf(action.task);
let beg = state.slice(0, idx);
let end = state.slice(idx + 1);
let newState = [[...beg, ...end], [...state[1], action.task]];
return newState;
default:
return state;
}
}
The whole app is live at https://jeengland.github.io/molehills/ in case you wanted to see the problem and error live.
Sorry if I've put too much I just have no idea where the problem could be happening. The react error is woefully unspecific so I don't know what could be causing it.
Thanks in advance for any help.
edit: I successfully solved the problem with the advice from the two below comments, and also the realization I was using event.target when I should have been using event.target.innerHTML. More details in the comments.
There are multiple problems while reviewing the code.
1) Invoking the event handler instead of passing a function:
<li onClick = {this.completeHandler(event.target)}
2) event.target is undefined. Instead, pass the task
<li onClick = {this.completeHandler.bind(this, task)}
3) task might be an object (event object) which is giving the error. Use something like task name within the li tag
<li onClick = {this.completeHandler(event.target)} key={idx}>{task.name}</li>
4) Instead of doing mapDispatchToProps, you can use the object version, sort of easier shortcut when params match.
const mapDispatchToProps = {
submitNewTask: addTask,
completeTask: compTask
}
In the onClick
onClick = {this.completeHandler(event.target)}
You are invoking this.completeHandler by having the ().
Change it to:
onClick = {this.completeHandler.bind(this)}
And change the function argument to event
completeHandler(event) {
this.props.completeTask(event.target)

Redux, calling another action case statement depending on outcome

I'm creating a packing list for a holiday.
I have these 2 cases:
switch(action.type) {
case 'ADD_ITEM': {
const items = [].concat(state.items).concat(action.data)
return { items }
}
case 'ITEM_ALREADY_ADDED': {
state.items.map((item) => (
item.name === action.data.name ? alert('Item already added') : item
))
return { ...state }
}
}
When I type into the input another item and then click add, it calls the second case statement (item already exists). tTis is working, however, I broke the add item case.
So I'm trying to do a ternary and I'm not sure what to put on the right-hand side of the : if the item isn't there. This is where I want to do my check and then call the other action.
So two questions:
How can I do this?
Is this the best place to be doing the ternary/check?
For your first question
you can do it like this
switch(action.type){
case 'ADD_ITEM': {
const items = [].concat(state.items).concat(action.data)
return { ...state, items }
}
// dont do the alert here, instead access the items array in your component using connect and mapStateToProps. do looing there and check whether it is already existing or not. depending on which dispatch action. you dont need to have ITEM_ALREADY_ADDED case.
}
// your component and for your second question
class A extends React.Component {
add = () => {
const {items} = this.props
// write yout logic here and check whether the item is already availle
//and accordingly dispatch the action
}
render() {
return (
<div>
<button onClick={this.add}>Add</button>
</div>
)
}
}
function mapStateToProps(state) {
items : state.yourreducrename.items,
}
Note : this is just higher level overview, please adjust the code
accordingly

More than one state property change in redux

I was using redux to check how it scales with my application. There are few things, that I found as roadblock when I was using it.
There are high possibility that I am not thinking the redux way / not using the way it was supposed to be used or have not read the doc properly.
I have read the basic section this docs.
The problem statement is fairly simple.
I have two property in store
{
x: 10,
y: 20
}
lets say x is the x-position of the point and y is the y-position of the point. There is one condition if the x goes above 50, y value becomes equal to x.
So I have this
let x = (state = 10, action ) => {
if (action.type === 'cx') {
return action.value;
}
else {
return state;
}
}
let y = (state = 20, action ) => {
if (action.type === 'cy') {
return action.value;
}
else {
return state;
}
}
let model = redux.combineReducers({x: x, y: y});
let store = redux.createStore(model);
let actions = {
changeX: (val) => {
if ( val > 50) {
store.dispatch(actions.changeY(val));
}
return {type: 'cx', value: val }
},
changeY: (val) => ({type: 'cy', value: val })
}
console.log('INITIAL STATE', '---', store.getState());
store.subscribe(() => {
console.log('SUB2', '---', store.getState());
// Paint the dom with new state
});
So the moment
store.dispatch(actions.changeX(60));
is called the subscriber's function gets called twice hence the two times dom painting happen.
Is there a redux-way / workaround to solve this?
You are trying to relate to x and y as part of the same sub model equation - when one is updated, the other maybe updated also.
Using combineReducer you can update related state in the same reducer.
According to Redux guide, if you want that states to be separated, sometimes combineReducer is not enough, and you can breach that pattern into more openly reducer.
The combineReducers utility included with Redux is very useful, but is
deliberately limited to handle a single common use case: updating a
state tree that is a plain Javascript object, by delegating the work
of updating each slice of state to a specific slice reducer. It does
not handle other use cases, such as a state tree made up of
Immutable.js Maps, trying to pass other portions of the state tree as
an additional argument to a slice reducer, or performing "ordering" of
slice reducer calls. It also does not care how a given slice reducer
does its work.
The common question, then, is "How can I use combineReducers to handle
these other use cases?". The answer to that is simply: "you don't -
you probably need to use something else". Once you go past the core
use case for combineReducers, it's time to use more "custom" reducer
logic, whether it be specific logic for a one-off use case, or a
reusable function that could be widely shared. Here's some suggestions
for dealing with a couple of these typical use cases, but feel free to
come up with your own approaches.
An example that is given related for this case:
function combinedReducer(state, action) {
switch(action.type) {
case "A_TYPICAL_ACTION" : {
return {
a : sliceReducerA(state.a, action),
b : sliceReducerB(state.b, action)
};
}
case "SOME_SPECIAL_ACTION" : {
return {
// specifically pass state.b as an additional argument
a : sliceReducerA(state.a, action, state.b),
b : sliceReducerB(state.b, action)
}
}
case "ANOTHER_SPECIAL_ACTION" : {
return {
a : sliceReducerA(state.a, action),
// specifically pass the entire state as an additional argument
b : sliceReducerB(state.b, action, state)
}
}
default: return state;
}
}

Redux Actions And Shared Validation

I have an array of steps in my redux store. My my steps reducer manipulates the list of step by handling a series of actions:
ADD_STEP_REQUEST
REMOVE_STEP_REQUEST
MOVE_STEP_UP_REQUEST
MOVE_STEP_DOWN_REQUEST
So far so good.
I now have a requirement to perform a kind of validation every time there is a change to the order of the steps (or when a step is added or removed). During this validation I need to check each step to see if the step that precedes it fulfils certain criteria. I don't want to reject the changes if it doesn't. I just want to set an isInvalid flag on the step and ultimately change the way a step looks in the UI.
The simplest way I can handle this is adding a validateOrder() function (that applies the flags and returns the steps) that is run by the reducer at the end of each case statement:
case ADD_STEP_REQUEST: {
const amendedSteps = // add a step
return validateOrder(amendedSteps);
}
case REMOVE_STEP_REQUEST: {
const amendedSteps = // remove a step
return validateOrder(amendedSteps);
}
case MOVE_STEP_UP_REQUEST: {
const amendedSteps = // reorder steps
return validateOrder(amendedSteps);
}
case MOVE_STEP_DOWN_REQUEST: {
const amendedSteps = // reorder steps
return validateOrder(amendedSteps);
}
However, this feels wrong because I need to repeat the validateOrder call across all the case statements.
Is there a better way to handle this?
A reducer is just a function. You can wrap it with another function:
const yourReducer = (state = 'your initial state', action) => {
switch (action.type) {
case ADD_STEP_REQUEST:
// ...
return amendedSteps
case ...
}
}
const validatedReducer = (state, action) => {
switch (action.type) {
case ADD_STEP_REQUEST:
case REMOVE_STEP_REQUEST:
case MOVE_STEP_UP_REQUEST:
case MOVE_STEP_DOWN_REQUEST:
return validateOrder(yourReducer(state, action))
default:
return yourReducer(state, action)
}
}
Now you separate the responsibilities. The original reducer doesn't need to care about the validation. The validatedReducer will take care of that.
If the validateOrder must be applied to all the cases, then you don't need the switch statement in validatedReducer, so it will become:
const validatedReducer = (state, action) =>
validateOrder(yourReducer(state, action))

Categories