API
import axios from "axios"
const url = "http://localhost:5000/posts"
export const fetchPosts = () => async () => await axios.get(url)
export const createPost = async (post) => await axios.post(url, post)
ACTION
export const fetchPosts = async (dispatch) => {
try {
const {data} = await api.fetchPosts()
dispatch({
type: types.FETCH_POSTS,
payload: data
})
} catch (err) {
console.error(err)
}
}
STORE
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './index'
import thunk from 'redux-thunk'
export default function configureStore() {
return createStore(rootReducer, applyMiddleware(thunk))
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux'
import configureStore from './Redux/store/configureStore'
const store = configureStore();
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
I want to make get request to my posts by axios. There is no problem in post request but I can't get one.
useEffect(() => {
dispatch(fetchPosts())
}, [dispatch])
When I use this method it throws this error :
Error: Actions must be plain objects. Instead, the actual type was: 'Promise'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions.
There is no any syntax or import/export mistake.
Why even if I inserted redux thunk to store it throws me this middleware error ?
You need to update fetchPosts return a function
export const fetchPosts = () => async (dispatch) => {
try {
const {data} = await api.fetchPosts()
dispatch({
type: types.FETCH_POSTS,
payload: data
})
} catch (err) {
console.error(err)
}
}
export const fetchPosts = () => async () => await axios.get(url)
the function above returns a promise
try
export const fetchPosts = async () => {
let res = await axios.get(url)
return res;
}
There is a slight problem in your action. Action creators are functions that return actions. You want to return an action (In this case a function) from your action creator.
So your action shoule be:
export const fetchPosts = () => async (dispatch) => {
// Added this part ^^^^^^
try {
const {data} = await api.fetchPosts()
dispatch({
type: types.FETCH_POSTS,
payload: data
})
} catch (err) {
console.error(err)
}
}
Alternatively, you can make this change to your code:
useEffect(() => {
dispatch(fetchPosts)
// Removed the () ^
}, [dispatch])
Related
I just started with React-Redux and I decided to put such action as axios request in a separate file:
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
import axios from "axios";
const GetRequest = () => {
const dispatch = useDispatch();
async function fetchData() {
try {
const commits = await axios.get(`url`
);
const commitsData = await commits.data;
dispatch({ type: "GET_REQUEST", payload: { commitsData } });
} catch (err) {
await console.log(err);
}
}
useEffect(() => {
fetchData();
}, []);
return <div></div>;
};
export default GetRequest;
But how i can dispatch this if my store and react app can't see this file .How to correctly declare the store about the existence of this file.(Because the option with rendering this file in App.js seems terribly wrong to me)
The best way is to use thunk for this logic and dispatch thunk.
import { createAsyncThunk } from '#reduxjs/toolkit';
import axios from 'axios';
// Define thunk somewhere
export const getRequest = createAsyncThunk('getRequest', async (payload: string, thunkAPI) => {
try {
const commits = await axios.get(payload);
const commitsData = await commits.data;
thunkAPI.dispatch({ type: 'GET_REQUEST', payload: { commitsData } });
} catch (err) {
console.log(err);
}
});
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
// Use thunk in your code
export function SomeComponent() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getRequest('https://example.com'));
}, []);
return <div />;
}
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 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 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});
});
};}
I'm getting this error every time I try to dispatch my action:
Error: Actions must be plain objects. Use custom middleware for async
actions.
I've installed redux-thunk and without async actions, it's working.
Store config:
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import { composeWithDevTools } from 'redux-devtools-extension';
import reducers from '../reducers/index';
const logger = createLogger();
export default createStore(reducers, composeWithDevTools(applyMiddleware(thunk)));
UI:
...
import { connect } from 'react-redux';
import { getCities } from '../../actions/cities';
...
componentDidMount = async () => {
try {
const cities = await this.props.getCities();
console.log(cities);
} catch (error) {
console.log(`errorhome: ${error}`);
}
SplashScreen.hide();
}
...
const mapDispatchToProps = dispatch => ({
changeNetworkStatus: () => dispatch(changeNetworkStatus),
getCities: () => dispatch(getCities),
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Action:
import database from '../config/utils';
export const GET_CITIES_START = 'GET_CITIES_START';
export const GET_CITIES_FINISHED = 'GET_CITIES_FINISHED';
export const GET_CITIES_ERROR = 'GET_CITIES_ERROR';
const getCititesStart = () => ({ type: GET_CITIES_START });
const getCititesFinished = cities => ({ type: GET_CITIES_FINISHED, cities });
const getCititesError = error => ({ type: GET_CITIES_ERROR, error });
export const getCitites = () => async (dispatch) => {
dispatch(getCititesStart());
try {
const cities = [];
const snap = await database.ref('cities').once('value');
console.log('snap: ', snap);
snap.forEach((element) => {
const city = {
city: element.key,
coordinate: element.val().coordinate,
};
cities.push(city);
});
dispatch(getCititesFinished(cities));
} catch (error) {
dispatch(getCititesError(error));
}
};
EDIT: If I add logger to middlewares too, the error message is this:
TypeError: Cannot read property 'type' of undefined
Thanks for your help!
Actions are functions that return a object with action's data, that data is a object with a type property.
You're dispatching action like this:
dispatch(getCities)
You should dispatch action like this:
dispatch(getCities())