I'm new to redux, but i have a problem that i don't understand and it can not fix it.
The problem is, when i want to dispatch inside my action, I've got an error who said :
dispatch is not a function
Yep, he is undefined and this is the point, why he is undefined ?
export const clearError = dispatch => {
console.log('clear error')
console.log(dispatch)
dispatch({
type: 'CLEAR_ERROR'
})
}
I call the clearError action from Main.js, maybe my initialization is not correct, but i've tried several way, like bindActionCreators...
Main.js (how i pass props and dispatch with connect)
const mapStateToProps = (state) => state
const mapDispatchToProps = {
clearError: clearError
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Main)
I'm really confused because in other component I use two others actions made by the same way and everything it's ok...
I don't know if it can help you to understand the origin of the problem but i show you how i configure the store to call configureStore() in App.js.
import {applyMiddleware, createStore} from "redux";
import thunk from 'redux-thunk';
import reducers from './reducers'
const configureStore = () => {
const middleware = [thunk]
return createStore(reducers, applyMiddleware(...middleware))
}
export default configureStore
I'm listening every help and advices about redux and the best way to use it !
Really thanks to you for reading ❤️
The issue is that you are not returning a function from your action creator with dispatch as one of the arguments:
export const clearError = () => dispatch => {
Under the hood, when you pass your action creators to mapDispatchToProps, Redux "maps" that dispatch argument to your action creators, and then assigns the mapped action creators to Component properties.
This is also why you have to call this.props.clearError() instead of just clearError().
Related
hello guys I am trying to fetch initial data for redux store from given api !
here is my first approach from using store.dispatch:
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import reducer from "./reducer";
import { fetchNews } from "./actions";
const store = createStore(
reducer,
compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
store.dispatch(fetchNews());
export default store;
and second approach is dispatching action from the component lifecycle method like this:
import React from "react";
import axios from "axios";
import InfiniteScroll from "react-infinite-scroller";
import { useSelector, useDispatch } from "react-redux";
import New from "./New";
import { fetchNews } from "../store/actions";
const Main = () => {
const news = useSelector(state => state.news);
const dispatch = useDispatch();
React.useEffect(() => {
dispatch(fetchNews());
}, [dispatch]);
return (
<>
{news.length > 0 ? (
news.map(data => (
<New key={data.id} subTitle={data.created_at}>
{data.title}
</New>
))
) : (
<p>News not found</p>
)}
</>
);
};
OK both method works but i really want to know what is the better approach here or is there any other better method! thank you!
Dispatching action from the component lifecycle method is the recommended approach and is what should be followed in order to maintain the separation of concern design on which Redux is based on please check the attached image.
The second one. The file in which you create the store should always be all about creating the store and exporting it as is.
By the way, on your useEffect, try removing the dispatch from the variables array:
React.useEffect(() => {
dispatch(fetchNews());
}, []);
This way it will fetch the data on component mount, just like ComponentDidMount.
I wouldn't say dispatching from component lifecycle is the recommended approach , it certainly is used by many developers, but it couples in you component with data fetching (is that a good idea) ? Or should your component not know about how data is fetched and just consume it in some way. Your choice.
You could use your own custom middleware to send the request.
With that approach you have greater flexibility , you could dispatch actions like type:LOADING_DATA and any others depending on setting state while loading your data. Once the data is returned dispatch other action(s) which could be handled by other custom middleware(s) to normalise data etc and finally send it to your reducer.
This approach is described very well in https://leanpub.com/thinking-in-Redux or https://www.youtube.com/watch?v=JUuic7mEs-s
I personally liked his ideas and perhaps you would find them useful.
Usually in my Redux projects I have action and reducer files that come in pairs. So in my actions folder I will have a posts.js and in my reducers folder I will also have a posts.js. The posts action file usually just dispatch action types that are subscribed to in the posts reducer file.
But now I need to dispatch an action type from the posts action file that is subscribed to from the authors reducer file. Is this okay or considered an anti-pattern?
That isn't anti-pattern at all. It's good code-reusability and especially useful for error-handling.
Consider the following example:
posts.js
import { GET_POSTS } from "./actions/types"
import { setErrors } from "./actions/errors"
import axios from "axios"
export const getPosts = () => {
return (dispatch) => {
axios.get("/api/posts")
.then((res) => {
dispatch({
type: GET_POSTS,
payload: res.data
})
})
.catch((errors) => {
dispatch(setErrors(errors.response.data))
}
}
}
errors.js
const setErrors = (errors) => {
return {
type: SET_ERRORS,
payload: errors
}
}
So instead of defining a completely new post-related errors action inside posts.js, it makes sense to just import an existing one that is subscribed to your errors-reducer (if any). The same could be said about using your author and post actions together.
It depends on which design pattern you are following.
As you may have all your actions in a single file.
For your case you can export the action creator from one file and just import it where you want same action creator.
This way you can have the same syncing between action creators and reducers as you are talking about.
In order to access to the props of the component inside the mapDispatchToProps i chained my connect like so
export default connect(mapStateToProps, null)(connect(mapStateToProps, mapDispatchToProps)(MyComponent));
And then i manage to access to the props inside the mapDispatchToProps like so
const mapDispatchToProps = (dispatch,ownProps) => {
const myProp = ownProps.theProp
}
Is it a bad things to do ?
Any alternative exists ?
Is it a bad things to do?
IMO, It is certainly bad. connect() is an HOC. connect(...)(connect(...)(MyComponent)) is redundant.
Any alternative exists ?
Use mergeProps instead or break the components properly and use redux-saga to use a common interaction point (the redux store).
The correct way to connect mapDispatch to props and mapStateToProps is like this:
export default connect(mapStateToProps, mapDispatchToProps)(MetaDataTaggingApp);
Also I don't think you should have to access props in mapDispatchToProps.
mapDispatchToProps is basically just telling your component which dispatch actions it's allowed to use.
const mapDispatchToProps = {
aReduxAction,
anotherReduxAction
}
If you need to pass props into these dispatches, it should be when you are invoking them.
Does anyone know how to wrap a React component with React.memo when one is using the connect function from react-redux?
For example, how would you modify the following?
let Button = (props: Props) => (
<button onClick={props.click}>{props.value}</button>
);
Button = connect(
mapStateToProps,
mapDispatchToProps
)(Button);
I've tried:
let Button = React.memo((props: Props) => (
<button onClick={props.click}>{props.value}</button>
));
Button = connect(
mapStateToProps,
mapDispatchToProps
)(Button);
However, the function returned by connect expects a component to be passed so it errors with:
Uncaught Error: You must pass a component to the function returned by
connect. Instead received {"compare":null}
React.memo is nothing but a HOC, so you can just use:
Without memo:
connect(
mapStateToProps,
mapDispatchToProps
)(Button);
With memo:
connect(
mapStateToProps,
mapDispatchToProps
)(React.memo(Button));
And even wrap to connect: (This should be the solution with connect)
React.memo(
connect(
mapStateToProps,
mapDispatchToProps
)(Button)
);
Like we do with withRouter: withRouter(connect(...)())
Same issue here. Fixed by upgrading react-redux to version 5.1.0.
Your solution should work (I didn't try copy-pasted like that), but you also have to update react-redux to the latest version.
By the way, I think the proper implementation of React.memo within many HOC would be to be the closest to the component itself : the goal of React.memo is to check if all the new props received by the component are the same as the last props. If any HOC transforms or adds any props to the component - which connect does by mapping the Redux store to the props, React.memo should be aware of it in order to decide wether or not to update the component.
So I would go for something like that :
//import what you need to import
const Component = props => <div>{/* do what you need to do here */}</div>
export default compose(
connect(mapStateToProps, dispatchToProps),
/* any other HOC*/
React.memo
)(Component);
Codesandbox demo
As the error message says, you need to pass a component to the returned function from connect.( which means the second pair of () in connect()() )
As React.Memo returns a component, pass it into the second function of connect.Here's how you can do this.
export const MemoizedDemoComponent = connect(mapStateToProps)(React.memo(DemoComponent);
Demo component:
import React from "react";
import { connect } from "react-redux";
const DemoComponent = props => (
<div>
<p>My demo component fueled by: {props.fuel}!</p>
<p>Redux: {props.state}</p>
</div>
);
const mapStateToProps = state => ({
state: "your redux state..."
});
// create a version that only renders on prop changes
export const MemoizedDemoComponent = connect(mapStateToProps)(
React.memo(DemoComponent)
);
For a working example check also codesandbox.
For someone who want to know why react-redux throw this error.
For me, I used version 5.0.7, react-redux/src/components/connectAdvanced.js line: 92
invariant(
typeof WrappedComponent == 'function',
`You must pass a component to the function returned by ` +
`${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
);
After upgrading this code is changed to :
invariant(
isValidElementType(WrappedComponent),
`You must pass a component to the function returned by ` +
`${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
);
How to check the WrappedComponent is changed to isValidElementType(WrappedComponent) which is exposed by react-is
So, yarn update react-redux to the version that mentioned by #Maxime Chéramy at least
I'm studying react and I have an example like this
//index.js
const store = createStore(reducer)
render(
<Provider store={store}>
<AddTodo />
</Provider>,
document.getElementById('root')
)
//Apptodo.js
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
let AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}}>
.......
Why didn't it get this.pros.store but simply call the dispatch() function ?
EDIT: How does it extract the dispatch from this.pros. Isn't the object this.pros.store ? and in this case why don't we just extract store ?
Thank you.
react-redux is the library that is passing these methods to your component as props.
dispatch() is the method used to dispatch actions and trigger state changes to the store. react-redux is simply trying to give you convenient access to it.
Note, however, that dispatch is not available on props if you do pass in actions to your connect function. In other words, in the code below, since I'm passing someAction to connect, dispatch() is no longer available on props.
The benefit to this approach, however, is that you now have the "connected" action available on your props that will automatically be dispatched for you when you invoke it.
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { someAction } from '../myActions';
const MyComponent = (props) => {
// someAction is automatically dispatched for you
// there is no need to call dispatch(props.someAction());
props.someAction();
};
export default connect(null, { someAction })(MyComponent);
Or if we were to use object destructuring as shown in the example you give...
const MyComponent = ({ someAction }) => {
someAction();
};
It's important to note, however, that you must invoke the connected action available on props. If you tried to invoke someAction(), you'd be invoking the raw, imported action — not the connected action available on props. The example given below will NOT update the store.
const MyComponent = (props) => {
// we never destructured someAction off of props
// and we're not invoking props.someAction
// that means we're invoking the raw action that was originally imported
// this raw action is not connected, and won't be automatically dispatched
someAction();
};
This is a common bug that people run into all the time while using react-redux. Following eslint's no-shadow rule can help you avoid this pitfall.
Your addTodo component has access to the store's state and methods(e.g, dispatch, getState, etc). So, when you hooked up your React view with the Redux store via the connect method, you had access to store's state and methods.
({ dispatch }) is simply using JS destructuring assignment to extract dispatch from this.props object.