So I'm using react + redux, and I'm continuing to get the following error: "Cannot read property 'then' of undefined". For some reason the promise isn't being returned. I'm also particularly new to using redux thunk.
Reducer
import { merge } from 'lodash';
import * as APIutil from '../util/articles_api_util';
import {
ArticleConstants
} from '../actions/article_actions';
const ArticlesReducer = (state = {}, action) => {
switch (action.type) {
case ArticleConstants.RECEIVE_ALL_ARTICLES:
debugger
return merge({}, action.articles);
default:
return state;
}
};
export default ArticlesReducer;
Store
import { createStore, applyMiddleware } from 'redux';
import RootReducer from '../reducers/root_reducer';
import thunk from 'redux-thunk';
import * as APIUtil from '../util/articles_api_util';
export const ArticleConstants = {
RECEIVE_ALL_ARTICLES: "RECEIVE_ALL_ARTICLES",
REQUEST_ALL_ARTICLES: "REQUEST_ALL_ARTICLES"
}
Actions
export function fetchArticles() {
return function(dispatch) {
return APIUtil.fetchArticles().then(articles => {
dispatch(receiveAllArticles(articles));
}).catch(error => {
throw(error);
});
};
}
export const requestAllArticles= () => ({
type: REQUEST_ALL_ARTICLES
});
export const receiveAllArticles = articles => ({
type: RECEIVE_ALL_ARTICLES,
articles
});
const configureStore = (preloadedState = {}) => (
createStore(
RootReducer,
preloadedState,
applyMiddleware(thunk)
)
);
export default configureStore;
APIUtil
export const fetchArticles = (success) => {
$.ajax({
method: 'GET',
url: `/api/articles`,
success,
error: ()=> (
console.log("Invalid Article")
)
});
};
Arrow functions only do implicit returns if you leave off the curly braces. As soon as you include curly braces, you have defined a function body, and need to explicitly return a value.
Your fetchArticles function is written as an arrow function with curly braces. However, you are not explicitly returning the result of the $.ajax() call. So, the return value of the function is undefined, and there's no promise returned that you can chain off of.
Related
On click of Button my Action is not getting dispatched. Below are all the files viz - action, reducer, root reducer, configSTore, Index and Component.
Please help me why my action is not getting dispatched on button click
Actions.js
import axios from 'axios';
const requestURL='JSONUrl';
let responseData = '';
export function fetchRequests() {
axios.get(requestURL)
.then((res) => {
responseData = res;
});
return responseData;
}
export const fetchDataAction = () => {
return {
type: 'FETCH_DATA',
data: fetchRequests()
};
}
export function fetchDataSuccessAction(err) {
return {
type: 'FETCH_DATA_SUCCESS',
err
};
}
export function fetchDataErrorAction(err) {
return {
type: 'FETCH_DATA_ERROR',
err
};
}
Reducer.js
export function fetchDataReducer(state = [], action) {
switch(action.type) {
case 'FETCH_DATA':
return action.data;
default: return state;
}
}
export function fetchDataSuccessReducer(state = [], action) {
switch(action.type) {
case 'FETCH_DATA_SUCCESS':
return action.err;
default: return state;
}
}
export function fetchDataErrorReducer(state = [], action) {
switch(action.type) {
case 'FETCH_DATA_ERROR':
return action.err;
default: return state;
}
}
RootReducer
import {combineReducers} from 'redux';
import { fetchDataAction, fetchDataSuccessAction, fetchDataErrorAction}
from '../actions/fetchData';
export default combineReducers({
fetchDataAction,
fetchDataSuccessAction,
fetchDataErrorAction
});
configStore.js
import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers/rootReducer";
import thunk from 'redux-thunk';
export default function configureStore() {
const enhance = applyMiddleware(thunk);
return createStore(
rootReducer,
enhance
);
}
INdex.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import reportWebVitals from './reportWebVitals';
import Header from './App';
import configureStore from './store/configureSTore';
import {CounterApp} from './counter';
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={configureStore()}>
<Header favcol="yellow"/>
<CounterApp />
</Provider>,
document.getElementById('root')
);
reportWebVitals();
My Component File
import React, { useState } from 'react';
import { fetchDataAction, fetchRequests } from './actions/fetchData';
import { connect } from 'react-redux';
export class CounterApp extends React.Component {
constructor(props) {
super(props);
}
btnClick = () => {
return this.props.fetchDataAction;
}
render() {
return (
<div className="App">
<h1>Hello</h1>
<div>
<h1>Fetch Data click below</h1>
<button onClick={() => this.props.fetchDataAction}>
Fetch Data
</button>
{this.props.datafetchedFromApi}
</div>
</div>
)
}
}
const mapStateToProps = state => ({
datafetchedFromApi: state.data
});
const mapDispatchToProps = (dispatch) => ({
fetchDataAction: () => dispatch(fetchDataAction())
});
export default connect(mapStateToProps, mapDispatchToProps)(CounterApp);
On click of Button my Action is not getting dispatched. Below are all the files viz - action, reducer, root reducer, configSTore, Index and Component.
Please help me why my action is not getting dispatched on button click
The answer got too long, so first part is if you wanna fully understand why things went south. If you just want your problem solved so you can finally go pee, the second part is for you :).
First Part (Basically what asynchronous programming in JavaScript is, so any questions to what are asynchronous tasks in JS can be referred to this answer.)
Okay a couple of problems detected here. As others have pointed out make sure all the paths for imports are correct. Now assuming they are all correct, here's what you need to do to solve your problem.
First let's take a look at the top of your component file:
import React, { useState } from 'react';
import { fetchDataAction, fetchRequests } from './actions/fetchData';
import { connect } from 'react-redux';
...
And then the block where you have called fetchDataAction:
...
<button onClick={() => this.props.fetchDataAction}>
Fetch Data
</button>
...
Here what you have done is this.props.fetchDataAction, I don't see you passing fetchDataAction as a prop to this component, so it's most probably undefined that's why you get an error TypeError: this.props.fetchDataAction is not a function because of course undefined is not a function.
This was the first mistake I noticed.
Now, moving on to the second one. I'm gonna start with this
Dispatching actions in redux is synchronous.
So you cannot do something like the following:
export default function SomeComponent(props) {
const fetchAction = () => {
let payload;
//wait for 5 seconds for the async task(the setTimeout below is an async task) to populate the payload with data.
setTimeout(() => payload = "This is my data", 5000);
//then return the action object
return {
type: 'Data',
payload,
};
}
const handleClick = () => {
dispatch(fetchAction());
}
return (<>
<Button onClick={handleClick} />
</>);
}
The above will not throw any errors, but will certainly not do what I want it to do. The above will dispatch the following action object:
{
type: 'Data',
payload: undefined
}
which ofcourse is not what I want the payload to be.
And that's exactly what you're doing. Take a look at your fetchDataAction and fetchRequests functions:
...
let responseData = '';
export function fetchRequests() {
axios.get(requestURL)
.then((res) => {
responseData = res;
});
return responseData;
}
export const fetchDataAction = () => {
return {
type: 'FETCH_DATA',
data: fetchRequests()
};
}
...
Now I'll compare with the example I've given above:
Here your responseData is analogous to my payload
Your fetchRequests function is analogous to my setTimeout
Looks familiar? I'm sure by now it does. Plain simple answer as to why it doesn't work is that you're performing an async task, in your case you're making a network request with axios.get(requestUrl)...
Network requests are async(now if you don't know what async things are, check out https://javascript.info/callbacks which gives you idea about what those are. Also check out a video on it by TheNetNinja https://www.youtube.com/watch?v=ZcQyJ-gxke0 ), in simple words, \network requests take some time to get finished(just like setTimeout).
So the axios.get request takes some time to get the response back from the server. Now the other tasks(below it) won't wait for that request to get completed, instead js will execute those tasks immediately without waiting for the response.
I know this answer is getting too long. But I want you to understand, because trust me I have made the same mistakes before :).
So in your fetchRequests function:
export function fetchRequests() {
axios.get(requestURL) --- (1)
.then((res) => {
responseData = res; --- (2)
});
return responseData; --- (3)
}
In line 1, you start an async task. Remember the function inside then block will execute only after sometime. So the responseData is still undefined. Instead of line (2), line (3) will execute first, cause as I told you earlier, js won't wait for the response from the server(the technical wording is 'the thread doesn't get blocked by network request'.) So basically you're returning undefined from this function.
Also see this video by JavaBrains. He uses an excellent analogy to understand async tasks in js, and you might also learn about what the event loop is and about single threaded-ness of javascript.
https://www.youtube.com/watch?v=EI7sN1dDwcY
Now the Second part (I really wanna go pee):
Replace(I've pointed out where I have made changes)
Your component file with this.
import React, { useState } from 'react';
import { fetchDataAction, fetchRequests } from './actions/fetchData';
import { connect } from 'react-redux';
export class CounterApp extends React.Component {
constructor(props) {
super(props);
}
btnClick = () => {
return this.props.fetchDataAction;
}
render() {
return (
<div className="App">
<h1>Hello</h1>
<div>
<h1>Fetch Data click below</h1>
<button onClick={() => fetchDataAction()}> //this is the only change here
Fetch Data
</button>
{this.props.datafetchedFromApi}
</div>
</div>
)
}
}
const mapStateToProps = state => ({
datafetchedFromApi: state.data
});
const mapDispatchToProps = (dispatch) => ({
fetchDataAction: () => dispatch(fetchDataAction())
});
export default connect(mapStateToProps, mapDispatchToProps)(CounterApp);
And then in your action.js file:
import axios from 'axios';
const requestURL='JSONUrl';
let responseData = '';
export function fetchRequests() {
return axios.get(requestURL) //change is here
//removed return responseData and the 'then' block
}
export const fetchDataAction = () => {
return dispatch => { //almost whole of this function is changed and that's it
fetchRequests().then((res) => {
responseData = res;
dispatch({
type: 'FETCH_DATA',
data: responseData
})
});
};
}
export function fetchDataSuccessAction(err) {
return {
type: 'FETCH_DATA_SUCCESS',
err
};
}
export function fetchDataErrorAction(err) {
return {
type: 'FETCH_DATA_ERROR',
err
};
}
Now it should work. Tell me more if it doesn't. Remember this answer is assuming that you have all your functions imported properly into your files. I'll be making edits if this doesn't answer your question.
So the answer was to use 'thunk' - an official "async function middleware" for redux.
Also see this to learn more about handling 'async actions' and also using redux thunk:
https://redux.js.org/tutorials/fundamentals/part-6-async-logic
https://www.npmjs.com/package/redux-thunk
Try this
<button onClick={() => this.props.fetchDataAction()}>
Also if you need to dispatch the url from your components you can do this way
<button onClick={() => this.props.fetchDataAction(url)}>
const mapDispatchToProps = (dispatch) => {
return {
fetchDataAction: (url) => dispatch(fetchRequests(url))
};
};
And in Action.js
export function fetchRequests(url) {
axios.get(url)
.then((res) => {
responseData = res;
});
return responseData;
}
<button onClick={() => this.props.fetchDataAction()}>
<button onClick={() => this.props.fetchDataAction}>
Here you are trying to return the function definition alone ,not call the fetchDataAction onclick use onClick={() => this.props.fetchDataAction()} or pass a separate handler for the onclick as good practice.
For the other issue you have mentioned TypeError: this.props.fetchDataAction is not a function is because of the curly brackets used while importing fetchRequests
Remove the curly brackets
import fetchRequests from "./actions/fetchData";
This should resolve your issue
In my ReactJS / Typescript app, I'm getting the following error in my store.ts:
Parameter 'initialState' cannot be referenced in its initializer.
interface IinitialState {
fiatPrices: [];
wallets: [];
defaultCurrency: string;
}
const initialState = {
fiatPrices: [],
wallets: [],
defaultCurrency: ''
}
...
export function initializeStore (initialState:IinitialState = initialState) {
return createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(thunkMiddleware))
)
}
Anyone else run into this issue? Currently having to rely on // #ts-ignore
Entire store.ts file:
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunkMiddleware from 'redux-thunk'
interface IinitialState {
fiatPrices: [];
wallets: [];
defaultCurrency: string;
}
const initialState = {
fiatPrices: [],
wallets: [],
defaultCurrency: ''
}
export const actionTypes = {
GET_PRICES: 'GET_PRICES'
}
// REDUCERS
export const reducer = (state = initialState, action: any) => {
switch (action.type) {
case actionTypes.GET_PRICES:
return state
default:
return state
}
}
// MOCK API
export async function getProgress(dispatch: any) {
try {
const priceList = await fetchPrices();
return dispatch({ type: actionTypes.GET_PRICES, payload: priceList })
}
catch (err) {
console.log('Error', err);
}
}
// Wait 1 sec before resolving promise
function fetchPrices() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ progress: 100 });
}, 1000);
});
}
// ACTIONS
export const addLoader = () => (dispatch: any) => {
getProgress(dispatch);
}
// #ts-ignore
export function initializeStore (initialState:IinitialState = initialState) {
return createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(thunkMiddleware))
)
}
withReduxStore lib file
import React from 'react'
import { initializeStore, IinitialState } from '../store'
const isServer = typeof window === 'undefined'
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__'
function getOrCreateStore (initialState: IinitialState) {
// Always make a new store if server, otherwise state is shared between requests
if (isServer) {
return initializeStore(initialState)
}
// Create store if unavailable on the client and set it on the window object
// Waiting for (#ts-ignore-file) https://github.com/Microsoft/TypeScript/issues/19573 to be implemented
// #ts-ignore
if (!window[__NEXT_REDUX_STORE__]) {
// #ts-ignore
window[__NEXT_REDUX_STORE__] = initializeStore(initialState)
}
// #ts-ignore
return window[__NEXT_REDUX_STORE__]
}
// #ts-ignore
export default App => {
return class AppWithRedux extends React.Component {
// #ts-ignore
static async getInitialProps (appContext) {
// Get or Create the store with `undefined` as initialState
// This allows you to set a custom default initialState
const reduxStore = getOrCreateStore()
// Provide the store to getInitialProps of pages
appContext.ctx.reduxStore = reduxStore
let appProps = {}
if (typeof App.getInitialProps === 'function') {
appProps = await App.getInitialProps(appContext)
}
return {
...appProps,
initialReduxState: reduxStore.getState()
}
}
// #ts-ignore
constructor (props) {
super(props)
this.reduxStore = getOrCreateStore(props.initialReduxState)
}
render () {
return <App {...this.props} reduxStore={this.reduxStore} />
}
}
}
function initializeStore (initialState:IinitialState = initialState) { ... }
is not valid by any means, not just in TypeScript. It's incorrect to suppress the error with #ts-ignore.
initialState parameter shadows the variable of the same name from enclosing scope, so default parameter value refers the parameter itself. This will result in discarding default parameter value with ES5 target and in an error with ES6 target.
The parameter and default value should have different names:
function initializeStore (initialState:IinitialState = defaultInitialState) { ... }
Notice that the use of defaultInitialState isn't needed in a reducer, due to how initial state works. Initial state from createStore takes precedence if combineReducers is not in use.
I have a problem "Actions must be plain objects. Use custom middleware for async actions."
I'm using reactjs with this boilerplate (https://github.com/react-boilerplate/react-boilerplate/)
I waste 1 day to fix this problem, but no result. I was trying move fetch request to action (without saga) and result the same.
My component:
...
import { compose } from 'redux';
import injectReducer from 'utils/injectReducer';
import injectSaga from 'utils/injectSaga';
import { successFetching } from './actions';
import reducer from './reducers';
import saga from './saga';
class NewsListPage extends React.PureComponent {
componentDidMount() {
const { dispatch } = this.props
dispatch(saga())
}
...
};
NewsListPage.propTypes = {
isFetching: PropTypes.bool.isRequired,
isSuccess: PropTypes.bool.isRequired,
items: PropTypes.array.isRequired,
dispatch: PropTypes.func.isRequired,
}
const selector = (state) => state;
const mapStateToProps = createSelector(
selector,
(isFetching, isSuccess, items) => ({ isFetching, isSuccess,items })
);
function mapDispatchToProps(dispatch) {
return {
dispatch,
};
}
const withReducer = injectReducer({ key: 'NewsList', reducer });
const withSaga = injectSaga({ key: 'NewsList', saga });
const withConnect = connect(mapStateToProps, mapDispatchToProps);
export default compose(
withReducer,
withSaga,
withConnect
)(NewsListPage);
My actions:
export const NEWS_FETCH_LOADING = 'NEWS_FETCH_LOADING';
export const NEWS_FETCH_FAILURE = 'NEWS_FETCH_FAILURE';
export const NEWS_FETCH_SUCCESS = 'NEWS_FETCH_SUCCESS';
export function preFetching() {
return {
type: NEWS_FETCH_LOADING,
}
}
export function successFetching(json) {
return {
type: NEWS_FETCH_SUCCESS,
payload: json
}
}
export function failureFetching(error) {
return {
type: NEWS_FETCH_FAILURE,
payload: error
}
}
My reducers:
...
import { NEWS_FETCH_LOADING, NEWS_FETCH_FAILURE, NEWS_FETCH_SUCCESS } from './actions'
const INITIAL_STATE = {
isFetching: false,
isSuccess: false,
items: []
};
function NewsListReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case NEWS_FETCH_LOADING:
case NEWS_FETCH_FAILURE:
return Object.assign({}, state, {
isFetching: true,
isSuccess: false
})
case NEWS_FETCH_SUCCESS:
return Object.assign({}, state, {
isFetching: false,
isSuccess: true,
items: action.payload,
})
default:
return Object.assign({}, state)
}
}
export const rootReducer = combineReducers({
NewsListReducer
})
export default rootReducer
My saga:
import { call, put, select, takeLatest } from 'redux-saga/effects';
import {NEWS_FETCH_LOADING, NEWS_FETCH_FAILURE, NEWS_FETCH_SUCCESS, preFetching, successFetching, failureFetching} from './actions';
import request from 'utils/request';
export function* getNews() {
const requestURL ='%MY_URL%';
try {
const req = yield call(request, requestURL);
yield put(successFetching(req));
} catch (err) {
yield put(failureFetching(err));
}
}
export default function* NewsListSaga() {
yield takeLatest(NEWS_FETCH_SUCCESS, getNews);
}
EDIT:
#Alleo Indong, i tried your advice and its almost work.
I change in component mapDispatchToProps to
export function mapDispatchToProps(dispatch) {
return {
getData: () => dispatch(loadNews()),
};
}
And add new function to actions.js
export function loadNews() {
return {
type: NEWS_FETCH_SUCCESS,
}
}
But now ajax sent every seconds like in while cycle. I tried call this.props.getData(); in componentDidMount and in constructorand result same.
EDIT 2
In component i add
import * as actionCreators from './actions';
import { bindActionCreators } from 'redux';
In constructor i change
constructor(props) {
super(props);
const {dispatch} = this.props;
this.boundActionCreators = bindActionCreators(actionCreators, dispatch)
}
But here dispatch is undefined and in componentDidMount too.
And change mapDispatchToProps to
export function mapDispatchToProps(dispatch) {
return {
...bindActionCreators(actionCreators, dispatch),
};
}
Hello #AND and welcome to stackoverflow! As I mentioned in the comment on the main post, you are dispatching a GENERATOR instead of an object on your
dispatch(saga());
Here's an example to help you
On your component import the actions that you want to use like this.
import * actionCreators from './actions';
import { bindActionCreators } from 'redux';
Learn more about bindActionCreators here enter link description here
This will import all of the exported actionCreators that you created there.
In my opinion you don't need successFetching and failureFetching anymore as you can dispatch this actions later on on your saga
Then in your mapDispatchToProps you would want to register this actioncreator
function mapDispatchToProps(dispatch) {
return {
...bindActionCreators(actionCreators, dispatch),
};
}
Then on your saga, I want to point up some problems here as well.
First your function
export default function* NewsListSaga() {
yield takeLatest(NEWS_FETCH_SUCCESS, getNews);
}
Is actually what we call a watcher where it watch when a certain action was dispatch, in this function you are already waiting for the NEWS_FETCH_SUCCESS before getNews is called which is wrong, because you first have do the FETCHING before you will know if it is failed or success so yeah this function should be like this
export default function* NewsListSaga() {
yield takeLatest(NEWS_FETCH_LOADING, getNews);
}
This simple means that you will take all the latestNEWS_FETCH_LOADINGactions that was dispatched and will call thegetNews`.
then on your getNews generator function, you can do it like this
export function* getNews() {
const requestURL ='%MY_URL%';
try {
const req = yield call(request, requestURL);
yield put({type: NEWS_FETCH_SUCCESS, req});
} catch (err) {
yield put({type: NEWS_FETCH_FAILED, err});
}
}
in here
const req = yield call(request, requestURL);
You are saying that you will wait for the result of the request / service that you called, it might be a promise.
Then in here, this is why you won't need the functions successFetching and failureFetching functions anymore, since you can do it like this
yield put({type: NEWS_FETCH_SUCCESS, req});
One last important step that you have to do now is to call the actionCreator inside your componentDidMount()
like this
componentDidMount() {
const { preFetching } = this.props;
preFetching();
}
I am trying to dispatch an action. I found working examples for some actions, but not as complex as mine.
Would you give me a hint? What am I doing wrong?
I am using TypeScript and have recently removed all typings and simplified my code as much as possible.
I am using redux-thunk and redux-promise, like this:
import { save } from 'redux-localstorage-simple';
import thunkMiddleware from 'redux-thunk';
import promiseMiddleware from 'redux-promise';
const middlewares = [
save(),
thunkMiddleware,
promiseMiddleware,
];
const store = createStore(
rootReducer(appReducer),
initialState,
compose(
applyMiddleware(...middlewares),
window['__REDUX_DEVTOOLS_EXTENSION__'] ? window['__REDUX_DEVTOOLS_EXTENSION__']() : f => f,
),
);
Component - Foo Component:
import actionFoo from 'js/actions/actionFoo';
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Foo {
constructor(props) {
super(props);
this._handleSubmit = this._handleSubmit.bind(this);
}
_handleSubmit(e) {
e.preventDefault();
this.props.doActionFoo().then(() => {
// this.props.doActionFoo returns undefined
});
}
render() {
return <div onClick={this._handleSubmit}/>;
}
}
const mapStateToProps = ({}) => ({});
const mapDispatchToProps = {
doActionFoo: actionFoo,
};
export { Foo as PureComponent };
export default connect(mapStateToProps, mapDispatchToProps)(Foo);
Action - actionFoo:
export default () => authCall({
types: ['REQUEST', 'SUCCESS', 'FAILURE'],
endpoint: `/route/foo/bar`,
method: 'POST',
shouldFetch: state => true,
body: {},
});
Action - AuthCall:
// extremly simplified
export default (options) => (dispatch, getState) => dispatch(apiCall(options));
Action - ApiCall:
export default (options) => (dispatch, getState) => {
const { endpoint, shouldFetch, types } = options;
if (shouldFetch && !shouldFetch(getState())) return Promise.resolve();
let response;
let payload;
dispatch({
type: types[0],
});
return fetch(endpoint, options)
.then((res) => {
response = res;
return res.json();
})
.then((json) => {
payload = json;
if (response.ok) {
return dispatch({
response,
type: types[1],
});
}
return dispatch({
response,
type: types[2],
});
})
.catch(err => dispatch({
response,
type: types[2],
}));
};
From redux-thunk
Redux Thunk middleware allows you to write action creators that return
a function instead of an action
So it means that it doesn't handle your promises. You have to add redux-promise for promise supporting
The default export is a middleware function. If it receives a promise,
it will dispatch the resolved value of the promise. It will not
dispatch anything if the promise rejects.
The differences between redux-thunk vs redux-promise you can read here
Okay, after several hours, I found a solution. redux-thunk had to go first before any other middleware. Because middleware is called from right to left, redux-thunk return is last in chain and therefore returns the Promise.
import thunkMiddleware from 'redux-thunk';
const middlewares = [
thunkMiddleware,
// ANY OTHER MIDDLEWARE,
];
const store = createStore(
rootReducer(appReducer),
initialState,
compose(
applyMiddleware(...middlewares),
window['__REDUX_DEVTOOLS_EXTENSION__'] ? window['__REDUX_DEVTOOLS_EXTENSION__']() : f => f,
),
);
Im new to React and Redux and still kinda confused a little bit.
My goal is to render a bunch of json datas in the HTML by using GET request. I'm using react and redux to manage the state of the objects, but I believe my problem is that the data is not even there
so basically whenever someone request a URL /courses , he/she will see bunch of data in json.
I get the error in the component
TypeError: Cannot read property 'map' of undefined
Here's the code
Action
export function getCourses() {
return (dispatch) => {
return fetch('/courses', {
method: 'get',
headers: { 'Content-Type', 'application/json' },
}).then((response) => {
if (response.ok) {
return response.json().then((json) => {
dispatch({
type: 'GET_COURSES',
courses: json.courses
});
})
}
});
}
}
Reducer
export default function course(state={}, action) {
switch (action.type) {
case 'GET_COURSES':
return Object.assign({}, state, {
courses: action.courses
})
default:
return state;
}
}
Component
import React from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
class Course extends React.Component {
allCourses() {
return this.props.courses.map((course) => {
return(
<li>{ course.name }</li>
);
});
}
render() {
return (
<div>
<ul>
{ this.allCourses() }
</ul>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
courses: state.courses
}
}
export default connect(mapStateToProps)(Course);
Index reducer, where i combine everything
import { combineReducers } from 'redux';
import course from './course';
export default combineReducers({
course,
});
Configure Store , where i store the intial state and the reducer
import { applyMiddleware, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk),
typeof window == 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
)
);
return store;
}
I believe why the data is not there is because i didn't call the action? any help would be appreciated.
mapStateToProps takes the root state as an argument (your index reducer, which is also the root reducer), not your course reducer. As far as I can tell this is the structure of your store:
-index <- This is the root reducer
-course
So to get the courses from that state, in your component:
// state is the state of the root reducer
const mapStateToProps = (state) => {
return {
courses: state.course.courses
}
}
Also, you might consider initialising the state of the course reducer with an empty array of courses, so if you have to render the component before the action is fired, you won't get the error.
const initialState = {
courses: []
};
export default function course(state= initialState, action) {
...
}
Finally, you're not firing the action at all, so you will never actually get the courses, I assume you want them to be retrieved once the Course component is loaded, for that you can use the componentDidMount event in your component.
First of all, you need to map the action to a property of the component
// Make sure you import the action
import { getCourses } from './pathToAction';
...
const mapDispatchToProps = (dispatch) => {
return {
onGetCourses: () => dispatch(getCourses())
};
}
// Connect also with the dispatcher
export default connect(masStateToProps, mapDispatchToProps)(Course);
Now call the onGetCourses property when the component mounts
class Course extends React.Component {
componentDidMount() {
this.props.onGetCourses();
}
...
}
its because props sometime can be undefined so you have to write a condtion like this
allCourses() {
if(this.props.courses){
return this.props.courses.map((course) => {
return(
<li>{ course.name }</li>
);
});
}
else {
return [];
}