Send metadata within an action to the reducer in redux - javascript

I have a component which builds onto the Select component from Ant Design https://ant.design/components/select/
<SomeComponent
onSelect = { this.props.handleSelect }
onDeselect = { this.props.handleDeselect }
selectionList = { valuesList }
value = { values }/>
onSelect triggeres the action this.props.handleSelect
export function handleSelect(value) {
return dispatch => {
dispatch(actionCreator(HANDLE_SELECT, value));
}
}
That actions goes into the reducer
case HANDLE_SELECT: {
const newValues = value_select(state, action);
return {
...state,
find: {
...state.a,
values: newValues
}
}
}
Finally, value_select is called to do all the magic
export const value_select = function(state, action) {
...
const newData = {
XYZ: action.payload
}
return newData
}
This brings me to my question.
Is it possible to send further metadata with the action? Imagine I use the component <SomeComponent.../> several times. I would not know which of the rendered components triggered the action when the onSelect is fired.
If I want to process the information in value_select = function(state, action) {... later, I want to know which component caused the action to process my data properly. I need to set XYZ in value_select() dynamically, depending on which <SomeComponent.../> caused the action. action.payload only gives me what is saved in value in <SomeComponent.../>, nothing more.
Is there a way to send some more information with the onSelect or is that bad practice and I would need an action for each component <SomeComponent.../> anyway?

Absolutely. It's your action and your reducer, you can attach any information you want to it.
The most common approach for structuring an action is the Flux Standard Action approach, which expects your actions to look like {type, payload, meta, error} but it's really up to you what you put into your actions.
For some more ideas, you might want to read through the Structuring Reducers - Reusing Reducer Logic section of the Redux docs.

Related

Updating Redux state issue?

This is a piece of the code:
const cart = useSelector((state) => state.cart);
// const [totalSum, setTotalSum] = useState(0.0);
// const dispatch = useDispatch();
const updateSum = () => {
var newSum = 0;
var i;
for (i = 0; i < productsInCart.length; i++) {
newSum = newSum + productsInCart[i].prod.price * productsInCart[i].count;
}
// setTotalSum(newSum);
cart.totalAmount = newSum;
// dispatch(setTotal(newSum));
};
Some of the lines are commented out, so let me explain:
I have a redux set up. Here I'm manually updating the redux state cart.totalAmount = newSum. But it doesn't display the update until I manually save my code file with Ctrl + S.
However, if I do keep and uncomment the useState() parts(// const [totalSum, setTotalSum] = useState(0.0); // setTotalSum(newSum);)which is commented out right now, it works perfectly fine. The problem is I am not using the useState() anywhere else, it kinda left there from previous stages of coding.
So trying a different way I removed useState() completely and tried dispatching the reducer method to update the state // const dispatch = useDispatch(); // dispatch(setTotal(newSum)); Now the app crashing when I go to that screen.
THIS IS THE REDUCER CODE JUST IN CASE:
import { ADD_TO_CART, CLEAR_CART, SET_TOTAL } from "../actions/cart";
import ProductInCart from "../../models/ProductInCart";
const initState = {
productsInCart: [],
totalAmount: 0,
};
const cartReducer = (state = initState, action) => {
switch (action.type) {
case ADD_TO_CART:
return {
productsInCart: state.productsInCart.concat(
new ProductInCart(
Math.random().toString(),
action.product,
action.num
)
),
totalAmount: state.totalAmount,
};
case CLEAR_CART:
const newCart = [];
return {
...state,
productsInCart: newCart,
totalAmount: 0,
};
case SET_TOTAL:
return { ...state, totalAmount: action.totalAmount };
default:
return state;
}
};
export default cartReducer;
I understand I'm not getting the idea with the states, how they work and updating them. Can't see what it is. THANKS IN ADVANCE.
I dont know what you are trying to achieve with this piece of code.
dispatch(setTotal(newSum));
For Redux to work you need to have Action, Reducer and Middleware if you are using one. More on this matter can be found here.
In your case I'll use Action and Reducer.
First we need a Reducer which stores previous state and returns new state if updated.
Let's say we are updating Total Amount. Then our Reducer will be.
case TOTAL_AMOUNT:
return {...state,TotalAmount:action.payload}
action.payload will contain the values that will update your previous value in TotalAmount.
Now, where will we get this payload from?
We will get it from Action.
Our action will look like.
export const UpdateTotalAmount = (data) => ({type:TOTAL_AMOUNT,payload:data})
Remember we needed to pass payload to Reducer? This is where we are sending that payload. So, this action is receiving data from our UI and passing that data to the reducer.
Now, how do we receive this data from UI? It actually is one line code.
In our UI dispatch action and pass the value.
dispatch(UpdateTotalAmount(newSum));
Now everytime we call updateSum function and send new value our state in reducer will get updated.
you can use the state in any React Components using useSelector.
const desiredName = useSelector((state)=>state.cartReducer.TotalAmount)
Also dont forget to add cartReducer to Root Reducer. More here.
Hope you understood. Sorry for bad English.
You can try by connecting the component with store using connect ("which is a method of react-redux library")
It takes two methods as an argument one of them is mapDispatchToProps which will provide you the dispatch method in your functional component.
Try using that instead of using dispatch hook or preferably you can make a POC on
https://codesandbox.io/s/react-redux-template-forked-dbw6n?file=/src/App.js:450-463
so we can have a better understanding of your problem
Thanks

What's the point of Actions and Reducers in Redux?

I have been using Redux for a while, but I still can't see the point of actions and reducers.
As described in the docs, a reducer can be summarised as (previousState, action) => newState. The same principle applies for React's useReducer.
So this reducer function basically handles all actions, which seems like a violation of the Single Responsibility Principle. I'm sure that there's a good reason to do it this way, but I don't see it.
It would make more sense to me to just have a function per action. So instead of having an ADD_TODO action you would have a addTodo(previousState, todoText) => newState function. This would reduce (no pun intended) a lot of boilerplate code and might even give a slight performance improvement as you no longer need to switch through the action types.
So my question is: What's the advantage of having a reducer as opposed to a single-action function?
So if your question is why do we use reducers at all?
The real boilerplate of Flux is conceptual: the need to emit an
update, the need to register the Store with a Dispatcher, the need for
the Store to be an object (and the complications that arise when you
want a universal app).
That is a fundamental design choice redux has made as it is inspired from flux.
If you do not like the switch cases, and there by the reducer size. You can have something like this below:
export const todos = createReducer([], {
[ActionTypes.ADD_TODO]: (state, action) => {
const text = action.text.trim()
return [...state, text]
}
})
Above is a function that lets us express reducers as an object mapping from action types to handlers.
createReducer can be defined as :
function createReducer(initialState, handlers) {
return function reducer(state = initialState, action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action)
} else {
return state
}
}
}
You can read more on this here
Actually it is a silly design and makes no sense.
It is just a kind of over-engineering. Because in most case, you could have just used a simpler function to update a state instead of doing it a silly way by making it two functions (reducer and action).
By adopting redux in your project, you may write tons of reducer and action codes which looks extremely ugly and difficult to do code reviews
Try to imagine, if useState function is design by redux way!
const [todos, setTodos] = useState([])
this one line code now would looks like this(tons of ugly codes):
export interface TODO {
name: string
}
export interface ITodosAction {
type:
| "SET_TODOS";
payload: {
todos?: TODO[];
};
}
export default function todosReducer(
state: TODO[] = [],
action: ITodosAction
): TODO[] {
export function setTodos(todos: TODO[]): ITodosAction {
return {
type: "SET_TODOS",
payload: { todos }
};
}
const todos = useSelector(state=>state.todos)
dispatch(setTodos(["A", "B"]));
if useState became like this, we would have to suffer using react (thanks to react's team for not adopting an over-engineering and silly design like redux).
Remember: never try to figure out why this design is good when it has already brought you trouble. We use redux not because redux is good (instead it is a worst design i've ever seen) but because we don't have many choices
I think you're not appreciating how flexible reducers are, you can create as many of them as you like:
const initialState = {
key: 'someValue',
key2: 'someValue',
key3: {
key31: 'someValue',
key32: 'someValue',
key33: []
}
}
// all the below reducers can live in separate files and only handle a single action, if they want, no switches involved at all
const key = (state = initialState.key, action) => { /* the logic to be performed at 'key' */ }
const key2 = (state = initialState.key, action) => { /* the logic to be performed at 'key2' */ }
const key31 = (state = initialState.key3.key31, action) => { /* the logic to be performed at 'key3.key31' */ }
const key32 = (state = initialState.key3.key32, action) => { /* the logic to be performed at 'key3.key32' */ }
const key33 = (state = initialState.key3.key33, action) => { /* the logic to be performed at 'key3.key33' */ }
const key3 = combineReducers({key31,key32,key33})
// this is the top level reducer for your store
const myTopLevelReducer = combineReducers({key,key2,key3});

Passing multiple props and actions in react / redux

I'm using React and Redux in my web app.
In the login page, I have multiple fields (inputs).
The login page in composed from multiple components to pass the props to.
I was wondering how should I pass the props and update actions.
For example, lets assume I have 5 inputs in my login page.
LoginPage (container) -> AuthenticationForm (Component) -> SignupForm (Component)
In the LoginPage I map the state and dispatch to props,
and I see 2 options here:
mapStateToProps = (state) => ({
input1: state.input1,
...
input5: state.input5
})
mapDispatchToProps = (dispatch) => ({
changeInput1: (ev) => dispatch(updateInput1(ev.target.value))
...
changeInput5: (ev) => dispatch(updateInput5(ev.target.value))
})
In this solution, I need to pass a lot of props down the path (the dispatch actions and the state data).
Another way to do it is like this:
mapStateToProps = (state) => ({
values: {input1: state.input1, ..., input5: state.input5}
})
mapDispatchToProps = (dispatch) => ({
update: (name) => (ev) => dispatch(update(name, ev.target.value))
})
In this solution, I have to keep track and send the input name I want to update.
How should I engage this problem?
It seems like fundamental question, since a lot of forms have to handle it,
but I couldn't decide yet what would suit me now and for the long run.
What are the best practices?
I think best practice would be to handle all of this logic in the React component itself. You can use component's state to store input's data and use class methods to handle it. There is good explanation in React docs https://reactjs.org/docs/forms.html
You probably should pass data in Redux on submit. Ether storing whole state of the form as an object, or not store at all and just dispatching action with api call.
TL;DR. it's a more 'general' coding practice. But let's put it under a react-redux context.
Say if you go with your first approach, then you will probably have 5 actionCreators as:
function updateInput1({value}) { return {type: 'UPDATE_INPUT1', payload: {value}} }
...
function updateInput5({value}) { return {type: 'UPDATE_INPUT5', payload: {value}} }
Also if you have actionTypes, then:
const UPDATE_INPUT1 = 'UPDATE_INPUT1'
...
const UPDATE_INPUT5 = 'UPDATE_INPUT5'
The reducer will probably look like:
function handleInputUpdate(state = {}, {type, payload: {value}}) {
switch (type) {
case UPDATE_INPUT1: return {..., input1: value}
...
case UPDATE_INPUT5: return {..., input5: value}
default: return state
}
}
What's the problem? I don't think you're spreading too many props in mapStateToProps/mapDispatchToProps, Don't repeat yourself!
So naturally, you want a more generic function to avoid that:
const UPDATE_INPUT = 'UPDATE_INPUT'
function updateInput({name, value}) { return {type: UPDATE_INPUT, payload: {name, value}} }
function handleInputUpdate(state = {inputs: null}, {type, payload: {name, value}}) {
switch (type) {
case UPDATE_INPUT: return {inputs: {...state.inputs, [name]: value}}
default: return state
}
}
Finally, the "selector" part, based upon how the state was designed, get component's props from it would be fairly trivial:
function mapStateToProps(state) { return {inputs: state.inputs} }
function mapDispatchToProps(dispatch) { return {update(name, value) { dispatch(updateInput(name, value)) } }
In summary, it's not necessarily a redux/react problem, it's more how you design app state, redux just offers you utilities and poses some constraints to enable "time traveling" (state transitions are made explicit within a mutation handler based on a separate action).
Best practice to handle this problem is having a local state on your Form Component and managing it locally because I believe it's not a shared state. onSubmit you could dispatch your action passing down the state to the action which is required in making an API call or posting it to your server.
If you try to keep updating your store as the user types, it will keep dispatching the action which might cause problems in future. You read more here Handling multiple form inputs in react

Redux-form 6.0+ change all field values

I am trying to change multiple values in redux-form. I have them in one object so basically I want to override redux-form state values with my object values. One way to accomplish it is to run this.props.reset() followed by multiple this.props.change() events for each property. It works but it sends too many events and is slow. The second thing I tried is to run this.props.initialize(data,false) and this works but validation isn't rerun so I can easily submit the form without validation.
Is there a way to run one event to override form state with my object?
I am scared it is not possible. I had the same problem some time ago, and reading all the documentation in redux-form I got to conclude you have to use the action creators. Either change either autofill.
If you use initialize, you are initializing the values, it is meant to use for async initialization of data, therefore, it does not validate as you say.
Long ago in previous versions, they had a "defaultValue" concept. But they removed it. If you don't really need to have the last update, maybe it's worthy for you to check if that somehow would help you.
NOTE
I recommend you to follow this issue thread. They talk about it there.
It is possible. I achieved it in React using Redux via the Create-React-App file structure.
Using the stateProps/dispatchProps pattern.
You should already know about actions and reducers to use this.
Here is the project I originally started with https://medium.com/#notrab/getting-started-with-create-react-app-redux-react-router-redux-thunk-d6a19259f71f
I included that so you will know what I am talking about when I use terms like reducers and actions.
In you actions/index file
import makeAction from "./makeActionCreator";
const clearMultiplePropertiesInState = "clearMultiplePropertiesInState";
export default {
constants: {
clearMultiplePropertiesInState,
},
creators: {
clearMultiplePropertiesInState: makeAction(clearMultiplePropertiesInState
}
};
In your reducers/{your-reducer}.js
import actions from '../actions';
const { constants } = actions;
const INITIAL_STATE = {
name: "Dr Hibbert",
age: "40",
height: "180cm"
};
//#region const declarations
const { clearMultiplePropertiesInState } = constants;
//#endregion
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case clearMultiplePropertiesInState: {
var fields = action.data;
var theState = {
...state
};
for(var i = 0; i < fields.length; i++) {
theState[fields[i]] = "";
}
return theState;
}
default:
if (!action.type.includes('##')) {
console.log(`No action for: ${action.type} type`);
}
return state;
}
}
So the three items you want to clear are the state.name, state.age and state.height
import React from "react";
import { connect } from "react-redux";
import { Form, Icon, Button, Modal } from "semantic-ui-react";
import actions from "common/actions";
const { creators } = actions;
const MyComponent = ({ stateProps, dispatchProps }) => {
return (
<React.Fragment>
<Button
disabled={disableOkButton}
onClick={() => {
dispatchProps.clearMultiplePropertiesInState(["name", "age", "height"]);
}}
primary
labelPosition='right'
icon='checkmark'
content="Create Club"
loading={stateProps.modalOkButtonLoading}
/>
</React.Fragment>
);
}
function mapStatetoProps(state) {
return {
stateProps: {
name: state.name,
age: state.age,
height: state.height
}
};
}
function mapDispatchToProps(dispatch) {
return {
dispatchProps: {
clearMultiplePropertiesInState: (fieldNames) => {
dispatch(creators.clearMultiplePropertiesInState(fieldNames));
}
}
};
}
export default connect(mapStatetoProps, mapDispatchToProps)(MyComponent);
As I said you need to be well versed in using React with Redux to understand this but it is possible. This example shows I reset 3 values at the same time. So imaging passing new values as well...
I generally have a changeSinglePropInState action that I use (didnt include in code) which it passes the fieldName and the fieldValue it wants to change in state as I didnt want to create an action for every single item in my state.
Also if you can wrap your head around it, this changes one property of an object inside the state
case addItemToWishList: {
return {
...state,
buyer: {
...state.buyer,
wishlist: action.data
}
};
}

Run reducer after state is updated by another reducer

Let's say I've got an app with two reducers - tables and footer combined using combineReducers().
When I click on some button two actions are being dispatched - one after another: "REFRESH_TABLES" and "REFRESH_FOOTER".
tables reducer is listening for the first action and it modifies the state of tables. The second action triggers footer reducer. The thing is it needs current state of tables in order to do it's thing.
My implementation looks something like below.
Button component:
import React from 'react';
const refreshButton = React.createClass({
refresh () {
this.props.refreshTables();
this.props.refreshFooter(this.props.tables);
},
render() {
return (
<button onClick={this.refresh}>Refresh</button>
)
}
});
export default refreshButton;
ActionCreators:
export function refreshTables() {
return {
type: REFRESH_TABLES
}
}
export function refreshFooter(tables) {
return {
type: REFRESH_FOOTER,
tables
}
}
The problem is that the props didn't update at this point so the state of tables that footer reducer gets is also not updated yet and it contains the data form before the tables reducer run.
So how do I get a fresh state to the reducer when multiple actions are dispatched one after another from the view?
Seems you need to handle the actions async so you can use a custom middleware like redux-thuk to do something like this:
actions.js
function refreshTables() {
return {
type: REFRESH_TABLES
}
}
function refreshFooter(tables) {
return {
type: REFRESH_FOOTER,
tables
}
}
export function refresh() {
return function (dispatch, getState) {
dispatch(refreshTables())
.then(() => dispatch(refreshFooter(getState().tables)))
}
}
component
const refreshButton = React.createClass({
refresh () {
this.props.refresh();
},
{/* ... */}
});
Although splitting it asynchronous may help, the issue may be in the fact that you are using combineReducers. You should not have to rely on the tables from props, you want to use the source of truth which is state.
You need to look at rewriting the root reducer so you have access to all of state. I have done so by writing it like this.
const rootReducer = (state, action) => ({
tables: tableReducer(state.tables, action, state),
footer: footerReducer(state.footer, action, state)
});
With that you now have access to full state in both reducers so you shouldn't have to pass it around from props.
Your reducer could then looks like this.
const footerReducer = (state, action, { tables }) => {
...
};
That way you are not actually pulling in all parts of state as it starts to grow and only access what you need.

Categories