ReduxForm: passing initialValues from mapStatetoProps - redux

I'm trying to get the initialValues of the form setup as follows but it doesn't work:
const mapStateToProps = (state, props) => ({
initialValues: state.charts[props.resourceId],
});
const mapDispatchToProps = ...
export default reduxForm({
form: 'ChartForm',
})(connect(mapStateToProps, mapDispatchToProps)(ChartForm));
Of course if I do something like this will work:
export default reduxForm({
form: 'ChartForm',
initialValues: {
title: "Some Title",
...
}
})(connect(mapStateToProps, mapDispatchToProps)(ChartForm));
That is not what I need though. I need to get the initial values from a resource in my store that has id == this.props.resourceId
Could anybody point out what is wrong with the first solution?

You need to pass the initialValues props into the form. To do that wrap the form with the connect function. See Initialize From State in the documentation.
export default connect(mapStateToProps, mapDispatchToProps)(reduxForm({
form: 'ChartForm',
})(ChartForm));

Related

Getting this error "Invariant failed: A state mutation was detected inside a dispatch, in the path: todoReducer.1."

I tried everything like spread operator but nothing works.
Here is my reducer
//state is an array of objects.
const initialState = [
{taskName: "kkkkk", isEdit: false},
]
export const todoReducer = (state=initialState, action) =>{
switch(action.type){
case 'add' :
const temp=
{
taskName: action.payload.taskName,
isEdit: action.payload.isEdit
}
state.push(temp);
return {state}
default: return state
}
}
The error message indicates that you are using Redux Toolkit - that is very good. The problem is that you are not using createSlice or createReducer and outside of those, in Redux you are never allowed to assign something to old state properties with = or call something like .push as it would modify the existing state.
Use createSlice instead:
const initialState = [
{taskName: "kkkkk", isEdit: false},
]
const slice = createSlice({
name: 'todos',
reducers: {
add(state, action) {
state.push(action.payload)
}
}
})
export const todoReducer = slice.reducer;
// this exports the auto-generated `add` action creator.
export const { add } = slice.actions;
Since the tutorial you are currently following seems to be incorporating both modern and completely outdated practices, I would highly recommend you to read the official Redux Tutorial instead, which shows modern concepts.

React-redux dispatch action onclick using hooks and redux toolkit

Fairly new to redux, react-redux, and redux toolkit, but not new to React, though I am shaky on hooks. I am attempting to dispatch an action from the click of a button, which will update the store with the clicked button's value. I have searched for how to do this high and low, but now I am suspecting I am thinking about the problem in React, without understanding typical redux patterns, because what I expect to be possible is just not done in the examples I have found. What should I be doing instead? The onclick does seem to capture the selection, but it is not being passed to the action. My goal is to show a dynamic list of buttons from data collected from an axios get call to a list of routes. Once a button is clicked, there should be a separate call to an api for data specific to that clicked button's route. Here is an example of what I currently have set up:
reducersRoutes.js
import { createSlice } from "#reduxjs/toolkit";
import { routesApiCallBegan } from "./createActionRoutes";
const slice = createSlice({
name: "routes",
initialState: {
selected: ''
},
{... some more reducers...}
routeSelected: (routes, action) => {
routes.selected = action.payload;
}
},
});
export default slice.reducer;
const { routeSelected } = slice.actions;
const url = '';
export const loadroutes = () => (dispatch) => {
return dispatch(
routesApiCallBegan({
url,
{...}
selected: routeSelected.type,
})
);
};
createActionRoutes.js
import { createAction } from "#reduxjs/toolkit";
{...some other actions...}
export const routeSelected = createAction("routeSelection");
components/routes.js:
import { useDispatch, useSelector } from "react-redux";
import { loadroutes } from "../store/reducersRoutes";
import { useEffect } from "react";
import { routeSelected } from "../store/createActionRoutes";
import Generic from "./generic";
const Routes = () => {
const dispatch = useDispatch();
const routes = useSelector((state) => state.list);
const selected = useSelector((state) => state.selected);
useEffect(() => {
dispatch(loadroutes());
}, [dispatch]);
const sendRouteSelection = (selection) => {
dispatch(routeSelected(selection))
}
return (
<div>
<h1>Available Information:</h1>
<ul>
{routes.map((route, index) => (
<button key={route[index]} className="routeNav" onClick={() => sendRouteSelection(route[0])}>{route[1]}</button>
))}
</ul>
{selected !== '' ? <Generic /> : <span>Data should go here...</span>}
</div>
);
};
export default Routes;
Would be happy to provide additional code if required, thanks!
ETA: To clarify the problem - when the button is clicked, the action is not dispatched and the value does not appear to be passed to the action, even. I would like the selection value on the button to become the routeSelected state value, and then make an api call using the routeSelected value. For the purpose of this question, just getting the action dispatched would be plenty help!
After writing that last comment, I may actually see a couple potential issues:
First, you're currently defining two different action types named routeSelected:
One is in the routes slice, generated by the key routeSelected
The other is in createActionRoutes.js, generated by the call to createAction("routeSelection").
You're importing the second one into the component and dispatching it. However, that is a different action type string name than the one from the slice - it's just 'routeSelection', whereas the one in the slice file is 'routes/routeSelected'. Because of that, the reducer logic in the slice file will never run in response to that action.
I don't think you want to have that separate createAction() call at all. Do export const { routeSelected } = slice.actions in the slice file, and dispatch that action in the component.
I'm also a little concerned about the loadroutes thunk that you have there. I see that you might have omitted some code from the middle, so I don't know all of what it's doing, but it doesn't look like it's actually dispatching actions when the fetched data is retrieved.
I'd recommend looking into using RTK's createAsyncThunk API to generate and dispatch actions as part of data fetching - see Redux Essentials, Part 5: Async Logic and Data Fetching for examples of that.

