Sign in Go Pro

React Native with Redux

Dispatching Actions to a Second Redux Reducer

This lesson is for PRO members.

Upgrade today to get access to all the PRO lessons.

Unlock this lesson
Autoplay

Previous

About

Actions are usually split up into files based on which reducer will handle them. This doesn't have to be the case though. In our application, we'll make a new auth actions file, and create an action that will let the user log into the application by typing in a username.

Summary of Content:

  • Connect the Login component
  • Create a new auth actions file, and a new login action.
  • In the reducer, handle the login action using a switch/case statement
  • Dispatch the login action when a user logs in
  • Create a "Logout" button that dispatches a null username, which logs the user out

Instructor

Links

Comments

Excellent series.
One additional video and topic would make it even more excellent:
Taking the existing redux solution, and adding a simple implementation of redux-persist so that the App will persist it's state to disk (probably using local storage) when closing, and restoring it's state from disk when launched or coming back into focus. This would prevent the necessity of querying the server for an initial pull every time the App runs and would help the app to still run with lack of network connection.
And besides all of that, it would help me to figure out how to do this, since I attempted it today and am failing miserably ;)
Please?

Thanks! Glad you liked it.

Yes, that would be a good next step!

What's giving you trouble with redux persist? It should be possible to wrap your entire store to save it, and then load on app launch - but there could be some tricky parts (I remember that redux-persist does a few things automatically, so can be tricky to debug).

The other thing you could do is to write and read from storage manually - You can use async storage to save what you need, and then pull it back on the componentDidMount of the top level component.

The setup is where I am stuck.
I built my redux pages, Components, and functions by following your series here of redux integration.
But, go figure, all of the redux-persist setup instructions I can find are all slightly different than what I have from following along here.
So, I am given a valiant effort in getting it going but have nothing but errors, since I don't know enough about react-native and redux to just figure out what subtle changes are necessary to get it working.
If you could show the necessary changes to the "App.js" and "reducers/index.js" files (which appear to be the only files that hold the initial setup changes) then it would be a major help in getting this to work.

Hm, let's see if I can decode the instructions a bit... If you're looking at the line from redux-persist docs:

import rootReducer from './reducers'

Then that rootReducer is whatever your reducer is. So take your main reducer, and wrap it with the persistReducer function (passing in the config that they have; so:

const persistedReducer = persistReducer(persistConfig, rootReducer)

And now that persistedReducer looks like it becomes your main reducer.

so you can create your store now with that persistedReducer,

and then you also have to wrap that store with persistStore in order for it to save...

It does look complex, but basically what you're doing is wrapping your current reducer so that it persists, and then taking the output of that and wrapping it...

does that help at all?

Okay, using your guidance I have made some headway but have run into a roadblock.
1) installed redux-persist

2) installed async-storage

3) App.js imports:
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { persistStore, persistReducer } from 'redux-persist'
import AsyncStorage from '@react-native-community/async-storage';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

4) App.js - modified and added the following:
const persistConfig = {
key: 'root',
storage: AsyncStorage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = createStore(persistedReducer, applyMiddleware(thunk))
const persistedStore = persistStore(store)

5) App.js, in export default class App, I modified the provider:
export default class App extends React.Component {
render() {
//console.log('props', this.props)
//persistedStore.purge()
return (
{Provider store={persistedStore}>
{AppContainer />
{/Provider>
)
}
}

6) when it runs, (due to console.log in the reducer) in the console and Xcode console I can see the new store action types PERSIST & REHYDRATE with my state being passed as it's payload

7) but I cannot get past an error that stops everything before any U.I. rendering, causing a white blank screen overplayed by the redbox error:
TypeError: undefined is not an object (evaluating 'state.initialList.items')

8) stuck

Ah, that's happening because you're trying to access .items on the initialList before the initialList is loaded. Try switching that error line to this, which will only display something once there is something to display:

state.initialList && state.initialList.items

Does that get you past that error?

Well, trying to find the error occurrence is rather tricky on this one.
I believe the only places this "state.initialList.items" code is used is in the 2 Component files, in the mapStateToProps functions.
All of the other occurrences of "initialList.items" state stuff is in "this.props.initialList.items".

You'll have to change all of those this.props.initialList.items to "this.props.initialList && this.props.initialList.items" as well then, OR just not show the components that contain that until the initialList is loaded

1) In my 2 Component files handling the Views for the 2 tabs, I have used:
const mapStateToProps = (state, ownProps) => {
return {
items: state.initialList.items,
token: state.initialList.token,
loading: state.initialList.loading,
error: state.initialList.error
}
}

2) when I comment out the first line "items: state.initialList.items," and run it, I get the same error but on the 2nd line:
undefined is not an object (evaluating 'state.initialList.token')

3) so I believe this proves that this error is happening in these "mapStateToProps" functions for redux-connect

4) ideas on how to resolve?

Yeah - anywhere you try to access the initialList, you'll have to "gate" that with "state.initialList && state.initialList.whatever"; if you don't like the repetitiveness of that, you can write a little helper function to do it for you; and that helper function is actually called a "selector" in redux.

I find selectors to make learning a bit tricky though (just another layer of abstraction); so I just recommend adding a bunch of "state.initialList &&" everywhere you use it. Then, once it's working, you can abstract that if you want.

