I've got a simple component that calls an action when a user loads a page, and inside that action, I'm trying to dispatch another action to set the loggedIn state of the store to true or false:
import React, { Component } from 'react'
import { Link, browserHistory } from 'react-router'
import $ from 'jquery'
class Login extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
this.props.actions.guestLoginRequest()
}
render() {
return (
<div>
<div classNameName="container">
<div className="row">
We are signing you in as a guest
</div>
</div>
</div>
)
}
}
export default Login
I can get the login information when the guestLoginRequest action is called, but when I try to dispatch another action inside of it, nothing happens:
guestLoginRequest: function(){
var ref = new Firebase("https://penguinradio.firebaseio.com");
ref.authAnonymously(function(error, authData) {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
return dispatch => {
dispatch(actions.setLoginStatus(true, authData))
console.log("dispatched");
};
}
});
}
I get an error of Uncaught ReferenceError: dispatch is not defined when I remove the return dispatch => { } statement. In my store I am using redux-thunk, so I can dispatch inside of actions:
// Store.js
import { applyMiddleware, compose, createStore } from 'redux'
import rootReducer from './reducers'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
let finalCreateStore = compose(
applyMiddleware(thunk, logger())
)(createStore)
export default function configureStore(initialState = { loggedIn: false }) {
return finalCreateStore(rootReducer, initialState)
}
I am mapping the dispatch to props in my app.js as well:
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Just in case it could be helpful, here is my client.js and reducer files:
// client.js
import React from 'react'
import { render } from 'react-dom'
import App from '../components/App'
import configureStore from '../redux/store'
import { Provider } from 'react-redux'
let initialState = {
loggedIn: false
}
let store = configureStore(initialState)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
// Reducer.js
import { combineReducers } from 'redux'
let LoginStatusReducer = function reducer(loggedIn = false, action) {
switch (action.type) {
case 'UPDATE_LOGIN_STATUS':
return loggedIn = action.boolean
default:
return loggedIn
}
}
export default LoginStatusReducer
const rootReducer = combineReducers({
loggedIn: LoginStatusReducer
})
export default rootReducer
Any ideas why my dispatch function isn't working? I'm confused since I did set up redux-thunk with my store, and I'm using code similar to the docs when I call return dispatch => { }. Is there something I'm missing? Thank you in advance for any advice!
You need your action to return a function to utilize the thunk middleware, then redux will inject the dispatcher into it. You mixed your dispatcher invocation with the implementation detail. The following snippet fixes both defects.
guestLoginRequest: function(){
return function (dispatch) {
var ref = new Firebase("https://penguinradio.firebaseio.com");
ref.authAnonymously(function(error, authData) {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
dispatch(actions.setLoginStatus(true, authData))
console.log("dispatched");
}
});
}
}
In addition, you need to dispatch your action correctly on the Login class.
dispatch(this.props.actions.guestLoginRequest())
Your action invocation is always done by invoking dispatch. The flow should be something like this:
React component --> dispatch ---> API call (thunk middleware) --> dispatch ---> reducer
Make sure useDispatch imported
import { useDispatch } from "react-redux";
First, you need to import
import { useDispatch } from "react-redux";
then call it.
const dispatch = useDispatch();
now you are ready for use.
Related
I've created a react app using create-react-app and react-redux. I dispatch an action using mapDispatchToProps on clicking a button and it returns a payload. But when I try to retrieve the props using mapStateToProps in my component, it returns the initial state.
What am I doing wrong?
I have tried to debug thoroughly and I realize that the action is dispatched and the payload makes it to the action creator. But the reducer isn't triggered after the action is dispatched.
It might be how I am calling it or how I have set up my reducer as well.
index.js file:
import React from 'react';
import './css/index.css';
import App from './App/App';
import * as serviceWorker from './serviceWorker';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
import thunk from 'redux-thunk';
const store = createStore(rootReducer, applyMiddleware(thunk));
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
reducers/Reducer.js
reducers/Reducer.js
import { GET_RESP } from '../modules/actions'
function getInitialState () {
return {
predictions: [],
score: null
}
}
function gesResp (state, payload) {
return {
...state,
predictions: payload.predictions,
score: payload.score
}
}
export function Reducer (state, action) {
if (!state) {
return getInitialState();
}
switch(action.type) {
case GET_RESP:
return gesResp(state, action.payload);
default:
return state;
}
}
export default Reducer;
reducers/index.js
import { combineReducers } from 'redux';
import Reducer from './Reducer'
const rootReducer = combineReducers({
Reducer
});
export default rootReducer;
action.js
import axios from 'axios';
// actions:
export const GET_RESP = 'GET_RESP';
// action creators:
export function gesResp (payload) {
return {
type: GET_RESP,
payload: payload
}
}
export function fetchRecommendations (description, resp) {
let url = 'myendpointurl';
let requestPayload = {
description: description,
resp: resp
}
return (dispatch) => {
return axios.post(url, requestPayload)
.then(function(res) {
gesResp(res.data);
})
}
}
component file: (I'm only posting related code):
handleSubmit () {
this.props.fetchMyRecommendations(Desc,
this.state.htmlContent);
}
const mapStateToProps = state => {
return {
predictions: state.Reducer.predictions,
score: state.Reducer.score
};
}
const mapDispatchToProps = dispatch => {
return {
fetchMyRecommendations: (Desc, userScore) =>
dispatch(fetchRecommendations(Desc, userScore))
};
}
export default connect(mapStateToProps, mapDispatchToProps)
(HomePage);
Ideally what I want is in the mapStateToProps to return the predictions array and the resp score.
I can see that they are being returned in the network call and showing up the actions call as well. Thanks in advance to whoever can help! :)
You need to dispatch getReccommendations to actually trigger the reducer for your asynchronous action. Try the following:
export function fetchRecommendations (job_description, resume) {
let url = 'myendpointurl';
let requestPayload = {
job_description: job_description,
resume: resume
};
return (dispatch) => {
return axios.post(url, requestPayload)
.then(function(res) {
dispatch(getReccommendations(res.data));
});
}
}
Hopefully that helps!
I'm attempting to implement redux into a relatively simple app, however my actions don't seem to be triggering the reducers properly. Through console logging the action seems to be firing, but the respective reducer isn't being executed.
App.js:
import {Provider} from 'react-redux';
import configureStore from './src/config/configureStore.js';
const store = configureStore();
export default class App extends React.Component {
render() {
return (
<Provider store = {store}>
<RootStack />
</Provider>
);
}
}
configureStore.js:
import {createStore, applyMiddleware} from 'redux';
import reducers from '../reducers';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
const store = createStore (
reducers,
applyMiddleware(thunk)
);
return store;
}
actions/index.js:
export const saveRisk = (payload) => {
console.log('saved RISK!');
return (dispatch) => {
dispatch({type: 'risk_chosen',payload: payload});
}
}
reducers/index.js:
import { combineReducers } from 'redux';
import RiskReducer from './RiskReducer';
export default combineReducers({
risk_level: RiskReducer
});
RiskReducer.js
const INITIAL_STATE = {risk_level: false};
export default (risk = INITIAL_STATE, action) => {
if(action.type === 'risk_chosen') {
console.log('RISK REDUCER SUCCESSFUL')
return {
...risk, risk_level: action.payload
};
}
console.log('REDUCER RISK:');
console.log(risk);
return risk;
}
RiskTolerance.js (A child component within RootStack which is using redux):
import { connect } from 'react-redux';
import {saveRisk} from '../../actions'
#connect(state => ({risk_level: state.risk_level.risk_level}, {saveRisk}))
export default class RiskTolerance extends React.Component {
// ...
componentDidMount(){
console.log(this.props.risk_level);
// ^^returns undefined, despite the reducer initializing it to "false"
let riskVal = 'something'
this.props.saveRisk(riskVal)
}
// ...
}
EDIT: I have changed the initial value in the reducer to an appropriate object but my reducer is still not working after the action is called. Any ideas?
Thank you!
There is problem with initial state in your reducer. Make changes as shown below:
INITIAL_STATE = { risk_level: false }
Figured it out, when calling the action I needed to write:
this.props.dispatch(this.props.saveRisk(riskVal))
Thanks for your help everyone!
Problem
I wired up my react application with a Redux store, added an api action to gather data from my backend including middleware redux-promise. Most everything seems to work as I can see my store in the React web editor along with the combine reducer keys. When I have my action called, it works and console logs the completed promise. However, my reducers never run. I thought it was an issue with my dispatch on the main container, but I've tried every way that I can think of at this point - regular dispatch() and bindActionCreators. HELP!
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import promiseMiddleware from 'redux-promise';
import RootReducer from './reducers';
const createStoreWithMiddleware = applyMiddleware(promiseMiddleware)(createStore)
let store = createStore(RootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));`
Combine Reducers
import { combineReducers } from 'redux';
import ReducerGetPostings from './reducer_get_postings'
const rootReducer = combineReducers({
postingRecords: ReducerGetPostings
})
export default rootReducer;
Reducer
import { FETCH_POSTINGS } from '../actions/get_postings'
export default function (state = null, action) {
console.log('action received', action)
switch (action.type) {
case FETCH_POSTINGS:
return [ action.payload ]
}
return state;
}
Action API
import axios from 'axios';
import { url } from '../api_route';
export const FETCH_POSTINGS = 'FETCH_POSTINGS'
export function fetchpostings() {
const postingRecords = axios.get(`${url}/api/postings`)
console.log('Postings', postingRecords)
return {
type: FETCH_POSTINGS,
payload: postingRecords
};
}
Container
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { fetchpostings } from '../../actions/get_postings.js'
class Dashboard extends Component {
//....lots of other functionality already built here.
componentDidMount() {
axios.get(`${url}/api/postings`)
.then(res => res.data)
.then(
(postingRecords) => {
this.setState({
postingData: postingRecords,
postingOptions: postingRecords
});
},
(error) => {
this.setState({
error
})
}
)
// primary purpose is to replace the existing api call above with Redux Store and fetchpostings action creator
fetchpostings()
}
}
function mapDispatchToProps(dispatch) {
// return {actions: bindActionCreators({ fetchpostings }, dispatch)}
return {
fetchpostings: () => dispatch(fetchpostings())
}
}
export default connect(null, mapDispatchToProps)(Dashboard);
You are not dispatching your action, when you call fetchpostings() in componentDidMount you are calling the method imported from actions/get_postings.js, not the method that will dispatch.
Try this.props.fetchpostings() instead.
You also did not bind state to props you need to do that as well.
I am using 'react-fileupload' to upload files on my server. In case of success I receive response with content of this file. So in one component I want to upload file and change stores state and in another component I want to show that data.
But i don't know why my dispatch function doesn't work.
Component with uploader:
import React, { Component } from 'react';
import FileUpload from 'react-fileupload';
import { connect } from 'react-redux';
import { updateOverview } from '../actions/index';
import { bindActionCreators } from 'redux';
class Header extends Component {
render() {
const options = {
baseUrl: 'http://127.0.0.1:8000/api/upload_file',
chooseAndUpload: true,
uploadSuccess: function(res) {
console.log('success');
updateOverview(res.data);
},
uploadError: function(err) {
alert(err.message);
}
};
return (
<div>
<FileUpload options={options} ref="fileUpload">
<button
className="yellow darken-2 white-text btn-flat"
ref="chooseAndUpload">
Upload
</button>
</FileUpload>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ updateOverview }, dispatch);
}
export default connect(null, mapDispatchToProps)(Header);
Component where data is shown:
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Overview extends Component {
renderContent() {
console.log(this.props.overview);
if (!this.props.overview) {
return <div> Upload file!</div>;
}
return this.props.overview;
}
render() {
return (
<div>
<h1>Overview</h1>
{this.renderContent()}
</div>
);
}
}
function mapStateToProps({ overview }) {
return { overview };
}
export default connect(mapStateToProps)(Overview);
Action creator:
import { FETCH_OVERVIEW } from './types';
export function updateOverview(data) {
return { type: FETCH_OVERVIEW, payload: data };
}
reducer index.js
import { combineReducers } from 'redux';
import overviewReducer from './overviewReducer';
export default combineReducers({
overview: overviewReducer
});
overviewReducer.js
import { FETCH_OVERVIEW } from '../actions/types';
export default function(state = null, action) {
switch (action.type) {
case FETCH_OVERVIEW:
return action.payload;
default:
return state;
}
}
The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn't aware of Redux, and you don't want to pass dispatch or the Redux store to it.
Your Header component already knows how to create action.
Considering the your Home component need ,your don't need of bindActionCreators.
The correct way to do this.
const mapDispatchToProps = dispatch => {
return {
callUpdateOverview: () => {
dispatch({ updateOverview });
}
}
}
And in the Header render method :
this.props.updateOverview(res.data);
EDIT :
In your Home Component render method,
const homeThis = this; //save `this` object to some variables
^^^^^^^^^^^^^^^^^^^^^^
const options = {
baseUrl: ..,
chooseAndUpload: ..,
uploadSuccess: function (res) {
homeThis.props.callUpdateOverview();// call using `homeThis`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
};
In your code, you are calling
updateOverview(res.data);
actually which should be called like,
this.props.updateOverview(res.data);
Because, the redux will listen only to the dispatch bound actions, so to enable that, we use connect function from react-redux package, so that redux will know to update itself upon the action execution.
connect will bind your action to the component props on this.props, so it is very essential to use this.props.action() and not just action()
Here is my index.js where I initially dispatch an action to read my list of locations:
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import configureStore from './store/configureStore';
import {Provider} from 'react-redux';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import {loadLocationList} from './actions/locationActions';
import './css/styles.css';
const store = configureStore();
render(
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>,
document.getElementById('app')
);
Then here is my action where I get the data & then create an action out of it:
export function loadLocationListSuccess(alistingData) {
return { type: types.LOAD_LOCATION_LIST_SUCCESS, listingData: alistingData};
}
export function loadLocationList() {
return function(dispatch){ //we return a function that accepts a parameter, we just called it dispatch
//dispatch(fetchCallActions.fetchCallStart("")); // we dispatch a function fetchCallStart to indicate the start of our call, this is to keep in check with our asynchronous function calls
let link = 'http://example.com:8399/location';//our fetch url
console.log(link); //we log our link, just for debug purposes
return fetch(link) //start fetch
.then(function(response) {
return response.json();
}).then(function(json) {
dispatch(loadLocationListSuccess(json));
}).catch(function(ex) {
console.log('parsing failed', ex);
});
};
}
Then here is my reducer:
import * as types from '../actions/actionTypes';
export default function locationReducer(state = [], action) {
switch(action.type) {
case types.LOAD_LOCATION_LIST_SUCCESS:
return {listingData: action.listingData};
default:
return state;
}
}
Then here is my mapStateToProps & connect function:
function mapStateToProps(state, ownProps) {
return {
// we'll call this in our component -> this.props.listingData
listingData: state.listingData
};
}
export default connect(mapStateToProps, mapDispatchToProps)(homePage);
For some reason, it cannot read state.listingData or am I actually doing it wrongly? Anyone can help me with this problem?
I tried logging state.listingData and it showed undefined
Here is my configureStore:
import {createStore, applyMiddleware} from 'redux';
import rootReducer from '../reducers';
import reduxImmutableStateInvariant from 'redux-immutable-state-invariant';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk, reduxImmutableStateInvariant())
);
}
Here is my combined Reducer:
import {combineReducers} from 'redux';
import courses from './courseReducer';
import locations from './locationReducer';
const rootReducer = combineReducers({
courses,
locations
});
export default rootReducer;
Did I not connect it to the store properly?
Recent update:
Logging JSON.stringify(state) in mapStateToProps would actually shows the result. Thanks guys.
The correct path turned out to be state.locations.listingData because I think in my combined Reducer I included the reducer as locations so maybe thats why the state for it is state.locations. Hope this helps anyone with the problem.
Can you show the code of configureStore file? The problem might be there, may be you forgot to add reducer to list of reducers.
Does the action works right? Did you log data before dispatch(loadLocationListSuccess(json));?
UPD:
Because of rootReducer. Each reducer creates their own key in store. When you combine your reducers in rootReducer, like:
import locations from './locationReducer';
const rootReducer = combineReducers({
courses,
locations
});
It creates store with this kind of structure:
const store = {
courses: {},
locations: {}
}
So, after that you dispatched action and reducer changed the data to this:
const store = {
courses: {},
locations: {
listingData: someData
}
}
If you want to access to listingData like: state.listingData, you need to change a little your reducer and combineReducer to:
export default function listingData(state = {}, action) {
switch(action.type) {
case types.LOAD_LOCATION_LIST_SUCCESS:
return action.listingData;
default:
return state;
}
}
...
import listingData from './locationReducer';
const rootReducer = combineReducers({
courses,
listingData
});