I am trying to get redux working in my react-native app. Basically, I have a signIn action defined in my authActions.js file:
const signInAction = () => {
return {
type: 'signIn',
};
};
export { signInAction };
Then I have an authReducer defined as this in authReducer.js:
const initialState = {
isAuthenticated: false,
}
const authReducer = (state = initialState, action) => {
switch(action.type) {
case "signIn":
return Object.assign({}, state, {
isAuthenticated: true,
})
default: return state;
}
};
export default authReducer;
I combine that reducer in my rootReducer.js file
import { combineReducers } from 'redux';
import auth from 'app/src/redux/reducers/authReducer.js';
const rootReducer = combineReducers({
auth,
});
export default rootReducer;
and then created a store in reduxIndex.js:
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from 'app/src/redux/reducers/rootReducer.js';
let store = createStore(rootReducer, applyMiddleware(thunkMiddleware));
export default store;
I wrapped my app in a <Provider> component, and that seems to be working fine (I can read from the state and see the value of isAuthenticated. However, when I try to dispatch an action using mapDispatchToProps in one of my views the function is undefined:
// More imports
// ...
import { connect } from 'react-redux';
import { signInAction } from 'app/src/redux/actions/authActions.js';
const mapStateToProps = (state) => {
return {};
}
const mapDispatchToProps = (dispatch) => {
return {
onSignIn: () => { dispatch(signInAction) },
};
}
class SignIn extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
}
}
onSignInPress() {
// ******* this is where the error occurrs ****
this.props.onSignIn();
}
render() {
const {navigation} = this.props;
return (
<View style={SignInStyles.container}>
<ScrollView>
<View>
<Button
large
title="SIGN IN"
backgroundColor={colors.primary}
onPress={this.onSignInPress}
/>
</View>
</ScrollView>
</View>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(SignIn);
I cant really see where I am going wrong, but im sure its a simple mistake somewhere. The specific error I get is :
"undefined is not an object. Evaluating this.props.onSignIn"
The onSignInPress callback isn't bound to any particular object, so when it gets called this is undefined.
The easy way to fix it is to use arrow syntax to make it always be bound. In your class definition:
onSignInPress = () => {
this.props.onSignIn();
}
Google found me this Medium article from Miron Machnicki which explains the differences and possible alternative syntaxes in pretty good detail.
Related
I am using react-redux with redux and redux-toolkit. And according to this example, i created an async dispatch that calls the reducer action when resolved.
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
export const BlogSlice = createSlice({
name: "Blog",
initialState: {
BlogList: null,
},
reducers: {
getBlogList: (state, action) => {
console.log(action.payload);
state.BlogList = action.payload;
}
},
});
export const { getBlogList } = BlogSlice.actions;
export const getBlogListAsync = (user_id) => (dispatch) => {
axios.get(`/api/blog/getblogs/${user_id}`).then((res) => {
console.log(res.data);
dispatch(getBlogList(res.data.result));
});
};
export const selectBlogList = (state) => state.Blog.BlogList;
export default BlogSlice.reducer;
I have used it in a component accordingly so that, the component dispatches getBlogListAsync and that logs the res.data but getBlogList is not being dispatched. I tried putting other console.log() but don't understand what is wrong.
A similar Slice is working perfectly with another Component.
It is hard to say for sure what's wrong here because there is nothing that is definitely wrong.
res.data.result?
You are logging res.data and then setting the blog list to res.data.result. My best guess as to your mistake is that res.data.result is not the the correct property for accessing the blogs, but I can't possibly know that without seeing your API.
console.log(res.data);
dispatch(getBlogList(res.data.result));
missing middleware?
Is there any chance that "thunk" middleware is not installed? If you are using Redux Toolkit and omitting the middleware entirely, then the thunk middleware will be installed by default. Also if this were the case you should be getting obvious errors, not just nothing happening.
it seems fine...
I tested out your code with a placeholder API and I was able to get it working properly. Maybe this code helps you identify the problem on your end. Code Sandbox Demo.
import React from "react";
import { createSlice, configureStore } from "#reduxjs/toolkit";
import axios from "axios";
import { Provider, useDispatch, useSelector } from "react-redux";
export const BlogSlice = createSlice({
name: "Blog",
initialState: {
BlogList: null
},
reducers: {
getBlogList: (state, action) => {
console.log(action.payload);
state.BlogList = action.payload;
}
}
});
export const { getBlogList } = BlogSlice.actions;
const store = configureStore({
reducer: {
Blog: BlogSlice.reducer
}
});
export const getBlogListAsync = (user_id) => (
dispatch: Dispatch
) => {
// your url `/api/blog/getblogs/${user_id}`
const url = `https://jsonplaceholder.typicode.com/posts?userId=${user_id}`; // placeholder URL
axios.get(url).then((res) => {
console.log(res.data);
// your list: res.data.result <-- double check this
const list = res.data; // placeholder list
dispatch(getBlogList(list));
});
};
export const selectBlogList = (state) => state.Blog.BlogList;
const Test = () => {
const dispatch = useDispatch();
const blogs = useSelector(selectBlogList);
const user_id = "1";
return (
<div>
<button onClick={() => dispatch(getBlogListAsync(user_id))}>
Load Blogs
</button>
<h3>Blog Data</h3>
<div>{JSON.stringify(blogs)}</div>
</div>
);
};
export default function App() {
return (
<Provider store={store}>
<Test />
</Provider>
);
}
Trying to get user input with action,all is working i get my console.logs about how inputVal changes,but when I try to print this in i get undefined in console
Should I use like mapDispatchToProps or I don't need this,since I'm passing actions as second param into mapStateToProps
actions:
export const inputChange = val => {
return {
type: INPUT_CHANGE,
payload: val
};
};
reducer:
import { INPUT_CHANGE } from './actionTypes';
const initialState = {
inputVal: ''
};
export default (state = initialState, action) => {
switch (action.type) {
case INPUT_CHANGE:
return {
...state,
inputVal: action.payload
};
default:
return state;
}
};
mainPage:
const mapStateToProps = state => {
console.log(state);
return state;
};
class MainPage extends Component {
onInput = e => {
this.props.inputChange(e.target.value);
console.log(this.props.inputChange(e.target.value));
};
render() {
console.log(this.props.inputVal);
return (
<div>
<input onChange={this.onInput}></input>
<p>{this.props.}</p>
</div>
);
}
}
export default connect(
mapStateToProps,
{
addToCart,
removeFromCart,
selectItem,
inputChange
}
)(MainPage);
combinedReducers:
import { combineReducers } from 'redux';
import AddItem from './addItem/reducer';
import InputReducer from './reducerInput';
export default combineReducers({
AddItem,
InputReducer
});
I've tried to this.props.inputVal.
Since you have combineReducers, you should use these keys to access in mapStateToProps.
From the redux docs:
The state produced by combineReducers() namespaces the states of each
reducer under their keys as passed to combineReducers()
You can control state key names by using different keys for the
reducers in the passed object. For example, you may call
combineReducers({ todos: myTodosReducer, counter: myCounterReducer })
for the state shape to be { todos, counter }.
So your mapStateToProps must be like:
const mapStateToProps = state => {
console.log(state);
return {
inputVal: state.InputReducer.inputVal
}
};
A minimal working code sandbox:
https://codesandbox.io/s/cold-meadow-pxtu3
I'm new to react and reactNative.
What is "dispatch is not a function. dispatch is an instance of Object."?
mapStateToProps works well.
However, mapDispatchToProps don't work.
I need to handle the nested action.
My Question is that
1. How Can I solve this problem(I want to just dispatch.)?
My code is below.
import React, { Component } from 'react'
import { View, Text } from 'react-native'
import { connect } from 'react-redux'
class User extends Component<Props> {
render() {
return (
<View>
<Text>{this.props.name}</Text>
<Text onPress={this.props.onKabaya}>kabaya?</Text>
</View>
);
}
}
const mapStateToProps = state => ({
name: state.User.user.name
})
const mapDispatchToProps = dispatch = ({
onKabaya: state => dispatch({ type: 'ADD_XXX' })
})
export default connect(mapStateToProps, mapDispatchToProps)(User);
//reducer
const INITIAL_STATE = { //nested action?
user: {
name: 'JOE'
},
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'ADD_XXX':
return {
user: {
name: 'XXX'
}
};
default:
return state;
}
}
Is there js ninja?
thanks.
When I submit my SignIn Form validateAndSignInUser is called and this dispatch signInUser that sends to backend an email and password to get a session token. That works right.
After signInUser returns values signInUserSuccess is dispatched, and I verified this works.
But after that the UserReducer is not beign activated and I don't understand why. What is wrong?
I have this action in my SignInFormContainer.js:
import { reduxForm } from 'redux-form';
import SignInForm from '../components/SignInForm';
import { signInUser, signInUserSuccess, signInUserFailure } from '../actions/UsersActions';
const validateAndSignInUser = (values, dispatch) => {
return new Promise ((resolve, reject) => {
let response = dispatch(signInUser(values));
response.payload.then((payload) => {
// if any one of these exist, then there is a field error
if(payload.status != 200) {
// let other components know of error by updating the redux` state
dispatch(signInUserFailure(payload));
reject(payload.data); // this is for redux-form itself
} else {
// store JWT Token to browser session storage
// If you use localStorage instead of sessionStorage, then this w/ persisted across tabs and new windows.
// sessionStorage = persisted only in current tab
sessionStorage.setItem('dhfUserToken', payload.data.token);
// let other components know that we got user and things are fine by updating the redux` state
dispatch(signInUserSuccess(payload));
resolve(); // this is for redux-form itself
}
}).catch((payload) => {
// let other components know of error by updating the redux` state
sessionStorage.removeItem('dhfUserToken');
dispatch(signInUserFailure(payload));
reject(payload.data); // this is for redux-form itself
});
});
}
const mapDispatchToProps = (dispatch) => {
return {
signInUser: validateAndSignInUser /*,
resetMe: () => {
// sign up is not reused, so we dont need to resetUserFields
// in our case, it will remove authenticated users
// dispatch(resetUserFields());
}*/
}
}
function mapStateToProps(state, ownProps) {
return {
user: state.user
};
}
// connect: first argument is mapStateToProps, 2nd is mapDispatchToProps
// reduxForm: 1st is form config, 2nd is mapStateToProps, 3rd is mapDispatchToProps
export default reduxForm({
form: 'SignInForm',
fields: ['email', 'password'],
null,
null,
validate
}, mapStateToProps, mapDispatchToProps)(SignInForm);
This three actions in UserActions.js
import axios from 'axios';
//sign in user
export const SIGNIN_USER = 'SIGNIN_USER';
export const SIGNIN_USER_SUCCESS = 'SIGNIN_USER_SUCCESS';
export const SIGNIN_USER_FAILURE = 'SIGNIN_USER_FAILURE';
export function signInUser(formValues) {
const request = axios.post('/login', formValues);
return {
type: SIGNIN_USER,
payload: request
};
}
export function signInUserSuccess(user) {
console.log('signInUserSuccess()');
console.log(user);
return {
type: SIGNIN_USER_SUCCESS,
payload: user
}
}
export function signInUserFailure(error) {
console.log('signInUserFailure()');
console.log(error);
return {
type: SIGNIN_USER_FAILURE,
payload: error
}
}
And this is my reducer UserReducer.js
import {
SIGNIN_USER, SIGNIN_USER_SUCCESS, SIGNIN_USER_FAILURE,
} from '../actions/UsersActions';
const INITIAL_STATE = {user: null, status:null, error:null, loading: false};
export default function(state = INITIAL_STATE, action) {
let error;
switch(action.type) {
case SIGNIN_USER:// sign in user, set loading = true and status = signin
return { ...state, user: null, status:'signin', error:null, loading: true};
case SIGNIN_USER_SUCCESS://return authenticated user, make loading = false and status = authenticated
return { ...state, user: action.payload.data.user, status:'authenticated', error:null, loading: false}; //<-- authenticated
case SIGNIN_USER_FAILURE:// return error and make loading = false
error = action.payload.data || {message: action.payload.message};//2nd one is network or server down errors
return { ...state, user: null, status:'signin', error:error, loading: false};
default:
return state;
}
}
Reducers combined:
import { combineReducers } from 'redux';
import { UserReducer } from './UserReducer';
import { reducer as formReducer } from 'redux-form';
const rootReducer = combineReducers({
user: UserReducer,
form: formReducer // <-- redux-form
});
export default rootReducer;
configureStore.js
import {createStore, applyMiddleware, combineReducers, compose} from 'redux';
import thunkMiddleware from 'redux-thunk';
import {devTools, persistState} from 'redux-devtools';
import rootReducer from '../reducers/index';
let createStoreWithMiddleware;
// Configure the dev tools when in DEV mode
if (__DEV__) {
createStoreWithMiddleware = compose(
applyMiddleware(thunkMiddleware),
devTools(),
persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
)(createStore);
} else {
createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
}
export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState);
}
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import configureStore from './store/configureStore';
import {renderDevTools} from './utils/devTools';
const store = configureStore();
ReactDOM.render(
<div>
{/* <Home /> is your app entry point */}
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>
{/* only renders when running in DEV mode */
renderDevTools(store)
}
</div>
, document.getElementById('main'));
Can anyone see what's wrong with my thunks? The inner code is never called but the outer code is. This is an example thunk:
export function selectCustomer(customerId) {
console.log("This appears in the console fine");
return (dispatch, getState) => {
console.log("This doesn't.. why don't you run..?");
dispatch(loadCustomerToEdit(customerId));
}
};
This is how I'm wiring it up to the component events:
import React, { Component, PropTypes } from 'react';
import CustomerEditForm from './CustomerEditForm.jsx';
import { editCustomer, selectCustomer, selectNewCustomer, saveCustomer } from '../redux/action_creators.jsx';
export const CustomerContainer = React.createClass({
componentWillMount() {
const customerId = FlowRouter.getParam('_id');
if (customerId) {
this.sub = Meteor.subscribe('CustomerCompany.get', customerId, this.setCustomerInState);
} else {
this.props.selectNewCustomer();
}
},
setCustomerInState() {
console.log("setCustomerInState");
this.props.selectCustomer(FlowRouter.getParam('_id'));
},
// Snip
render() {
console.log("CustomerContainer.render()", this.props);
if (this.sub && !this.sub.ready) {
return (<h1>Loading</h1>);
}
return (
<CustomerEditForm
customer = {this.props.customer}
onChange = {this.props.onChange}
onSave = {this.props.onSave}
errors = {this.props.customer.errors}
isValid = {this.props.customer.isValid}
salesRegionOptions={SalesRegions.find().fetch()}
/>
);
}
});
CustomerContainer.propTypes = {
customer: PropTypes.object,
onSave: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
selectCustomer: PropTypes.func.isRequired,
selectNewCustomer: PropTypes.func.isRequired
};
function mapStateToProps(state) {
console.log("CustomerContainer.mapStateToProps", state)
return {
customer: state.userInterface.customerBeingEdited
};
}
function mapDispatchToProps(dispatch) {
//console.log("CustomerContainer.mapDispatchToProps", Actions.customerSave)
return {
onSave: saveCustomer,
onChange: editCustomer,
selectCustomer,
selectNewCustomer
};
}
export default connect(mapStateToProps, mapDispatchToProps
)(CustomerContainer);
And this is my store setup:
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import rootReducer from './reducers.jsx';
import thunk from 'redux-thunk';
const middleware = [ thunk ]
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore)
const store = createStoreWithMiddleware(rootReducer)
export default store;
You'll no doubt recognise a lot of this code as it is adapted from the excellent examples in the redux documentation.
The selectCustomer function is called, so mapDispatchToProps seems to have wired the selectCustomer function to the component, it's just that the method returned by selectCustomer isn't called.
The problem is your mapDispatchToProps function. react-redux does not automatically wrap your action creators if you pass it a function, only if you pass it an object! (or if you bind them manually with bindActionCreators)
Try changing your connect call to this, and it should work:
connect(mapStateToProps, {
onSave: saveCustomer,
onChange: editCustomer,
selectCustomer,
selectNewCustomer
})(YourComponent);