mapStateToProps not executing - javascript

I want my firebase UI auth to be checked in multiple containers and also redirect to the main page after the authentication is done.
When authentication is done I can see the actions updating and the reducer updating - but then mapstatetoprops does not do anything with the new reducer state
Loginpage that received props and based on that renders
import React from 'react';
import { bindActionCreators } from 'redux'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';
import { Redirect } from 'react-router-dom'
import firebase from 'firebase';
import { connect } from 'react-redux';
import { signIn, signOut } from '../reducer/actions'
import { auth } from '../firebase'
class LoginPage extends React.PureComponent {
// Configure FirebaseUI.
uiConfig = {'FirebaseUI Config'}
componentDidMount = () => {
auth.onAuthStateChanged((user) => { // gets user object on authentication
console.log('OnAuthStateChanged', user)
console.log('Check If Props Change in AuthChange', this.props.isauthed)
if (user) {
this.props.signIn(user)
} else {
this.props.signOut(user)
}
});
}
render() {
console.log('Check If Props Change in Render', this.props.isauthed)
if (!this.props.isauthed) {
return (
<div>
<h1>My App</h1>
<p>Please sign-in:</p>
<StyledFirebaseAuth uiConfig={this.uiConfig} firebaseAuth={firebase.auth()} />
</div>
);
}
return (
<Redirect to='/' />
)
}
}
export default (LoginPage);
JS that should dispatch and update the props
import { connect } from 'react-redux';
import { signIn, signOut } from '../reducer/actions'
import { bindActionCreators } from 'redux'
import LoginPage from './LoginPage';
const mapStateToProps = (state) => {
console.log('LOGINmapstatetoprops', state.Authed)
return {
isauthed: state.Authed.isauthed,
}
}
const mapDispatchToProps = (dispatch) => {
console.log('LOGINmapDISPATCHoprops')
return bindActionCreators({signIn,signOut},dispatch)
}
export default connect(mapStateToProps,mapDispatchToProps)(LoginPage);
The reducer
import LoginPage from '../components/LoginPage';
import firebase from 'firebase';
const initialState = {
isauthed: false,
error: ''
}
const AuthReducer = (state = initialState, action) => {
console.log('this is an action',action)
switch (action.type) {
case 'IsSignedIn':
return state = [
...state,
{
isauthed: action.payload
}
]
break;
case 'IsNotSignedIn':
return state= [
...state,{
isauthed: action.payload
}]
break;
default: return state
}
}
export default AuthReducer;
This is the actions file
export const signIn = (user) => {
console.log('this is from actions',user)
return {
type: 'isSignedIn',
payload: true
}
}
export const signOut = (user) => {
console.log(user)
return {
type: 'isNotSignedIn',
payload: false
}
}
Any reason why Mapstatetoprops is idle?
I do not think a simple scenario like this needs componentwillreceiveprops method

there is a problem in the reducer, you should return an object and not an array, change to :
case 'IsSignedIn':
return {
...state,
isauthed: action.payload
}

Related

React-Native (redux) error: Could not find "store" in context of "Connect(App)"