The other option is to gate the ENTIRE app with a "loading..." screen or something while the state re-hydrates. Then you can have something like:

{isLoading && }
{!isLoading && }

in a high level component

Partial success!
This code stops that particular error:
const mapStateToProps = (state, ownProps) => {
if (state.initialList) {
return {
items: state.initialList.items,
token: state.initialList.token,
loading: state.initialList.loading,
error: state.initialList.error
}
} else {
return {}
}
}

But, it also does NOT seem to reload the Component after the state re-hydrates, so this idea is not a working solution.

So, another place I could integrate your idea is somehow in the AppContainer:
const AppContainer = createAppContainer(
createBottomTabNavigator(
{
// this creates the tab slots
Home: HomeStack,
Settings: SettingsStack,
},
{
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, tintColor }) =>
getTabBarIcon(navigation, focused, tintColor),
}),
tabBarOptions: {
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
},
}
)
);

OR
Maybe somehow in the Tab Stacks:
const HomeStack = createStackNavigator({
Home: ConnectedHomeScreen,
Details: ConnectedDetailsScreen,
});
const SettingsStack = createStackNavigator({
Settings: ConnectedSettingsScreen,
Details: ConnectedDetailsScreen,
});

I have no idea how to go about doing this, so thanx in advance for your continued help in this endeavor.

Hm, it definitely should re-render after the redux store rehydrates. ... could you put a few log statements in there? Is the store always coming back as blank? are there any errors from the rehydration? or maybe: ... is the store blank, so it's coming back as blank, which means redux doesn't have anything in there to rehydrate?

In the case of the if/then statement in the mapStateToProps function, wouldn't this only get called once when the Component is first instantiated? I thought only the render() function is called when the state changes. Hense it never re-runs after state is finally loaded.

My state during rehydrate is showing in the console as:
'Reducers initialList store action:', { type: 'persist/REHYDRATE',
payload:
{ initialList:
{ items:
[ { fieldData: { recordID: 1234, fxcLastNameencoded: 'Captain' } },
{ fieldData: { _recordID: 2345, fx
cLastName_encoded: 'Starbucks' } } ],
token: '',
loading: false,
connection: null,
error: null },
_persist: { version: -1, rehydrated: true } },
err: undefined,
key: 'root' }

mapStateToProps should get called every time the Redux store changes. If you put a console log statement in mapStateToProps, does it get called both before and after the rehydration? (it should)

Good idea, to put logs inside that function to see.
Lack of coffee - it's what I will blame as to why I didn't do it sooner ;)

So, this log shows some oddities for sure.
Rather than copy/paste a huge blob, I will translate:
...
'Reducers initialList store action:', { type: '@@redux/INIT6.e.7.h.w' }
'Reducers initialList store action:', { type: '@@redux/PROBEUNKNOWNACTIONi.3.n.x.r.i' }
'Reducers initialList store action:', { type: '@@redux/INIT6.e.7.h.w' }
'Reducers initialList store action:', { type: 'persist/PERSIST', register: [Function: register], rehydrate: [Function: rehydrate] }
...
'App props', { rootTag: 1 }
HomeScreen mapStateToProps (NO state) triggered...
HomeScreen render triggered...
HomeScreen mapStateToProps (NO state) triggered...
HomeScreen render triggered...
'Reducers initialList store action:', { type: 'persist/REHYDRATE', payload:
{ initialList:
{ items:
[ { fieldData: { recordID: 1234, fxcLastNameencoded: 'Captain' } },
{ fieldData: { _recordID: 2345, fx
cLastName_encoded: 'Starbucks' } } ],
token: '',
loading: false,
connection: null,
error: null },
_persist: { version: -1, rehydrated: true } },
err: undefined,
key: 'root' }
HomeScreen mapStateToProps (NO state) triggered...
[GESTURE HANDLER] Initialize gesture handler for root view
...
A FEW SECONDS LATER I PRESS THE 2ND TAB (SETTINGS)
...
SettingsScreen mapStateToProps (NO State) triggered...
SettingsScreen render triggered...
SettingsScreen render triggered...

Hmm; It's hard to tell what's going on via replies (sorry! probably not the best medium for debugging :) ) - I would say: maybe take a step back with redux persist, and try it out on a much smaller project first? Just so you can understand what it's really doing... I don't have a ton of experience with it - so I'm not sure what's going on sorry!

Scraped redux-persist after these unresolved errors.
Tried again with AsyncStorage.
Using a bit of tutorial code I got past all the hiccups and it works!!

I have noticed that the same error I got with react-persist occurs when I remove a snippet of code, so I'm curious if this provides you any clue as to our earlier dilemma.

In my main default tab Component that displays the initialList data, I currently have:
constructor(props) {
super(props);
this.state = {
bogusField: 'when I delete this after adding AsyncStorage, it crashes and complains about this.state being null or some such garbage'
}
}

Removing this generates the same error I had in redux-persist.
Think it's a related error?
Even with redux do I need to have a non-null local state or something like that in order for things to work properly?
I'm thinking it all has to do with the time involved with pulling data off of disk versus the Component being created much quicker...

Not sure, sorry - yes, it has to do with the component being created before the data is read from disk. Just have to make sure you're gating (with &&) your access to things that may not be there.

>
You need to go PRO to post comments.

Lessons in React Native with Redux