Access props in an ES7 decorator - reactjs

I'm working on asynchronously validating my redux-form. But the example doesn't really show how to use redux state, to send off an action and get the result from the state. So how can I access the props from #connect in the reduxForm decorator to achieve this?
#connect(
state => (...)
dispatch => ({
actions: auth
})
)
#reduxForm({
form: 'auth.signup',
fields,
(values, dispatch) => ({
// dispatch validation action here
})
})
Also, putting the function directly in the decorator throws up a syntax error but the logic has to be within there to have access to the props, right?

You don't need to connect again. Redux-form allow you to pass mapStateToProps and mapDispatchToProps as the second and third parameter. So you just need,
#reduxForm({
form: 'auth.signup',
fields,
(values, dispatch) => ({
// dispatch validation action here
})
}, mapStateToProps, mapDispatchToProps).
mapStateToProsp and mapDispatchToProps both take in props of the wrapped component as the second parameter.

The second parameter to both mapStateToProps and mapDispatchToProps is an object representing the props passed to the component.
The convention is to call this parameter ownProps:
#connect(
(state, ownProps) => ...,
(dispatch, ownProps) => ...
)
The redux-form documentation states that its map*ToProps functions should be the same.

My question wasn't really about where to put the map*ToProps functions, I was just blind to the fact that redux-form gives you a dispatch parameter which allows you to bind the action creator again purely for the use of running actions for validation.
It also requires moving the function out of the decorator into a constant just like the example linked in the question.
Here's a full example for anybody interested:
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { reduxForm } from 'redux-form';
import * as auth from 'actions/auth';
const fields = [
'username',
'password'
];
const asyncValidate = (values, dispatch) => {
return new Promise((resolve, reject) => {
if(values.username) {
let authActions = bindActionCreators(auth, dispatch);
authActions.checkUsernameValid(values.username);
resolve();
}
});
};
#reduxForm(
{
form: 'auth.login',
fields,
asyncValidate,
asyncBlurFields: [ 'username' ]
},
state => ({
usernameValid: state.auth.usernameValid,
usernameValidError: state.auth.usernameValidError
}),
dispatch => ({
authActions: bindActionCreators(auth, dispatch)
})
)
export default class Login extends Component {
// Component here which has access to authActions
// if the form successfully submits.
}

Related

How to use redux-toolkit createSlice with React class components

