I observed that this problem is common, but I didn't find a solution for my case.
I'm trying to redirect the user to another navigator in react native, using react and redux with redux-thunk. If I display just Home screen it works fine, but when I'm redirecting from SignIn screen to Home, it goes into an infinite loop, I found the problem is in the dispatch function.
import {
FETCHING_CATEGORIES_REQUEST,
FETCHING_CATEGORIES_SUCCESS,
FETCHING_CATEGORIES_FAILURE,
} from "../types"
import { Categories } from "../../services/firebase"
export const fetchingCategoriesRequest = () => ({
type: FETCHING_CATEGORIES_REQUEST,
})
export const fetchingCategoriesSuccess = data => ({
type: FETCHING_CATEGORIES_SUCCESS,
payload: data,
})
export const fetchingCategoriesFailure = error => ({
type: FETCHING_CATEGORIES_FAILURE,
payload: error,
})
export const fetchCategories = () => {
return async dispatch => {
dispatch(fetchingCategoriesRequest())
Categories.get()
.then(data => dispatch(fetchingCategoriesSuccess(data)))
.catch(error => dispatch(fetchingCategoriesFailure(error)))
}
}
Routing
import { createSwitchNavigator } from "react-navigation"
import PrivateNavigator from "./private"
import PublicNavigator from "./public"
const Navigator = (signedIn = false) => {
return createSwitchNavigator(
{
Private: {
screen: PrivateNavigator,
},
Public: {
screen: PublicNavigator,
},
},
{
initialRouteName: signedIn ? "Private" : "Public",
},
)
}
export default Navigator
Redirecting
import React from "react"
import { Spinner } from "native-base"
import { connect } from "react-redux"
import Navigator from "../navigation"
class AppContainer extends React.Component<any, any> {
render() {
const { isLogged, loading } = this.props.auth
const Layout = Navigator(isLogged)
return loading ? <Spinner /> : <Layout />
}
}
const mapStateToProps = state => {
return {
...state,
}
}
export default connect(
mapStateToProps,
{},
)(AppContainer)
Be careful with mapStateToProps, you should only select the part of the store you're interested in, otherwise performance problems could occur
const mapStateToProps = state => ({auth: state.auth});
A little explanation how react-redux connect works,
each time there is a modification in the store (from the reducers), the mapStateToProps functions of all the connected components are executed
if the one prop in the returned object is different from the previous one (the operator === is used) then the component is re-rendered otherwise it does nothing.
In your example, as you select all the props of the store, your component will be re-rendered for each modification in the store
Related
I made a reducer that fetches admins, and I want it to display certain admins when I call it in my reducer but I am getting Undefined.
I am still very new to redux so apologies for my mistakes.
I tried to include all the relevant folders:
App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../store/actions';
class App extends Component {
async componentDidMount() {
fetch(constants.adminUrl + '/admins/data', {
method: 'GET'
}).then((res) => {
return res.json()
}).then(async (res) => {
this.props.setAdminsInColumns(res.admins)
}).catch((error) => {
toast.error(error.message)
})
}
render() {
return (
{/* SOME CODE */}
);
}
}
let app = connect(null, actions)(App);
export default app;
columnsReducer.js
import { FETCH_ADMINS } from '../actions/types'
import { Link } from 'react-router-dom'
import constants from '../../static/global/index'
import React from 'react';
import { toast } from 'react-toastify'
const initialState = {
admins: [],
{
Header: "Responsible",
accessor: "responsibleAdmin",
style: { textAlign: "center" },
// Place where I want to fetch certain admins and get undefined
Cell: props => <span>{props.value && this.state.admins.name ? this.state.admins.find(admin => admin.id === props.value).name : props.value}</span>
}
}
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_ADMINS:
return { ...state, admins: action.admins}
default:
return state
}
}
index.js
import { FETCH_ADMINS } from "./types"
/**
* starts loader for setting admin
*/
export const setAdminsInColumns = (admins) => async dispatch => {
dispatch({ type: FETCH_ADMINS, admins })
}
types.js
export const FETCH_ADMINS = 'fetch_admins'
When I console.log(action.admins) inside the switch case FETCH_ADMINS in the columnsReducer.js file, I can see all the admin information I want, is there a way to make the state global in the columnsReducer.js file so I can read it?
Any help is appreciated!
use mapStateToProps in the connect method. like below
let mapStateToProps = (state)=>{
return {
admins :[yourcolumnsReducer].admins
}
}
let app = connect(mapStateToProps, actions)(App);
//you can use this.props.admins inside your component
MapStateToProps reference
I want my function below, "VerifyEmailResend" to have access to the two dispatch methods inside of mapDispatchToProps. How do I do that?
VerifyEmailResend() is exported because i want it available to be called throughout my application.
This app is written in React using Redux.
I know normally the connect method is used, but connect is for react components specifically. Is there something similar here I am missing?
import React, { Component, Fragment } from 'react'
import { api } from '../../api'
import { bool, func } from 'prop-types'
import VerifyEmailResent from './VerifyEmailResent'
import VerifyEmailVerified from './VerifyEmailVerified'
import { connect } from 'react-redux'
class VerifyEmail extends Component {
static propTypes = {
// has the verify email been resent \\
verifyEmailResent: bool,
// have you already verified your email \\
verifyEmailVerified: bool,
// set status of verify email \\
onSetVerifyEmailResent: func.isRequired,
// set status of the email verified \\
onSetVerifyEmailVerified: func.isRequired
}
resent = () => {
const { onSetVerifyEmailResent } = this.props
onSetVerifyEmailResent(false)
}
verified = () => {
const { onSetVerifyEmailVerified } = this.props
onSetVerifyEmailVerified(false)
}
render() {
const { verifyEmailResent, verifyEmailVerified } = this.props
return (
<Fragment>
{verifyEmailResent && (
<VerifyEmailResent action={this.resent} />
)}
{verifyEmailVerified && (
<VerifyEmailVerified action={this.verified} />
)}
</Fragment>
)
}
}
const mapStateToProps = state => ({
verifyEmailResent: state.eventListenerState.verifyEmailResent,
verifyEmailVerified: state.eventListenerState.verifyEmailResent
})
const mapDispatchToProps = dispatch => ({
onSetVerifyEmailResent: verifyEmailResent =>
dispatch({ type: 'VERIFY_EMAIL_RESENT_SET', verifyEmailResent }),
onSetVerifyEmailVerified: verifyEmailVerified =>
dispatch({ type: 'VERIFY_EMAIL_RESENT_VERIFIED', verifyEmailVerified })
})
const VerifyEmailResend = () => () => {
api.user.resendEmailVerification().then(data => {
if (data.resent) {
//onSetVerifyEmailResent(true)
}
if (data.verified) {
//onSetVerifyEmailVerified(false)
}
})
}
export connect(null, mapDispatchToProps)(VerifyEmailResend)
export default connect(
mapStateToProps,
mapDispatchToProps
)(VerifyEmail)
I am not sure what you're confused at. But you can do it:
const VerifyEmailResend = () => {}
export default connect(null, mapDispatchToProps)(VerifyEmailResend)
As per you need a named export, you can do it like:
export {
VerifyEmailResend: connect(null, mapDispatchToProps)(VerifyEmailResend)
}
And you can import it normally like:
import { VerifyEmailResend } from '..'
And as per your comment, you can call it like followings depending your need of field:
{ VerifyEmailResend() }
Or like:
<VerifyEmailResend />
I am fairly new to React and Redux and I have an issue with my component not updating on the final dispatch that updates a redux store. I am using a thunk to preload some data to drive various pieces of my site. I can see the thunk working and the state updating seemingly correctly but when the data fetch success dispatch happens, the component is not seeing a change in state and subsequently not re rendering. the interesting part is that the first dispatch which sets a loading flag is being seen by the component and it is reacting correctly. Here is my code:
actions
import { programsConstants } from '../constants';
import axios from 'axios'
export const programsActions = {
begin,
success,
error,
};
export const loadPrograms = () => dispatch => {
dispatch(programsActions.begin());
axios
.get('/programs/data')
.then((res) => {
dispatch(programsActions.success(res.data.results));
})
.catch((err) => {
dispatch(programsActions.error(err.message));
});
};
function begin() {
return {type:programsConstants.BEGIN};
}
function success(data) {
return {type:programsConstants.SUCCESS, payload: data};
}
function error(message) {
return {type:programsConstants.ERROR, payload:message};
}
reducers
import {programsConstants} from '../constants';
import React from "react";
const initialState = {
data: [],
loading: false,
error: null
};
export function programs(state = initialState, action) {
switch (action.type) {
case programsConstants.BEGIN:
return fetchPrograms(state);
case programsConstants.SUCCESS:
return populatePrograms(state, action);
case programsConstants.ERROR:
return fetchError(state, action);
case programsConstants.EXPANDED:
return programsExpanded(state, action);
default:
return state
}
}
function fetchPrograms(state = {}) {
return { ...state, data: [], loading: true, error: null };
}
function populatePrograms(state = {}, action) {
return { ...state, data: action.payload, loading: false, error: null };
}
function fetchError(state = {}, action) {
return { ...state, data: [], loading: false, error: action.payload };
}
component
import React from "react";
import { connect } from 'react-redux';
import { Route, Switch, Redirect } from "react-router-dom";
import { Header, Footer, Sidebar } from "../../components";
import dashboardRoutes from "../../routes/dashboard.jsx";
import Loading from "../../components/Loading/Loading";
import {loadPrograms} from "../../actions/programs.actions";
class Dashboard extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.dispatch(loadPrograms());
}
render() {
const { error, loading } = this.props;
if (loading) {
return <div><Loading loading={true} /></div>
}
if (error) {
return <div style={{ color: 'red' }}>ERROR: {error}</div>
}
return (
<div className="wrapper">
<Sidebar {...this.props} routes={dashboardRoutes} />
<div className="main-panel" ref="mainPanel">
<Header {...this.props} />
<Switch>
{dashboardRoutes.map((prop, key) => {
let Component = prop.component;
return (
<Route path={prop.path} component={props => <Component {...props} />} key={key} />
);
})}
</Switch>
<Footer fluid />
</div>
</div>
);
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error
});
export default connect(mapStateToProps)(Dashboard);
The component should receive updated props from the success dispatch and re render with the updated data. Currently the component only re renders on the begin dispatch and shows the loading component correctly but doesn't re render with the data is retrieved and updated to the state by the thunk.
I've researched this for a couple days and the generally accepted cause for the component not getting a state refresh is inadvertent state mutation rather than returning a new state. I don't think I'm mutating the state but perhaps I am.
Any help would much appreciated!
Update 1
As requested here's the code for creating the store and combining the reducers
store:
const loggerMiddleware = createLogger();
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(
thunk,
loggerMiddleware)
);
export const store = createStore(rootReducer, enhancer);
reducer combine:
import { combineReducers } from 'redux';
import { alert } from './alert.reducer';
import { programs } from './programs.reducer';
import { sidenav } from './sidenav.reducer';
const rootReducer = combineReducers({
programs,
sidenav,
alert
});
export default rootReducer;
The 2nd param is expected to be [preloadedState]:
export const store = createStore(rootReducer, {} , enhancer);
axios.get return a promise that you need to await for to get your data:
Try this:
export const loadPrograms = () => async (dispatch) => {
dispatch(programsActions.begin());
try {
const res = await axios.get('/programs/data');
const data = await res.data;
console.log('data recieved', data)
dispatch(programsActions.success(data.results));
} catch (error) {
dispatch(programsActions.error(error));
}
};
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
data: state.programs.data,
});
Action Call
import React from 'react';
import { connect } from 'react-redux';
import { loadPrograms } from '../../actions/programs.actions';
class Dashboard extends React.Component {
componentDidMount() {
// Try to call you action this way:
this.props.loadProgramsAction(); // <== Look at this
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
});
export default connect(
mapStateToProps,
{
loadProgramsAction: loadPrograms,
},
)(Dashboard);
After three days of research and refactoring, I finally figured out the problem and got it working. Turns out that the version of react-redux is was using (6.0.1) was the issue. Rolled back to 5.1.1 and everything worked flawlessly. Not sure if something is broken in 6.0.1 or if I was just using wrong.
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 new to react native + redux. I have an react native application where user first screen is login and after login am showing page of list of categories from server. To fetch list of categories need to pass authentication token, which we gets from login screen or either if he logged in previously then from AsyncStorage.
So before redering any component, I am creating store and manully dispatching fetchProfile() Action like this.
const store = createStore(reducer);
store.dispatch(fetchProfile());
So fetchProfile() try to reads profile data from AsyncStorage and dispatch action with data.
export function fetchProfile() {
return dispatch => {
AsyncStorage.getItem('#myapp:profile')
.then((profileString) => {
dispatch({
type: 'FETCH_PROFILE',
profile: profileString ? JSON.parse(profileString) : {}
})
})
}
}
so before store get populated, login page get rendered. So using react-redux's connect method I am subscribing to store changes and loading login page conditionally.
class MyApp extends React.Component {
render() {
if(this.props.profile)
if(this.props.profile.authentication_token)
retunr (<Home />);
else
return (<Login />);
else
return (<Loading />);
}
}
import { connect } from 'react-redux';
const mapStateToProps = (state) => {
return {
profile: state.profile
}
}
module.exports = connect(mapStateToProps, null)(MyApp);
So first 'Loading' component get rendered and when store is populated then either 'Login' or 'Home' component get rendered. So is it a correct flow? Or is there a way where I can get store populated first before any compnent render and instead of rendering 'Loading' component I can directly render 'Login' or 'Home' Component.
Verry common approach is to have 3 actions for an async operation
types.js
export const FETCH_PROFILE_REQUEST = 'FETCH_PROFILE_REQUEST';
export const FETCH_PROFILE_SUCCESS = 'FETCH_PROFILE_SUCCESS';
export const FETCH_PROFILE_FAIL = 'FETCH_PROFILE_FAIL';
actions.js
import * as types from './types';
export function fetchProfile() {
return dispatch => {
dispatch({
type: types.FETCH_PROFILE_REQUEST
});
AsyncStorage.getItem('#myapp:profile')
.then((profileString) => {
dispatch({
type: types.FETCH_PROFILE_SUCCESS,
data: profileString ? JSON.parse(profileString) : {}
});
})
.catch(error => {
dispatch({
type: types.FETCH_PROFILE_ERROR,
error
});
});
};
}
reducer.js
import {combineReducers} from 'redux';
import * as types from './types';
const isFetching = (state = false, action) => {
switch (action.type) {
case types.FETCH_PROFILE_REQUEST:
return true;
case types.FETCH_PROFILE_SUCCESS:
case types.FETCH_PROFILE_FAIL:
return false;
default:
return state;
}
};
const data = (state = {}, action) => {
switch (action.type) {
case types.FETCH_PROFILE_SUCCESS:
return action.data;
}
return state;
};
export default combineReducers({
isFetching,
data
});
So you can get isFetching prop in your component and show/hide Loader component
You can load all your data during the splash screen and then load the others screens after that. I did it like this. Hope it helps
class Root extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
store: configureStore( async () => {
const user = this.state.store.getState().user || null;
if (categories && categories.list.length < 1) {
this.state.store.dispatch(categoriesAction());
}
this.setState({
isLoading: false
});
}, initialState)
};
}
render() {
if (this.state.isLoading) {
return <SplashScreen/>;
}
return (
<Provider store={this.state.store}>
<AppWithNavigationState />
</Provider>
);
}
}
Redux and Redux Persist (https://github.com/rt2zz/redux-persist) will solve your problem.
Don't make them complex.