How to properly connect a ReactJS component to Redux using react-redux? - javascript

I´m trying to build my first connection between a React component to Redux to gather data from my node API.
This is a simple component for now, but it will grow in the future and will derive subcomponents that will allow me to build a full User CRUD interface (list, edit, delete, view, etc.). In that sense, I need to connect the action functions to the object this.props so that it can be inheritated by the child components.
Here is my code:
Component UserGrid - A table data grid that will load users information
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as userActions from '../../actions/userActions';
class UserGrid extends React.Component {
componentWillMount() {
this.props.fetchUsers();
}
render() {
const mappedUsers = users.map(user => <li>{user.email}</li>);
return (
<div>
<ul>{mappedUsers}</ul>
</div>
);
}
}
function mapStateToProps(state) {
return {
users: state.users
}
}
export default connect(
mapStateToProps,
bindActionCreators(userActions, dispatch)
)(UserGrid);
My userAction, that effectively will load users from an AJAX call:
import axios from "axios";
export function fetchUsers() {
return function(dispatch) {
axios.get("/api/users")
.then((response) => {
dispatch({type: "FETCH_USERS_FULFILLED", payload: response.data});
})
.catch((err) => {
dispatch({type: "FETCH_USERS_REJECTED", payload: err});
})
}
}
With this code I´m getting the following error:
Uncaught ReferenceError: dispatch is not defined
at Object.<anonymous> (bundle.js:37984)
at __webpack_require__ (bundle.js:556)
at fn (bundle.js:87)
at Object.<anonymous> (bundle.js:37711)
at __webpack_require__ (bundle.js:556)
at fn (bundle.js:87)
at Object.<anonymous> (bundle.js:8466)
at __webpack_require__ (bundle.js:556)
at fn (bundle.js:87)
at Object.<anonymous> (bundle.js:588)
(anonymous) # bundle.js:37984
__webpack_require__ # bundle.js:556
fn # bundle.js:87
(anonymous) # bundle.js:37711
__webpack_require__ # bundle.js:556
fn # bundle.js:87
(anonymous) # bundle.js:8466
__webpack_require__ # bundle.js:556
fn # bundle.js:87
(anonymous) # bundle.js:588
__webpack_require__ # bundle.js:556
(anonymous) # bundle.js:579
(anonymous) # bundle.js:582
What is the correct way to connect my component to the Redux services created?

