I've been banging my head around this for a while now. I setup redux-thunk so that I can do api calls but i keep getting
Actions must be plain objects. Use custom middleware for async actions.
on my onClick event triggers. Apologies if this is a duplicate but I couldn't find anything that would solve the problem. As far as I can tell I'm creating the action properly. Any help greatly appreciated.
store.js
import { applyMiddleware, createStore, compose } from 'redux'
import { syncHistoryWithStore } from 'react-router-redux'
import { browserHistory } from 'react-router'
import logger from "redux-logger"
import thunk from "redux-thunk"
// import the root reducer
import rootReducer from './reducers/index'
import paperData from './data/paperData'
import articleData from './data/articleData'
// create an object for the default data
const defaultState = { paperData, articleData };
// enable Redux Dev Tools
const enhancers = compose(
window.devToolsExtension
? window.devToolsExtension()
: f => f
);
const middleware = applyMiddleware(
logger(),
thunk);
const store = createStore(rootReducer,
defaultState,
enhancers,
middleware);
export const history = syncHistoryWithStore(browserHistory, store);
// hot reloading the reducer
if (module.hot) {
module.hot.accept('./reducers/', () => {
const nextRootReducer = require('./reducers/index').default;
store.replaceReducer(nextRootReducer)
})
}
export default store
index.js
import React from 'react'
import { render } from 'react-dom'
import Homepage from './containers/homepage';
import ArticleList from './containers/article-list';
// import css
// import components
import App from './components/App'
// import react router
import { Router, Route, IndexRoute } from 'react-router'
import { Provider } from 'react-redux'
import store, { history } from './store'
const router = (
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={Homepage} />
<Route path="paperlist/:word" component={ArticleList} />
</Route>
</Router>
</Provider>
);
render(router, document.getElementById('root'));
App.js
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as actionCreators from '../actions/actionCreators';
import Main from '../containers/main';
function mapStateToProps(state) {
return {
paperData: state.paperData,
articleData: state.articles
}
}
function mapDispachToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch)
}
const App = connect(mapStateToProps, mapDispachToProps)(Main);
export default App;
Here is how I'm calling the onClick event in my homepage
import React, {Component} from 'react';
import '../../styles/homepage.sass'
import WordCloud from './word-cloud';
const homepage = React.createClass ({
handleSubmit(e) {
e.preventDefault();
const searchQuery = this.refs.query.value;
console.log(this.refs.query.value);
this.props.generatePapers(searchQuery);
},
render() {
let query = this.props.query;
return (
<div className="input-group center">
<WordCloud {...this.props} />
<input id="search-input-box" type="text" className="form-control searchBox"
placeholder="Search artists..." ref="query"
>
</input>
<button id="search-button" className="btn btn-lg searchButton"
onClick={this.handleSubmit}>
<span className="glyphicon glyphicon-search" aria-hidden="true">
</span> Search
</button>
</div>
);
}
});
export default homepage;
actionCreators.js
import axios from "axios";
export const generatePapers = (query) => {
const request = axios.get("http://localhost:8888/generateWordcloud/" + query);
return (dispatch) => {
request.then(({data}) => {
dispatch({
type: "GENERATE_WORDCLOUD",
payload: data
})
})
};
};
You are not creating your store correctly. From the docs of redux:
createStore(reducer, [preloadedState], [enhancer])
createStore accepts 3 arguments: the root reducer, optionally the default preloaded state, and the enhancers.
Your are passing the redux-thunk as an unknown 4th argument. Your code should like more like:
const store = createStore(reducer, composeWithDevTools(
applyMiddleware(...middleware),
// other store enhancers if any
));
Related
I have the following modules for my React app:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import MainApp from './MainApp';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<MainApp />
</React.StrictMode>,
document.getElementById('root')
);
MainApp.js
import React from 'react';
import App from './containers/app';
import './App.css';
import {ConnectedRouter} from 'connected-react-router'
import {Provider} from 'react-redux';
import {Route, Switch} from 'react-router-dom';
import configureStore, {history} from './store';
export const store = configureStore();
const MainApp = () =>
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route path="/" component={App}/>
</Switch>
</ConnectedRouter>
</Provider>;
export default App;
store/index.js
import {applyMiddleware, compose, createStore} from 'redux';
import reducers from '../reducers/index';
import {createBrowserHistory} from 'history'
import {routerMiddleware} from 'connected-react-router';
import createSagaMiddleware from 'redux-saga';
import rootSaga from '../sagas/index';
const history = createBrowserHistory();
const routeMiddleware = routerMiddleware(history);
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware, routeMiddleware];
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default function configureStore(initialState) {
const store = createStore(reducers(history), initialState,
composeEnhancers(applyMiddleware(...middlewares)));
sagaMiddleware.run(rootSaga);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers/index', () => {
const nextRootReducer = require('../reducers/index');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
export {history};
containers/dataviewer/index.js
import React from 'react';
import {connect} from 'react-redux';
import {
connectPath
} from '../../actions/Paths';
import Button from '#material-ui/core/Button';
import ButtonGroup from '#material-ui/core/ButtonGroup';
import TabularViewer from './TabularViewer';
import CollapsableViewer from './CollapsableViewer';
import ChartViewer from './ChartViewer';
class DataViewer extends React.Component {
constructor() {
super();
this.state = {
displayStyle: null
}
this.handleClick = this.handleClick.bind(this);
}
handleClick = (style) => {
this.setState({displayStyle: style})
}
DisplayControllers = () => {
return (
<ButtonGroup variant="text" color="primary" aria-label="outlined primary button group">
<Button color={this.state.displayStyle === 'tabular'? 'secondary': 'primary'} onClick={() => this.handleClick('tabular')}>Tabular</Button>
<Button color={this.state.displayStyle === 'collapsable'? 'secondary': 'primary'}onClick={() => this.handleClick('collapsable')}>Colapsable</Button>
<Button color={this.state.displayStyle === 'chart'? 'secondary': 'primary'} onClick={() => this.handleClick('chart')}>Gráfico</Button>
</ButtonGroup>
)
}
DisplayComponent = () => {
switch (this.state.displayStyle) {
case 'tabular':
return <TabularViewer />
case 'collapsable':
return <CollapsableViewer />
case 'chart':
return <ChartViewer />
default:
return <p>Seleccione un estilo de desplegado.</p>;
}
}
render() {
return (
<div>
<p>Data Viewer</p>
<this.DisplayControllers />
<this.DisplayComponent />
</div>
)
}
}
const mapStateToProps = ({paths}) => {
const {connection, path, data} = paths;
return {
connection,
path,
data
}
}
export default connect(mapStateToProps, {connectPath})(DataViewer)
Te problem is that I get the following error when the component is mounted:
Error: Could not find "store" in the context of "Connect(DataViewer)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(DataViewer) in connect options.
I can't see what I am missing.
EDIT
containers/app.js
import React from 'react';
import DataViewer from './DataViewer';
class App extends React.Component {
render() {
return (
<div className="App">
<header className="App-header">
<DataViewer
database_host="127.0.0.1'"
database_port="5432"
database_name="TCDigital"
database_user="postgres"
database_password="xxxx"
path="Ventas"
displayStyle="collapsable"
/>
</header>
</div>
);
}
}
export default App;
I've got a Standard React-Redux Application with react router v4, webpack 4 and I'm trying to perform lazy loading of components via loadable-components library, so webpack can create chunks and load them on demand. The problems seems it's always rendering the Dashboard (refer code below) Component inside the <Switch>, no matter the route.
I don't understand the cause. I found a similar problem: React-Router v4 rendering wrong component but matching correctly but it follows a different pattern and I can't or don't understand how to apply that solution into my issue.
I'm posting The main AppContainer and the routes file:
AppContainer.js:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Provider } from 'react-redux'
import ReduxToastr from 'react-redux-toastr'
import 'react-redux-toastr/src/styles/index.scss'
import { Offline, Online } from 'react-detect-offline'
import Footer from 'components/Footer/'
import { Modal, ModalHeader, ModalBody } from 'reactstrap'
import { Translate } from 'react-redux-i18n'
import { PersistGate } from 'redux-persist/integration/react'
import { Router, Switch, Route } from 'react-router-dom'
import PrivateRoute from 'components/PrivateRoute'
import { Dashboard, PasswordReset, PasswordResetEdit, SignIn } from 'routes'
// Layout
import CoreLayout from 'layouts/CoreLayout'
class AppContainer extends Component {
static propTypes = {
history: PropTypes.object.isRequired,
persistor: PropTypes.object.isRequired, // redux-persist
store : PropTypes.object.isRequired
}
render () {
const { history, store, persistor } = this.props
const opened = true
const toastrDefaultTimeout = 8000
const newToastrAlwaysOnTop = false
return (
<div>
<Online>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<div style={{ height: '100%' }}>
<ReduxToastr
timeOut={toastrDefaultTimeout}
newestOnTop={newToastrAlwaysOnTop}
preventDuplicates
position='top-right'
transitionIn='fadeIn'
transitionOut='fadeOut'
progressBar />
<Router history={history}>
<CoreLayout {...this.props} >
<Switch>
<Route exact path='/sign-in' component={SignIn} />
<Route exact path='/passwords/new' component={PasswordReset} />
<Route exact path='/passwords/edit' component={PasswordResetEdit} />
<PrivateRoute exact path='/' component={Dashboard} persistor={persistor} />
</Switch>
</CoreLayout>
</Router>
</div>
</PersistGate>
</Provider>
</Online>
<Offline>
<div className='app'>
<div className='app-body'>
<main className='main align-items-center align-self-center'>
<div className='container'>
<div className='animated fadeIn'>
<Modal isOpen={opened} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}><Translate value={`views.shared.no-internet.title`} /></ModalHeader>
<ModalBody>
<div className='card-block'>
<div className='row'>
<div className='col-sm-12'>
<Translate value='views.shared.no-internet.description' />
</div>
</div>
</div>
</ModalBody>
</Modal>
</div>
</div>
</main>
</div>
<Footer />
</div>
</Offline>
</div>
)
}
}
export default AppContainer
routes/index.js:
// We only need to import the modules necessary for initial render
import loadable from 'loadable-components'
import { hot } from 'react-hot-loader'
// Component split-code (lazy load)
let Dashboard,
SignIn,
PasswordReset,
PasswordResetEdit
// Needed for HMR
if (__DEV__) {
Dashboard = hot(module)(loadable(() => import('./Dashboard')))
PasswordReset = hot(module)(loadable(() => import('./PasswordReset')))
PasswordResetEdit = hot(module)(loadable(() => import('./PasswordResetEdit')))
SignIn = hot(module)(loadable(() => import('./SignIn')))
} else {
Dashboard = loadable(() => import('./Dashboard'))
SignIn = loadable(() => import('./SignIn'))
PasswordReset = loadable(() => import('./PasswordReset'))
PasswordResetEdit = loadable(() => import('./PasswordResetEdit'))
}
export { Dashboard, PasswordReset, PasswordResetEdit, SignIn }
And for anyone curious, here is the PrivateRoute component:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Route, Redirect } from 'react-router-dom'
import { connect } from 'react-redux'
import { validateSession } from 'modules/sessions/session'
const mapStateToProps = state => ({
session: state.session
})
const mapDispatchToProps = (dispatch) => {
return {
validateSession: () => { dispatch(validateSession()) }
}
}
class PrivateRoute extends Component {
componentDidMount () {
this.props.validateSession(this.props.persistor)
}
render () {
const { component: Component, session, ...rest } = this.props
return (
<Route {...rest} render={(routeProps) =>
this.props.session.userSignedIn ? <Component {...routeProps} />
: <Redirect to={{
pathname: '/sign-in',
state: { from: routeProps.location }
}} />
}
/>)
}
}
PrivateRoute.propTypes = {
component: PropTypes.func.isRequired,
persistor: PropTypes.object.isRequired,
session: PropTypes.object.isRequired,
validateSession: PropTypes.func.isRequired
}
export default connect(mapStateToProps, mapDispatchToProps)(PrivateRoute)
If I remove the if (_DEV_) block in routes/index.js and always do it like the 'else' block everything works okay, however I lose HMR, meaning I have to refresh the browser to see code changes take effect.
Edit
For anyone wandering where the history prop comes from:
Main app entry point (in webpack.config.js). src/main.js:
import React from 'react'
import ReactDOM from 'react-dom'
import createStore from './store/createStore'
import { hot } from 'react-hot-loader'
import AppContainer from './containers/AppContainer'
import { persistStore } from 'redux-persist'
// ========================================================
// Store Instantiation
// ========================================================
const initialState = window.___INITIAL_STATE__
const { history, store } = createStore(initialState)
// begin periodically persisting the store
let persistor = persistStore(store)
// ========================================================
// Render Setup
// ========================================================
const MOUNT_NODE = document.getElementById('root')
let render = () => {
ReactDOM.render(
<AppContainer store={store} persistor={persistor} history={history} />,
MOUNT_NODE
)
}
// This code is excluded from production bundle
if (__DEV__) {
if (module.hot) {
// Development render functions
const renderApp = render
const renderError = (error) => {
const RedBox = require('redbox-react').default
ReactDOM.render(<RedBox error={error} />, MOUNT_NODE)
}
// Wrap render in try/catch
render = () => {
try {
renderApp()
} catch (error) {
console.error(error)
renderError(error)
}
}
// Setup hot module replacement
module.hot.accept('./containers/AppContainer', () =>
render(require('./containers/AppContainer').default)
)
}
}
// ========================================================
// Go!
// ========================================================
export default hot(module)(render)
render()
createStore.js:
import { applyMiddleware, compose, createStore } from 'redux'
import thunk from 'redux-thunk'
import { apiMiddleware } from 'redux-api-middleware'
import { createLogger } from 'redux-logger'
import makeRootReducer from './reducers'
import { routerMiddleware } from 'react-router-redux'
// I18n
import { syncTranslationWithStore, loadTranslations, setLocale } from 'react-redux-i18n'
import { translationsObject } from 'translations/index'
// Router history
import createHistory from 'history/createBrowserHistory'
// Raven for Sentry
import Raven from 'raven-js'
import createRavenMiddleware from 'raven-for-redux'
export default (initialState = {}) => {
// ======================================================
// Middleware Configuration
// ======================================================
const logger = createLogger()
const history = createHistory()
const historyMiddleware = routerMiddleware(history)
const middleware = [historyMiddleware, apiMiddleware, thunk, logger]
if (__PROD__) {
Raven.config(`${__SENTRY_DSN__}`).install()
middleware.push(createRavenMiddleware(Raven))
}
// ======================================================
// Store Enhancers
// ======================================================
const enhancers = []
let composeEnhancers = compose
const composeWithDevToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
if (typeof composeWithDevToolsExtension === 'function') {
composeEnhancers = composeWithDevToolsExtension
}
// ======================================================
// Store Instantiation and HMR Setup
// ======================================================
const store = createStore(
makeRootReducer(),
initialState,
composeEnhancers(
applyMiddleware(...middleware),
...enhancers
)
)
store.asyncReducers = {}
// DEPRECATED in react-router v4: To unsubscribe, invoke `store.unsubscribeHistory()` anytime
// store.unsubscribeHistory = browserHistory.listen(updateLocation(store))
if (module.hot) {
const reducers = require('./reducers').default
module.hot.accept('./reducers', () => {
store.replaceReducer(reducers(store.asyncReducers))
})
}
syncTranslationWithStore(store)
store.dispatch(loadTranslations(translationsObject))
store.dispatch(setLocale('es_AR'))
return { history, store }
}
I try to build a custom template. I follow the instructions for the App.js from the page https://marmelab.com/admin-on-rest//CustomApp.html.
I connect my component PostList to react-redux. I see the request in the inspector but the store is still empty.
I don't know what it miss to do for working fine.
App.js
import React from 'react';
// redux, react-router, redux-form, saga, and material-ui
// form the 'kernel' on which admin-on-rest runs
import { combineReducers, createStore, compose, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import createHistory from 'history/createBrowserHistory';
import { Switch, Route } from 'react-router-dom';
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux';
import { reducer as formReducer } from 'redux-form';
import createSagaMiddleware from 'redux-saga';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import AppBar from 'material-ui/AppBar';
// prebuilt admin-on-rest features
import {
adminReducer,
localeReducer,
crudSaga,
jsonServerRestClient,
TranslationProvider,
} from 'admin-on-rest';
// your app components
import PostList from './posts';
// your app labels
const messages = {
en: {
'main.heading': 'retranslate #{{ versionNumber }}',
'main.subtitle': 'Real simple translations for react.',
'current.language': 'Your current language is {{ currentLanguage }}',
'bold.thing': 'This <b>text</b> is bold',
},
et: {
'main.heading': 'retranslate #{{ versionNumber }}',
'main.subtitle': 'Väga lihtsad tõlked reactile.',
'current.language': 'Teie hetke keel on {{ language }}',
'bold.thing': 'See <b>tekst</b> on tumedam',
},
};
// create a Redux app
const reducer = combineReducers({
admin: adminReducer,
locale: localeReducer(),
form: formReducer,
routing: routerReducer,
});
const sagaMiddleware = createSagaMiddleware();
const history = createHistory();
const store = createStore(reducer, undefined, compose(
applyMiddleware(sagaMiddleware, routerMiddleware(history)),
window.devToolsExtension ? window.devToolsExtension() : f => f,
));
const restClient = jsonServerRestClient('http://jsonplaceholder.typicode.com');
sagaMiddleware.run(crudSaga(restClient));
const Dash = () => {
return <div>Dash</div>;
};
// bootstrap redux and the routes
const App = () => (
<Provider store={store}>
<TranslationProvider messages={messages}>
<ConnectedRouter history={history}>
<MuiThemeProvider>
<div>
<AppBar title="My Admin" />
<Switch>
<Route exact path="/" render={(routeProps) => <Dash {...routeProps} />} />
<Route exact path="/posts" hasCreate render={(routeProps) => <PostList resource="posts" {...routeProps} />} />
</Switch>
</div>
</MuiThemeProvider>
</ConnectedRouter>
</TranslationProvider>
</Provider>
);
export default App;
posts.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { crudGetList as crudGetListAction} from 'admin-on-rest';
class PostList extends Component {
componentWillMount() {
this.props.crudGetList('posts', {page: 1, parPage: 10}, {field: 'id', order: 'ASC'});
}
render() {
return <div>Posts</div>
}
}
function mapStateToProps(state) {
console.log(state.admin.resources);
return {
list: []
}
}
export default connect(mapStateToProps, {
crudGetList: crudGetListAction
})(PostList)
Our documentation has been updated today. You're missing the following line:
store.dispatch(declareResources([{ name: 'posts' }]));
You can insert it just before declaring your restClient
Struggling to dispatch an action from my React component. This is my first Redux app. Everything seems to be working fine, but if it was I would not be posting this question. I am using Redux devTool to debug my app. If I use the dispatcher from the devTools my reducer is triggered with no problem. However I am unable to dispatch the same action from my React components. I added a breakpoint in my action to see if it was being triggered. I definately is and it is also returning a valid action (type & payload). Here is my code:
store.js
import {createStore, applyMiddleware, compose} from 'redux'
import {createLogger} from 'redux-logger'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import reducers from './reducers/index'
const logger = createLogger()
const store = createStore(
reducers,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__(),
compose(
applyMiddleware(thunk, promise, logger)
)
)
export default store
reducers (index.js)
import {combineReducers} from 'redux'
import userReducer from './userReducer'
const allReducers = combineReducers({
user: userReducer
})
export default allReducers
client.js
import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'
import store from './store'
import Router from './modules/router'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import CustomTheme from './modules/theme'
import injectTapEventPlugin from 'react-tap-event-plugin'
require('../scss/style.scss')
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
injectTapEventPlugin();
ReactDOM.render(
<Provider store={store}>
<MuiThemeProvider muiTheme={CustomTheme}>
<Router/>
</MuiThemeProvider>
</Provider>,
document.getElementById('app')
);
userReducer.js
export default function (state = {loggedIn: false}, action) {
console.log("THIS IS THE REDUCER: STATE: ", state, " - ACTION: ", action)
switch (action.type) {
case 'LOGIN':
return {...state, loggedIn: action.payload}
}
return state;
}
userActions.js
export const login = () => {
console.log("TEST")
return {
type: 'LOGIN',
payload: true
}
}
login.js
import React from 'react'
import ReactDOM from 'react-dom'
import LoginForm from '../containers/loginform'
class Login extends React.Component {
render() {
return (
<LoginForm/>
)
}
}
export default Login
loginform.js
import React, {PropTypes} from 'react'
import ReactDOM from 'react-dom'
import {Redirect} from 'react-router-dom'
import {connect} from 'react-redux'
import {login} from '../actions/userActions'
import RaisedButton from 'material-ui/RaisedButton'
import TextField from 'material-ui/TextField'
class LoginForm extends React.Component {
constructor(props) {
super(props)
}
loginReq(e){
e.preventDefault()
this.props.login()
}
render() {
return (
<div>
<form className='login-form-container' onSubmit= {this.loginReq.bind(this)}>
<div className='login-form-row'>
<TextField
ref='email'
hintText='Email'
floatingLabelText='Email'
className='login-form-field'/>
</div>
<div className='login-form-row'>
<TextField
ref='password'
hintText='Password'
floatingLabelText='Password'
type='password'
className='login-form-field'/>
</div>
<div className='login-form-row'>
<RaisedButton
type= 'submit'
label='Login'
className='login-form-button'/>
</div>
</form>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
loggedIn: state.user.loggedIn
}
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(login())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm)
Please could you give me some guidance to how else i can debug to find out why this dispatch is not working. I have tried adding the action object straight into that dispatch function. still no luck. I get no errors in the console nothing. My console.logs are only printed when the view renders and when i click on the login submit button.
Console Screenshot
Finally found my issue. My middleware implementation was causing the issue. I was passing in promise incorrectly. Should be:
import {createStore, applyMiddleware} from 'redux'
import {composeWithDevTools} from 'redux-devtools-extension'
import {createLogger} from 'redux-logger'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import reducers from './reducers/index'
const logger = createLogger()
const middleware = applyMiddleware(promise(), logger, thunk)
const store = createStore(reducers, composeWithDevTools(middleware))
export default store
Also found that redux-devtools-extension was cleaner for Redux devTools.
My hunch would be how you are trying to invoke the function to dispatch the action. Firstly, bind the function to this in the component constructor (See the React docs on event handlers here for more info). Secondly, just pass the function to onSubmit.
class LoginForm extends React.Component {
constructor(props) {
super(props)
this.loginReq = this.loginReq.bind(this);
}
loginReq(e) {
e.preventDefault()
this.props.login()
}
render() {
return (
<div>
<form className='login-form-container' onSubmit={this.loginReq}>
<div className='login-form-row'>
<TextField
ref='email'
hintText='Email'
floatingLabelText='Email'
className='login-form-field'/>
</div>
<div className='login-form-row'>
<TextField
ref='password'
hintText='Password'
floatingLabelText='Password'
type='password'
className='login-form-field'/>
</div>
<div className='login-form-row'>
<RaisedButton
type= 'submit'
label='Login'
className='login-form-button'/>
</div>
</form>
</div>
)
}
}
An alternative way to bind the function to this is to remove the bind statement in the constructor and use an arrow function for the form prop, like this:
onSubmit={e => this.loginReq(e)}
modify action:
export const login = () => {
return function (dispatch) {
console.log('here');
dispatch({
type: 'LOGIN',
payload: true
});
}
}
I guess you'd like => syntax in that case.
I am not sure if I am even setting up this redux-react project correctly. I am confused as to how I can actually start using store within my react app.
When I try to console.log store I am getting undefined. I have gotten most of this from a boilerplate and am unsure of how some of these parts interact. Currently I have an index.js with
import { Provider } from 'react-redux'
import { configureStore } from './store/configureStore';
const store = configureStore()
import { Root} from './containers/Root';
import Home from './containers/Home'
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={Root}>
<IndexRoute component={Home} />
</Route>
</Router>
</Provider>,
document.getElementById('root')
);
Root.js :
import React, { Component } from 'react';
import DevTools from './DevTools';
import MyNavbar from '../components/MyNavbar';
import Footer from '../components/Footer'
module.exports = class Root extends Component {
render() {
const { store } = this.props;
console.log(store)
return (
<div>
<MyNavbar />
{this.props.children}
<Footer />
{/* Being the dev version of our Root component, we include DevTools below */}
{/*<DevTools />*/}
</div>
);
}
};
Home component:
import React, { Component, PropTypes } from 'react';
import { Row, Col, Grid } from 'react-bootstrap'
import HowItWorks from '../components/HowItWorks'
import GetStarted from '../components/GetStarted'
import Setup from './Setup'
export default class Home extends Component {
render() {
// we can use ES6's object destructuring to effectively 'unpack' our props
return (
<section>
<div className="slider-wrapper">
<GetStarted />
</div>
<Grid>
<div className="howwork-wrapper">
<Row >
<Col md={12}>
<HowItWorks />
</Col>
</Row>
</div>
</Grid>
</section>
);
}
}
configureStore.js :
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from '../reducers';
import createLogger from 'redux-logger';
import thunk from 'redux-thunk';
import DevTools from '../containers/DevTools';
const logger = createLogger();
const finalCreateStore = compose(
applyMiddleware(logger, thunk),
DevTools.instrument()
)(createStore);
module.exports = function configureStore(initialState) {
const store = finalCreateStore(rootReducer, initialState);
if (module.hot) {
module.hot.accept('../reducers', () =>
store.replaceReducer(require('../reducers'))
);
}
return store;
};
reducers/index.js:
import { combineReducers } from 'redux';
import auth from './auth'
const rootReducer = combineReducers({
auth
});
export default rootReducer;
reducers/auth.js:
import { LOGIN, LOGIN_FAIL, LOGOUT } from '../constants/ActionTypes'
export default function auth(state = {}, action) {
switch (action.type) {
case LOGIN:
return state;
case LOGIN_FAIL:
return state ;
case LOGOUT:
return state ;
default:
return state;
}
}
constants/ActionTypes:
export const LOGIN = 'LOGIN';
export const LOGIN_FAIL = 'LOGIN_FAIL';
export const LOGOUT = 'LOGOUT';
You need to connect your components to get access to the store/state. To do this, modify your Root component like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import DevTools from './DevTools';
import MyNavbar from '../components/MyNavbar';
import Footer from '../components/Footer'
class Root extends Component {
render() {
const { state } = this.props;
console.log(state)
return (
<div>
<MyNavbar />
{this.props.children}
<Footer />
{/* Being the dev version of our Root component, we include DevTools below */}
{/*<DevTools />*/}
</div>
);
}
};
const mapStateToProps = (state) => {
return {
state: state
}
}
module.exports = connect(mapStateToProps)(Root);
A few notes, since you are transpiling anyway, you could export instead of module.exports in your declaration. Also, generally you do not want to expose your entire state to a single component. You can connect multiple components (make them "containers") by following this pattern.
The following is an example component connected to your state.
import React, { Component } from 'react';
import { connect } from 'react-redux';
export class SomeComponent extends Component {
render() {
const { someKey, dispatchSomething } = this.props;
return (
<div onClick={dispatchSomething}>
<h1>My rendered someKey variable: {someKey}</h1>
</div>
);
}
};
const mapStateToProps = (state) => {
return {
someKey: state.someReducer.someKey
}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatchSomething: () => dispatch(someActionCreator())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SomeComponent);
References
react-redux API: connect