I originally thought this was an issue with react-redux
, axios
, lodash
, and/or react-dropzone
; however, files are uploading fine, but this error still triggers. (Even though it is says "failed" in console, the files are being posted to the server).
I am starting to think the issue might be related to react-router-redux
based on what I read here:
https://github.com/reactjs/react-router-redux/issues/182
However, the suggestions are not working for me.
Anyway, I am getting an Uncaught TypeError: Cannot read property 'type' of undefined
that I can't really tell what is causing it or how to resolve it. Here are some screenshots of the errors:
So here is what should be relevant to react-router
. Just not sure what is going on.
The entry point:
// ./react/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { routerMiddleware, ConnectedRouter } from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
// Render on every route
import App from './components/app';
import Navbar from './containers/global/navbar';
import Footer from './containers/global/footer';
// Reducers
import rootReducer from './reducers';
// SCSS for the project
import styles from '../assets/scss/main.scss';
// IE polyfill error fix
require('es6-promise').polyfill();
var axios = require('axios');
const history = createHistory();
const initialState = {};
const enhancers = [];
const middleware = [thunk, routerMiddleware(history)];
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.devToolsExtension
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
}
const composedEnhancers = compose(applyMiddleware(...middleware), ...enhancers);
const protectedRoute = compose(Timers, RequireAuth);
const store = createStore(rootReducer, initialState, composedEnhancers);
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<Navbar />
<App />
<Switch>
// various routes go here...
</Switch>
{/*<Footer />*/}
</div>
</ConnectedRouter>
</Provider>
, document.querySelector('.container'));
The global reducers and those that pertain to the action that triggers the error.
// ./react/reducers/index.js
import { combineReducers } from 'redux';
import { reducer as form } from 'redux-form';
import { routerReducer } from 'react-router-redux';
import documentReducer from './documents';
const rootReducer = combineReducers({
form,
router: routerReducer,
documents: documentReducer,
});
export default rootReducer;
// ./react/reducers/documents.js
import {
DOCUMENTS
} from '../actions/documents';
export default function(state = {}, action) {
switch(action.type) {
case DOCUMENTS:
return {
...state,
survey_id: action.payload.survey_id,
};
default:
return state;
}
return state;
}
Finally, the action that is being called when the error is raised. One important thing to point out: the error does not occur when the the axios.post
is not in _.map
(actually any kind of array iteration). However, that only sends one file which is not the behavior I am after.
// ./react/actions/documents.js
import _ from 'lodash';
import axios from 'axios';
import { push } from 'react-router-redux';
import { ROOT_URL } from '../../config/config.json';
// Establish the different types
export const DOCUMENTS = 'documents';
export function submitDocument(files) {
const uploaders = _.map(files, f => {
const formData = new FormData();
formData.append('file', f);
return axios.post(
`${ROOT_URL}/api/documents/fileupload`,
formData,
{ headers:
{
'content-type': 'multipart/form-data',
'Authorization': 'JWT ' + sessionStorage.getItem('token')
}
}
)
});
axios.
all(uploaders)
.then(response => {
console.log('Success');
})
.catch(error => {
console.log('Failed');
})
}
The container that calls the action would probably be helpful as well:
// ./react/containers/documents/submit_documents.js
import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { submitDocument } from '../../actions/documents';
import Dropzone from 'react-dropzone';
class SubmitDocuments extends Component {
constructor() {
super();
this.state = {
filesToBeSent: [],
filesPreview: [],
}
this.handleClick = this.handleClick.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.onDrop = this.onDrop.bind(this);
}
handleSubmit(event) {
event.preventDefault();
this.props.submitDocument(this.state.filesToBeSent);
}
onDrop(acceptedFiles) {
var filesToBeSent = this.state.filesToBeSent;
_.map(acceptedFiles, f => {
filesToBeSent.unshift(f);
});
filesToBeSent = _.uniqBy(filesToBeSent, 'name');
var filesPreview = [];
_.map(filesToBeSent, i => {
filesPreview.unshift(
<div key={i.name}>
<h5>{i.name} - {i.size} bytes</h5>
</div>
)
});
this.setState({
filesToBeSent,
filesPreview
});
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className='panel panel-default'>
<div className='panel-heading'>
<h4><strong>Submit Documents</strong></h4>
</div>
<div className='panel-body'>
<Dropzone className='dropzone' onDrop={this.onDrop}>
<h3>Click to add files or drag files here to upload</h3>
</Dropzone>
{this.state.filesPreview}
<button type='submit' disabled={this.state.filesPreview.length < 1} className='btn btn-primary'>Submit</button>
<button type='button' className='btn btn-danger' onClick={this.handleClick}>Cancel</button>
</div>
</div>
</form>
);
}
}
function mapStateToProps(state) {
return {
survey_id: state.documents.survey_id
}
}
export default connect(mapStateToProps, { submitDocument })(SubmitDocuments);
Also, because webpack
is mentioned in the error, here is the config for it:
var path = require('path')
var webpack = require('webpack')
var BundleTracker = require('webpack-bundle-tracker')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
context: __dirname,
entry: [
'../react/index'
],
output: {
path: path.resolve('./src/assets/bundles/'),
filename: './js/[name]-[hash].js'
},
plugins: [
new BundleTracker({filename: './src/config/webpack-stats.json'}),
new ExtractTextPlugin({filename: './css/[name].[hash].css', allChunks: true})
],
module: {
loaders: [
// {test: /\.(jpe?g|png|gif|svg)$/i, loader: "url-loader?name=img/[name].[ext]"},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ["react", "es2015", "stage-1"]
}
},
{
test: /\.json$/,
loader: ['json-loader']
},
{
test: /\.scss$/,
loader: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
},
],
},
resolve: {
extensions: ['*', '.js', '.jsx', '.gif']
}
}
Anyway, not sure what is causing it and have been trying to resolve it for several days now. Everything works fine otherwise, it just looks unprofessional.
Redux thunk action creators return a function with the arguments (dispatch, getState)
, and inside that function, you dispatch()
actions when your data is available.
Your code is likely erroring because you haven't returned anything from your action creator at all. Since it's async, return the inner function:
export function submitDocument(files) {
return function (dispatch, getState) {
const uploaders = _.map(files, f => {
const formData = new FormData();
formData.append('file', f);
return axios.post(
`${ROOT_URL}/api/documents/fileupload`,
formData,
{ headers:
{
'content-type': 'multipart/form-data',
'Authorization': 'JWT ' + sessionStorage.getItem('token')
}
}
)
});
axios.
all(uploaders)
.then(response => {
console.log('Success');
dispatch({ type: DOCUMENTS, payload: ... });
})
.catch(error => {
console.log('Failed');
dispatch({ type: 'YOUR_ERROR_TYPE', payload: ... });
})
};
}