In the component you wish to connect you need to define the function mapDispatchToProps. This function will take dispatch as a parameter, and will receive this parameter when connect calls the function.
Here is some example code pulled from one of my recent projects.
function mapDispatchToProps(dispatch) {
return {
adminActions: bindActionCreators(adminOrdersActions, dispatch),
schoolActions: bindActionCreators(schoolOrdersListActions, dispatch),
userActions: bindActionCreators(userActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AllOrders);
Now dispatch is defined.
EDIT: To clarify, doing this will now give you access to your actions with the following syntax. this.props.actions.yourAction, or whatever you called the actions in the mapDispatchToProps.
This is your code with my changes:
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as userActions from '../../actions/userActions';
class UserGrid extends React.Component {
componentWillMount() {
console.log(JSON.stringify(this.props));
//this.props.fetchUsers();
this.props.actions.fetchUsers();
}
render() {
const mappedUsers = users.map(user => <li>{user.email}</li>)
return (
<div>
<ul>{mappedUsers}</ul>
</div>
)
}
}
function mapStateToProps(state) {
return {
users: state.users
}
}
function mapDispatchToProps(dispatch) {
// this function will now give you access to all your useractions by simply calling this.props.actions.
//if the key of this object would not be actions, but rather foobar, you will get your actions by
// calling this.props.foobar
return {
actions: bindActionCreators(userActions, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserGrid);

I don't think you want to use bindActionCreators here. Here's what the docs say (emphasis mine):
Normally you should just call dispatch directly on your Store instance. If you use Redux with React, react-redux will provide you with the dispatch function so you can call it directly, too.
The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn't aware of Redux, and you don't want to pass dispatch or the Redux store to it.
Since that's not what you're doing, you should instead call dispatch directly. With react-redux, connect will inject it into your props, so you can just do this:
componentWillMount() {
this.props.dispatch(fetchUsers());
}

As Jordan said I would not use bindActionCreators.
Your component should look something like this:
import React from 'react';
import { connect } from 'react-redux';
import myAction from './my-action';
class MyComponent extends React.Component {
componentDidMount() {
this.props.myAction();
}
render() {
return "Hi!"
}
}
const mapStateToProps = (state) => ({
myState: state.myState
});
const mapDispatchToProps = (dispatch) => ({
myAction: () => dispatch(myAction())
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

Related

Failed prop type: The prop `auth` is marked as required in `Navbar`, but its value is `undefined`

I'm using useSelector instead of mapStateToProps function in my functional component called NavBar.js.
When I run the program, I see this error in the console:
"Failed prop type: The prop auth is marked as required in Navbar, but its value is undefined."
How should I solve it?
import React from 'react';
import { Link, withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect, shallowEqual, useSelector } from 'react-redux';
import { logoutUser } from '../../actions/authActions';
import { clearCurrentProfile } from '../../actions/profileActions';
const Navbar = (props) => {
const selectedData = useSelector((state) => state, shallowEqual)
const { isAuthenticated, user } = selectedData.auth;
return(
//Code......
)
}
Navbar.propTypes = {
logoutUser: PropTypes.func.isRequired,
clearCurrentProfile: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
export default withRouter(connect(null, { logoutUser, clearCurrentProfile })(Navbar));
The problem is you are no longer mapping the state to props but your component is still expecting the prop. To solve this problem, you need to remove auth from propTypes.
Navbar.propTypes = {
logoutUser: PropTypes.func.isRequired,
clearCurrentProfile: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired // this is the problem
};
Another unrelated issue I am seeing is you're using connect to map the action creators to props. I'd suggest removing the use of connect entirely by using useDispatch() to dispatch your actions. Learn more
auth is not imported or declared in function
and is also not exported.
it appears only in PropTypes definition.
this might be the problem?

Connecting Redux to my React APP: Actions may not have an undefined "type" property

I'm new to Redux and i'm having a lot problems trying to connect Redux to my React App, whenever i type something on my input box i get this error:
Uncaught Error: Actions may not have an undefined "type" property. Have you misspelled a constant?
at dispatch (eval at ./node_modules/redux/es/createStore.js (bundle.js:4248), :164:13)
at handleSearchTermChange (eval at ./js/Landing.jsx (bundle.js:757), :78:7)
at Object.executeOnChange (eval at ./node_modules/react-dom/lib/LinkedValueUtils.js (bundle.js:2779), :132:34)
at ReactDOMComponent._handleChange (eval at ./node_modules/react-dom/lib/ReactDOMInput.js (bundle.js:2899), :239:38)
at Object.ReactErrorUtils.invokeGuardedCallback (eval at ./node_modules/react-dom/lib/ReactErrorUtils.js (bundle.js:3019), :69:16)
at executeDispatch (eval at ./node_modules/react-dom/lib/EventPluginUtils.js (bundle.js:2739), :85:21)
at Object.executeDispatchesInOrder (eval at ./node_modules/react-dom/lib/EventPluginUtils.js (bundle.js:2739), :108:5)
at executeDispatchesAndRelease (eval at ./node_modules/react-dom/lib/EventPluginHub.js (bundle.js:2723), :45:22)
at executeDispatchesAndReleaseTopLevel (eval at ./node_modules/react-dom/lib/EventPluginHub.js (bundle.js:2723), :56:10)
at Array.forEach ()
This is my Landing.jsx:
// #flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import type { RouterHistory } from 'react-router-dom';
import { setSearchTerm } from './actionCreators';
class Landing extends Component {
props: {
searchTerm: string,
handleSearchTermChange: Function,
history: RouterHistory
};
goToSearch = (event: SyntheticEvent) => {
event.preventDefault();
this.props.history.push('/search');
};
render() {
return (
<div className="landing">
<h1>svideo</h1>
<form onSubmit={this.goToSearch}>
<input
onChange={this.props.handleSearchTermChange}
value={this.props.searchTerm}
type="text"
placeholder="Search"
/>
</form>
<Link to="/search">or Browse All</Link>
</div>
);
}
}
const mapStateToProps = state => ({ searchTerm: state.searchTerm });
const mapDispatchToProps = (dispatch: Function) => ({
handleSearchTermChange(event) {
dispatch(setSearchTerm(event.target.value));
}
});
export default connect(mapStateToProps, mapDispatchToProps)(Landing);
This is my store.js
import { createStore } from 'redux';
import reducers from './reducers';
const store = createStore(reducers);
export default store;
This is my action.js file:
/* This file will be a bunch of constants*/
export const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
My actionCreators.js
import { SEARCH_SEARCH_TERM } from './actions';
/* Takes in a "searchterm" and it returns type and payload */
export function setSearchTerm(searchTerm: string) {
return { type: SEARCH_SEARCH_TERM, payload: searchTerm };
}
And the reducer.js:
import { SET_SEARCH_TERM } from './actions';
const DEFAULT_STATE = {
searchTerm: ''
};
const setSearchTerm = (state, action) => Object.assign({}, state, { searchTerm: action.payload });
const rootReducer = (state = DEFAULT_STATE, action: Action) => {
switch (action.type) {
case SET_SEARCH_TERM:
return setSearchTerm(state, action);
default:
return state;
}
};
export default rootReducer;
Really hope somebody can shed me a light here because I'm pretty new to the whole React, Redux, Flow and Type world.
Ok, so the problem was that basically, I was trying to import a function that didn't exist because of a typo:
This is the declaration on my action.js file:
/* This file will be a bunch of constants*/
export const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
and i was trying to import the const like this:
import { SEARCH_SEARCH_TERM } from './actions';
export function setSearchTerm(searchTerm: string) {
return { type: SEARCH_SEARCH_TERM, payload: searchTerm };
}
SEARCH_SEARCH_TERM is undefined, I changed it to SET_SEARCH_TERM and now it's working. Kudos to #markerikson for his comment on the original post.

Uncaught TypeError: Cannot set property propTypes of function Settings(props)

The prop types are working fine with normal react component but fails with Redux connected component.
Error details
Uncaught TypeError: Cannot set property propTypes of function Settings(props)
Complete error details
Uncaught TypeError: Cannot set property propTypes of function Settings(props) {
_classCallCheck(this, Settings);
var _this = _possibleConstructorReturn(thi...<omitted>... } which has only a getter
at Object.eval (Settings.js?384b:533)
at eval (Settings.js:509)
at Object../src/Navigation/Components/Settings.js (main.b2f70c3c5e2d43b8884a.js?b2f70c3c5e2d43b8884a:14403)
at __webpack_require__ (main.b2f70c3c5e2d43b8884a.js?b2f70c3c5e2d43b8884a:725)
at fn (main.b2f70c3c5e2d43b8884a.js?b2f70c3c5e2d43b8884a:102)
at eval (routes.js?5665:15)
at Object../src/routes.js (main.b2f70c3c5e2d43b8884a.js?b2f70c3c5e2d43b8884a:14763)
at __webpack_require__ (main.b2f70c3c5e2d43b8884a.js?b2f70c3c5e2d43b8884a:725)
at fn (main.b2f70c3c5e2d43b8884a.js?b2f70c3c5e2d43b8884a:102)
at eval (index.js?b635:2)
I am using prop-types v15, react v16, react-redux v3 lib's.
Settings.js Component:
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import React, { Component } from 'react';
class Settings extends Component {
constructor(props) {
super(props);
}
static get propTypes() {
return {
loginId:React.PropTypes.string,
loading: React.PropTypes.bool,
updateEmail: React.PropTypes.func
}
}
render(){
return(
<div>{this.props.text}</div>
)
}
}
function mapStateToProps(state) {
let text = "Hello Settings";
return {
text
}
}
Settings.propTypes = {
loginId: PropTypes.string,
loading: PropTypes.bool
}
export default connect(mapStateToProps, null)(Settings);
mapStateToProps is meant to send variables from the redux store to the react component.
But you are using it to just set a prop.
But, in line with the comment from Bartek, you could try this:
const mapStateToProps = state => {
return {
text: "hello settings"
}
}
The way you should use mapStateToProps is more like this:
const mapStateToProps = state => {
return {
text: state.text
}
}

this.props.fb is not a function

Iam new to redux/react . Iam a doing a simple rest call to fetch some details by triggering an action . But iam getting the following error
BSC.js?d219:24 Uncaught TypeError:
this.props.fb is not a function.
My code is as follows
Actions-
export function fb(){
return dispatch => {
dispatch({
type: SUCCESS,
payload: {"key1":"value1","key2":"value2"}
})
}
}
Container -
import {connect} from 'react-redux';
import React, { Component } from 'react';
import {fb} from '../../actions/b/bSA';
export class BSC extends React.Component {
componentDidMount(){
this.props.fb();
}
render(){
return <div></div>
}
}
export default connect(null, {fB})(BSC);
Reducer
const initialState = {
loading: true,
error: null,
};
export default function bSR(state = initialState, action) {
switch(action.type) {
case SUCCESS:
return {
...state,
loading: false,
};
default:
// ALWAYS have a default case in a reducer
return state;
}
}
What could be the issue ?
Just spit balling, but I believe you are lacking a MapStateToProps function which would actually attach that function.
You need to declare this method in your container component file.
const mapDispatchToProps = dispatch => {
return bindActionCreators({
fb
}, dispatch);
};
where bindActionCreators is:
import {bindActionCreators} from "redux";
Now pass it to connect method:
export default connect(null, mapDispatchToProps)(BSC);
What happens here is mapDispatchToProps function gets dispatch as function parameter and it has to be passed to action creator to actually dispatch the action. Here bindActionCreators calls your action creator method with the dispatch as parameter and you will be able to dispatch the required action there.

What's the '#' (at symbol) in the Redux #connect decorator?

I am learning Redux with React and stumbled upon this code. I am not sure if it is Redux specific or not, but I have seen the following code snippet in one of the examples.
#connect((state) => {
return {
key: state.a.b
};
})
While the functionality of connect is pretty straightforward, but I don't understand the # before connect. It isn't even a JavaScript operator if I am not wrong.
Can someone explain please what is this and why is it used?
Update:
It is in fact a part of react-redux which is used to connects a React component to a Redux store.
The # symbol is in fact a JavaScript expression currently proposed to signify decorators:
Decorators make it possible to annotate and modify classes and properties at design time.
Here's an example of setting up Redux without and with a decorator:
Without a decorator
import React from 'react';
import * as actionCreators from './actionCreators';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
function mapStateToProps(state) {
return { todos: state.todos };
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actionCreators, dispatch) };
}
class MyApp extends React.Component {
// ...define your main app here
}
export default connect(mapStateToProps, mapDispatchToProps)(MyApp);
Using a decorator
import React from 'react';
import * as actionCreators from './actionCreators';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
function mapStateToProps(state) {
return { todos: state.todos };
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actionCreators, dispatch) };
}
#connect(mapStateToProps, mapDispatchToProps)
export default class MyApp extends React.Component {
// ...define your main app here
}
Both examples above are equivalent, it's just a matter of preference. Also, the decorator syntax isn't built into any Javascript runtimes yet, and is still experimental and subject to change. If you want to use it, it is available using Babel.
Very Important!
These props are called state props and they are different from normal props, any change to your component state props will trigger the component render method again and again even if you don't use these props so for Performance Reasons try to bind to your component only the state props that you need inside your component and if you use a sub props only bind these props.
example:
lets say inside your component you only need two props:
the last message
the user name
don't do this
#connect(state => ({
user: state.user,
messages: state.messages
}))
do this
#connect(state => ({
user_name: state.user.name,
last_message: state.messages[state.messages.length-1]
}))

Categories