I'm new in react-redux, and I have a problem with communication between reducer and store.
This is the idea on which I base:
I have a component "Login", that contains a button and two text inputs, and when i click that, I send the action to the reducer. Then, I update the state, and send it to the UI again (thats the way i understand the logic, correct me if i'm wrong). The problem occurs in the reducer, it never enter there, but yes in the action file (tested with console.logs). Maybe the connect is not working? or is in the store part?
Here I detach how I did it
action.js, with two operations only
const logout = () => {
return {
type: "USER_LOGOUT",
payload: false,
};
};
const login = () => {
return {
type: "USER_LOGIN",
payload: true,
};
};
export { logout, login };
reducer.js implementation, only change one boolean value
const initialState = {
logged: false,
};
export default (state = initialState, action) => {
if (action.type === "USER_LOGOUT") {
return {
...state,
logged: false,
};
}
if (action.type === "USER_LOGIN") {
return {
...state,
logged: true,
};
}
return state;
};
index.js (store), here's how i declare the store part
import { createStore, combineReducers } from "redux";
import loginReducer from "./reducer";
const reducers = combineReducers({
loginReducer,
});
const store = createStore(reducers);
export default store;
Login.js, only the touchable part
import { logout, login } from "../redux/actions";
import { connect } from "react-redux";
...
connect(null, { logout, login }, Login);
...
<TouchableOpacity>
...
onPress={() => checkValidation()}
...
</TouchableOpacity>
Here checkValidation, who calls the action "login"
checkValidation() =>
...
login();
...
You are not dispatching the action. To make Redux aware of an action you must dispatch it.
If you are using a class component you need to connect the component and pass it the dispatch action from redux.
I suggest you to use the hooks because its way easier.
1-Import the useDispatch hook
import { useDispatch } from "react-redux";
2-Create the dispatch:
const dispatch = useDispatch();
3-Dispatch your action:
checkValidation() =>
...
// Since your function login already returns the action object:
dispatch(login());
...
Related
I just tried make simply reducer in react redux but it never called. After a lot trial i have no idea why it's not working. console.log in action is showing but reducer never is called.
import React from "react";
import { connect } from "react-redux";
import * as actions from "store/actions";
function Login(props) {
const login = (e) => {
e.preventDefault();
props.login();
};
return (
<form onSubmit={login}>
<button> login </button>
</form>
);
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(actions.login),
};
};
export default connect(null, mapDispatchToProps)(Login);
actions file- i'm here console.log is showing correctly
import * as actionsTypes from "./actionTypes";
export const logout = () => {
return {
type: actionsTypes.AUTH_LOGOUT,
};
};
export const login = () => {
console.log("i'm here")
return {
type: actionsTypes.AUTH_LOGIN,
};
};
reducer
import * as actionTypes from "../actions/actionTypes";
const initialState = {
isLogged: false,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.AUTH_LOGIN:
return {
...state,
isLogged: true,
};
case actionTypes.AUTH_LOGOUT:
return {
...state,
isLogged: false,
};
default:
return state;
}
};
export default reducer;
many thanks for help
Probably, you forget to make a configuration of the store itself? :)
Something like that:
// at configureStore.js
import { createStore } from 'redux';
import reducer from '../path/to/your/root/reducer'; // assuming that you use combineReducer function to gather all reducers in one place
export default createStore(reducer);
Then in your app root you need to wrap entry component with the store provider:
import store from './store/configureStore';
import { Provider } from 'react-redux';
export default () => (
<Provider store={store}>
<AppRootComponent />
</Provider>
);
AppRootComponent -> your initial app component
For reference - how to configure store
UPD:
Looks like you were trying to pass the action creator to the dispatch function, instead of invoking it actually. Just make a call of that creator in the dispatch:
login: () => dispatch(actions.login()),
BTW, here is the working example of your case
I'm developing React/Redux application and I've got problem with getting one particular state from redux store after dispatching an action. I don't have any idea why is that happening, because I haven't experienced such issue with other states. Here is my code:
Reducer
import {SET_CURRENT_USER, SET_LECTURES} from '../actions/actionTypes';
import isEmpty from 'lodash/isEmpty';
const initialState = {
isAuthenticated: false,
user: {},
lectures: []
}
export default (state = initialState, action = {}) => {
switch(action.type) {
case SET_CURRENT_USER:
return {
isAuthenticated: !isEmpty(action.user),
user: action.user
};
case SET_LECTURES:
return {
lectures: action.lectures
}
default: return state;
}
}
Action creator and dispatching action
import { SET_LECTURES } from './actionTypes';
export const setLectures = (lectures) => {
return {
type: SET_LECTURES,
lectures
}
}
export const lecture = (lectures) => {
return dispatch => {
console.log(lectures);
dispatch(setLectures(lectures));
}
}
The problem is with SET_LECTURES action type, in particular lectures property of action object. In the component from which I want to get state lectures, I do mapStateToProps as follows:
const mapStateToProps = function(state) {
return {
notifications: state.notifications,
lectures: state.lectures
}
}
/*
*Code of the class
*/
export default connect(mapStateToProps, null)(AddQuestionForm);
I've skipped code which triggers dispatching action type SET_LECTURES, because it's working fine. I've also used React Developer Tools for tracking states, and there is lectures state. I just can't get this state from my component, when I do console.log(this.props.lectures) from ComponentDidMount(), it shows undefined. Could you explain what am I doing wrong here? I would appreciate any help.
You forgot about dispatch:
export const lectureAction = lectures => dispatch => {
return dispatch => {
dispatch(setLectures(lectures));
}
}
In Component:
import { bindActionCreators } from 'redux';
const mapStateToProps = function(state) {
return {
notifications: state.notifications
}
}
// use map dispatch for actions:
const mapDispatchToProps = dispatch =>
bindActionCreators({
lectures: lectureAction
}, dispatch);
/*
*Code of the class
*/
// connect map dispatch here:
export default connect(mapStateToProps, mapDispatchToProps)(AddQuestionForm);
Now you have an access to this.props.lectures(someParams) function in your Component which dispatch an action.
I have the mapStateToProps workflow down, but what if I want to respond to actions in a way that doesn't fit well into the state => props paradigm? For instance:
this.props.dispatch(saveArticle(...))
// on successful save, redirect to article page
If I'm just using regular old XHRs rather than actions, it would look something like this:
saveArticle(...).then(article => this.router.push(`/articles/${article.id}`))
It's not clear how this would fit in with the standard React/Redux workflow; I've seen people suggest that the saveArticle() action creator could fire off the router change, but I want to keep those separate; I should be able to save an article without being forced to redirect.
A workaround could be to do it in mapStateToProps; have the action set a flag or something, like articleWasSaved, and have the component that does the saving look for that prop and redirect if it sees it, but that seems really ugly, especially if multiple things are looking for that update, since it would likely require the component(s) to clear the flag.
Is there a simple/standard solution I'm missing?
Redux-thunk allows you to dispatch functions as actions. It is ideally to dispatch async operations.
Here I've created an example I think It will be useful for you:
actions.js
export const tryingAsyncAction = () => {
return {
type: 'TRYING_ASYNC_ACTION'
}
}
export const actionCompleted = () => {
return {
type: 'ACTION_COMPLETED'
}
}
export const errorAsyncAction = (error) => {
return {
type: 'ERROR_ASYNC_ACTION',
error
}
}
export function someAsynAction() {
return dispatch => {
dispatch(tryingAsyncAction())
ApiService.callToAsyncApi(...)
.then(() => {
dispatch(actionCompleted())
}, (cause) => {
dispatch(errorAsyncAction(cause))
})
}
}
reducer.js
const initialState = {
tryingAction: false,
actionCompleted: false,
error: null,
shouldRedirect: false,
redirectUrl: null
}
export default function reducer(state = initialState, action) {
switch (action.type) {
case 'TRYING_ASYNC_ACTION':
return Object.assign({}, state, {
tryingAction: true
})
case 'ACTION_COMPLETED':
return Object.assign({}, state, {
tryingAction: false,
actionCompleted: true,
shouldRedirect: true,
redirectUrl: 'someUrl'
})
case 'ERROR_ASYNC_ACTION':
return Object.assign({}, state, {
tryingAction: false,
actionCompleted: false,
error: action.error
})
default:
return state
}
}
Your createStore file
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk' //npm install --save redux-thunk
//Other imports...
const store = createStore(
reducer,
applyMiddleware(
thunkMiddleware
)
)
YourComponent.js
componentWillReceiveProps(nextProps){
if(nextProps.shouldRedirect && nextProps.redirectUrl)
this.router.push(`/articles/${article.id}`)
}
Let me know if there is something you dont understand. I will try to clarify
You could make use of react-thunk in this case.
actions/index.js
export function saveArticle(data) {
return (dispatch, getState) => (
api.post(data).then(response => {
dispatch({ type: 'SAVE_ARTICLE', payload: response })
return response;
})
)
}
reducer/index.js
import { combineReducers } from 'redux';
const initialState = {
list: [],
current: null,
shouldRedirect: false,
redirectTo: null
};
export function articles(state = initialState, action) {
switch(action.type) {
case 'SAVE_ARTICLE':
return {
shouldRedirect: true,
redirectTo: '/some/url',
current: action.payload,
list: [...state.list, action.payload]
};
default: return state;
}
}
export default combineReducers({ articles });
store/index.js
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)
);
component/index.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as Actions from 'actions/index';
class MyComponent extends Component {
_handleSubmit = () => {
// get form values somehow...
// const values = getFormValues();
this.props.saveArticle(values).then(response => {
// you can handle you redirect here as well,
// since saveArticle is returning a promise
});
};
componentWillReceiveProps(nextProps) {
// you can handle the redirection here listening to changes
// on shouldRedirect and redirectTo that will be triggered
// when the action 'SAVE_ARTICLE' is dispatched
if(nextProps.shouldRedirect && nextProps.redirectTo) {
this.routes.push(nextProps.redirectTo);
}
}
render() {
// just an example
return (
<form onSubmit={this._handleSubmit}>
{ /* ... other elements here */ }
</form>
)
}
}
export default connect(
state => ({
articles: state.articles.list,
article: state.articles.current,
redirectTo: state.articles.redirectTo,
shouldRedirect: state.articles.shouldRedirect
}),
Actions
)(MyComponent);
PS: I'm using some babel syntax sugar here, so make sure you're the following presets are set in your .babelrc.
es2015
stage-2
stage-0
react
I'm working on setting up a user login screen in React Native using Recompose, with separate actions and reducer files, but my reducer is never being called. Currently, there is just a login button that triggers a doUserLogin() recompose handler:
loginScreen.js
import React from 'react';
import { Button, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { withHandlers, compose } from 'recompose';
import { loginUser } from './user/userActions';
const LoginScreen = ({ user, doUserLogin }) => {
return (
<View style={styles.loginContainer}>
{user ? <Text>Hi, {user.name}!</Text> : <Text>NOT Logged in!</Text>}
<Button title="Log In" onPress={doUserLogin} />
</View>
);
};
export default compose(
connect((state, props) => ({
...state.user,
})),
withHandlers({
doUserLogin: props =>
(event) => {
console.log('LOGIN USER IN HANDLER'); // <-- THIS IS WORKING
loginUser();
},
}),
)(LoginScreen);
The doUserLogin() handler in turn calls loginUser() in my actions file:
userActions.js:
import { LOGIN_REQUEST } from './actionTypes';
export const loginUser = () => {
return (dispatch) => {
console.log('In action'); // <-- THIS IS WORKING
dispatch({ type: LOGIN_REQUEST });
};
};
So far, so good. However, when I dispatch(), my reducer is never called. But the reducer is picking up other actions (from navigation, etc.) - it simply isn't receiving the action from loginUser() above:
userReducer.js:
import { LOGIN_REQUEST } from './actionTypes';
const userReducer = (state = initialState, action) => {
console.log('In reducer'); <-- ** THIS IS NEVER CALLED **
switch (action.type) {
case LOGIN_REQUEST:
return Object.assign({}, state, {
isFetching: true,
});
case LOGOUT:
return initialState;
default:
return state;
}
};
export default userReducer;
Any suggestions would be greatly appreciated.
Ok, looks like I was able to figure this out. In a nutshell, in loginScreen.js I needed to add mapStateToProps and mapDispatchToProps functions, which are passed to connect. withHandlers can then dispatch the loginUser() function in my actions file as a prop.
updated loginScreen.js
import React from 'react';
import { Button, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { withHandlers, compose } from 'recompose';
import { loginUser } from './user/userActions';
const LoginScreen = ({ user, doUserLogin }) => {
return (
<View style={styles.loginContainer}>
{user ? <Text>Hi, {user.name}!</Text> : <Text>NOT Logged in!</Text>}
<Button title="Log In" onPress={doUserLogin} />
</View>
);
};
const mapStateToProps = state => ({
...state.user,
});
const mapDispatchToProps = dispatch => ({
loginUser: () => {
dispatch(loginUser());
},
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withHandlers({
doUserLogin: props =>
() => {
console.log('LOGIN USER IN HANDLER');
props.loginUser();
},
}),
)(LoginScreen);
Any additional advice/suggestions would still be appreciated.
Actually, for this particular case, you can dismiss completely withHandlers helper.
You only need to pass the action creator to the react-redux connect function, in order to bind it to the dispatch function, just as you shown. Even more, check connect docs. You can access the props of the component, in the 3rd parameter of connect, and further create handlers that depend on props.
In your case it could be something like this
const mergeProps = (stateProps, dispatchProps, ownProps) => {
return Object.assign({}, ownProps, stateProps, dispatchProps, {
doUserLogin: () => {
console.log('LOGIN USER IN HANDLER');
console.log('accessing a prop from the component', ownProps.user);
dispatchProps.loginUser();
}
});
}
export default connect(mapStateToProps,
mapDispatchToProps,
mergeProps)(LoginScreen);
Notice how we can create new functions, that will be available as a new prop to the component, in a similar way to withHandler helper
I just started to experiment with react and redux and I face couple of issues on the way.
When I try to render async data on route change the dispatched action is getting fired twice. First is undefined and than comes the real data.
Here is my store
import { createStore, combineReducers, applyMiddleware } from 'redux'
import createLogger from 'redux-logger'
import thunk from 'redux-thunk'
import { routerReducer, routerMiddleware, push } from 'react-router-redux'
import reducers from '../reducers'
import { browserHistory } from 'react-router';
const middleware = [ thunk ];
if (process.env.NODE_ENV !== 'production') {
middleware.push(createLogger());
}
middleware.push(routerMiddleware(browserHistory));
// Add the reducer to your store on the `routing` key
const store = createStore(
combineReducers({
reducers,
routing: routerReducer
}),
applyMiddleware(...middleware),
)
export default store;
reducer
export const RESOLVED_GET_PROFILE = 'RESOLVED_GET_PROFILE'
const profileReducer = (state = {}, action) => {
switch (action.type) {
case 'SET_PROFILE':
return {profile: action.profile}
default:
return state;
}
};
export default profileReducer;
actions
import * as types from './actionTypes';
import Api from '../middleware/Api';
export function getProfile() {
return dispatch => {
dispatch(setLoadingProfileState()); // Show a loading spinner
Api.get('profile').then(profile => {
dispatch(doneFetchingProfile);
dispatch(setProfile(profile));
}).catch(error => {
dispatch(showError(error));
throw(error);
});
}
}
function setProfile(data) {
return {
type: types.SET_PROFILE,
profile: data
}
}
function setLoadingProfileState() {
return {
type: types.SHOW_SPINNER,
loaded: false
}
}
function doneFetchingProfile() {
return {
type: types.HIDE_SPINNER,
loaded: true
}
}
function showError() {
return {
type: types.SHOW_ERROR,
loaded: false,
error: 'error'
}
}
and here is my component
import React, {PropTypes, Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import * as profileActions from '../../../actions/profileActions';
class Profile extends Component {
static propTypes = {
profile: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this.state = {
profile:{
username: '',
password: '',
email: ''
}
}
this.onUpdate = this.onUpdate.bind(this)
}
onUpdate(event) {
alert()
}
componentDidMount() {
//here I dispatch the action
this.props.actions.getProfile()
}
componentWillReceiveProps(nextProps) {
}
render() {
console.log(this.props)
//this.props.profile on first is undefined and then filled
const { profile } = this.props.profile
return (
<div>
</div>
);
}
}
function mapStateToProps(state) {
return {
profile: state.default.profile,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(profileActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
what do I wrong?
You said //this.props.profile on first is undefined and then filled
That's because in the first render, state.profile is undefined, until the request response arrives and the setProfile action is dispatched.
There's also the problem Andrew noted that you're calling dispatch(doneFetchingProfile). Since you're using redux-thunk, that will trigger calling doneFetchingProfile(dispatch, getState), but the action HIDE_SPINNER will never get dispatched.
UPDATE: There's nothing wrong with your code. You can see before SHOW_SPINNER the output of console.log(this.props) and there's no profile because there's no profile in state as well.
Then when your request succeeds, profile is set in state, then passed to your component and then you can see in the log that profile is set. Those are not actions dispatched, this is the log of your props.
The first time is undefined because the initial state declared in the reducer is {} (there's no profile here as well).
If you change
const profileReducer = (state = {}, action) => {
to
const profileReducer = (state = {profile: 'some initial value'}, action) => {
you'll see that the first console.log(this.props) will show profile with the value 'some initial value' and then change to the remote data.
This is what happening here
Your component render and show undefined on console because there is no profile data so far.
After component mount it call componentDidmount which fire an action to fetch data from url.
You get data from api and update the redux state which update your component as well.
Therefore you render function is called again and this time it shows the profile data.
There is nothing dispatching two times. The code is perfectly fine.
You dispatching 2 actions
dispatch(doneFetchingProfile);
dispatch(setProfile(profile));
First of them have no data, and it's look like tihs action set to state some data and update your component.