Dispatch an action before rendering in react native redux? - javascript

I'm trying to log user in with firebase authentication and I ran into this problem that when I use onAuthStateChanged method in action creator to get is user logged in before or not. When users is logged in before so I just call login success and navigate them to main screen, if not I just dispatch another action and show login screen, but I still see login screen for like half a second if login success action dispatched, I'm new to react native and redux, can anyone help me solve this issue, thank you guys so much!!
here is my index.js
import { checkLogin } from './actions';
class LoginScreen extends Component {
componentDidMount() {
this.props.checkLogin();
}
componentWillReceiveProps(nextProp) {
if (nextProp.loggedIn) {
this.props.navigation.navigate('main');
}
}
//... some code
render() {
if (this.props.isChecking) {
return <Spinner />
}
return this.renderContent();
}
}
const mapStateToProps = ({ auth }) => {
const { email, password, error, loading, loggedIn, isChecking } = auth;
return { email, password, error, loading, loggedIn, isChecking };
};
export default connect(mapStateToProps, {
emailChanged, passwordChanged, emailLogin, checkLogin
})(LoginScreen);
and here is my actions.js
import firebase from 'firebase';
export const checkLogin = () => {
return(dispatch) => {
firebase.auth().onAuthStateChanged(user => {
if (user) {
dispatch({ type: EMAIL_LOGIN_SUCCESS, payload: user });
} else {
dispatch({ type: CHECK_LOGIN_FAILED });
}
})
}
}
// some code
and reducer.js
const INITIAL_STATE = {
// ...
isChecking: true,
}
export default (state = INITIAL_STATE, action) => {
console.log(action);
switch (action.type) {
// ...
case CHECK_LOGIN_FAILED:
return { ...state, isChecking: false };
case EMAIL_LOGIN_SUCCESS:
return { ...state, user: action.payload, error: '', loading: false, email: '', password: '', loggedIn: true, isChecking: false };
default:
return state;
}
}

I think this happens because the previous state of isChecking is remembered by your reducer from the previous rendering of your component. I bet if you reset the content and settings of your simulator that you would not first see the login page. However, to fix this I would recommend dispatching an action before firebase.auth().onAuthStateChanged to tell your reducer that you are in an "isChecking" state.

Related

Component not rerendering on redux store change

Im really struggling through this but I cannot get my component to rerender even though I can see the store is changing and yes Im not sure Im not mutating my original state.
Reducer:
const AssessmentReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ASSESSMENT_INFO:
return { ...state, assessment: action.payload }
default:
return state
}
}
Action:
import { GET_ASSESSMENT_INFO } from '../reducers/AssessmentReducer'
import API from '../../api'
export function getAssessment(assessment) {
return {
type: GET_ASSESSMENT_INFO,
payload: assessment,
}
}
export function getAssesmentInfo(id) {
return function (dispatch) {
return API.get_all('assessments/' + id).then(
(response) => dispatch(getAssessment(response)),
(error) => console.log('An error occurred', error)
)
}
}
Component:
componentDidMount() {
const assessmentId = this.props.match.params.assessment_id
this.props.getAssesmentInfo(assessmentId)
}
/** A whole bunch of stuff in here ***/
render() {
console.log(this.props.assessment)
}
function mapStateToProps(state) {
return {
//screenQuestions: state.ScreenQuestionsReducer.screenQuestions,
assessment: state.AssessmentReducer.assessment,
}
}
export default connect(mapStateToProps, { getAssesmentInfo })(
withStyles(styles)(AssessmentTabs)
)
Im seeing my initial state being logged in the render, Can validate the action is called and dispatched to the reducer with the correct payload. But the console log in the render is never hit again. Using redux dev tools, the state is being updated correctly as well.
Any ideas?
EDIT:
as soon as a click a button in my component and update the state, it reads in the new props from redux no problem

Receives from mapStateToProps the old state, not the new one