I've started using the redux-toolkit slicers in functional components, (example from react-redux example)
slicer:
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: state => {
state.value += 1;
},
decrement: state => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
use in component:
const count = useSelector(selectCount);
const dispatch = useDispatch();
return (
<button
className={styles.button}
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
)
my question is how can I use this slicer in the class component since I cant use hooks inside them.
I've tried using connect (from redux) but I can't find a way to "stitch" the actions and selectors from the slicer to my component.
I couldn't find any documentation on this as well.
Class vs. function components and redux-toolkit vs "vanilla" redux are two independent decisions that don't have any impact on each other. (Though you should be aware that function components and hooks are recommended over class components for everything React).
I've tried using connect (from redux) but I can't find a way to "stitch" the actions and selectors from the slicer to my component.
How do the docs "stitch" the actions and selectors when using useDispatch and useSelector? Do that, but with the connect higher-order component instead.
The increment() function in the docs example that you posted doesn't just magically exist, it needs to be imported from the slice. You can export the entire actions object and use actions.increment but you usually see the actions exported as individual variables.
From the docs:
Most of the time, you'll probably want to use ES6 destructuring syntax to pull out the action creator functions as variables, and possibly the reducer as well:
Your slice file might look like this:
const counterSlice = createSlice( /* same as before */ );
// destructure actions and reducer from the slice (or you can access as counterSlice.actions)
const { actions, reducer } = counterSlice;
// export individual action creator functions
export const { increment, decrement, incrementByAmount } = actions;
// often the reducer is a default export, but that doesn't matter
export default reducer;
The first argument of connect is mapStateToProps, where you use selectors (either inline arrow functions state => state.something or selector functions that you import) to create an object of props from the state. That might look like:
const mapStateToProps = (state) => ({
count: state.counter.value
});
The second argument mapDispatchToProps is optional. If you pass an object with your action creators, your component will receive versions of those action creators that are already bound to dispatch. You would be able to call this.props.increment() directly rather than this.props.dispatch(increment()). You will see this syntax commonly used in tutorials with connect.
import React from "react";
import { connect } from "react-redux";
import { increment, decrement } from "./counterSlice";
class MyComponent extends React.Component {
render() {
return (
<div>
<h1>Count is {this.props.count}</h1>
<button onClick={() => this.props.increment()}>
Increment
</button>
<button onClick={() => this.props.decrement()}>
Decrement
</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
count: state.counter.value
});
const mapDispatchToProps = { increment, decrement };
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
If you leave off the mapDispatchToProps argument entirely, your component receives the raw dispatch function. You would call the dispatch on you imported action creators like this.props.dispatch(increment()). This syntax is more similar to how useDispatch is used. Both connect and useDispatch give you access to the dispatch function and you can call that function with an action that you create from an action creator function like increment() or decrement().
import React from "react";
import { connect } from "react-redux";
import { increment, decrement } from "./counterSlice";
class MyComponent extends React.Component {
render() {
return (
<div>
<h1>Count is {this.props.count}</h1>
<button onClick={() => this.props.dispatch(increment())}>
Increment
</button>
<button onClick={() => this.props.dispatch(decrement())}>
Decrement
</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
count: state.counter.value
});
export default connect(mapStateToProps)(MyComponent);
Complete CodeSandbox

Dispatch method in ReactJS

I was basically building an authentication system with reactJS by going through some references online.
I got really confused by the code segment below
import React, { Component } from "react";
import { Link, withRouter } from "react-router-dom";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { registerUser } from "../../actions/authActions";
.
.
.
const newUser = {
name: this.state.name,
email: this.state.email,
password: this.state.password,
password2: this.state.password2
};
this.props.registerUser(newUser, this.props.history);
};
.
.
.
const mapStateToProps = state => ({
auth: state.auth,
errors: state.errors
});
export default connect(
mapStateToProps,
{ registerUser }
)(withRouter(Register));
So basically, registerUser is an action, but instead of invoking it through the dispatch method, it's accessed by this.props? Why is that?
Also, registerUser is passed as an argument to the connect() method, why?
That is a common pattern with redux. connect takes two arguments. The first maps the state and the second maps the dispatch actions. By using connect to wrap your component, both state and actions will be available through the props to your component.
It's a bit easier to understand when you actually map the dispatch calls:
const mapStateToProps = state => ({
auth: state.auth,
errors: state.errors
});
const mapDispatchToProps = dispatch => {
return {
registerUser: (user) => dispatch(registerUser(user))
}
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(Register));
This is called mapDispatch to props. You can pass it as 2nd argument to connect funtion.
mapDispatchToProps is called with dispatch function as an argument
const mapDispatchToProps = dispatch => {
return {
registerUser: (user) => dispatch(registerUser(user))
}
};
and then pass it to connect function as 2nd argument
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(Register));
The above can be simplified, by using a shorthand (Recommended by react-redux library)
Shorthand
Shorthand is by simply passing the actions in an object (in this case the registerUser)
export default connect(
mapStateToProps,
{ registerUser }
)(withRouter(Register));
For more info https://react-redux.js.org/using-react-redux/connect-mapdispatch

React Redux CreateSlice Post Api is not taking the updated values

I have an enpoint that takes formData and API gets POST method only. When the page renders it takes the initial values but I have another form to change the parameters, once I change the params it does not send the updated values.
PS. Using Redux Create Slice here is my code;
Form Page
parametersChange = form => {
... };
formatData = data => {
return form;
};
And my createSlice Method;
export const functionName = (data = {}) => (dispatch, getState) => {
........
}));
};
this.props.getLossAvoidanceList({...data }); };
This should update my createSlice Method, however, the datain my createSlice method is always returns undefined. Where am I doing wrong ?
const mapStateToProps = (state) => ({
.....
});
const mapDispatchToProps = {
...
};
export default connect(
....
)(withRouter(LossAvoidance));
I show your code and nothing found any wrong but I think the problem is in declaration of HOC
try that way
import { compose } from "redux";
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(LossAvoidance);

this.props... is not a function react react-redux