I'm getting this weird error in my react-native app. Where I'm using redux to save the user when its is fetched from the firebase database.
Here is the error:
and here is my code in App.js:
import { StatusBar } from 'expo-status-bar';
import React, { useEffect } from 'react';
import { StyleSheet, Text, View, ToastAndroid } from 'react-native';
import firestore from '#react-native-firebase/firestore';
import RNBootSplash from "react-native-bootsplash";
import auth from '#react-native-firebase/auth';
// Navigation
import { NavigationContainer } from '#react-navigation/native';
import BottomTabNavigator from './src/navigation/BottomTabNavigation';
import AuthenticationStack from './src/navigation/AuthenticationStack';
// redux imports
import { Provider } from 'react-redux';
import { saveUserPrefs, saveUser } from './src/redux';
import {store} from './src/redux'
import { connect } from 'react-redux';
const App = (props) => {
const { saveUserPrefs, saveUser } = props;
let isLoggedIn = false;
useEffect(() => {
auth().onAuthStateChanged((user) => {
// if not already login go back to login screen
if(!user){
isLoggedIn = false;
} else {
isLoggedIn = true
RNBootSplash.hide({ fade: true }); // hide the splash screen
ToastAndroid.show("Logged In", ToastAndroid.SHORT);
// save the user & userPrefs in store (redux)
saveUser(user);
saveUserPrefs(user.uid);
}
});
}, [])
return (
<Provider store={store}>
<NavigationContainer>
{isLoggedIn ? <BottomTabNavigator /> : <AuthenticationStack />}
</NavigationContainer>
</Provider>
);
}
const mapStateToProps = (state) => ({
userReducer: state.userReducer
})
export default connect(mapStateToProps, { saveUserPrefs , saveUser})(App);
and here is my redux code:
import axios from 'axios';
import thunk from 'redux-thunk';
import { combineReducers, createStore, applyMiddleware } from 'redux'
import firestore from '#react-native-firebase/firestore';
// Actions
export const saveUserPrefs = (userId) => {
return async (dispatch) => {
try {
console.log('in userlogin function');
const data = await firestore().collection('Users').doc(userId).collection('userPrefs').get();
dispatch({ type: 'SAVE_USER_PREFS', payload: data.docs[0]._data });
} catch (err) {
dispatch({ type: 'ON_ERROR', payload: err });
}
}
}
export const saveUser = (user) => {
return async (dispatch) => {
try {
dispatch({ type: 'SAVE_USER', payload: user})
} catch (err) {
dispatch({ type: 'ON_ERROR', payload: err });
}
}
}
// reducers
const userReducer = (state = {}, action) => {
switch(action.type){
case 'SAVE_USER_PREFS':
return {
...state,
userPrefs: action.payload
}
case 'SAVE_USER':
return {
...state,
user: action.payload
}
case 'ON_ERROR':
return {
...state,
appError: action.payload
}
default:
return state;
}
}
// root reducer
export const rootReducer = combineReducers({
userReducer,
})
// store
export const store = createStore(rootReducer, applyMiddleware(thunk));
I'm new to redux but I read that I have to put the connect method in the component I'm using to get the actions in order to save in the store.
in the App component you cannot connect to the reduxjust wrap your App within another component, that have the Provider
Example code
const AppWrapper = () => {
return (
<Provider store={store}>
<App />
</Provider>
);
};
And remember to delete <Provider store={store}> in your App component

Function not receiving updated redux state even on re-render