I'm new to the site, and new to React. I would be very happy if someone would help me please.
I built a chat, which allows messages to be sent between users, and everything works great. But there is a problem with the mapStateToProps in my opinion, because when I add a new message, the state itself only changes when I refresh the page. I want it to change immediately, that I will see the conversation in chat immediately and not in the refresh of the page.
This is the code I wrote down, please I would be happy if someone would help me, if I need to add more code I will do it.
Explanation of the code, I have a chat component, where I do mapStateToProps, and use state what redux, I have another chatReducer file, which is responsible for managing the state of the chat, and in fact maybe there is a problem, because I update the state, I do not get it. Only after refreshing the page, I get it.
I have another chatAction file - through which I call chatReducer.
I think the problem is very small, I probably did not enter the correct code in ChatReducer or mapStateToProps, but other than that everything works.
Please I would be happy if anyone would help me, I am new here on the site.
Another thing, the problem is this, I have an array of conversations. It comes from the mapStateToProps, from the state.chat. Once I add a new call through the submitMessage function, I manage to add the message to the database, but the state itself is not updated directly, that's the problem. I want when someone sends a straight message that it will be updated in chat.
chat component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getRealtimeUsers } from '../redux/actions/chatActions';
import { updateMessage } from '../redux/actions/chatActions';
import { getRealtimeConversations } from '../redux/actions/chatActions';
class chat extends Component {
state = {
message: '',
chatStarted: false,
chatUser: '',
userUid: null
}
componentDidMount() {
this.props.getRealtimeUsers();
}
initChat = (user) => {
this.setState({
chatStarted: true,
chatUser: user.handle,
userUid: user.handle
});
}
submitMessage = (e) => {
const msgObj = {
user_uid_1: this.props.user.credentials.handle,
user_uid_2: this.state.userUid,
message: this.state.message
}
if (this.state.message !== "") {
this.props.updateMessage(msgObj);
this.setState({ message: '' });
}
console.log(msgObj);
}
render() {
return (
<section>
<div>
<div>
{
this.state.chatStarted ?
this.props.chat.conversations.map(con =>
<div>
<p>{con.message}</p>
</div>)
: null
}
</div>
{
this.state.chatStarted ?
<div>
<textarea
value={this.state.message}
onChange={(e) => this.setState({ message: e.target.value })}
placeholder="Write Message"
/>
<button onClick={this.submitMessage}>Send</button>
</div> : null
}
</div>
</section>
);
}
}
const mapStateToProps = (state) => ({
data: state.data,
chat: state.chat,
user: state.user
});
export default connect(
mapStateToProps,
{ getRealtimeUsers, updateMessage, getRealtimeConversations }
)(chat);
chat reducer
import { userConstants } from "../types"
const initialState = {
users: [],
conversations: []
}
export default function (state = initialState, action) {
switch (action.type) {
case `${userConstants.GET_REALTIME_USERS}_REQUEST`:
return {
...state
}
case `${userConstants.GET_REALTIME_USERS}_SUCCESS`:
return {
...state,
users: action.payload
}
case userConstants.GET_REALTIME_MESSAGES:
return {
...state,
conversations: action.payload
}
case `${userConstants.GET_REALTIME_MESSAGES}_FAILURE`:
return {
...state,
conversations: []
}
default:
return state;
}
}
chat actions
import { userConstants } from "../types";
import axios from 'axios';
export const getRealtimeUsers = () => (dispatch) => {
dispatch({ type: `${userConstants.GET_REALTIME_USERS}_REQUEST` });
axios
.get('/realtimeUsers')
.then((res) => {
console.log(res);
dispatch({
type: `${userConstants.GET_REALTIME_USERS}_SUCCESS`,
payload: res.data
});
})
.catch((err) => console.log(err))
}
export const updateMessage = (msgObj) => (dispatch) => {
axios.post('/updateMessage', msgObj)
.then(() => {console.log("message added") })
.catch((err) => console.log(err));
}
export const getRealtimeConversations = (user) => (dispatch) => {
axios.get('/realtimeConversations',
{
params: {
user: JSON.stringify(user)
}
}
)
.then((res) => {
dispatch({
type: userConstants.GET_REALTIME_MESSAGES,
payload: res.data
});
})
.catch((err) => console.log(err))
}