How to intercept when the action start and store is updated?

I am using NGRX. I have many actions and I want to know when actions start and when the store is updated.
The idea is to have a centralized way to get the information no matter what action is executed. I need to know when the store updates without subscribing to all selectors.
updateTitle ----> title is updated.
best,
Hmendez
You have to explicitly define actions
e.g.
import { createAction, props } from "#ngrx/store";
export enum HeaderActionTypes {
UpdateTitle = '[Title] Update Title',
UpdateTitleSuccess = '[Title] Update Title success'
}
export const UpdateTitle = createAction(HeaderActionTypes.UpdateTitle)
export const UpdateTitleSuccess = createAction(HeaderActionTypes.UpdateTitleSuccess, props<{ payload: string }>())
in reducer you can catch actions and update the state
e.g
import { createReducer, on } from "#ngrx/store";
export const initialState = {
... // Additional state properties can go here.
updatingTitle: false,
title: ''
}
export const reducer = createReducer(
initialState,
on(HeaderActionTypes.UpdateTitle, (state) => {
return {
...state,
updatingTitle: true
}
}),
on(HeaderActionTypes.UpdateTitleSuccess, (state, { payload }) => {
return {
...state,
updatingTitle: false,
title: payload
}
})
)
If title is getting updated with async call you have to add an Effect
Effect, Checkout the documentation.
Selectors, Use selectors to read state and bind it to UI.

Redux - how to access the store "dispatch" after providing it with Provider?

I have the following files:
index.js
const store = createStore(...)
ReactDOM.render(<Provider store={store}><BrowserRouter><App/></BrowserRouter></Provider>, document.getElementById('root'));
The App component: (App.js)
const App = withRouter(connect(mapStateToProps, mapDispatchToProps)(Main))
export default App
So then how i can access store.dispatch inside of the Main component?
If i try to do it by store.dispatch({...}) i get:
'store' is not defined no-undef
If mapDispatchToProps looks like:
const mapDispatchToProps = dispatch => ({
myAction1: () => dispatch(myAction1())
});
connect(mapStateToProps, mapDispatchToProps)(Main)
...then in the component, you can call this.props.myAction1()
If mapDispatchToProps uses bindActionCreators:
const actions = { myAction1, myAction2 };
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
...then in the component, you can call this.props.myAction1() and this.props.myAction2()
If mapDispatchToProps is undefined:
connect(mapStateToProps)(Main)
then in the component, you can access this.props.dispatch
dispatch function should be available through this.props
this.props.dispatch()
Your component shouldn't access the store directly - connect abstracts that away.
Please see our new React-Redux docs page on connect: Dispatching Actions with mapDispatchToProps for a complete description of how to handle dispatching actions.

Use async react-select with redux-saga

I try to implement a async react-select (Select.Async). The problem is, we want to do the fetch in redux-saga. So if a user types something, the fetch-action should be triggered. Saga then fetches the record and saved them to the store. This works so far.
Unfortunately loadOptions has to return a promise or the callback should be called. Since the newly retrieved options get propagated with a changing property, I see no way to use Select.Async together with saga to do the async fetch call. Any suggestions?
<Select.Async
multi={false}
value={this.props.value}
onChange={this.onChange}
loadOptions={(searchTerm) => this.props.options.load(searchTerm)}
/>
I had a hack where i assigned the callback to a class variable and resolve it on componentWillReceiveProps. That way ugly and did not work properly so i look for a better solution.
Thanks
redux-saga is for handling side effects like asynchronously receiving options for react-select. That's why you should leave the async stuff to redux-saga. I have never used react-select but by just looking at the documentation I would solve it this way:
Your component gets very simple. Just get value and options from your redux store. optionsRequested is an action creator for the OPTIONS_REQUESTED action:
const ConnectedSelect = ({ value, options, optionsRequested }) => (
<Select
value={value}
options={options}
onInputChange={optionsRequested}
/>
)
export default connect(store => ({
value: selectors.getValue(store),
options: selectors.getOptions(store),
}), {
optionsRequested: actions.optionsRequested,
})(ConnectedSelect)
A saga definition watches for OPTIONS_REQUESTED action that is trigged by onInputChange, loads the data with given searchTerm from server and dispatches OPTIONS_RECEIVED action to update redux store.
function* watchLoadOptions(searchTerm) {
const options = yield call(api.getOptions, searchTerm)
yield put(optionsReceived(options))
}
In other words: Make your Component as pure as possible and handle all side-effect/async calls in redux-saga
I hope this answer was useful for you.
The main idea is that you are capable to dispatch redux actions using application context from
import React from 'react';
import { connect } from 'react-redux';
import Select from '#components/Control/Form/Skin/Default/Select';
import { reduxGetter, reduxSetter, required as req } from '#helpers/form';
import { companyGetTrucksInit } from "#reduxActions/company";
import AppContext from '#app/AppContext';
const FIELD_NAME = 'truck';
export const getReduxValue = reduxGetter(FIELD_NAME);
export const setReduxValue = reduxSetter(FIELD_NAME);
const SelectCompanyTruck = (props) => {
const {
required,
validate=[]
} = props;
const vRules = [...validate];
if (required)
vRules.push(req);
return (
<AppContext.Consumer>
{({ dispatchAction }) => (
<Select
loadOptions={(inputValue, callback) => {
function handleResponse(response) {
const { data: { items } } = response;
const options = items.map(i => ({ label: i.name, value: i.id }));
callback(options);
}
dispatchAction(companyGetTrucksInit, { resolve: handleResponse, inputValue });
}}
name={FIELD_NAME}
{...props}
/>
)}
</AppContext.Consumer>
);
}
export default SelectCompanyTruck;

Resources