React Native and Redux Course -> Table of Contents:
Any word that is highlighted in yellow represents a "tooltip." You can hover over it and see some additional information about that particular term. Any edits to that functionality can be investigagted by heading out to tooltipster.com.
React-Native Documentation | Stephen Grinder Tutorial | Atom Shortcut Cheat Sheet
Router Flux Documentation
You've got to use the Terminal to bring up the Simulator and then you'll need to run some commands in the Terminal. It looks like this... A) Search Bar B) Terminal Commands If you're running Parallels, should you attempt to write anything in the console, you're going to run into a problem in that your Simulator will open up Google Chrome on the Windows side which will result in an error. A) Chrome for Mac 1) "Command->D" to Debug Your Code Using the Terminal to set up your new application. A) Terminal Command to Setup New App This is a great little tool that will alert you to code errors before you try running your application. A) Atom Preferences B) Linter Configuration C) .eslintrc A quick run down of all the major concepts and machinery that we covered in the first part of the tutorial where we built our first app. A) Components 1) Class Level Components 2) Functional Components B) Props 1) Decontstructing Props This particular project was a bear despite it having been introduced as an "easier" segment in the tutorial. The name of the project is "Tech Stack" and there's a lot to absorb... A) What is Redux? 1) Redux Playground B) Installing Redux C) Bare Bones Beginning 1) App.js 2) The Header D) Random Challenges and Problems 1) Simulator Not Working 2) Expected the reducer to be a function... D) Review and Recap 1) Redux 2) Flowchart 3) Reducers 4) Actions 5) mapStateToProps A "thick" app, but loaded with some great features and knowledge...! A) Logging In Using Redux 1) Same Variable / Different Object a) a) EMAIL CHANGED Dilemma & Utility 2) Establishing the Connection 3) this.renderError() -> Warning: Functions are not valid as a React child.... 4) Firebase Gotcha
You've got to use the Terminal to bring up the Simulator and then you'll need to run some commands in the Terminal. It looks like this... A) Search Bar (back to top...) You can navigate to Applications / Utilities to get to the Terminal, or you can click on "command -> space" to bring up the Search bar. Type in "Terminal," and you'll see the application in the directory. Double click on it and you're up and running. B) Terminal Commands (back to top...) You'll have to navigate to your project directory. Your Terminal will open up by default to your "user" folder. To see where it is that your files are, including your user directory, start by hitting "command -> shift -> G." That will open up the "Go To" window. Type in "~/Library/." That will open up your Library folder and from there you'll be able to see all of your files. Your Terminal, by default, will open up in your personal user directory. For the sake of this tutorial, your React folder is in your user directory so start by typing in "cd React / ." After that, all you need to do is type in "react-native run-ios." If you're running Parallels, should you attempt to write anything in the console, you're going to run into a problem in that your Simulator will open up Google Chrome on the Windows side which will result in an error. A) Chrome for Mac (back to top...) The code that lead to this problem / resolution was this: import React, { Component } from 'react'; import { View, Text } from 'react-native'; class AlbumList extends Component { componentWillMount() { console.log('componentWillMount in AlbumList'); } render() { return (  Album List!!! ); } } export default AlbumList; You run this code and the system will open up Google Chrome on the Windows side leading to an error. To get around this, just open up Google Chrome on the Mac side and navigate to this URL: http://localhost:8081/debugger-ui. That's all it takes! Click here for more information. 1) "Command->D" to Debug Your Code (back to top...) Once you've got Chrome working, you'll want to click on your Simulator and type "command-D" and highlight, "Debug JS Remotely." On occasion you might get a big red screen that says "runtime is working." Just restart the Simulator and do the "Command-D" thing and you should be all set. Click here and here for more info. Using the Terminal to set up your new application. A) Terminal Command to Setup New App (back to top...) To set up a new app, navigate to the directory where you want to set up your new app and then type in the command "react-native init new_project_name." This is a great little tool that will alert you to code errors before you try running your application. A) Atom Preferences (back to top...) The first thing you need to do is navigate to Atom's Preferences and do a search for "linter-eslint." You'll want to install what you see in the image to the right. The packages you want to pay attention to are "linter" and "linter-eslint." B) Linter Configuration (back to top...) You're going to need to install a Linter Configuration file into your project directory. This will dictate how things are being evaluated by Linter and what will trigger its errors etc. To do that, navigate to your project directory on your Terminal and type in the following command: npm install --save-dev eslint-config-rallycoding C) .eslintrc (back to top...) The last thing you're going to do is tell Atom to use "eslint" and give it a specific file to refer to that will point to the "rallycoding" dynamic you just defined as far as the config file. To do that, you'll set up a new file in your project's route directory called, ".eslintrc." Make sure you preface it with a "." In that file, you'll put: { "extends": "rallycoding" } ...and that's all there is to it! A) Components (back to top...) 1) Class Level Components (back to top...) These components are your upper echelon where you've got "state" as well as "life cycle" methods... class AlbumList extends Component { state = { albums: [] }; //used to "record" user reactions componentWillMount() { axios.get('https://rallycoding.herokuapp.com/api/music_albums') // fetch a list of albums .then(response => this.setState({ albums: response.data }));// update "state" to include a list of albums } renderAlbums() { return this.state.albums.map(album => <AlbumDetail key={album.title} album={album} /> //rendering one AlbumDetail component for every album retrieved ); 2) Functional Components (back to top...) These components are your "pretty boys," in that you're using them in the context of leting your user "see" data:
const AlbumDetail = ({ album }) => { const { title, artist, thumbnail_image, image, url } = album; const { thumbnailStyle, headerContentStyle, thumbnailContainerStyle, headerTextStyle, imageStyle } = styles; return ( <Card> <CardSection> <View style={thumbnailContainerStyle}> <Image style={thumbnailStyle} source={{ uri: thumbnail_image }} /> </View> <View style={headerContentStyle}> <Text style={headerTextStyle}>{title}</Text> <Text>{artist}</Text> </View> </CardSection>
B) Prop (back to top...) A "prop" is any type of data that's being passed from a parent to a child. 1) Decontstructing Props (back to top...) Whenever you "deconstruct" your props, you're breaking things down in a way that makes it easier to code. This website gives some good instruction / commentary: amido.com Here's an example from the code: <CardSection> <Button onPress={() => Linking.openURL(url)}> Buy Now </Button> </CardSection> This is the "CardSection" component as it appears on "AlbumDetail.js." Now, look at how you code the "Buy Now" text in the actual "Button" component: <TouchableOpacity onPress={(onPress, children)} style={buttonStyle}> <Text style={textStyle}> {children} </Text> </TouchableOpacity> Both the "onPress" functionality and "children" represent props that you're passing into your button, but given the aformentioned code you can see the actual, digital "trail" that exists betwen the "Buy Now" child that's referenced in the AlbumSection.js parent and the way the "child" is referenced in the "button.js" code. This particular project was a bear despite it having been introduced as an "easier" segment in the tutorial. The name of the project is "Tech Stack" and there's a lot to absorb... A) What is Redux? (back to top...) First of all, here's a great resource to refer to when you're wanting to wrap your head around Redux: Redux Tutorial. Bottom line: It's a library that runs independent of React. They "play well together," however, thanks to a hierarchy that looks like what you see to the right. 1) Redux Playground (back to top...) Stephen Grider offers a "playground" where you can fool around and kick the tires of some Redux code at https://stephengrider.github.io and he goes into some detail with it in his tutorials. B) Installing Redux (back to top...) You're actually going to be installing two different functionalities to make this work: React Redux and Redux. The command you'll use for this in your Teriminal is: npm install -- save redux react-redux C) Bare Bones Beginning (back to top...) 1) App.js (back to top...) Initially, you're going to have your "App.js" file sitting in your root directory. We're going to change that and put that into a new directory called, "src." Once that's in place, we're change our index.js file (in the root directory) to this: index.js import { AppRegistry } from 'react-native'; import App from './src/App'; //change the URL from "./" to "./src/" AppRegistry.registerComponent('tech_stack', () => App); And we'll change the "App.js" file to this: App.js import React from 'react'; import { View } from 'react-native'; const App = () => { return ( <View /> ); }; export default App; That gives us a blank screen with no errors. 2) The Header (back to top...) We've built these components before so we're just copying and pasting them from the components / common directory into the same directories (we'll need to make them) in the current project. Once we've got our Header.js etc in the new components / common folder, we'll add this to our app.js file: import React from 'react'; import { View } from 'react-native'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; import reducers from './reducers'; import { Header } from './components/common'; const App = () => { return ( <Provider store={createStore(reducers)}> <View> <Header headerText="Tech Stack" /> </View> </Provider> ); }; export default App; D) Random Challenges and Problems (back to top...) This particular project had several challenges that needed to be overcome. Here are some notes so if these problems surface again, I've got a head start. 1) Simulator Not Working (back to top...) For whatever reason, the Simulator kept giving me an error. The message on the Simulator was, "No bundle URL present." In order to clear the canvas and get things back in order, I ran this in the Terminal: rm -rf ios/build/; kill $(lsof -t -i:8081); react-native run-ios That did it! 2) Expected the reducer to be a function... (back to top...) This error may or may not be a thing, but it was solved simply by "Save All." Refer to the image to the right and take heed! You can spend a lot of time looking for an error the doesn't exist. All you need to do is make sure that ALL your files have been saved! 3) Nothing was returned from render (back to top...) Look at this and notice the code that's highlighted in red... import React, { Component } from 'react'; import { connect } from 'react-redux'; class LibraryList extends Component { render() { return; } } const mapStateToProps = state => { console.log(state); }; export default connect()(LibraryList); You won't get an error as far as ESLint is concerned, but you will get the "Nothing was returned from render..." error. To remedy that you have to include the "mapStateToProps" dynamic into your connect function. Like this: const mapStateToProps = state => { console.log(state); }; export default connect(mapStateToProps)(LibraryList); D) Review and Recap (back to top...) 1) Redux (back to top...) Redux and React are two different technologies and there needs to be some subtle intervention deployed in order for them to play nice together. That's what yo uhave with the "Provider." At the top of your app.js page, you've got this: import { Provider } from 'react-redux'; // react redux is the tech that allows React and Redux to talk to tone another import { createStore } from 'redux'; import reducers from './reducers'; const App = () => { return ( <Provider store={createStore(reducers)}> // your Provider is what creates your Redux Store and that's where your magic is database magic is happening... etc. ); }; There's more to this that will be covered in more detail later, but it's the terminology and basic systemic scaffolding that I want to capture here. 2) Flowchart (back to top...) Here's a good diagram to help envision the "flow" of Redux and how Reducers are controlling / impacting the "state" of the "store..."
3) Reducers (back to top...) Your Reducers are what are used to controll and influence your "state." You combine all your Reducers in the "index.js" file in your Reducers library like this: import { combineReducers } from 'redux'; import LibraryReducer from './LibraryReducer'; import SelectionReducer from './SelectionReducer'; export default combineReducers({ libraries: LibraryReducer, // the "libraries" key is what determines how the app's store looks or how it's formed selectedLibraryId: SelectionReducer }); This is the same kind of approach that you're taking with the components you put in the "common" directory. The "reducer" itself looks like this: import data from './LibraryList.json'; export default () => data; This pulls your data from the "LibraryList.json" file. This one contains an action which is what the user triggers when it clicks on a title to expand the text below it: export default (state = null, action) => { switch (action.type) { case 'select_library': return action.payload; default: return state; } }; This particular "reducer" is extremely common in the way it represents an "action." 4) Actions (back to top...) This is your "index.js" file in the "actions" directory. Technically, this is called an "Action Creator." export const selectLibrary = (libraryId) => { return { type: 'select_library', payload: libraryId }; }; An object with a type property is an action. This is how you're going to update your Reducer. 5) mapStateToProps (back to top...) "mapStateToProps" is what allows your app to interface with your "state" and translate that to a series of "props." One thing that proved to be a bit of a challenge in that it was easy to overlook was the way in which "mapStateToProps" had to be wired up. This was mentioned before, but it's worth repeating: export default connect(mapStateToProps)(LibraryList); In this diagram, remember that "const" is setting up a variable while "state" is an object. It's a noble pursuit to comprehend the difference and why it matters in this syntax. A "thick" app, but loaded with some great features and knowledge...! A) Logging In Using Redux (back to top...) 1) Same Variable / Different Object (back to top...) The first thing that's actually quite clever and, at the same time, absolutely necessary to get your head around, is the way in which two variables are set up that point to the same object. When the "action" is triggered, it affects one of the variables that is then compared to the other variable that is still referecing the object in its original state. That's how the system knows whether or not someone is attempting to login!
a) EMAIL CHANGED Dilemma & Utility (back to top...) At one point while working through this, the Simulator didn't respond when you put any new text in the email field. You could click on it, but it wouldn't change when you put any info in it. The problem was in the "actions - > index.js" page: import { EMAIL_CHANGED } from './types'; export const emailChanged = (text) => { return { type: "EMAIL_CHANGED", payload: text }; }; Originally, I had the "EMAIL_CHANGED" documented as "EMAIL_CHANGED". By putting it into quotes, you were no longer looking for a variable, you were now looking for a string and that's not going to work. Let's break this down because you will see it again throughout this app: Here's a portion of the LoginForm.js code; <Input label="Email" placeholder="email@email.com" onChangeText={this.onEmailChange.bind(this)}1 value={this.props.email} /> 1. "onChangeText" is referred to towards the top of the page as an  Event Handler . It's calling an "action" and that looks like this: onEmailChange(text) { this.props.emailChanged(text); } What makes this so clever is the way you're utilizing what's illustrated in the graphic above, as far as the "Actions Creator, to evaluate what has changed in the email field. Real quick! This is your "actions->index.js" file: import { EMAIL_CHANGED } from './types';1 export const emailChanged = (text) => { 2 return { type: EMAIL_CHANGED, 3 payload: text 4 }; }; 1. EMAIL_CHANGED is coming from your "actions -> types.js" page that reads: export const EMAIL_CHANGED = 'email_changed'; You're establishing a variable (const) as EMAIL_CHANGED which is, in effect, a smart way to establish your your "type" properties in your actions in a concise and consistent way. The science behind it is that when you're referring to your index.js page, you're referring to the necessary "type" properties in a way that is direct and poses less of a risk for writing flawed code every time you call an action. 2. emailChanged is your "action." This is your "Action Creator!" The first line, specifically the "(text)" part, is dictating the way in which this action is being called. In this instance, it's going to be called with "text." 3. "type: EMAIL_CHANGED" refers to the type property which is defined in your index.js page 4. This is your payload - what will be returned - which, in this case, is going to be text. 2) Establishing the Connection (back to top...) You've got your Event Handler as well as your Create Action. Now you connect everything in a way where everyone's talking to one another. You begin by importing your "Connect" and your "Action" into your "LoginForm.js" page: import { connect } from 'react-redux'; import { emailChanged } from '../actions'; You setup your "connect" at the bottom of the page and incorporate your "mapStateToProps" property that now possible because of having established an "action."
What's happening now is that the user enters something into the email field...
You're using the Event Handler that's documented at the top of the page as onEmailChange(text) { this.props.emailChanged(text); ...and triggered by what you've got in the email field: onChangeText={this.onEmailChange.bind(this)} value={this.props.email} // this specifies the value of this field as what's being entered by the user in that moment
That triggers the "action" that you have specified in your "actions -> index.js" file which, in this case, is "emailChanged." That looks like this: import { EMAIL_CHANGED } from './types'; export const emailChanged = (text) => { //will be called with text return { //always returns an action type: EMAIL_CHANGED, //it always has a type property which in this case is "EMAIL_CHANGED" //which was defined in types.js payload: text }; }; This is just the "action." The real functionality is the Reducer that is being called by the "action." The "Reducer" is specified in your connect function...
The "connect" function looks like this: export default connect(mapStatetoProps, { emailChanged })(LoginForm); It only makes sense when you look at the "mapStateToProps" const just above this line... const mapStatetoProps = state => { return { email: state.auth.email // "auth" is your Reducer }; };
And here's your Reducer. This is where the actual functionality is happening... import { EMAIL_CHANGED } from '../actions/types'; const INITIAL_STATE = { email: '' }; export default (state = INITIAL_STATE, action) => { switch (action.type) { case EMAIL_CHANGED: // here's where your action type comes in to play and dictate systemic flow return { ...state, email: action.payload }; // you're changing the value of the email field to what's in the payload section as dictated by the action default: return state; } };
This is what's being used to reproduce the value of the email field as the user is entering their email address... 3) this.renderError() -> Warning: Functions are not valid as a React child.... (back to top...) At one point during this app, I got this error: "Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it." It was pointing to this: renderError() { if (this.props.error) { return ( <View style={{ backgroundColor: 'white' }}> <Text style={styles.errorTextStyle}> {this.props.error} </Text> </View> ); } } ...which didn't make a whole lot of sense, because everything looks just fine. The problem was here: {this.renderError} This is on line #55. You have to write it like this: {this.renderError()}. Without those two (), you'll get that error. 4) Firebase Gotcha (back to top...) At one point in the course a "gotcha" was demonstrated with "Firebase" in that if you put some random code where it doesn't belong, "Firebase" is going to assume that something went awry with the previous database interaction and it will route the user to the appropriate "catch." For example: export default (state = INITIAL_STATE, action) => { console.log(action); switch (action.type) { case EMAIL_CHANGED: return { ...state, email: action.payload }; case PASSWORD_CHANGED: return { ...state, password: action.payload }; case LOGIN_USER_SUCCESS: banana; // this will throw an error, but watch the way it goes down when it's a part of a firebase dynamic return { ...state, user: action.payload, error: '' }; case LOGIN_USER_FAIL: return { ...state, error: 'Authentication Failed.' }; default: return state; } }; "banana" doesn't make any sense and you would expect the code to crash. It doesn't. Rather, it assumes the previous "Firebase" interaction went south and will route the system according to the appropriate "catch." So, it will look like this: export const loginUser = ({ email, password }) => { return (dispatch) => { firebase.auth().signInWithEmailAndPassword(email, password) .then(user => loginUserSuccess(dispatch, user)) //here's where your "banana" showed up. It will go ahead and move to your "catch" phrase which is "createUserWithEmailAndPassword..." .catch(() => { firebase.auth().createUserWithEmailAndPassword(email, password) .then(user => loginUserSuccess(dispatch, user)) .catch(() => loginUserFail(dispatch)); }); }; };