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
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 a bit confused because this method of updating my store has worked for me in the past - Though the setup is a little different.
In its simplest form, my action gets called, but my dispatch seems to be getting neglected
(reduxMulti and thunk middleware are applied to my store which enables async and multiple actions to be dispatched. )
export function modalDocDeleteAction() {
// This logs as expected
console.log("MODAL/ACTIONS.JS // Document Delete Action Fired");
return dispatch => {
// We don't make it here :(
console.log("MODAL/ACTIONS.JS // Document Delete Action Fired In Return");
dispatch([{ type: MODAL_DOC_DELETE }, modalOpenAction()]);
};
}
Some random JS file:
I can successfully fire my action if I dispatch as below, so I don't think it's anything to do with my setup:
import { modalDocDeleteAction } from "../presentation/modal/actions";
import store from "../container/store";
export function someFunction() {
store.dispatch(modalDocDeleteAction());
});
From component: (I've left comments in as I think I may need to use mapStateToProps in the future)
When I try to dispatch from a component is where I seem to be having issues. I'm confused as this method has worked for me previously... But this is the first time I've tried to update my store to modify a component within another component (Hopefully that makes sense)
Imports:
import { connect } from "react-redux";
import { modalDocDeleteAction } from "./modal/actions"; // This is the correct location
Component:
const ProdRow = ({ document, index, edit, view, promoteRevert, remove }) => {
// I've removed a bunch of unnecessary stuff out of here.
return (
<TableRow>
<TableCell colSpan="2">
<ActionsIcon
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
onClick={modalDocDeleteAction} // This fires my console log (above), but never makes it to the actual dispatch
>
<path
id="cross"
d="M21,6.611,19.389,5,13,11.389,6.611,5,5,6.611,11.389,13,5,19.389,6.611,21,13,14.611,19.389,21,21,19.389,14.611,13Z"
transform="translate(-5 -5)"
fill="#3c3c3c"
/>
<title>Delete</title>
</ActionsIcon>
)}
</TableCell>
</TableRow>
);
};
Redux / Export:
// const mapStateToProps = state => ({
// modalTitle: state.modalReducer.modalTitle,
// modalText: state.modalReducer.modalText,
// closeText: state.modalReducer.closeText,
// continueText: state.modalReducer.continueText,
// completeAction: state.modalReducer.completeAction,
// visible: state.modalReducer.visible
// });
const mapDispatchToProps = dispatch => ({
modalDocDeleteAction: () => dispatch(modalDocDeleteAction())
});
export default connect(
// mapStateToProps,
null,
mapDispatchToProps
)(Results);
This is the exact setup I use to modify my modal component within itself - I think because I'm trying to update the store "externally" is where the problems are coming from. Any help would be great as I'm very stuck! Thanks!
Redux bits:
ACTIONS.js Removed excess code
import {
MODAL_OPEN,
MODAL_CLOSE,
MODAL_COMPLETE_USER_ACTION,
MODAL_DOC_DELETE,
MODAL_DOC_PUBLISH,
MODAL_DOC_REVERT,
MODAL_DOC_CREATE_EXIT,
MODAL_DOC_EDIT_EXIT
} from "./actionTypes";
import store from "../../container/store";
export function modalOpenAction() {
console.log("MODAL/ACTIONS.JS // Modal Open Action Fired");
return dispatch => {
dispatch({ type: MODAL_OPEN });
};
}
export function modalDocDeleteAction() {
console.log("MODAL/ACTIONS.JS // Document Delete Action Fired");
console.log(store.getState());
return dispatch => {
console.log(
"MODAL/ACTIONS.JS // Document Delete Action Fired In Return"
);
dispatch([{ type: MODAL_DOC_DELETE }, modalOpenAction()]);
};
}
REDUCER.js Removed excess code
const initialState = {
modalTitle: "Placeholder Title",
modalText: "Placeholder Text",
closeText: "Placeholder Close",
continueText: "Placeholder Continue",
visible: false,
completeAction: false
};
function modalReducer(state = initialState, action) {
switch (action.type) {
case "MODAL_OPEN":
return {
...state,
visible: true,
completeAction: false
};
case "MODAL_DOC_DELETE":
return {
...state,
modalTitle: "Delete Document?",
modalText:
"Deleting a document will permanently remove it from S3",
closeText: "No, Keep Document",
continueText: "Yes, Delete Document"
};
STORE.js
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reduxMulti from "redux-multi";
import notificationReducer from "../presentation/notification/reducer";
import modalReducer from "../presentation/modal/reducer";
const reducers = combineReducers({
notificationReducer,
modalReducer
});
const store = createStore(reducers, applyMiddleware(thunk, reduxMulti));
console.log("STORE.JS // Default State:");
console.log(store.getState());
export default store;
Working example:
Following the exact same process as above in my Modal component works as expected:
Imports:
import { connect } from "react-redux";
import Button from "../../form/button";
import { modalCloseAction, modalCompleteUserAction } from "./actions";
Component (Styled Components):
function Component({ ...props }) {
const {
modalTitle,
modalText,
closeText,
continueText,
visible,
modalCloseAction,
modalCompleteUserAction
} = props;
console.log("MODAL.JSX // Modal State:");
console.log(props);
return (
<div>
<ModalContainer visible={visible}>
<Overlay visible={visible} />
<Modal visible={visible}>
<ModalTitle>{modalTitle}</ModalTitle>
<ModalText>{modalText}</ModalText>
<ModalButtons>
<ModalButton
buttonStyle="Back"
onClick={modalCloseAction}
>
{closeText}
</ModalButton>
<ModalButton
buttonStyle="Primary"
onClick={modalCompleteUserAction}
>
{continueText}
</ModalButton>
</ModalButtons>
</Modal>
</ModalContainer>
</div>
);
}
Redux/Export
const mapStateToProps = state => ({
modalTitle: state.modalReducer.modalTitle,
modalText: state.modalReducer.modalText,
closeText: state.modalReducer.closeText,
continueText: state.modalReducer.continueText,
completeAction: state.modalReducer.completeAction,
visible: state.modalReducer.visible
});
const mapDispatchToProps = dispatch => ({
modalCloseAction: () => dispatch(modalCloseAction()),
modalCompleteUserAction: () => dispatch(modalCompleteUserAction())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Component);
Dispatch Actions:
export function modalCloseAction() {
return dispatch => {
dispatch({ type: MODAL_CLOSE });
};
}
export function modalCompleteUserAction() {
return dispatch => {
dispatch([{ type: MODAL_COMPLETE_USER_ACTION }, modalCloseAction()]);
};
}
I am trying to learn the react and for that I am trying to create a sample todo app. I have a python flask backend which servers as REST server and react as web server.
Everything works find an I am able to show todos and delete particular todo as well. However now I have started learning Redux, and that seems really confusing.
I am not sure how to make call to my rest server. Following just returns promise, not sure how to get the data, rather than promise.
store.js
import axios from 'axios'
import { createStore } from 'redux'
export const ADD_TODO = 'ADD_TODO'
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
export const listTodo = todos => ({
type: 'LIST_TODO',
todos
})
const add_todo = (id, text) => {
return axios.post("http://localhost:5001/todos", {id:id, data:text})
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(listTodo(Response.data))
})
}
const initialState ={
todos: {},
new_todo: ''
}
function todoApp(state = initialState, action) {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
return Object.assign({}, state, {
new_todo: action.text
})
default:
return state
}
}
const store = createStore(todoApp)
export default store
app.js
import React, {Component} from 'react'
import {connect} from 'react-redux'
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo('testing')}>fetch_Data</button>
</div>
);
}
}
export default connect() (App)
index.js
ReactDOM.render(<Provider store={store}> <App /> </Provider>,
document.getElementById('root'));
Firstly, you should export the actions you have created which will then be imported and used in the components using the connect HOC.
You can dispatch the 'fetch_data' action to get the data in your component. Also, you can dispatch 'addTodo' action to add new todo in the list.
export const ADD_TODO = 'ADD_TODO';
export const GET_TODO = 'GET_TODO';
export const fetch_data = () => {
return (dispatch) => axios.get("http://localhost:5001/todos")
.then(response => {
dispatch({type: GET_TODO, todos: response.data});
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
});
Use the actions constants like ADD_TODO, GET_TODO to save or to update the redux state in reducers
const todoApp = (state = initialState, action) => {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
const todos = {...state.todos};
todos[action.id] = action.text;
return Object.assign({}, state, {
todos: todos
});
case GET_TODO:
return Object.assign({}, state, {
todos: action.todos
});
default:
return state
}
}
Importing the actions and then call the function you have added in the 'mapDispatchToProps' to dispatch the actions.
import React, {Component} from 'react'
import {connect} from 'react-redux';
import { addTodo, fetch_data } from "../store";
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo(todoId, 'testing')}>fetch_Data</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
todos: state.todoApp.todos
});
const mapDispatchToProps = (dispatch) => ({
addTodo: (id, text) => dispatch(addTodo(id, text)),
fetch_data: () => dispatch(fetch_data())
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
redux is based on actions and reducers, basically reducers are pure functions which means no side effects as for example api calls, I'd advice you read more about redux and how to use redux with redux-chunk for making api calls
You make this work like this. You need to dispatch action when you have response.
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
})
I am new to React/Redux, and appreciate your help. I am taking a Udemy course on this topic. The course instructor creates a component like this.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions';
class User extends Component {
componentDidMount(){
this.props.fetchUser(this.props.userId);
}
render(){
const { user } = this.props;
if(!user) return null;
return(
<div className="header"> User Info: {user.name}</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return { user: state.users.find( user => user.id === ownProps.userId)};
};
export default connect(mapStateToProps, { fetchUser })(User)
my question: why inside the componentDidMount() he is prefixing fetchUsers() with this.props?
it is not the case that he is passing fetchUsers() as props from the parent component. This is how the parent is using this component <User userId={post.userId}/>
Note: this code works
It is because of this line :
export default connect(mapStateToProps, { fetchUser })(User)
the second parameter to connect is called mapDispatchToProps, It adds the actions to props
From the docs :
connect can accept an argument called mapDispatchToProps, which lets
you create functions that dispatch when called, and pass those
functions as props to your component.
const mapDispatchToProps = dispatch => {
return {
// dispatching plain actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' })
}
}
Your code is using the “object shorthand” form.
The way the mapDispatchToProps in the example is shorthanded. It might be easier to tell what is going if it was written like so:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions';
class User extends Component {
componentDidMount(){
this.props.fetchUser(this.props.userId);
}
render(){
const { user } = this.props;
if(!user) return null;
return(
<div className="header"> User Info: {user.name}</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return { user: state.users.find( user => user.id === ownProps.userId)};
};
const mapDispatchToProps = () => ({
fetchUser
});
export default connect(mapStateToProps, mapDispatchToProps)(User)
Maybe this shows it more clearly, but the dispatch function (fetchUser) is being mapped to the components properties. Just like the state value (user) is being mapped to the properties of the component. I think you just got confused because of the shorthand that was used.
I am learning react native and i am building an application. For some concepts, I am not able to understand where the magic happens. I am using redux store for the managing the data.
I have a stateless login component.
export class Login extends Component {
onChangeText = (key, value) => {
this.props.user[key] = value
}
render() {
const { user, fetchUserDetails } = this.props
return (
<View style={styles.container}>
<Text style={styles.heading}>Login</Text>
<TextInput
placeholder='Email'
onChangeText={val => this.onChangeText('email', val)}
value={user.email}
/>
<TextInput
placeholder='Password'
onChangeText={val => this.onChangeText('password', val)}
value={user.password}
/>
<TouchableOpacity onPress={this.fetchUserDetails(user)}>
<View style={styles.button}>
<Text style={styles.buttonText}>Login</Text>
</View>
</TouchableOpacity>
</View>
)
}
}
This is my Login Container
class LoginContainer extends Component {
render () {
return (
<Login/>
)
}
}
const mapStateToProps = (state) => ({
user: state.loginReducer.user,
})
const mapDispatchToProps = {
...fetchUserDetails,
}
export default connect(mapStateToProps, mapDispatchToProps)(Login)
my reducer looks like this:
const initialState = {
user: {
email: '',
password: '',
}
}
const loginReducer = (state = initialState, action) => {
switch(action.type) {
case GET_USER:
return Object.assign({}, state, {
user: action.user
})
default:
return state
}
return state
}
export default loginReducer
My actions look something like this:
export const GET_USER = 'GET_USER'
export function fetchUserDetails (user) {
console.log("executing fetch user action")
if (user === '')
{
alert('please complete form')
}
return {
type: GET_USER,
user
}
}
My root reducer:
import { combineReducers } from 'redux';
import loginReducer from './loginReducer'
const rootReducer = combineReducers({
loginReducer
})
export default rootReducer
My configure Store:
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import rootReducer from './reducers'
const persistConfig = {
key: 'mykey',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = createStore(persistedReducer)
const persistedStore = persistStore(store)
export default store
I need to have a stateless component which updates directly the state of the user attributes in the redux store. I am not able to follow how the state or actions will be passed to the Login component. Any Explanation will be appreciated.
Managing redux store in react-native is basically same as you do in react.
From what I understand you are trying to store user details in redux store on every onChangeText event and reflect the updated state in Login component.
Firstly you should use a separate action reducer pair for setting user details in redux. Also You most probably want to call some API on form submission and store the response in redux, For that you might need another pair of action and reducer. I'll leave that to you
Here's how u can manage user-details in redux...
Your stateless login component.
export class Login extends Component {
onChangeText = (value, key) => {
this.props.setUserDetails({
...this.props.user,
[key]: value
})
}
render() {
const { user, onSubmitForm } = this.props
console.log('user===>', this.props);
return (
<View style={styles.container}>
<Text style={styles.heading}>Login</Text>
<TextInput
placeholder='Email'
onChangeText={val => this.onChangeText(val, 'email')}
placeholderTextColor={'rgba(0,40,70,0.5)'}
value={user.email}
/>
<TextInput
placeholder='Password'
onChangeText={val => this.onChangeText(val, 'password')}
placeholderTextColor={'rgba(0,40,70,0.5)'}
value={user.password}
/>
<TouchableOpacity onPress={() => onSubmitForm(user)}>
<View style={styles.button}>
<Text style={styles.buttonText}>Login</Text>
</View>
</TouchableOpacity>
</View>
)
}
}
...
Your Login Container.
class LoginContainer extends Component {
onSubmitForm = () => {
// Checking Validations
const { name, email } = this.props;
if (!name || !email) {
alert('Please fill the form')
return;
}
// call some API for verification and handle the response here
}
render () {
return (
<Login
user={this.props.user}
setUserDetails={this.props.setUserDetails}
onSubmitForm={this.onSubmitForm}
/>
)
}
}
const mapStateToProps = (state) => ({
user: state.userReducer.user,
})
const mapDispatchToProps = dispatch => ({
setUserDetails: payload => dispatch(setUserDetails(payload)),
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer)
...
Your Reducer for setting user details
const initialState = {
user: {
email: '',
password: '',
}
}
const userReducer = (state = initialState, action) => {
switch(action.type) {
case 'SET_USER_DETAILS':
return Object.assign({}, state, {
user: action.user
})
default:
return state
}
return state
}
export default userReducer
...
Your store will remain same and rootReducer should be
import { combineReducers } from 'redux';
import userReducer from './reducer'
const rootReducer = combineReducers({
userReducer
})
export default rootReducer
...
Finally your Action
export const SET_USER_DETAILS = 'SET_USER_DETAILS'
export function setUserDetails (user) {
return {
type: 'SET_USER_DETAILS',
user
}
}
...
Hope it helps.
Hope that helps:
Login:
You must NEVER update a component props inside the said component.
From the React documentation:
Props are Read-Only
If you want your state (the truth) to be stored in the login component, then store it as a proper state and send this local state on submit:
onChangeText = (key, value) => {
this.setState((state) => ({ ...state, [key] => value}))
}
However, if you want to store your state in redux, you will need to create an action that can be triggered to update the redux state. This action needs to be passed to your component props and called like this onChangeText={val => this.props.onChangeText('email', val)}
Also, your calling the fetchUserDetails function on render, where you should be passing a callback. this.fetchUserDetails does not exists, this.props.fetchUserDetails does. The login code becomes
<TouchableOpacity onPress={() => fetchUserDetails(user)}>
Login Container:
mapDispatchToProps must be a function that takes dispatch as first parameter OR an object where each function is an action creator. From the Redux documentation:
If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props.
So the code you wrote:
const mapDispatchToProps = {
...fetchUserDetails,
}
Is equivalent to this code
function mapDispatchToProps(dispatch) {
return {
fetchUserDetails: (user) => dispatch(fetchUserDetails(user))
},
}
The dispatch function is where the magic happens, every action that is dispatched is passed down to your reducers where you can create a new state based on the action.