User logged-in Persistence with jwt Tokens and React after Reload

I am working with React and Jwt Tokens and so far a user can stay logged in after going to a login page. I have a problem when I reload the page that the user won't stay logged in. Currently when a user logs in, I store the jwt Token in localStorage. For some reason, I don't understand why I'm not returning a valid user even when I have the jwt Token in localStorage. Any help is appreciated!
import React, { useReducer, createContext } from 'react'
import jwtDecode from "jwt-decode";
const initialState = {
user: null
}
if(localStorage.getItem('jwtToken')) {
const decodedToken = jwtDecode(localStorage.getItem('jwtToken'))
if(decodedToken.exp * 1000 < Date.now()) {
localStorage.removeItem('jwtToken')
}
else {
initialState.user = null;
}
}
const AuthContext = createContext({
user:null,
login: (userData) => {},
logout: () => {}
})
function authReducer(state, action) {
switch(action.type) {
case 'LOGIN':
return {
...state,
user: action.payload
}
case 'LOGOUT':
return {
...state,
user:null
}
default: return state
}
}
function AuthProvider(props) {
const [state, dispatch] = useReducer(authReducer, initialState)
function login(userData) {
localStorage.setItem("jwtToken", userData.token)
dispatch({
type:'LOGIN',
payload:userData
})
}
function logout() {
localStorage.removeItem("jwtToken")
dispatch({type:'LOGOUT'})
}
return (<AuthContext.Provider
value={{ user: state.user, login, logout}}
{...props} /> )
}
export { AuthContext, AuthProvider }
^^^ My Authentication file
^^ My menu bar component renders dynamically based on if there is a user or not. It's not rendering a logged-in user after reloading so my authentication file must be doing something wrong.
Thanks! Any help is appreciated.

React Redux API call, data not making it back to component

In the last couple of days I have been working on my Redux api call. I am actually having a problem getting the data back to the view component. Currently I'm able to see the data in the in the action generator, so I know at least I'm able to get it. However, nothing is showing in the view. I imagine it may have something to do with when it's loading. This is why I tried to load it when the component is rendering.
https://djangoandreact.herokuapp.com/user/1 is what is not loading.
codesandbox: https://codesandbox.io/s/zlor60q3jm?from-embed
Should be able to go to /user/1 at the end similar to going to /1 brings up an article(Tough Hope)
Heres the view component:
import React from "react";
import { connect } from "react-redux";
import { fetchUser } from "../store/actions/userActions";
class UserDetailView extends React.Component {
componentDidMount() {
const userID = this.props.match.params.userID;
fetchUser(userID); //fixed
}
render() {
const { user } = this.props.user;
console.log(user);
return (
<div>
<h3>{user.username}</h3>
</div>
);
}
}
const mapStateToProps = state => ({
user: state.user
});
const mapDispatchToProps = (dispatch, ownProps) => ({
fetchUser: dispatch(fetchUser(ownProps.match.params.userID))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserDetailView);
Action generator
import axios from "axios";
import { thunk } from "react-redux";
export function fetchUser(userID) {
console.log(userID);
return dispatch => {
return axios.get(`/api/user/${userID}`).then(res => {
dispatch(fetchUserSuccess(res.data));
console.log(res.data); // loads data
});
};
}
// Handle HTTP errors since fetch won't.
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const FETCH_USER_BEGIN = "FETCH_USER_BEGIN";
export const FETCH_USER_SUCCESS = "FETCH_USER_SUCCESS";
export const FETCH_USER_FAILURE = "FETCH_USER_FAILURE";
export const fetchUserBegin = () => ({
type: FETCH_USER_BEGIN
});
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: { user }
});
export const fetchUserFailure = error => ({
type: FETCH_USER_FAILURE,
payload: { error }
});
Reducers(which are probably fine):
import {
FETCH_USER_BEGIN,
FETCH_USER_SUCCESS,
FETCH_USER_FAILURE
} from "../actions/actionTypes";
const initialState = {
user: {},
loading: false,
error: null
};
export default function userReducer(state = initialState, action) {
switch (action.type) {
case FETCH_USER_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_USER_SUCCESS:
return {
...state,
loading: false,
user: action.payload.user
};
case FETCH_USER_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
user: {}
};
default:
return state;
}
}
folks. I found it.
case FETCH_USER_SUCCESS:
return {
...state,
loading: false,
user: action.payload.user
};
user is supposed to be user:action.payload
Also, the user action was supposed to be
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: user
})
WOOOOW. But, honestly, I learned so much about Redux in the last two sleepless nights, it was worth the pain. Really was. Now, instead of copy pasta, I know what an action generator is and does, and reducer (obvi)