I'm currently using Redux, Redux Thunk with NextJS and been trying to figure out how to access the updated redux state inside a function of a functional component.
As you can see in my code below, in the handleSubmit function, I want to update the redux state and then check the state value and decided which route it should take the user to.
Previously in my old project, using mapStateToProps with a Class component, I was able to access the updated redux state inside my handleSubmit function however when using a functional component both options (useSelector hook or mapStateToProps with connect()) doesn't seem to work.
At first I thought the component wasn't re-rendering however when checking the state in useEffect(), I can see that the state is getting updated and the component is able to view the updated values.
Is there something I'm clearly missing or is this way not possible with functional components?
loginPage.tsx
import LoginForm, { FormData } from 'components/Forms/LoginForm';
import Layout from 'components/Layout';
import { FORM_ERROR } from 'final-form';
import StatusCodes from 'lib/enums/statusCodes';
import { storeAuthToken } from 'lib/helpers/auth';
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { ApplicationState } from 'redux/store';
const LoginPage = () => {
const router = useRouter();
const dispatch = useDispatch();
const { auth } = useSelector((state: ApplicationState) => ({ auth: state.auth }));
const handleSubmit = async (values: FormData) => {
if (values && values.username && values.password) {
try {
// Updates redux store
await storeAuthToken(dispatch, values.username, values.password);
} catch (error) {
if (error === StatusCodes.BadRequest) {
return { [FORM_ERROR]: 'Sorry, you have entered incorrect details. Please try again' };
} else {
return { [FORM_ERROR]: 'Sorry, there was an issue trying to log you in' };
}
}
// Can't see updated values
console.log('Auth: ', auth);
if (auth.parsedJwt && auth.parsedJwt.changePassword) {
router.push({ pathname: '/update-password' });
return;
}
router.push('/dashboard');
}
};
useEffect(() => {
// Can see the updated values
console.log('New Auth: ', auth);
}, [auth]);
return (
<Layout hideProfileMenu title="Login">
<LoginForm onSubmit={handleSubmit} />
</Layout>
);
};
export default LoginPage;
I've attached the store and reducer incase I've set it up wrong.
store.tsx
import Auth from 'lib/interfaces/auth';
import { Context, createWrapper, HYDRATE, MakeStore } from 'next-redux-wrapper';
import { AnyAction, applyMiddleware, CombinedState, combineReducers, createStore, Dispatch, Reducer } from 'redux';
import thunkMiddleware, { ThunkAction, ThunkDispatch, ThunkMiddleware } from 'redux-thunk';
import authReducer from './auth/reducer';
export interface ApplicationState {
auth: Auth
}
const isDebug = process.env.NODE_ENV !== 'production';
const bindMiddleware = (middleware: ThunkMiddleware) => {
if (isDebug) {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(middleware));
}
return applyMiddleware(middleware);
};
const combinedReducer: Reducer<ApplicationState> = combineReducers<ApplicationState>({
auth: authReducer
});
const reducer = (state: ApplicationState, action: AnyAction) => {
if (action.type === HYDRATE) {
const nextState: CombinedState<ApplicationState> = {
...state,
...action.payload
};
return nextState;
} else {
return combinedReducer(state, action);
}
};
const makeStore: MakeStore<ApplicationState> = (_context: Context) => createStore(reducer as Reducer<ApplicationState, AnyAction>, bindMiddleware(thunkMiddleware));
export const wrapper = createWrapper<ApplicationState>(makeStore, { debug: isDebug });
reducer.tsx
import Auth from 'lib/interfaces/auth';
import { Reducer } from 'redux';
import { ActionTypes, AuthAction } from './actions';
const reducer: Reducer<Auth, AuthAction> = (state: Auth = {} as Auth, action: AuthAction): Auth => {
switch (action.type) {
case ActionTypes.UpdateToken:
return Object.assign({}, state, { token: action.token });
case ActionTypes.UpdateRefreshToken:
return Object.assign({}, state, { refreshToken: action.refreshToken });
case ActionTypes.UpdateParsedJwt:
return Object.assign({}, state, { parsedJwt: action.parsedJwt });
case ActionTypes.UpdateUuid:
return Object.assign({}, state, { uuid: action.uuid });
default:
return state;
}
};
export default reducer;
I think the simplest solution is to return the auth object from 'storeAuthToken' as it keeps the error handling and result in the same logical flow, and it fixes the asynchronous issue identified in the comments.
import LoginForm, { FormData } from 'components/Forms/LoginForm';
import Layout from 'components/Layout';
import { FORM_ERROR } from 'final-form';
import StatusCodes from 'lib/enums/statusCodes';
import { storeAuthToken } from 'lib/helpers/auth';
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { ApplicationState } from 'redux/store';
const LoginPage = () => {
const router = useRouter();
const dispatch = useDispatch();
const handleSubmit = async (values: FormData) => {
if (values && values.username && values.password) {
try {
// Updates redux store
const authResult = await storeAuthToken(dispatch, values.username, values.password);
if (authResult.parsedJwt && authResult.parsedJwt.changePassword) {
router.push({ pathname: '/update-password' });
return;
}
} catch (error) {
if (error === StatusCodes.BadRequest) {
return { [FORM_ERROR]: 'Sorry, you have entered incorrect details. Please try again' };
} else {
return { [FORM_ERROR]: 'Sorry, there was an issue trying to log you in' };
}
}
router.push('/dashboard');
}
};
return (
<Layout hideProfileMenu title="Login">
<LoginForm onSubmit={handleSubmit} />
</Layout>
);
};
export default LoginPage;

Redux - Dispatching Action (onClick Event)