I have a problem with dispatching a action from componentDidMount...
error is : TypeError: this.props.postDetails is not a function
Action.js
export const postDetails = data => ({
type: "POST_DETAILS",
post: data
})
Container/GetDetails.js
import Details from '../components/Details'
import { postDetails } from '../actions'
const mapStateToProps = state => ({ post: state.post });
const mapDispatchToProps = dispatch => bindActionCreators({postDetails}, dispatch);
const GetDetails = connect(
mapStateToProps,
mapDispatchToProps
)(Details)
export default GetDetails
Component/Details.js
import React from 'react'
import { postDetails } from '../actions'
class Details extends React.Component {
constructor(props){
super(props);
}
componentDidMount() {
console.log("did mount details");
this.props.postDetails();
}
render() {
return (
<div>
Details page
</div>
)
}
}
export default Details;
Can someone help me? Why i have this error?
In App.js (or wherever you are importing the Details component), are you using the path to your GetDetails container (not component)?
I moved state from a component to a container and forgot to update the import path which gave me this same error. Updating the import path to the container took care of it.
Edit:
For example, I have an apiLanding folder that has apiLanding.js (the component) and apiLanding-container.js (the container).
In my app.js, I needed to change
import apiLanding from './components/apiLanding/apiLanding';
to
import apiLanding from './components/apiLanding/apiLanding-container';
That way, the app now has access to the redux state and actions. This was a silly mistake and may not be your issue, but wanted to share just in case the import path was overlooked.
You have to return an object, where the keys are your props. See docs.
const mapDispatchToProps = dispatch => ({ postDetails: bindActionCreators({postDetails}, dispatch) })
Or, you can use the shorthand notation:
const GetDetails = connect(
mapStateToProps,
{ postDetails }
)(Details)
I don't see bindActionCreator imported. Use eslint to get rid of these errors
There are two things which don't really seem right to me. Personally I never used bindActionCreators. I would just create my mapDispatchToProps as following:
const mapDispatchToProps = dispatch => {
return {
postDetails: () => dispatch(actions.postDetails)
};
};
But also your postDetails call expects an argument, which you should add in your function call. So your mapDispatchToProps would look like this:
const mapDispatchToProps = dispatch => {
return {
postDetails: (data) => dispatch(actions.postDetails(data))
};
};
Also you're importing your action as postDetails. Are you sure that this is just one action? And not a combination of all actions in your file? Note how I added your function as actions.postDetails instead of just postDetails.

Multiple actionCreators in single component

I want to have multiple actionCreators dispatched into one component. I know you can do this with state
export default connect(
(state: ApplicationState) => Object.assign({}, state.search, state.resources),
ResourcesState.actionCreators// i have another actionCreator I want to add
)(Home) as typeof Home;
But not sure the syntax to do this with actionCreators. I have read into
mapDispatchToProps
But not sure how to implement.
There are a few ways to set up dispatching of Redux actions in React components:
Use connect(mapState)(MyComponent). By default, your component will be given props.dispatch, and you can call props.dispatch({type : "SOME_ACTION"}).
Pass a mapDispatchToProps function as the second argument to connect. Inside, you can create new function references that call dispatch:
function mapDispatchToProps(dispatch) {
return {
addTodo : (text) => dispatch({type : "ADD_TODO", text})
}
}
You can also use the Redux bindActionCreators utility instead:
function mapDispatchToProps(dispatch) {
return bindActionCreators({addTodo, toggleTodo}, dispatch);
}
Finally, you can pass an object full of action creators directly to connect:
const actions = {addTodo, toggleTodo};
export default connect(mapState, actions)(MyComponent);
I highly recommend the fourth approach, which I also talk about in my blog post Idiomatic Redux: Why Use Action Creators?.
mapDispatchToProps is the second argument in connect. So for example:
import customAction from 'actions-directory-in-your-app'
const mapStateToProps = () => {} // no implementing anything for example purposes
const mapDispatchToProps = () => ({ customAction })
const ConnectedContainer = connect(
mapStateToProps,
mapDispatchToProps
)(YourContainer)
customAction becomes now a prop in YourContainer so you can use it the same way other props within your component.
the Second argument to connect takes an object or a function so you can add
export default connect(
(state: ApplicationState) => Object.assign({}, state.search, state.resources),
{
ResourcesState.actionCreators,
some_other_action_creators,
some_more
}
)(Home) as typeof Home;
Also read through this answer on Stackoverflow for more information on how to use action creators
Why is there no need for a mapDispatchToProps function here?
The second argument to connect takes an object, so you can use of ES6 syntax and avoid the use of mapDispatchToProps.
import { yourAction } from './your_actions_folder'
class Home extends Component{
....
//For dispatch a action you only call the action Creator
this.props.yourAction()
}
export default connect(mapStateToProps,{yourAction})(Home)

Resources