Dispatch() calls a function but .then() doesn't work on React-Redux

I'm creating my first React-Redux App. I'm using yo generator-redux and following this repo and official documenation. I have rendered de SignIn Presentational Component and it works fine, show errors if inputs are blanks. The problem is at dispatching. I use Thunk Middleware but the repo doesn't.
I have used console.log() to explore how deeper is working my code and I found that the Component Actions are being called, the AJAX request (with axios) is working fine, but the .then() (I think) is not working but doesn't throw errors.
This is my code:
Action
actions/UsersActions.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';
//Get current user(me) from token in localStorage
export const ME_FROM_TOKEN = 'ME_FROM_TOKEN';
export const ME_FROM_TOKEN_SUCCESS = 'ME_FROM_TOKEN_SUCCESS';
export const ME_FROM_TOKEN_FAILURE = 'ME_FROM_TOKEN_FAILURE';
export const RESET_TOKEN = 'RESET_TOKEN';
//log out user
export const LOGOUT_USER = 'LOGOUT_USER';
axios.defaults.baseURL = location.href.indexOf('10.1.1.33') > 0 ? 'http://10.1.1.33:8080/api/v1' : 'http://10.1.1.33:8080/api/v1';
export function signInUser(formValues) {
const request = axios.post('/login', formValues);
console.log(request);
// It works fine and receives the resposen when is invoked from Container
return {
type: SIGNIN_USER,
payload: request
};
}
export function signInUserSuccess(user) {
return {
type: SIGNIN_USER_SUCCESS,
payload: user
}
}
export function signInUserFailure(error) {
return {
type: SIGNIN_USER_FAILURE,
payload: error
}
}
export function meFromToken(tokenFromStorage) {
//check if the token is still valid, if so, get me from the server
const request = axios.get('/me/from/token?token=${tokenFromStorage}');
return {
type: ME_FROM_TOKEN,
payload: request
};
}
export function meFromTokenSuccess(currentUser) {
return {
type: ME_FROM_TOKEN_SUCCESS,
payload: currentUser
};
}
export function meFromTokenFailure(error) {
return {
type: ME_FROM_TOKEN_FAILURE,
payload: error
};
}
export function resetToken() {//used for logout
return {
type: RESET_TOKEN
};
}
export function logOutUser() {
return {
type: LOGOUT_USER
};
}
Component
components/SignInForm.js
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
class SignInForm extends Component {
static contextTypes = {
router: PropTypes.object
};
componentWillUnmount() {
// Invoked immediately before a component is unmounted from the DOM.
// Perform any necessary cleanup in this method, such as invalidating timers or
// cleaning up any DOM elements that were created in componentDidMount.
// Important! If your component is navigating based on some global state(from say componentWillReceiveProps)
// always reset that global state back to null when you REMOUNT
this.props.resetMe();
}
componentWillReceiveProps(nextProps) {
// Invoked when a component is receiving new props. This method is not called for the initial render.
if(nextProps.user && nextProps.user.status === 'authenticated' && nextProps.user.user && !nextProps.user.error) {
this.context.router.push('/');
}
//error
//Throw error if it was not already thrown (check this.props.user.error to see if alert was already shown)
//If u dont check this.props.user.error, u may throw error multiple times due to redux-form's validation errors
if(nextProps.user && nextProps.user.status === 'signin' && !nextProps.user.user && nextProps.user.error && !this.props.user.error) {
alert(nextProps.user.error.message);
}
}
render() {
const { asyncValidating, fields: { email, password }, handleSubmit, submitting, user } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.props.signInUser.bind(this))}>
<div>
<label>Email</label>
<input type="text" placeholder="email#4geeks.com.ve" {...email} />
<div>{email.touched ? email.error : ''}</div>
<div>{ asyncValidating === 'email' ? 'validating...' : ''}</div>
</div>
<div>
<label>Password</label>
<input type="password" {...password} />
<div>{password.touched ? password.error : ''}</div>
<div>{ asyncValidating === 'password' ? 'validating...' : ''}</div>
</div>
<button type="submit" disabled={submitting}>Submit</button>
</form>
</div>
);
}
}
export default SignInForm;
Container
containers/SignInFormContainer.js
import { reduxForm } from 'redux-form';
import SignInForm from '../components/SignInForm';
import { signInUser, signInUserSuccess, signInUserFailure } from '../actions/UsersActions';
// Client side validation
function validate(values) {
var errors = {};
var hasErrors = false;
if(!values.email || values.email.trim() == '') {
errors.email = "Enter a registered email.";
hasErrors = true;
}
if(!values.password || values.password.trim() == '') {
errors.password = "Enter password.";
hasErrors = true;
}
return hasErrors && errors;
}
// For any field errors upon submission (i.e. not instant check)
const validateAndSignInUser = (values, dispatch) => {
return new Promise ((resolve, reject) => {
console.log('this is showed');
dispatch(signInUser(values))
.then((response) => {
console.log('this console.log is not showed');
let data = response.payload.data;
// if any one of these exist, then there is a field error
if(response.payload.status != 200) {
// let other components know of error by updating the redux` state
dispatch(signInUserFailure(response.payload));
reject(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', response.payload.data.token);
// let other components know that we got user and things are fine by updating the redux` state
dispatch(signInUserSuccess(response.payload));
resolve(); // this is for redux-form itself
}
});
});
}
const mapDispatchToProps = (dispatch) => {
return {
signInUser: validateAndSignInUser
}
}
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);
Presentational/Page/View
presentational/SignIn.js
import React, { Component } from 'react';
import HeaderContainer from '../containers/HeaderContainer';
import SignInFormContainer from '../containers/SignInFormContainer';
class SignIn extends Component {
render() {
return (
<div>
<HeaderContainer />
<SignInFormContainer />
</div>
);
}
}
export default SignIn;
Reducers
reducres/UserReducer.js
import {
ME_FROM_TOKEN, ME_FROM_TOKEN_SUCCESS, ME_FROM_TOKEN_FAILURE, RESET_TOKEN,
SIGNIN_USER, SIGNIN_USER_SUCCESS, SIGNIN_USER_FAILURE,
LOGOUT_USER
} 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 ME_FROM_TOKEN:// loading currentUser("me") from jwttoken in local/session storage storage,
return { ...state, user: null, status:'storage', error:null, loading: true};
case ME_FROM_TOKEN_SUCCESS://return user, status = authenticated and make loading = false
return { ...state, user: action.payload.data.user, status:'authenticated', error:null, loading: false}; //<-- authenticated
case ME_FROM_TOKEN_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:'storage', error:error, loading: false};
case RESET_TOKEN:// remove token from storage make loading = false
return { ...state, user: null, status:'storage', error:null, loading: false};
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};
case LOGOUT_USER:
return {...state, user:null, status:'logout', error:null, loading: false};
default:
return state;
}
}
reducers/index.js
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;
Store
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'));
I hope you can help me! I don't know if something is wrong because I'm using Thunk and the example don't, or if something is missging.
Thank you guys!
It looks like you are using redux-thunk where as I am using redux-promise middlewares. They are totally different. You should change redux-thunk to redux-promise if you want to use the repo
I solved my issue. The difference is that I need to process de Promise received from signInUser throug the attribute that has it.
I had to receive the response in response and then access to the Promise in response.payload. In addition, I had to use .then() and .catch() to handle it.
// For any field errors upon submission (i.e. not instant check)
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
});
});
}

Categories