I'm learning redux.
Wrote some simple components for fetching data from API.
Everything worked fine until I added routing.
I'm wrapping my Router with Provider. I have route '/cars' and component VisibleCarList which accesses store using connect. When accessing '/cars' via Link or direct URL(tried both, same result) mapStateToProps is not invoked at all, can't see any redux props.
However if I go to for example '/example' with routed App component which has VisibleCarList in it everything works fine.
Spent a few hours already and I still don't understand why it can't connect to the store. Any ideas?
index.js
const store = createStore( reducer, composeEnhancers(
applyMiddleware(thunkMiddlewares)))
ReactDOM.render(
<Provider store={store}>
<Router>
<Route path="/example" component={App}/>
<Route path="/cars" component={VisibleCarList}/>
</Router>
</Provider>
, document.getElementById('root'));
VisibleCarList
export class VisibleCarList extends Component {
componentDidMount() {
this.props.getCarsPage(0);
}
render() {
...
}
}
const mapStateToProps = (state) => {
return{
cars: state.cars,
pagination: state.pagination,
}
}
const mapDispatchToProps = {
getCarsPage: fetchCarPage,
setPage: setCurrentPage,
}
export default connect(mapStateToProps, mapDispatchToProps)(VisibleCarList)
App.js
function App(props) {
return (
<Box bgcolor="primary.light">
<NavBar />
<VisibleCarList></VisibleCarList> //WORKS FINE
</Box>
);
}
export default App;
I think the import statement may be wrong.
You have to use import VisibleCarList from '...'; rather than import { VisibleCarList } from '...';.
Currently within my app I have multiple Routes using React Router that are wrapped within the Redux provider.
Provider
<Provider store={store}>
<Router>
<Route exact path ="/" component = {LoginPage}></Route>
<Route exact path="/login" component ={LoginPage}/>
<Route path ="/change" component={MasterPage}/>
<Route path="/change/form" component={ChangeForm}/>
<Route path="/change/table" component={ExpandTable} />
</Router>
</Provider>
As it stands I'm confused how I'm supposed to being passing/accessing the state of the store within the components.
The only state that I really need to be storing is the current status of the user login and want to use this to change what is rendered on the other routes.
Reducer
I have a reducer which looks like:
export default (state = {}, action) => {
switch (action.type) {
case 'SIMPLE_ACTION':
return {
loggedIn: action.loggedIn,
User: action.User,
Group: action.Group
}
default:
return state
}
}
Action
And an action which looks like
export const simpleAction = () => dispatch => {
dispatch({
type: 'SIMPLE_ACTION',
payload: data
})
}
Now I'm currently working under the assumption that connecting them to the store using
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
would allow me to access the store within that component, but it seems that isn't the case.
I'm a little confused as to how to access the store state within the component and also how to properly write the action/reducer to change this state.
So I found where I was going wrong, I needed to pass the props to the route using:
render={(props) => <LoginPage {...props} login={store.getState()} />}>
And in my configureStore() I was passing rootReducer instead of the simple reducer I had made.
Your assumption is correct. It's a good practice to follow the connect pattern. You can create containers for components and do the connection there.
Something like this:
LoginPage.js
const LoginPage = ({loggedIn}) => {
console.log(loggedIn);
return null; // Write your markup here
}
LoginPage.propTypes = {
loggedIn: PropTypes.bool
}
export default LoginPage;
LoginPageContainer.js
import LoginPage from './LoginPage';
let LoginPageContainer = ({loggedIn}) => {
return <LoginPage loggedIn={loggedIn} />
};
LoginPageContainer.propTypes = {
loggedIn: PropTypes.bool
//...
}
const mapStateToProps = state => {
return {
loggedIn: state.........
};
};
LoginPageContainer = connect(mapStateToProps)(LoginPageContainer);
export default LoginPageContainer;
NOTE: This approach is usually used for more complex logic. In your case the LoginPage itself can be a container, so you don't need separate component.
I am trying to figure out a way to store the authentication state of a user inside the redux store. Suppose isAuthenticated store the state of user if they are logged-in or not. Now, I have a cookie(httpOnly) sent by the server which remembers the user, so that they don't need to enter there credentials every time they visit the app.
Flow: User some day logged in to the application and didn't logged out and closed the browser. Now, he returns and visit my app. Since, the cookie was there in browser, this will be sent automatically(without user interaction) by the application and if the cookie is valid, the isAuthenticated: true. Very simple requirement.
Tracking the authentication status should be the first thing done by the application, so I put that logic at very first, before the App.js renders.
class App extends Component {
store = configureStore();
render() {
return (
<Provider store={this.store}>
<ConnectedRouter history={history}>
<>
<GlobalStyle />
<SiteHeader />
<ErrorWrapper />
<Switch>
<PrivateHomeRoute exact path="/" component={Home} />
<Route exact path="/login" component={LoginPage} />
<PrivateHomeRoute path="/home" component={Home} />
........code
}
This is the configureStore()
export const history = createBrowserHistory();
const configureStore = () => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer(history),
composeEnhancers(applyMiddleware(sagaMiddleware, routerMiddleware(history)))
);
sagaMiddleware.run(rootSaga);
store.dispatch({ type: AUTH.AUTO_LOGIN });
return store;
};
store.dispatch({ type: AUTH.AUTO_LOGIN }); is the code where I am trying the application to do the auto-login as the first operation in the application. This action is handled by a redux-saga
function* handleAutoLogin() {
try {
const response = yield call(autoLoginApi);
if (response && response.status === 200) {
yield put(setAuthenticationStatus(true));
}
} catch (error) {
yield put(setAuthenticationStatus(false));
}
}
function* watchAuthLogin() {
yield takeLatest(AUTH.AUTO_LOGIN, handleAutoLogin);
}
autoLoginApi is the axios call to the server which will carry the cookie with it. setAuthenticationStatus(true) is action creator which will set the isAuthenticated to true false.
So, yes this is working BUT not as expected. Since, the app should first set the isAuthenticated first and then proceed with the App.js render(). But, since setting the isAuthenticated take some seconds(api call), the application first renders with the isAuthenticated: false and then after the AUTH.AUTO_LOGIN gets completed, then the application re-render for authenticaed user.
What's the problem then? For the normal component it may not be the problem, e.g this SiteHeader component
class SiteHeader extends React.Component {
render() {
const { isLoggedIn } = this.props;
if (isLoggedIn === null) {
return "";
} else {
if (isLoggedIn) {
return (
<LoggedInSiteHeader />
);
} else {
return (
<LoggedOutSiteHeader />
);
}
}
}
}
const mapStateToProps = ({ auth, user }) => ({
isLoggedIn: auth.isLoggedIn,
});
export default connect(
mapStateToProps,
null
)(SiteHeader);
But, this solution doesn't work for the Custom routing.
const PrivateHomeRoute = ({ component: ComponentToRender, ...rest }) => (
<Route
{...rest}
render={props =>
props.isLoggedIn ? (
<ComponentToRender {...props} />
) : (
<Redirect to="/login" />
)
}
/>
);
const mapStateToProps = auth => ({
isLoggedin: auth.isLoggedIn
});
export default connect(
mapStateToProps,
null
)(PrivateHomeRoute);
PrivateHomeRoute gets resolved before the redux store gets updated, hence the Route always goes to "/login".
I am looking for a solution, where the application doesn't proceed further until the authentication action doesn't complete. But, I am no clue what and where to put that code?
Few things I tried:
async await on configureStore() - Error came
async await on App.js - Error
PS: Libraries I am using redux, redux-saga,react-router-dom, connected-react-router, axios
One way I figured out:
Create a separate component MyRouteWrapper which will return the routes based on the isLoggedIn status. To, resolve the issue I stop the routes to render until the auto-login changes the isLoggedIn state.
I set the default state of isLoggedIn to null. Now, if the state is null the MyRouteWrapper will return an empty string, and once the state gets changes to true/false, it will return the routes, and then respective components get rendered.
I changed my App.js
const store = configureStore();
class App extends Component {
render() {
return (
<Provider store={store}>
<ConnectedRouter history={history}>
<MyRouteWrapper />
</ConnectedRouter>
</Provider>
);
}
}
export default App;
The component which make sure to return the Route only when the state gets changed to true/false
const MyRouteWrapper = props => {
if (props.isLoggedIn === null) {
return "";
} else {
return (
<>
<GlobalStyle />
<SiteHeader />
<ErrorWrapper />
<Switch>
<ProtectedHomeRoute
exact
path="/"
component={Home}
isLoggedIn={props.isLoggedIn}
/>
<Route path="/profile/:id" component={Profile} />
<Route path="/login" component={LoginPage} />
</Switch>
</>
);
}
};
const mapStateToProps = ({ auth }) => ({
isLoggedIn: auth.isLoggedIn
});
export default connect(mapStateToProps)(MyRouteWrapper);
This solved the issue.
I am still curious to know the solutions(better) anyone have in there mind.
In index.js push directly or throw dispatch works well:
...
import { push } from 'react-router-redux'
const browserHistory = createBrowserHistory()
export const store = createStore(
rootReducer,
applyMiddleware(thunkMiddleware, routerMiddleware(browserHistory))
)
// in v5 this line is deprecated
export const history = syncHistoryWithStore(browserHistory, store)
history.push('/any') // works well
store.dispatch(push('/any')) // works well
ReactDOM.render((
<Provider store={store}>
<Router history={history}>
<App />
</Router>
</Provider>
), document.getElementById('root'))
App.js
class App extends Component {
render() {
return (
<div className="app">
<Switch>
<Route path="/" component={Main} />
<Route path="/any" component={Any} />
</Switch>
</div>
);
}
}
export default withRouter(connect(/*...*/)(App))
but in redux-thunk action all attempts ends by rewriting url, but without re-rendering
...
export function myAction(){
return (dispatch) => {
// fetch something and then I want to redirect...
history.push('/any') // change url but not re-render
dispatch(push('/any')) // change url but not re-render
store.dispatch(push('/any')) // change url but not re-render
}
}
This myAction is calling fetch() inside and should redirect after success.
If I run this.props.history.push('/any') inside component, it works! but I need to run redirect inside thunk action after successful fetch()
I was trying wrap all components with withRouter or Route, but didn't help.
Inject history object into your component and use push like this:
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
#withRouter
#connect(({auth})=>({auth}))
class App extends Component {
// on redux state change
componentWillReceiveProps(nextProps) {
if(!nextProps.auth)
this.props.history.push('/login')
}
render() {
return (
<div>
<Button
// on button click
onClick={this.props.history.push('/')}
>
Home page
</Button>
</div>
);
}
}
I made workaround by delegating state of successfull fetch() to the component (thanks #oklas) where is history.push() or <Redirect> working:
{this.props.fetchSuccessfull && <Redirect to="/any" />}
But still waiting for better solution by calling push() directly from thunk action.
Well, let me then submit another not perfect solution by passing the history object in the dispatch to the action. I guess it's more a beginners-solution but is IMHO simple to understand (and therefore simple to maintain which is the most important thing in software-development)
Using <BrowserRouter> makes all React-compoments having the history in their props. Very convenient. But, as the problem description stated, you want it outside a React Component, like an action on Redux-Thunk.
Instead of going back to <Router> I chose to stick to BrowserRouter.
The history object cannot be accessed outside React Components
I did not like going back to <Router> and using something like react-router-redux
Only option left is to pass along the history object to the action.
In a Auth-ForgotPassword component:
const submitHandler = (data) => {
dispatch(authActions.forgotpassword({data, history:props.history}));
}
In the action function
export const forgotpassword = ({forgotpasswordData, history}) => {
return async dispatch => {
const url = settings.api.hostname + 'auth/forgotpassword'; // Go to the API
const responseData = await fetch(
url,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(forgotpasswordData),
}
);
history.push('/auth/forgotpassword/success');
}
}
And now we all wait for the final elegant solution :-)
I have token authentication from a server, so when my Redux app is loaded initially I need make a request to this server to check whether user is authenticated or not, and if yes I should get token.
I have found that using Redux core INIT actions is not recommended, so how can I dispatch an action, before app is rendered?
You can dispatch an action in Root componentDidMount method and in render method you can verify auth status.
Something like this:
class App extends Component {
componentDidMount() {
this.props.getAuth()
}
render() {
return this.props.isReady
? <div> ready </div>
: <div>not ready</div>
}
}
const mapStateToProps = (state) => ({
isReady: state.isReady,
})
const mapDispatchToProps = {
getAuth,
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
All of the answers here seem to be variations on creating a root component and firing it in the componentDidMount. One of the things I enjoy most about redux is that it decouples data fetching from component lifecycles. I see no reason why it should be any different in this case.
If you are importing your store into the root index.js file, you can just dispatch your action creator(let's call it initScript()) in that file and it will fire before anything gets loaded.
For example:
//index.js
store.dispatch(initScript());
ReactDOM.render(
<Provider store={store}>
<Routes />
</Provider>,
document.getElementById('root')
);
I've not been happy with any solutions that have been put forward for this, and then it occurred to me that I was thinking about classes needing to be rendered. What about if I just created a class for startup and then push things into the componentDidMount method and just have the render display a loading screen?
<Provider store={store}>
<Startup>
<Router>
<Switch>
<Route exact path='/' component={Homepage} />
</Switch>
</Router>
</Startup>
</Provider>
And then have something like this:
class Startup extends Component {
static propTypes = {
connection: PropTypes.object
}
componentDidMount() {
this.props.actions.initialiseConnection();
}
render() {
return this.props.connection
? this.props.children
: (<p>Loading...</p>);
}
}
function mapStateToProps(state) {
return {
connection: state.connection
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Startup);
Then write some redux actions to async initialise your app. Works a treat.
If you are using React Hooks, one single-line solution is
useEffect(() => store.dispatch(handleAppInit()), []);
The empty array ensures it is called only once, on the first render.
Full example:
import React, { useEffect } from 'react';
import { Provider } from 'react-redux';
import AppInitActions from './store/actions/appInit';
import store from './store';
export default function App() {
useEffect(() => store.dispatch(AppInitActions.handleAppInit()), []);
return (
<Provider store={store}>
<div>
Hello World
</div>
</Provider>
);
}
Update 2020:
Alongside with other solutions, I am using Redux middleware to check each request for failed login attempts:
export default () => next => action => {
const result = next(action);
const { type, payload } = result;
if (type.endsWith('Failure')) {
if (payload.status === 401) {
removeToken();
window.location.replace('/login');
}
}
return result;
};
Update 2018: This answer is for React Router 3
I solved this problem using react-router onEnter props. This is how code looks like:
// this function is called only once, before application initially starts to render react-route and any of its related DOM elements
// it can be used to add init config settings to the application
function onAppInit(dispatch) {
return (nextState, replace, callback) => {
dispatch(performTokenRequest())
.then(() => {
// callback is like a "next" function, app initialization is stopped until it is called.
callback();
});
};
}
const App = () => (
<Provider store={store}>
<IntlProvider locale={language} messages={messages}>
<div>
<Router history={history}>
<Route path="/" component={MainLayout} onEnter={onAppInit(store.dispatch)}>
<IndexRoute component={HomePage} />
<Route path="about" component={AboutPage} />
</Route>
</Router>
</div>
</IntlProvider>
</Provider>
);
With the redux-saga middleware you can do it nicely.
Just define a saga which is not watching for dispatched action (e.g. with take or takeLatest) before being triggered. When forked from the root saga like that it will run exactly once at startup of the app.
The following is an incomplete example which requires a bit of knowledge about the redux-saga package but illustrates the point:
sagas/launchSaga.js
import { call, put } from 'redux-saga/effects';
import { launchStart, launchComplete } from '../actions/launch';
import { authenticationSuccess } from '../actions/authentication';
import { getAuthData } from '../utils/authentication';
// ... imports of other actions/functions etc..
/**
* Place for initial configurations to run once when the app starts.
*/
const launchSaga = function* launchSaga() {
yield put(launchStart());
// Your authentication handling can go here.
const authData = yield call(getAuthData, { params: ... });
// ... some more authentication logic
yield put(authenticationSuccess(authData)); // dispatch an action to notify the redux store of your authentication result
yield put(launchComplete());
};
export default [launchSaga];
The code above dispatches a launchStart and launchComplete redux action which you should create. It is a good practice to create such actions as they come in handy to notify the state to do other stuff whenever the launch started or completed.
Your root saga should then fork this launchSaga saga:
sagas/index.js
import { fork, all } from 'redux-saga/effects';
import launchSaga from './launchSaga';
// ... other saga imports
// Single entry point to start all sagas at once
const root = function* rootSaga() {
yield all([
fork( ... )
// ... other sagas
fork(launchSaga)
]);
};
export default root;
Please read the really good documentation of redux-saga for more information about it.
Here's an answer using the latest in React (16.8), Hooks:
import { appPreInit } from '../store/actions';
// app preInit is an action: const appPreInit = () => ({ type: APP_PRE_INIT })
import { useDispatch } from 'react-redux';
export default App() {
const dispatch = useDispatch();
// only change the dispatch effect when dispatch has changed, which should be never
useEffect(() => dispatch(appPreInit()), [ dispatch ]);
return (<div>---your app here---</div>);
}
I was using redux-thunk to fetch Accounts under a user from an API end-point on app init, and it was async so data was coming in after my app rendered and most of the solutions above did not do wonders for me and some are depreciated. So I looked to componentDidUpdate(). So basically on APP init I had to have accounts lists from API, and my redux store accounts would be null or []. Resorted to this after.
class SwitchAccount extends Component {
constructor(props) {
super(props);
this.Format_Account_List = this.Format_Account_List.bind(this); //function to format list for html form drop down
//Local state
this.state = {
formattedUserAccounts : [], //Accounts list with html formatting for drop down
selectedUserAccount: [] //selected account by user
}
}
//Check if accounts has been updated by redux thunk and update state
componentDidUpdate(prevProps) {
if (prevProps.accounts !== this.props.accounts) {
this.Format_Account_List(this.props.accounts);
}
}
//take the JSON data and work with it :-)
Format_Account_List(json_data){
let a_users_list = []; //create user array
for(let i = 0; i < json_data.length; i++) {
let data = JSON.parse(json_data[i]);
let s_username = <option key={i} value={data.s_username}>{data.s_username}</option>;
a_users_list.push(s_username); //object
}
this.setState({formattedUserAccounts: a_users_list}); //state for drop down list (html formatted)
}
changeAccount() {
//do some account change checks here
}
render() {
return (
<Form >
<Form.Group >
<Form.Control onChange={e => this.setState( {selectedUserAccount : e.target.value})} as="select">
{this.state.formattedUserAccounts}
</Form.Control>
</Form.Group>
<Button variant="info" size="lg" onClick={this.changeAccount} block>Select</Button>
</Form>
);
}
}
const mapStateToProps = state => ({
accounts: state.accountSelection.accounts, //accounts from redux store
});
export default connect(mapStateToProps)(SwitchAccount);
If you're using React Hooks, you can simply dispatch an action by using React.useEffect
React.useEffect(props.dispatchOnAuthListener, []);
I use this pattern for register onAuthStateChanged listener
function App(props) {
const [user, setUser] = React.useState(props.authUser);
React.useEffect(() => setUser(props.authUser), [props.authUser]);
React.useEffect(props.dispatchOnAuthListener, []);
return <>{user.loading ? "Loading.." :"Hello! User"}<>;
}
const mapStateToProps = (state) => {
return {
authUser: state.authentication,
};
};
const mapDispatchToProps = (dispatch) => {
return {
dispatchOnAuthListener: () => dispatch(registerOnAuthListener()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Same solution as Chris Kemp mentions above. Could be even more generic, just a canLift func not tied to redux?
interface Props {
selector: (state: RootState) => boolean;
loader?: JSX.Element;
}
const ReduxGate: React.FC<Props> = (props) => {
const canLiftGate = useAppSelector(props.selector);
return canLiftGate ? <>{props.children}</> : props.loader || <Loading />;
};
export default ReduxGate;
Using: Apollo Client 2.0, React-Router v4, React 16 (Fiber)
The answer selected use old React Router v3. I needed to do 'dispatch' to load global settings for the app. The trick is using componentWillUpdate, although the example is using apollo client, and not fetch the solutions is equivalent.
You don't need boucle of
SettingsLoad.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {bindActionCreators} from "redux";
import {
graphql,
compose,
} from 'react-apollo';
import {appSettingsLoad} from './actions/appActions';
import defQls from './defQls';
import {resolvePathObj} from "./utils/helper";
class SettingsLoad extends Component {
constructor(props) {
super(props);
}
componentWillMount() { // this give infinite loop or no sense if componente will mount or not, because render is called a lot of times
}
//componentWillReceiveProps(newProps) { // this give infinite loop
componentWillUpdate(newProps) {
const newrecord = resolvePathObj(newProps, 'getOrgSettings.getOrgSettings.record');
const oldrecord = resolvePathObj(this.props, 'getOrgSettings.getOrgSettings.record');
if (newrecord === oldrecord) {
// when oldrecord (undefined) !== newrecord (string), means ql is loaded, and this will happens
// one time, rest of time:
// oldrecord (undefined) == newrecord (undefined) // nothing loaded
// oldrecord (string) == newrecord (string) // ql loaded and present in props
return false;
}
if (typeof newrecord ==='undefined') {
return false;
}
// here will executed one time
setTimeout(() => {
this.props.appSettingsLoad( JSON.parse(this.props.getOrgSettings.getOrgSettings.record));
}, 1000);
}
componentDidMount() {
//console.log('did mount this props', this.props);
}
render() {
const record = resolvePathObj(this.props, 'getOrgSettings.getOrgSettings.record');
return record
? this.props.children
: (<p>...</p>);
}
}
const withGraphql = compose(
graphql(defQls.loadTable, {
name: 'loadTable',
options: props => {
const optionsValues = { };
optionsValues.fetchPolicy = 'network-only';
return optionsValues ;
},
}),
)(SettingsLoad);
const mapStateToProps = (state, ownProps) => {
return {
myState: state,
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators ({appSettingsLoad, dispatch }, dispatch ); // to set this.props.dispatch
};
const ComponentFull = connect(
mapStateToProps ,
mapDispatchToProps,
)(withGraphql);
export default ComponentFull;
App.js
class App extends Component<Props> {
render() {
return (
<ApolloProvider client={client}>
<Provider store={store} >
<SettingsLoad>
<BrowserRouter>
<Switch>
<LayoutContainer
t={t}
i18n={i18n}
path="/myaccount"
component={MyAccount}
title="form.myAccount"
/>
<LayoutContainer
t={t}
i18n={i18n}
path="/dashboard"
component={Dashboard}
title="menu.dashboard"
/>