I am simply trying to connect() my LoginPage (component) to my Redux Store and dispatch in action via a onClick (event). When I console.log(this.props) my dispatch handler login() isn't in the component's props.
GitHub Repo -- https://github.com/jdavis-software/demo.git
Question: Why isn't my Redux Store either connection or dispatching the actions?
LoginPage:
import React, { Component} from 'react';
import { connect } from 'react-redux';
export class LoginPage extends Component<any> {
render(){
console.log('props doesnt have contain - login(): ', this.props)
return (<button onClick={ () => '' }>Login</button>)
}
}
const mapProps = state => ({ user: state.user })
const dispatchProps = (dispatch) => {
return {
login: () => dispatch({ type: 'USER_LOGGED_IN', payload: true})
}
}
export default connect(mapProps,dispatchProps)(LoginPage)
Redux Configuration:
import { IStore, IUser } from '#interfaces';
import { createStore, combineReducers } from 'redux';
import ReduxPromise from 'redux-promise';
// reducers
import userReducer from './user.reducer';
// define the intial global store state
const initialState:IStore = {
user: {
isAuthenticated: false
}
}
const appReducer = combineReducers({user: userReducer})
export default createStore(appReducer,initialState);
User Reducer:
// initial state
const initalState:IUser = {
isAuthenticated: false
}
// reducer
const userReducer = (state:IUser = initalState, { type, payload}: IPayload): IUser => {
console.log('user reducer start', state)
switch (type) {
case 'USER_LOGGED_IN':
state = { ...state, isAuthenticated: payload }
break;
default:
return state;
}
return state;
};
export default userReducer;
Root Page:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
// styles
import './index.scss';
// pages
import { App } from '#pages';
// store
import store from './core/store/store';
render(
<Provider store={store}>
<App/>
</Provider>, document.getElementById('app')
);
I checked your code on git repository. I found out that you're exporting the named export
export class LoginPage
and the default export,
export default connect(mapProps,dispatchProps)(LoginPage)
But when you're accessing it, you're accessing it as
import { /*Other components*/ , LoginPage } from '#pages'
So it is actually taking the named exported component which is not connected to store.
I suggest you to import as
import LoginPage , { /*Other components*/ } from '#pages'
This might solve your problem.
Return statements are missing in the properties of connect.
const mapProps = state => { return {user: state.user} }
const dispatchProps = (dispatch) => {
return {
login: () => dispatch({ type: 'USER_LOGGED_IN', payload: true})
}
}
export default connect(mapProps,dispatchProps)(LoginPage)
Updated:
Please check Redux-dispatch
try:
import React, { Component} from 'react';
import { connect } from 'react-redux';
export class LoginPage extends Component<any> {
render(){
console.log('props doesnt contain - login(): ', this.props)
return (
<button onClick={ this.props.login }>Login</button>
)
}
}
const mapProps = state => ({ user: state.user })
const dispatchProps = (dispatch) => ({
login: () => dispatch({ type: 'USER_LOGGED_IN', payload: true})
})
export default connect(mapProps,dispatchProps)(LoginPage)
to return an object with Arrow Functions you need to wrap your {} with ()

react redux: still getting empty object for this.props when using Connect for mapStateToProps and mapDispatchToProps

