I am really new to Redux and its concepts, especially middleware so I do apologise for any stupid errors.
In this project of mine, I need to use redux-thunk. I've looked at a few guides and explanations on how to apply them. I then kept receiving an error "Uncaught TypeError: Cannot read property 'dispatch' of undefined". I opened developer tools and got shown this error:
I have no idea if I am doing anything right. Below are the codes for my action creators and store.
actions/index.js
import axios from 'axios';
export function fetchLessons() {
console.log('called!');
return function(dispatch) {
axios.get(`${ROOT_URL}/lessons`)
.then((response) => {
dispatch(fetchLessonsSuccess(response))
})
.catch((err) => {
dispatch(fetchLessonsError(err))
})
}
}
function fetchLessonsError(){
return "An error has occured";
}
function fetchLessonsSuccess(response) {
return {
type: FETCH_LESSONS,
payload: request
};
}
index.js(store)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import { Router, browserHistory } from 'react-router';
import rootReducer from './reducers/index';
import routes from './routes';
import promise from 'redux-promise';
import thunk from 'redux-thunk';
const middleware = applyMiddleware(promise(), thunk);
const store = createStore(rootReducer, compose(middleware));
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>
, document.querySelector('.container'));
I believe your call to applyMiddleware() is slightly off. You want to pass the imported promise middleware directly, not call it: applyMiddleware(promise, thunk).
That function is basically a factory. Redux will call it and pass in the store's dispatch function, which the middleware can then use to dispatch actions whenever it's ready.
i am not totally sure but something like this
export function fetchLessons() {
console.log('called!');
return function(dispatch) {
return dispatch({
type: 'FETCH_LESSONS',
payload: axios.get(`${ROOT_URL}/lessons`)
.then((response) => {
dispatch(fetchLessonsSuccess(response))
})
.catch((err) => {
dispatch(fetchLessonsError(err))
});
});
};
}
function fetchLessonsError(){
return "An error has occured";
}
function fetchLessonsSuccess(response) {
return {
type: 'FETCH_LESSONS_FULFILLED',
payload: response
};
}
Related
I'm trying to implement a simple test case for using Redux-thunks with Next JS but keep getting the error
Error: Circular structure in "getInitialProps" result of page "/".
https://err.sh/zeit/next.js/circular-structure
I have gotten this all to work once before, and am sure I'm making some obvious error.
I'd appreciate any help you could provide. I've been poking at this for an hour and I'm not seeing where I'm going wrong...
I've traced it down to the dispatch within my thunk, that is dispatch(getItemsSuccess(data)) in the following code in action-creators.js. That is, if I remove that dispatch, I don't get the error.
// action-creators.js
import {GET_ITEMS_SUCCESS} from "./action-types"
import axios from 'axios'
export const getItemsSuccess = (data) => ({ type: GET_ITEMS_SUCCESS, data });
export const getItems = () => async (dispatch,getState) => {
try {
const data = await axios.get(`https://api.themoviedb.org/3/genre/movie/list?api_key=12345xyz`)
return dispatch(getItemsSuccess(data))
} catch(e) {
console.log(`error in dispatch in action-creators: ${e}`)
}
}
My _app.js is
import React from 'react'
import {Provider} from 'react-redux'
import App, {Container} from 'next/app'
import withRedux from 'next-redux-wrapper'
import configureStore from '../redux/configure-store'
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return {pageProps}
}
render() {
const {Component, pageProps, store} = this.props
return (
<Container>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</Container>
)
}
}
export default withRedux(configureStore, { debug: true })(MyApp);
and my index.js is
import React, {Component} from 'react'
import {connect} from 'react-redux'
import {getItems} from "../redux/action-creators"
class Index extends Component {
static async getInitialProps({store}) {
try {
await store.dispatch(getItems())
} catch(e) {
console.log(`error in dispatch in index.js: ${e.message}`)
}
}
render() {
return <div>Sample App</div>
}
}
export default connect(state => state)(Index)
and finally I configure the store thus
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './root-reducer';
const bindMiddleware = middleware => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
function configureStore(initialState = {}) {
const store = createStore(
rootReducer,
initialState,
bindMiddleware([thunk]),
);
return store;
}
export default configureStore;
Again, any help much appreciated -- I have been going over this for some time and am not seeing the missing piece...
When you return data from axios, one has to access the data within the data, to wit, instead of
const data = await
axios.get(`https://api.themoviedb.org/3/genre/movie/list?api_key=12345xyz`)
return dispatch(getItemsSuccess(data))
I should have written
axios.get(`https://api.themoviedb.org/3/genre/movie/list?api_key=12345xyz`)
return dispatch(getItemsSuccess(data.data))
Why This Error Occurred
getInitialProps is serialised to JSON using JSON.stringify and sent to the client side for hydrating the page.
However, the result returned from getInitialProps can't be serialised when it has a circular structure.
Possible Ways to Fix It
Circular structures are not supported, so the way to fix this error is removing the circular structure from the object that is returned from getInitialProps. In your case you just need to extract appropriate data like #Cerulean explained.
When I run my app, I get following errors.
Error: Given action "LoadEntries", reducer "entries" returned
undefined. To ignore an action, you must explicitly return the
previous state. If you want this reducer to hold no value, you can
return null instead of undefined.
Following is the action file.
import axios from 'axios';
export const loadEntries = () => {
return dispatch => {
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(res => {
dispatch({type: 'LoadEntries', payload: res.data});
}).catch(error => {
console.error("Error: " + error);
})
}
}
Following is the reducer.
export default (state = [], action) => {
console.log("Action: " + JSON.stringify(action) + " State: " + state);
switch (action.type) {
case 'LoadEntries':
return action.payload;
default:
return state;
}
}
When I run the app I get the following log in console for reducer.
Action: {"type":"LoadEntries"} State:
Following is the Index.js file.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import {Provider} from 'react-redux'
import reducer from './store/reducer';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
const store = createStore(reducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root'));
Also attached a screenshot of console.
Thanks.
You are not returning the action on both the resolve or reject of promise inside the action dispatcher.
Your dispatcher should always return an action.
I would also recommend to handle the rejection in a separate action.
I am new to reactjs and trying to integrate redux with my existing project.
This is my index.js file in store
import 'rxjs'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { reducer as formReducer } from 'redux-form'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import { createEpicMiddleware, combineEpics } from 'redux-observable'
import user, { userEpic } from './user/duck'
import app from './app'
// Bundling Epics
const rootEpic = combineEpics(
userEpic
)
// Creating Bundled Epic
const epicMiddleware = createEpicMiddleware(rootEpic)
// Define Middleware
const middleware = [
thunk,
promise(),
epicMiddleware
]
// Define Reducers
const reducers = combineReducers({
app,
user,
form: formReducer
})
// Create Store
export default createStore(reducers,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), applyMiddleware(...middleware))
And here is duck.js
const createUserEpic = (action$) =>
action$
.ofType(SIGNUP_CONCIERGE)
.mergeMap((action) => {
return Rx.Observable.fromPromise(api.signUpConcierge(action.payload))
.flatMap((payload) => ([{
type: SIGNUP_CONCIERGE_SUCCESS,
payload
}]))
.catch((error) => Rx.Observable.of({
type: SIGNUP_CONCIERGE_ERROR,
payload: { error }
}))
})
export const userEpic = combineEpics(
createUserEpic
)
which throws me error TypeError: action$.ofType(...).mergeMap is not a function
I have been getting this error since I updated react, react-redux, redux-observable versions.
What I am doing wrong here? Please help!!!
Try this:
First, import these functions at the very top of your file
import { mergeMap } from 'rxjs/operators';
import { ofType } from 'redux-observable';
Then, fix your codes like this (notice that, ofType() and mergeMap() is seperated by a comma, not a dot) :
const createUserEpic = action$ =>
action$.pipe( //fixed
ofType(SIGNUP_CONCIERGE),
mergeMap(action => {
return Rx.Observable.fromPromise(api.signUpConcierge(action.payload))
.flatMap(payload => [
{
type: SIGNUP_CONCIERGE_SUCCESS,
payload
}
])
.catch(error =>
Rx.Observable.of({
type: SIGNUP_CONCIERGE_ERROR,
payload: { error }
})
);
})
);
export const userEpic = combineEpics(createUserEpic);
You forgot the pipe() method and also importing ofType and mergeMap methods from the appropriate packages.
After importing those methods, in order to use them, you first need to use the pipe() method like this:
action$.pipe();
After that, you'll be able to use the ofType() and mergeMap() methods:
action$.pipe(
ofType(),
mergeMap()
);
Notice that they are separated by comma, not dot.
According to this github issue, every rxjs operator should be included before you use it.
And people suggest that whether you import rxjs on your index.js file (not a store/index.js but your project entry file).
or you can do import rxjs/add/operator/mergeMap in your duck.js.
Either way works and it's up to you which way to choose.
So I've recently started learning Redux and now I'm trying to make my first app with it, but I've stumbled upon a problem which I cannot resolve on my own. Basically I want a user to click a button (there will be authentication) and fetch all his or hers data from Firebase and display it.
Here is my index.js:
// Dependencies
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import createHistory from 'history/createBrowserHistory';
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux';
import ReduxPromise from "redux-promise";
import ReduxThunk from 'redux-thunk';
// Reducers
import rootReducer from './reducers';
// ServiceWorker
import registerServiceWorker from './registerServiceWorker.js';
// Styles
import './styles/index.css';
// Components
import App from './containers/App.js';
const history = createHistory();
const middleware = routerMiddleware(history);
// Create store
const store = createStore(
combineReducers({
...rootReducer,
router: routerReducer
}),
applyMiddleware(ReduxThunk, middleware, ReduxPromise)
)
ReactDOM.render((
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
), document.getElementById('root'));
registerServiceWorker();
And my main container, App.js:
import React, { Component } from 'react';
import { Route, Switch, withRouter } from 'react-router-dom'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import firebase from 'firebase';
import firebaseConfig from '../firebaseConfig.js';
// Actions
import { fetchAllMonths } from "../actions/index";
// Static components
import Nav from '../components/Nav.js';
// Routes
import CurrentMonth from '../components/CurrentMonth.js';
import AddNewMonth from '../components/AddNewMonth.js';
import Archive from '../components/Archive.js';
import Settings from '../components/Settings.js';
class App extends Component {
constructor (props) {
super(props);
this.login = this.login.bind(this);
}
componentWillMount() {
firebase.initializeApp(firebaseConfig);
firebase.auth().signInAnonymously().catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
});
}
login() {
this.props.fetchAllMonths();
}
render() {
if (this.props.data === undefined) {
return (
<button onClick={this.login}>Login</button>
)
} else if (this.props.data !== undefined) {
return (
<main className="main-container">
<Nav user="user"/>
<Switch>
<Route exact path='/' component={CurrentMonth}/>
<Route path='/aktualny' component={CurrentMonth}/>
<Route path='/nowy' component={AddNewMonth}/>
<Route path='/archiwum' component={Archive}/>
<Route path='/ustawienia' component={Settings}/>
</Switch>
</main>
);
}
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchAllMonths }, dispatch);
}
function mapStateToProps({ data }) {
return { data };
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App))
Main action, fetchAllMonths:
// import firebase from 'firebase';
// Firebase Config
// import axios from 'axios';
export const FETCH_ALL_MONTHS = 'FETCH_ALL_MONTHS';
export function fetchAllMonths() {
/*const database = firebase.database();
const data = database.ref('/users/grhu').on('value', function(snapshot) {
return snapshot.val();
});
console.log(data) */
const data = fetch('https://my-finances-app-ef6dc.firebaseio.com/users/grhu.json')
.then(async (response) => response.json())
.then(data => {
console.log(data);
return data;
}
)
console.log(data);
return {
type: FETCH_ALL_MONTHS,
payload: data
};
};
Reducers index.js:
import { combineReducers } from "redux";
import data from "./reducer_load_from_db";
const rootReducer = combineReducers({
data: data
});
export default rootReducer;
And finally my reducer:
import { FETCH_ALL_MONTHS } from "../actions/index";
export default function(state = [], action) {
switch (action.type) {
case FETCH_ALL_MONTHS:
return [action.payload.data, ...state];
default:
return state;
}
return state;
}
So I'm sure that fetch works fine, because console.log(data) gives me a valid JSON file, but second console.log(data) with the passed const gives me a promise, which then I send as a payload to a Reducer. CreateStore also seems to work, because in the React dev console I can see a "data" prop in App container. I use redux-promise which should resolve the Promise in payload and return a JSON to the store, but data remains undefined. Also tried redux-promise-middleware, but again, no luck.
What am I missing? I've looked at that code for several hours and I cannot understand what is wrong with it.
I'll appreciate all the answers, but i really want to understand the issue, not just copy-paste a solution.
Thanks in advance!
Initial Answer
From what I'm reading in your fetchAllMonths() action creator, you're setting a property on the action it returns called payload equal to the data returned from your API call.
return {
type: FETCH_ALL_MONTHS,
payload: data
};
If you logged action.payload in your reducer like so...
switch (action.type) {
case FETCH_ALL_MONTHS:
console.log(action.payload)
return [action.payload.data, ...state];
default:
return state;
}
I believe you'd see the data returned from your API call.
So then in your reducer you would be expecting action.payload as a property of the FETCH_ALL_MONTHS action. And you'd want to use the spread operator ...action.payload.
Also, to make your logic a little easier to read, why not try using an async action to fetch data and then dispatch an action that takes in the data returned from the API call?
Hope that helps!
Updated Answer
As I thought about this and read your reply to my answer, I think you may need to use an async action to ensure your data was successfully fetched. I made a really simple CodePen example using async actions.
https://codepen.io/joehdodd/pen/rJRbZG?editors=0010
Let me know if that helps and if you get it working that way.
I have the following
CatsPage.js:
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
//import * as catActions from '../../actions/catActions';
import CatList from './CatList';
import {loadCats} from '../../actions/catActions';
class CatsPage extends React.Component {
componentDidMount () {
this.props.dispatch(loadCats())
}
render() {
return (
<div>
<h1>Cats</h1>
<div>
<CatList cats={this.props.cats} />
</div>
</div>
);
}
}
CatsPage.propTypes = {
cats: PropTypes.array.isRequired
};
function mapStateToProps(state, ownProps) {
return {
cats: state.cats
};
}
export default connect(mapStateToProps)(CatsPage);
catActions.js
import * as types from './actionTypes';
import catApi from '../api/CatsApi';
export function loadCats() {
return function(dispatch) {
return catApi.getAllCats().then(cats => {
dispatch(loadCatsSuccess(cats));
}).catch(error => {
throw(error);
});
};
}
export function loadCatsSuccess(cats) {
return {type: types.LOAD_CATS_SUCCESS, cats};
}
I'm getting the following error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. at dispatch (createStore.js:166)
I'm a newbie try to learn how to use React + Redux. What am I doing wrong that I need to fix to make the dispatch work and loadCats()?
Thank you!
There's a chance you didn't properly install/configure your store. It should look something like this:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);