I am just learning react-redux and trying to fire a thunk, this is the thunk:
const getRepos = dispatch => {
try {
const url = `https://api.github.com/users/reduxjs/repos?sort=updated`;
fetch(url)
.then(response => response.json())
.then(json => {
console.log("thunk: getrepos data=", json);
});
} catch (error) {
console.error(error);
}
};
I hooked up my component to the store:
const bla = dispatch =>
bindActionCreators(
{
geklikt,
getRepos
},
dispatch
);
const Container = connect(
null,
bla
)(Dumb);
When I trigger the getRepos thunk I get:
Actions must be plain objects. Use custom middleware for async
actions.
What could be the issue? I included the middleware? link to code
sandbox
Please refactor your application structure, it's all in one file and extremely hard to read.
Things to consider:
Use a switch statement in your reducers
Separate components from containers: https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
Make sure to set initial reducer state: state={}, state=[] ...etc.
Simplify the action to either use .then().catch() or use async/await within a try/catch block.
In the meantime, here's a working version: https://codesandbox.io/s/oxwm5m1po5
actions/index.js
import { GEKLIKT } from "../types";
export const getRepos = () => dispatch =>
fetch(`https://api.github.com/users/reduxjs/repos?sort=updated`)
.then(res => res.json())
.then(data => dispatch({ type: GEKLIKT, payload: data }))
.catch(err => console.error(err.toString()));
/*
export const getRepos = () => async dispatch => {
try {
const res = await fetch(`https://api.github.com/users/reduxjs/repos?sort=updated`)
const data = await res.json();
dispatch({ type: GEKLIKT, payload: data }))
} catch (err) { console.error(err.toString())}
}
*/
components/App.js
import React from "react";
import Dumb from "../containers/Dumb";
export default () => (
<div className="App">
<Dumb />
</div>
);
containers/Dumb.js
import React from "react";
import { connect } from "react-redux";
import { getRepos } from "../actions";
let Dumb = ({ data, getRepos }) => (
<div>
hi there from Dumb
<button onClick={getRepos}>hier</button>
<pre>
<code>{JSON.stringify(data, null, 4)}</code>
</pre>
</div>
);
export default connect(
state => ({ data: state.data }),
{ getRepos }
)(Dumb);
reducers/index.js
import { combineReducers } from "redux";
import { GEKLIKT } from "../types";
const klikReducer = (state = {}, { payload, type }) => {
switch (type) {
case GEKLIKT:
return { ...state, data: payload };
default:
return state;
}
};
export default combineReducers({
data: klikReducer
});
root/index.js
import React from "react";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import rootReducer from "../reducers";
import App from "../components/App";
const store = createStore(rootReducer, applyMiddleware(thunk));
export default () => (
<Provider store={store}>
<App />
</Provider>
);
types/index.js
export const GEKLIKT = "GEKILKT";
index.js
import React from "react";
import { render } from "react-dom";
import App from "./root";
import "./index.css";
render(<App />, document.getElementById("root"));
You returned the promise in action. A promise is not a plain object and so the returned action would not be a plain object and hence the error.
Since you're using the thunk middleware your actions can be functions and here's how you'd do it.
const GET_REPOS_REQUEST = "GET_REPOS_REQUEST";
const GET_REPOS_SUCCESS = "GET_REPOS_SUCCESS";
const GET_REPOS_ERROR = "GET_REPOS_ERROR";
export function getRepos() {
return function action(dispatch) {
dispatch({type: GET_REPOS})
const url = `https://api.github.com/users/reduxjs/repos?sort=updated`;
const request = fetch(url);
return request.then(response => response.json())
.then(json => {
console.log("thunk: getrepos data=", json);
dispatch({type: GET_REPOS_SUCCESS, json});
})
.then(err => {
dispatch({type: GET_REPOS_ERROR, err});
console.log(“error”, err);
});
};
}
Arrow function way:
export getRepos = () =>{
return action = dispatch => {
dispatch({type: GET_REPOS})
const url = `https://api.github.com/users/reduxjs/repos?sort=updated`;
const request = fetch(url);
return request.then(response => response.json())
.then(json => {
console.log("thunk: getrepos data=", json);
dispatch({type: GET_REPOS_SUCCESS, json});
})
.then(err => {
console.log(“error”, err);
dispatch({type: GET_REPOS_ERROR, err});
});
};}
Related
I am trying to make use of thunk to make async calls to api, but I am still getting the error :
Unhandled Runtime Error: Actions must be plain objects. Use custom middleware for async actions.
This is my custom _app component:
// to connect redux with react
import { Provider } from 'react-redux';
import { createWrapper } from 'next-redux-wrapper';
import { createStore, applyMiddleware } from 'redux';
import reducers from '../redux/reducers';
import thunk from 'redux-thunk';
const store = createStore(reducers, applyMiddleware(thunk));
const AppComponent = ({ Component, pageProps }) => {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
AppComponent.getInitialProps = async (appContext) => {
let pageProps = {};
if (appContext.Component.getInitialProps) {
pageProps = await appContext.Component.getInitialProps(appContext.ctx);
};
return { ...pageProps }
}
// returns a new instance of store everytime its called
const makeStore = () => store;
const wrapper = createWrapper(makeStore);
export default wrapper.withRedux(AppComponent);
And this is the landing page where I am dispatching the action creator:
import { connect } from 'react-redux';
import { fetchPosts } from '../redux/actions';
import { bindActionCreators } from 'redux';
import { useEffect } from 'react';
import Link from 'next/link';
const LandingPage = (props) => {
useEffect(() => {
props.fetchPosts();
}, [props]);
return <div>
<Link href="/">
<a>Home</a>
</Link>
</div>
}
LandingPage.getInitialProps = async ({ store }) => {
store.dispatch(await fetchPosts());
}
const mapDispatchToProps = (dispatch) => {
return {
// so that this can be called directly from client side
fetchPosts: bindActionCreators(fetchPosts, dispatch)
}
}
export default connect(null, mapDispatchToProps)(LandingPage);
Action:
import api from '../../api';
// returning a function and dispatching manually to make use of async await to fetch data
export const fetchPosts = async () => async (dispatch) => {
const response = await api.get('/posts');
dispatch({
type: 'FETCH_POSTS',
payload: response
});
};
Sadly the GitHub Next + Redux example NEXT+REDUX is really complicated for me to understand as I am trying redux for the first time with NextJS.
And every blog post has it's own way of doing it and nothing seems to be working.
I do not want it to make it any more complicated. I would really appreciate if anyone could help me why I am getting this error?
the problem is not with next.js when you calling this :
LandingPage.getInitialProps = async ({ store }) => {
store.dispatch(await fetchPosts());
}
fetchPosts here is a Promise and dispatch dispatch action must be a plain object so to solve this remove async word from it like this :
export const fetchPosts = () => async (dispatch) => {
const response = await api.get('/posts');
dispatch({
type: 'FETCH_POSTS',
payload: response
});
};
butt if you want to wait for api response instead you need call it in the component like this :
const App= ()=>{
const dispatch = useDispatch()
useEffect(() => {
const fetch = async()=>{
try{
const response = await api.get('/posts');
dispatch({
type: 'FETCH_POSTS',
payload: response
});
}
catch(error){
throw error
}
}
fetch()
}, []);
return ....
}
I am getting a dispatch is not defined error from 'shopfront' code. I believe it is because i'm not passing the properties down to the next level but I'm not sure if that is correct or not. I want to be able to pass the dispatch function through to the product.actions code correctly.
I have tried to narrow down the problem as much as possible by removing unnecessary code. I have a user reducer that is working correctly but I don't know why this product reducer isn't
// products.reducer
const initialState = {
products: null,
error: null
};
const ProductReducer = (state = initialState, action) => {
let newState = null;
switch(action.type){
case "GET_ALL_PRODUCTS": newState = {
...state,
products: action.products
};
return newState;
case "GET_ALL_PRODUCTS_FAIL": newState = {
...state,
error: action.error
};
return newState;
default: return state;
}
};
export default ProductReducer;
// index
import React from "react";
import ReactDOM from "react-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import { register } from "./serviceWorker";
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import UserReducer from "./store/reducers/users.reducers";
import ProductReducer from "./store/reducers/products.reducer";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const appReducer = combineReducers({
usersRed: UserReducer,
productsRed: ProductReducer
});
const logger = (store) => {
return next => {
return action => {
console.log("Middleware dispatching ");
console.log(action);
const result = next(action);
console.log("Middleware next state ");
console.log(store.getState());
return result;
};
};
};
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const appStore = createStore(appReducer, composeEnhancers(applyMiddleware(logger, thunk)));
const app = (
<BrowserRouter>
<Provider store={appStore}>
<App />
</Provider>
</BrowserRouter>
);
ReactDOM.render(app, document.getElementById("root"));
register();
// shopfront
import React, { Component } from "react";
import { Container, Row, Col, InputGroup, InputGroupAddon, Button } from "reactstrap";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { Alert } from "reactstrap";
import * as actionMethods from "../../store/actions/index.actions";
import Product from "../../components/Product/Product";
class Shopfront extends Component {
state = {
onAlert: false,
internalError: null
};
componentDidMount() {
this.props.loadAllProducts(5);
console.log("component_did_mount_run")
}
render() {
let ProductsList = <h1>No Products Yet!</h1>;
if (this.props.products !== null) {
ProductsList = this.props.products.map(Product => {
return <Product
key={Product.id}
title={Product.name}
excerpt={Product.description}
medialink={Product.permalink}
ProductId={Product.id}
/>;
});
}
return (
<Container>
{ProductsList}
</Container>
);
}
};
const mapStateToProps = (state) => {
return {
products: state.productsRed.products,
error: state.productsRed.error
};
};
const mapDispatchToProps = (dispatch) => {
return {
loadAllProducts: (perpage) => { dispatch(actionMethods.loadAllProducts(perpage)) }
};
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Shopfront));
// index.actions
export {
loadAllProducts
} from "./product.actions";
// product.actions
import wcapi from "../../axios-wp";
export const loadAllProducts = (perpage) => {
wcapi.get("products", {
per_page: perpage,
})
.then((response) => {
// Successful request
let productsRes = response.data;
dispatch({ type: "GET_ALL_PRODUCTS", products: productsRes });
})
.catch((err) => {
// Invalid request, for 4xx and 5xx statuses
dispatch({ type: "GET_ALL_PRODUCTS_FAIL", error: err });
})
}
Thank you for your help!
it is because you are not returning dispatch from your loadAllProducts action
/ product.actions
import wcapi from "../../axios-wp";
export const loadAllProducts = (perpage) => (dispatch) => { //make this change
wcapi.get("products", {
per_page: perpage,
})
.then((response) => {
// Successful request
let productsRes = response.data;
dispatch({ type: "GET_ALL_PRODUCTS", products: productsRes });
})
.catch((err) => {
// Invalid request, for 4xx and 5xx statuses
dispatch({ type: "GET_ALL_PRODUCTS_FAIL", error: err });
})
}
I have ever faced such this issue before. Then I used return dispatch => {} like this:
export const loadAllProducts = perpage => {
return dispatch => {
wcapi.get("products", {
per_page: perpage,
})
.then((response) => {
// Successful request
let productsRes = response.data;
dispatch({ type: "GET_ALL_PRODUCTS", products: productsRes });
})
.catch((err) => {
// Invalid request, for 4xx and 5xx statuses
dispatch({ type: "GET_ALL_PRODUCTS_FAIL", error: err });
})
}
}
I am pretty sure i am returning an object and have used asyn and await on the promise within my action file. but this still keeps returing the error redux.js:205 Uncaught Error: Actions must be plain objects. Use custom middleware for async actions
https://codesandbox.io/s/frosty-nash-wdcjf?fontsize=14
my action file is returning an object
import axios from "axios";
export const LOAD_URL_STATUS = "LOAD_URL_STATUS";
export async function loadUrlStatus(url) {
const request = await axios
.get(url)
.then(response => {
console.log(response.status);
return response.status;
})
.catch(error => {
console.log("Looks like there was a problem: \n", error);
});
console.log(request);
console.log(LOAD_URL_STATUS);
return {
type: LOAD_URL_STATUS,
payload: request
};
}
it fails when calling this action in componenDidMount this.props.loadUrlStatus(url);
component
import React from 'react';
import TrafficLight from '../TrafficLight';
import {connect} from 'react-redux';
import {loadUrlStatus} from "../../actions";
//import {withPolling} from "../Polling";
//import Polling from "../Polling/polling";
import { bindActionCreators } from 'redux';
class TrafficLightContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
redOn: true,
yellowOn: false,
greenOn: false,
}
}
componentDidMount(){
console.log("componentDidMount")
const {pollingAction, duration, url} = this.props
//withPolling(this.props.loadUrlStatus(this.props.url),1)
/*
const {pollingAction, duration, url} = this.props
this.dataPolling = setInterval(
() => {
this.props.loadUrlStatus(url);
},
10000);
*/
this.props.loadUrlStatus(url);
};
componentWillUnmount() {
clearInterval(this.dataPolling);
}
render() {
console.log(this.props)
return (
<TrafficLight
Size={100}
onRedClick={() => this.setState({ redOn: !this.state.redOn })}
onGreenClick={() => this.setState({ greenOn: !this.state.greenOn })}
RedOn={this.state.redOn}
GreenOn={this.state.greenOn}
/>
)
}
}
const mapStateToProps = state => ({
...state
});
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
loadUrlStatus
},
dispatch
);
};
export default (
connect(mapStateToProps, mapDispatchToProps)(TrafficLightContainer));
index
import React from 'react';
import { render } from 'react-dom'
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import configureStore from './configureStore'
const store = configureStore();
const renderApp = () =>
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./App', renderApp)
}
renderApp();
serviceWorker.unregister();
The problem is that loadUrlStatus is async function, so it returns not object, but Promise, and object inside it promise.
To correct this, modify loadUrlStatus so it return another function. As you already applied thunk middleware during store creation, such function will be called inside redux. (You can see samples of async functions here)
export function loadUrlStatus(url) {
// Immediately return another function, which will accept dispatch as first argument. It will be called inside Redux by thunk middleware
return async function (dispatch) {
const request = await axios
.get(url)
.then(response => {
console.log(response.status);
return response.status;
})
.catch(error => {
console.log("Looks like there was a problem: \n", error);
});
console.log(request);
console.log(LOAD_URL_STATUS);
dispatch ({
type: LOAD_URL_STATUS,
payload: request
});
}
}
If you're using await in an action creator, you'll want to return a function from the action creator. Otherwise, return on object. A library like redux-thunk will help you do just that.
Your action creator would then look like this:
import axios from "axios";
export const LOAD_URL_STATUS = "LOAD_URL_STATUS";
export const loadUrlStatus(url) => async dispatch => {
try {
const response = await axios(url)
dispatch({
type: LOAD_URL_STATUS,
payload: response.status
})
} catch (error) {
// dispatch error
}
}
I am making a React-redux component to embed in Laravel blade file. Where in the react side,
I am using redux with the thunk, When I try to get data without thunk from the Laravel route, it getting properly.
But When I use an axios request in the action creator to get data asynchronously. It gives the:
'Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.'
This is the entry component of the react side.
Entry.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reducer from '../store/reducers/reducer';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
// console.log(getState());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('like_post'));
This is the App.js main component
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from 'prop-types';
import axios from 'axios';
import {getCurrentPostLikes} from '../store/actions/actions';
class App extends Component {
constructor(props) {
super(props)
var domain = window.location.hostname;
var url = window.location.pathname;
var urlsplit = url.split("/");
var post_slug = urlsplit[urlsplit.length - 1];
this.state={
post_slug: post_slug,
post_likes:''
}
}
kFormatter(num) {
return num > 999 ? (num / 1000).toFixed(1) + 'k' : num
}
componentDidMount() {
this.props.getCurrentPostLikes();
// axios.get(`/api/get_post_likes/${this.state.post_slug}`)
// .then(response => {
// console.log(response.data.likes);
// this.setState({
// post_likes: response.data.likes
// })
// })
}
render() {
return (
<div className="App">
<img src="/images/love.svg" alt="post like" width="50px" height="50px"/>
<p>{this.kFormatter(this.state.post_likes)}</p>
<p><span>{this.props.likes}</span></p>
</div>
);
}
}
export default connect(null, {getCurrentPostLikes})(App);
// export default connect( mapStateToProps, mapDispachToProps )(App);
This is the actions.js file /store/actions/actions.js
// types.js is also defined properly as
// export const GET_POST_LIKES = 'GET_POST_LIKES';
import axios from 'axios';
import {GET_POST_LIKES} from './types';
// Get current post likes
export const getCurrentPostLikes = () => dispatch => {
return dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}
}
Tried this action creator also, but still same error
export const getCurrentPostLikes = () => {
return dispatch => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}
}
This is the reducers.js file under /store/reducers/reducer.js
import { GET_POST_LIKES } from '../actions/types';
const initialState = {
likes: null
};
const reducer = (state=initialState, action) => {
const newState = {...state};
switch(action.type){
case 'GET_POST_LIKES':
return {
...state,
post: action.payload
}
case 'LIKE_UP':
newState.likes += action.value
break;
}
return newState;
};
export default reducer;
Now, This should return a field value of posts table, with post id = 2.
Your problem is at Entry.js, this line:
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
You are setting the thunk middleware as the second parameter. The second parameter is for the initial state.
The thunk middleware should be part of the composed enhancers in the third parameter of the createStore function.
The parameters should be applied as such:
createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
Full example:
import { createStore, applyMiddleware, compose } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import thunk from 'redux-thunk'
import createHistory from 'history/createHashHistory'
import rootReducer from './core/reducers'
export const history = createHistory()
const initialState = {}
const enhancers = []
const middleware = [thunk, routerMiddleware(history)]
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION__
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
}
const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
)
export default createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
This is the 'signature' of an action creator curried function using redux-thunk:
export const getCurrentPostLikes = () => async dispatch => {
const response = await axios.get(`/api/get_post_likes/2`);
dispatch({ type: GET_POST_LIKES, payload: response.data.likes });
};
This is how you want your action creator to look:
export const getCurrentPosts = () => {
return async function(dispatch, getState) {
const response = await axios.get("/api/get_post_likes");
dispatch({ type: "GET_POST_LIKES", payload: response });
};
};
Leave the Promises out of it and go with ES7 async/await syntax and also leave out the setTimeout, unless you can provide a really good justification for wanting to time out an asynchronous request that is already taking a non-zero amount of time to complete, maybe I don't understand, but you do want that data don't you? It's necessary for your reducers to do their jobs and eventually update state in your application.
What I have above is not perfect in that it's boilerplate, like you don't really need getState in there, yes it's part of middleware, but if you are not going to use something, no need to define it. Anyway I am trying to get you to a point where your action creator works again and you are sparing your eyes and your mind.
It seems your action has an extra dispatch =>
export const getCurrentPostLikes = () => dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}
I'm using react-redux and react-router. I need to redirect after an action is dispatched.
For example: I have registration a few steps. And after action:
function registerStep1Success(object) {
return {
type: REGISTER_STEP1_SUCCESS,
status: object.status
};
}
I want to redirect to page with registrationStep2. How can I do this?
p.s. In history browser '/registrationStep2' has not been visited. This page appears only after successful result registrationStep1 page.
With React Router 2+, wherever you dispatch the action, you can call browserHistory.push() (or hashHistory.push() if that’s what you use):
import { browserHistory } from 'react-router'
// ...
this.props.dispatch(registerStep1Success())
browserHistory.push('/registrationStep2')
You can do this from async action creators too if that is what you use.
Have you checked out react-router-redux? This library makes it possible to sync react-router with redux.
Here is an example from the documentation of how you can implement the redirection with a push action from react-router-redux.
import { routerMiddleware, push } from 'react-router-redux'
// Apply the middleware to the store
const middleware = routerMiddleware(browserHistory)
const store = createStore(
reducers,
applyMiddleware(middleware)
)
// Dispatch from anywhere like normal.
store.dispatch(push('/foo'))
Simplest solution for router version 4+:
We use "react-router-dom": "4.3.1"
It doesn't work with version 5+
export your browser history from the place where it was initialised
and use browserHistory.push('/pathToRedirect'):
Package history must be installed(example: "history": "4.7.2"):
npm install --save history
In my project I initialise browser history in index.js:
import { createBrowserHistory } from 'history';
export const browserHistory = createBrowserHistory();
Redirect in the action:
export const actionName = () => (dispatch) => {
axios
.post('URL', {body})
.then(response => {
// Process success code
dispatch(
{
type: ACTION_TYPE_NAME,
payload: payload
}
);
}
})
.then(() => {
browserHistory.push('/pathToRedirect')
})
.catch(err => {
// Process error code
}
);
});
};
To build on Eni Arinde previous answer's (I don't have the reputation to comment), here is how to use the store.dispatch method after an async action :
export function myAction(data) {
return (dispatch) => {
dispatch({
type: ACTION_TYPE,
data,
}).then((response) => {
dispatch(push('/my_url'));
});
};
}
The trick is to do it in the action files and not in the reducers, since reducers should not have side effects.
We can use "connected-react-router".
import axios from "axios";
import { push } from "connected-react-router";
export myFunction = () => {
return async (dispatch) => {
try {
dispatch({ type: "GET_DATA_REQUEST" });
const { data } = await axios.get("URL");
dispatch({
type: "GET_DATA_SUCCESS",
payload: data
});
} catch (error) {
dispatch({
type: "GET_DATA_FAIL",
payload: error,
});
dispatch(push("/notfound"));
}
};
};
Attention-- Please go to https://github.com/supasate/connected-react-router read the docs and set up the connected-react-router first, and then use "push" from connected-react-router.
You can use {withRouter} from 'react-router-dom'
Example below demonstrates a dispatch to push
export const registerUser = (userData, history) => {
return dispatch => {
axios
.post('/api/users/register', userData)
.then(response => history.push('/login'))
.catch(err => dispatch(getErrors(err.response.data)));
}
}
The history arguments is assigned to in the component as the a second parameter to the action creator (in this case 'registerUser')
An updated answer using hooks; for router v5 users.
Working on react-router-dom:5.1.2.
No installation of external package is required.
import { useHistory } from "react-router-dom";
function HomeButton() {
let history = useHistory();
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
You can use the history as you're previously used to.
More more details and APIs - read the manual
Here is the working copy of routing app
import {history, config} from '../../utils'
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import Login from './components/Login/Login';
import Home from './components/Home/Home';
import reducers from './reducers'
import thunk from 'redux-thunk'
import {Router, Route} from 'react-router-dom'
import { history } from './utils';
const store = createStore(reducers, applyMiddleware(thunk))
export default class App extends Component {
constructor(props) {
super(props);
history.listen((location, action) => {
// clear alert on location change
//dispatch(alertActions.clear());
});
}
render() {
return (
<Provider store={store}>
<Router history={history}>
<div>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
</div>
</Router>
</Provider>
);
}
}
export const config = {
apiUrl: 'http://localhost:61439/api'
};
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();
//index.js
export * from './config';
export * from './history';
export * from './Base64';
export * from './authHeader';
import { SHOW_LOADER, AUTH_LOGIN, AUTH_FAIL, ERROR, AuthConstants } from './action_types'
import Base64 from "../utils/Base64";
import axios from 'axios';
import {history, config, authHeader} from '../utils'
import axiosWithSecurityTokens from '../utils/setAuthToken'
export function SingIn(username, password){
return async (dispatch) => {
if(username == "gmail"){
onSuccess({username:"Gmail"}, dispatch);
}else{
dispatch({type:SHOW_LOADER, payload:true})
let auth = {
headers: {
Authorization: 'Bearer ' + Base64.btoa(username + ":" + password)
}
}
const result = await axios.post(config.apiUrl + "/Auth/Authenticate", {}, auth);
localStorage.setItem('user', result.data)
onSuccess(result.data, dispatch);
}
}
}
export function GetUsers(){
return async (dispatch) => {
var access_token = localStorage.getItem('userToken');
axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`
var auth = {
headers: authHeader()
}
debugger
const result = await axios.get(config.apiUrl + "/Values", auth);
onSuccess(result, dispatch);
dispatch({type:AuthConstants.GETALL_REQUEST, payload:result.data})
}
}
const onSuccess = (data, dispatch) => {
const {username} = data;
//console.log(response);
if(username){
dispatch({type:AuthConstants.LOGIN_SUCCESS, payload: {Username:username }});
history.push('/');
// Actions.DashboardPage();
}else{
dispatch({ type: AUTH_FAIL, payload: "Kullanici bilgileri bulunamadi" });
}
dispatch({ type: SHOW_LOADER, payload: false });
}
const onError = (err, dispatch) => {
dispatch({ type: ERROR, payload: err.response.data });
dispatch({ type: SHOW_LOADER, payload: false });
}
export const SingInWithGmail = () => {
return { type :AuthConstants.LOGIN_SUCCESS}
}
export const SignOutGmail = () => {
return { type :AuthConstants.LOGOUT}
}
signup = e => {
e.preventDefault();
const { username, fullname, email, password } = e.target.elements,
{ dispatch, history } = this.props,
payload = {
username: username.value,
//...<payload> details here
};
dispatch(userSignup(payload, history));
// then in the actions use history.push('/<route>') after actions or promises resolved.
};
render() {
return (
<SignupForm onSubmit={this.signup} />
//... more <jsx/>
)
}
while using react-router-dom version +5 you can't use useHistory hook in redux(redux toolkit).
So if you want to redirect after an action is dispatched you can get your history "via useHistory() hook" in your current page(component) then pass the history along with your payload as an argument to redux.
Hence you can easily manage your history in redux after an action is dispatched like this :
history.push ("somewhere)