I'm trying to follow this tutorial (https://medium.com/#stowball/a-dummys-guide-to-redux-and-thunk-in-react-d8904a7005d3) on React Redux but am getting an empty object when I print out this.props. It seems like mapStateToProps isn't actually setting this.props since react redux's connect isn't being called, but I'm not sure why
This is what the state should be (like in the tutorial), all I did was change the component's name ItemList to Home
Here's what I'm getting (nothing was mapped to the state):
actions/items.js
export function itemsHasErrored(bool) {
return {
type: 'ITEMS_HAS_ERRORED',
hasErrored: bool
};
}
export function itemsIsLoading(bool) {
return {
type: 'ITEMS_IS_LOADING',
isLoading: bool
};
}
export function itemsFetchDataSuccess(items) {
return {
type: 'ITEMS_FETCH_DATA_SUCCESS',
items
};
}
export function itemsFetchData(url) {
return (dispatch) => {
dispatch(itemsIsLoading(true));
fetch(url)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(itemsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(itemsFetchDataSuccess(items)))
.catch(() => dispatch(itemsHasErrored(true)));
};
}
components/Home.js
export class Home extends Component {
componentDidMount() {
console.log(this.props)
}
render() {
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
return <p>Loading…</p>;
}
return (
<ul>
{this.props.items.map((item) => (
<li key={item.id}>
{item.label}
</li>
))}
</ul>
);
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (url) => dispatch(itemsFetchData(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
reducers/index.js
export function itemsHasErrored(state = false, action) {
switch (action.type) {
case 'ITEMS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
export function itemsIsLoading(state = false, action) {
switch (action.type) {
case 'ITEMS_IS_LOADING':
return action.isLoading;
default:
return state;
}
}
export function items(state = [], action) {
switch (action.type) {
case 'ITEMS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
store/configureStore.js
import { applyMiddleware, createStore } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from '../reducers'
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
)
}
App.js
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import configureStore from './store/configureStore'
import { Home } from './components/Home'
const store = configureStore()
export default class App extends Component {
render () {
return (
<Provider store={store}>
<Home />
</Provider>
)
}
}
index.js (I am using create-react-app boilerplate)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
The issue is that you're exporting two components... the non-connected (via the named export Home with export class Home...) and connected (via export default). Then, you're importing and rendering the non-connected component:
import { Home } from './components/Home'
Since you want to use the connected component, you should be importing the default export like this:
import Home from './components/Home'
You may want to just export the connected component, unless you have some reason to use the unconnected one.

React redux display data from async action

I am building a react app and implementing redux for data. When I navigate to a particular route, I want to dispatch the action to fetch the data from an external API and then once the data comes back, display the data for the user.
Store :
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import { syncHistoryWithStore } from 'react-router-redux';
import { browserHistory } from 'react-router';
import rootReducer from '../reducers/index';
const initialState = {
marvel :{
characters: []
}
};
const store = createStore(rootReducer, initialState, applyMiddleware(thunkMiddleware, createLogger()));
export const history = syncHistoryWithStore(browserHistory, store);
if (module.hot) {
module.hot.accept('../reducers/', () => {
const nextRootReducer = require('../reducers/index').default;
store.replaceReducer(nextRootReducer);
});
}
export default store;
reducer :
import * as constants from '../constants/constants';
const initialState = {
characters: [],
isFetching: false,
errorMessage: null
};
const marvelReducer = (state = initialState, action) => {
switch (action.type) {
case constants.FETCH_MARVEL :
return Object.assign({},state,{isFetching: true});
case constants.FETCH_MARVEL_SUCCESS :
return Object.assign({}. state,{
characters: [...action.response],
isFetching: false
});
case constants.FETCH_MARVEL_ERROR :
return Object.assign({}, state,{
isFetching: false,
errorMessage: action.message
});
default :
return state;
}
};
export default marvelReducer;
actions:
import 'whatwg-fetch';
import * as constants from '../constants/constants';
export const fetchMarvel = (dispatch) => {
const MARVEL_API = 'http://gateway.marvel.com:80/v1/public/characters?apikey=e542b1d89f93ed41b132eda89b9efb2c';
dispatch({
type: constants.FETCH_MARVEL
});
return fetch(MARVEL_API).then(
response => {
dispatch({
type: constants.FETCH_MARVEL_SUCCESS,
response
});
},
error => {
dispatch({
type: constants.FETCH_MARVEL_ERROR,
message: error.message || 'Something went wrong with fetchMarvel'
});
});
};
component :
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/actions';
import '../styles/homeStyles.css';
class Home extends Component {
render() {
const { characters, isFetching, errorMessage } = this.props;
return (
<div>
<h1>React Starter App</h1>
<h2>This is the home page</h2>
</div>
);
}
}
function mapStateToProps(state) {
return {
characters: state.characters,
isFetching: state.isFetching,
errorMessage: state.errorMessage
};
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actions, dispatch) };
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
I know I'm not currently displaying the props anywhere in the application, but first I am just trying to get them populated.
What step am I missing to dispatch the action so that I can populate the props from state?
You are not dispatching the action anywhere. So nothing happens.
You probably want to do this in a React lifecycle hook, for example:
class Home extends Component {
componentDidMount() {
this.props.actions.fetchMarvel();
}
render() {
const { characters, isFetching, errorMessage } = this.props;
return (
<div>
<h1>React Starter App</h1>
<h2>This is the home page</h2>
</div>
);
}
}

Categories

Resources