├── .babelrc ├── .codeclimate.yml ├── .eslintignore ├── .eslintrc.js ├── .github ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── publish.yml │ └── verify.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── CNAME ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── PATRONS.md ├── README.md ├── SUMMARY.md ├── bin ├── api-docs-generate.js └── api-docs-upload.js ├── book.json ├── codecov.yml ├── docs ├── FAQ.md ├── GLOSSARY.md ├── api │ ├── README.md │ ├── ReactReduxFirebaseContext.md │ ├── ReactReduxFirebaseProvider.md │ ├── ReduxFirestoreContext.md │ ├── ReduxFirestoreProvider.md │ ├── constants.md │ ├── firebaseConnect.md │ ├── firebaseInstance.md │ ├── firestoreConnect.md │ ├── getFirebase.md │ ├── helpers.md │ ├── reducer.md │ ├── reducers.md │ ├── useFirebase.md │ ├── useFirebaseConnect.md │ ├── useFirestore.md │ ├── useFirestoreConnect.md │ ├── withFirebase.md │ └── withFirestore.md ├── auth.md ├── contributing.md ├── firestore.md ├── getting_started.md ├── integrations │ ├── README.md │ ├── react-chrome-redux.md │ ├── react-native.md │ ├── redux-form.md │ ├── redux-observable.md │ ├── redux-persist.md │ ├── redux-saga.md │ ├── reselect.md │ └── thunks.md ├── populate.md ├── queries.md ├── recipes │ ├── README.md │ ├── actions.md │ ├── auth.md │ ├── populate.md │ ├── profile.md │ ├── roles.md │ ├── routing.md │ ├── ssr.md │ ├── typescript.md │ └── upload.md ├── roadmap.md ├── static │ ├── BuildPhase.png │ ├── FirebaseOverview.png │ ├── PlistDownload.png │ ├── RegisterApp.png │ ├── UrlTypes.png │ └── dataFlow.png ├── storage.md ├── v2-migration-guide.md └── v3-migration-guide.md ├── examples ├── complete │ ├── firestore │ │ ├── .env │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ ├── public │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ ├── logo192.png │ │ │ ├── logo512.png │ │ │ ├── manifest.json │ │ │ └── robots.txt │ │ ├── src │ │ │ ├── App.css │ │ │ ├── App.js │ │ │ ├── App.test.js │ │ │ ├── Home.js │ │ │ ├── NewTodo.js │ │ │ ├── Todo.css │ │ │ ├── TodoItem.js │ │ │ ├── Todos.js │ │ │ ├── Todos.test.js │ │ │ ├── config.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── logo.svg │ │ │ ├── reducer.js │ │ │ ├── serviceWorker.js │ │ │ ├── setupTests.js │ │ │ └── store.js │ │ └── yarn.lock │ ├── material │ │ ├── .env.local │ │ ├── .eslintignore │ │ ├── .eslintrc.js │ │ ├── .firebaserc │ │ ├── .gitignore │ │ ├── .gitlab-ci.yml │ │ ├── .yo-rc.json │ │ ├── LICENSE │ │ ├── README.md │ │ ├── database.rules.json │ │ ├── firebase.json │ │ ├── firestore.indexes.json │ │ ├── firestore.rules │ │ ├── jsconfig.json │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── favicon.ico │ │ │ ├── humans.txt │ │ │ ├── index.html │ │ │ ├── manifest.json │ │ │ └── robots.txt │ │ ├── src │ │ │ ├── components │ │ │ │ ├── FormTextField │ │ │ │ │ ├── FormTextField.js │ │ │ │ │ └── index.js │ │ │ │ └── LoadingSpinner │ │ │ │ │ ├── LoadingSpinner.enhancer.js │ │ │ │ │ ├── LoadingSpinner.js │ │ │ │ │ ├── LoadingSpinner.styles.js │ │ │ │ │ ├── LoadingSpinner.test.js │ │ │ │ │ └── index.js │ │ │ ├── config.js │ │ │ ├── constants │ │ │ │ ├── formNames.js │ │ │ │ └── paths.js │ │ │ ├── containers │ │ │ │ ├── App │ │ │ │ │ ├── App.js │ │ │ │ │ └── index.js │ │ │ │ └── Navbar │ │ │ │ │ ├── AccountMenu.js │ │ │ │ │ ├── LoginMenu.js │ │ │ │ │ ├── Navbar.js │ │ │ │ │ ├── Navbar.styles.js │ │ │ │ │ └── index.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── layouts │ │ │ │ └── CoreLayout │ │ │ │ │ ├── CoreLayout.js │ │ │ │ │ ├── CoreLayout.styles.js │ │ │ │ │ └── index.js │ │ │ ├── modules │ │ │ │ └── notification │ │ │ │ │ ├── actionTypes.js │ │ │ │ │ ├── actions.js │ │ │ │ │ ├── components │ │ │ │ │ ├── Notifications.js │ │ │ │ │ └── useNotifications.js │ │ │ │ │ ├── index.js │ │ │ │ │ └── reducer.js │ │ │ ├── routes │ │ │ │ ├── Account │ │ │ │ │ ├── components │ │ │ │ │ │ ├── AccountForm │ │ │ │ │ │ │ ├── AccountForm.enhancer.js │ │ │ │ │ │ │ ├── AccountForm.js │ │ │ │ │ │ │ ├── AccountForm.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ ├── AccountPage │ │ │ │ │ │ │ ├── AccountPage.enhancer.js │ │ │ │ │ │ │ ├── AccountPage.js │ │ │ │ │ │ │ ├── AccountPage.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ └── ProviderDataForm │ │ │ │ │ │ │ ├── ProviderDataForm.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ │ ├── Home │ │ │ │ │ ├── components │ │ │ │ │ │ └── HomePage │ │ │ │ │ │ │ ├── HomePage.js │ │ │ │ │ │ │ ├── HomePage.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ │ ├── Login │ │ │ │ │ ├── components │ │ │ │ │ │ ├── LoginForm │ │ │ │ │ │ │ ├── LoginForm.enhancer.js │ │ │ │ │ │ │ ├── LoginForm.js │ │ │ │ │ │ │ ├── LoginForm.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ └── LoginPage │ │ │ │ │ │ │ ├── LoginPage.enhancer.js │ │ │ │ │ │ │ ├── LoginPage.js │ │ │ │ │ │ │ ├── LoginPage.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ │ ├── NotFound │ │ │ │ │ ├── components │ │ │ │ │ │ └── NotFoundPage │ │ │ │ │ │ │ ├── NotFoundPage.js │ │ │ │ │ │ │ ├── NotFoundPage.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ │ ├── Projects │ │ │ │ │ ├── components │ │ │ │ │ │ ├── NewProjectDialog │ │ │ │ │ │ │ ├── NewProjectDialog.enhancer.js │ │ │ │ │ │ │ ├── NewProjectDialog.js │ │ │ │ │ │ │ ├── NewProjectDialog.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ ├── NewProjectTile │ │ │ │ │ │ │ ├── NewProjectTile.js │ │ │ │ │ │ │ ├── NewProjectTile.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ ├── ProjectTile │ │ │ │ │ │ │ ├── ProjectTile.js │ │ │ │ │ │ │ ├── ProjectTile.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ └── ProjectsPage │ │ │ │ │ │ │ ├── ProjectsPage.enhancer.js │ │ │ │ │ │ │ ├── ProjectsPage.js │ │ │ │ │ │ │ ├── ProjectsPage.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── index.js │ │ │ │ │ └── routes │ │ │ │ │ │ └── Project │ │ │ │ │ │ ├── components │ │ │ │ │ │ └── ProjectPage │ │ │ │ │ │ │ ├── ProjectPage.enhancer.js │ │ │ │ │ │ │ ├── ProjectPage.js │ │ │ │ │ │ │ ├── ProjectPage.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ └── index.js │ │ │ │ ├── Signup │ │ │ │ │ ├── components │ │ │ │ │ │ ├── SignupForm │ │ │ │ │ │ │ ├── SignupForm.enhancer.js │ │ │ │ │ │ │ ├── SignupForm.js │ │ │ │ │ │ │ ├── SignupForm.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ └── SignupPage │ │ │ │ │ │ │ ├── SignupPage.enhancer.js │ │ │ │ │ │ │ ├── SignupPage.js │ │ │ │ │ │ │ ├── SignupPage.styles.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ ├── static │ │ │ │ ├── User.png │ │ │ │ └── logo.svg │ │ │ ├── store │ │ │ │ ├── createStore.js │ │ │ │ ├── location.js │ │ │ │ └── reducers.js │ │ │ ├── theme.js │ │ │ └── utils │ │ │ │ ├── components.js │ │ │ │ ├── form.js │ │ │ │ ├── hooks.js │ │ │ │ ├── index.js │ │ │ │ └── router.js │ │ ├── storage.rules │ │ └── yarn.lock │ ├── react-native-firebase │ │ ├── .babelrc │ │ ├── .gitignore │ │ ├── .watchmanconfig │ │ ├── App.js │ │ ├── App.test.js │ │ ├── README.md │ │ ├── app.json │ │ ├── index.android.js │ │ ├── index.ios.js │ │ ├── ios │ │ │ ├── GoogleService-Info.plist │ │ │ ├── Podfile │ │ │ ├── Podfile.lock │ │ │ ├── reactnativefirebase.xcodeproj │ │ │ │ ├── project.pbxproj │ │ │ │ └── xcshareddata │ │ │ │ │ └── xcschemes │ │ │ │ │ ├── reactnativefirebase-tvOS.xcscheme │ │ │ │ │ └── reactnativefirebase.xcscheme │ │ │ ├── reactnativefirebase.xcworkspace │ │ │ │ └── contents.xcworkspacedata │ │ │ ├── reactnativefirebase │ │ │ │ ├── AppDelegate.h │ │ │ │ ├── AppDelegate.m │ │ │ │ ├── Base.lproj │ │ │ │ │ └── LaunchScreen.xib │ │ │ │ ├── Images.xcassets │ │ │ │ │ └── AppIcon.appiconset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Info.plist │ │ │ │ └── main.m │ │ │ └── reactnativefirebaseTests │ │ │ │ ├── Info.plist │ │ │ │ └── reactnativefirebaseTests.m │ │ ├── package.json │ │ ├── src │ │ │ ├── Home.js │ │ │ ├── NewTodo.js │ │ │ ├── TodosList.js │ │ │ ├── createStore.js │ │ │ ├── index.js │ │ │ ├── reducers.js │ │ │ └── utils.js │ │ └── yarn.lock │ ├── react-native │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .watchmanconfig │ │ ├── App.js │ │ ├── README.md │ │ ├── __tests__ │ │ │ └── App-test.js │ │ ├── app.json │ │ ├── assets │ │ │ ├── fonts │ │ │ │ └── SpaceMono-Regular.ttf │ │ │ └── images │ │ │ │ ├── icon.png │ │ │ │ ├── robot-dev.png │ │ │ │ ├── robot-prod.png │ │ │ │ └── splash.png │ │ ├── babel.config.js │ │ ├── components │ │ │ ├── StyledText.js │ │ │ ├── TabBarIcon.js │ │ │ ├── Todo.js │ │ │ ├── TodosList.js │ │ │ └── __tests__ │ │ │ │ └── StyledText-test.js │ │ ├── config.js │ │ ├── constants │ │ │ ├── Colors.js │ │ │ └── Layout.js │ │ ├── navigation │ │ │ ├── AppNavigator.js │ │ │ └── MainTabNavigator.js │ │ ├── package.json │ │ ├── reducer.js │ │ ├── screens │ │ │ ├── HomeScreen.js │ │ │ ├── LinksScreen.js │ │ │ └── SettingsScreen.js │ │ ├── store.js │ │ └── yarn.lock │ ├── simple │ │ ├── .env │ │ ├── .firebaserc │ │ ├── .gitignore │ │ ├── README.md │ │ ├── database.rules.json │ │ ├── firebase.json │ │ ├── package.json │ │ ├── public │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ └── manifest.json │ │ ├── src │ │ │ ├── App.css │ │ │ ├── App.js │ │ │ ├── App.test.js │ │ │ ├── Home.js │ │ │ ├── NewTodo.js │ │ │ ├── Todo.css │ │ │ ├── TodoItem.js │ │ │ ├── Todos.js │ │ │ ├── config.js │ │ │ ├── favicon.ico │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── initFirebase.js │ │ │ ├── logo.svg │ │ │ ├── reducer.js │ │ │ └── store.js │ │ └── yarn.lock │ └── typescript │ │ ├── .env │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ │ ├── src │ │ ├── AddTodo.tsx │ │ ├── App.css │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ ├── Home.tsx │ │ ├── Todo.tsx │ │ ├── Todos.tsx │ │ ├── config.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── logo.svg │ │ ├── react-app-env.d.ts │ │ ├── reducer.ts │ │ ├── serviceWorker.ts │ │ └── store.ts │ │ ├── tsconfig.json │ │ ├── tslint.json │ │ └── yarn.lock └── snippets │ ├── decorators │ ├── App.js │ ├── README.md │ └── TodoItem.js │ ├── multipleQueries │ ├── App.js │ ├── README.md │ └── TodoItem.js │ ├── populates │ ├── App.js │ └── README.md │ ├── stateBasedQuery │ ├── Home.js │ ├── README.md │ └── Todos.js │ ├── watchEvent │ ├── Basic.js │ ├── README.md │ └── Recompose.js │ └── webpack2 │ ├── .babelrc │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ ├── app.js │ ├── config.js │ ├── reducer.js │ └── store.js │ └── webpack.config.js ├── index.d.ts ├── package-lock.json ├── package.json ├── src ├── ReactReduxFirebaseContext.js ├── ReactReduxFirebaseProvider.js ├── ReduxFirestoreContext.js ├── ReduxFirestoreProvider.js ├── actions │ ├── auth.js │ ├── query.js │ └── storage.js ├── constants.js ├── createFirebaseInstance.js ├── firebaseConnect.js ├── firestoreConnect.js ├── helpers.js ├── index.js ├── reducer.js ├── reducers.js ├── useFirebase.js ├── useFirebaseConnect.js ├── useFirestore.js ├── useFirestoreConnect.js ├── utils │ ├── actions.js │ ├── auth.js │ ├── events.js │ ├── index.js │ ├── populate.js │ ├── query.js │ ├── reducers.js │ └── storage.js ├── withFirebase.js └── withFirestore.js ├── test ├── .eslintrc.js ├── mocha.opts ├── mockData.js ├── setup.js ├── unit │ ├── actions │ │ ├── auth.spec.js │ │ ├── query.spec.js │ │ └── storage.spec.js │ ├── createFirebaseInstance.spec.js │ ├── firebaseConnect.spec.js │ ├── firestoreConnect.spec.js │ ├── helpers.spec.js │ ├── library.spec.js │ ├── reducer.spec.js │ ├── useFirebase.spec.js │ ├── useFirebaseConnect.spec.js │ ├── useFirestore.spec.js │ ├── useFirestoreConnect.spec.js │ ├── utils │ │ ├── actions.spec.js │ │ ├── auth.spec.js │ │ ├── events.spec.js │ │ ├── index.spec.js │ │ ├── populate.spec.js │ │ ├── query.spec.js │ │ └── storage.spec.js │ ├── withFirebase.spec.js │ └── withFirestore.spec.js └── utils.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["minify", { 4 | "mangle": false 5 | }], 6 | "@babel/preset-react", 7 | ["@babel/env", { 8 | "targets": { 9 | "chrome": 52, 10 | "browsers": ["last 2 versions", "safari >= 7"] 11 | } 12 | }] 13 | ], 14 | "plugins": [ 15 | "lodash", 16 | "add-module-exports", 17 | "@babel/plugin-proposal-class-properties" 18 | ], 19 | "env": { 20 | "es": { 21 | "comments": false 22 | }, 23 | "commonjs": { 24 | "comments": false 25 | }, 26 | "test": { 27 | "plugins": [ 28 | "@babel/plugin-transform-runtime", 29 | "@babel/transform-async-to-generator", 30 | ["module-resolver", { 31 | "root": ["./src"] 32 | }] 33 | ] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" # required to adjust maintainability checks 2 | 3 | languages: 4 | JavaScript: true 5 | 6 | checks: 7 | file-lines: 8 | enabled: true 9 | config: 10 | threshold: 740 11 | return-statements: 12 | enabled: true 13 | config: 14 | threshold: 6 15 | 16 | exclude_patterns: 17 | - "lib/**" 18 | - "test/**" 19 | - "examples/**" 20 | - "LICENSE" 21 | - "LICENSE.md" 22 | - "README.md" 23 | - "package.json" 24 | - "**/*.d.ts" 25 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/** 2 | node_modules/** 3 | dist/** 4 | es/** 5 | lib/** 6 | _book/** 7 | _site/** 8 | docs/** 9 | index.d.ts 10 | examples/** 11 | test/utils.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | extends: ['standard', 'standard-react', 'prettier', 'prettier/react'], 5 | plugins: ['babel', 'react', 'prettier', 'react-hooks'], 6 | settings: { 7 | react: { 8 | version: 'detect' 9 | } 10 | }, 11 | env: { 12 | browser: true, 13 | es6: true 14 | }, 15 | rules: { 16 | semi: [2, 'never'], 17 | 'no-console': 'error', 18 | 'prettier/prettier': [ 19 | 'error', 20 | { 21 | singleQuote: true, 22 | trailingComma: 'none', 23 | semi: false, 24 | bracketSpacing: true, 25 | jsxBracketSameLine: true, 26 | printWidth: 80, 27 | tabWidth: 2, 28 | useTabs: false 29 | } 30 | ] 31 | }, 32 | overrides: [ 33 | { 34 | files: ['./src/**/**.js'], 35 | plugins: ['jsdoc'], 36 | extends: ['plugin:jsdoc/recommended'], 37 | rules: { 38 | 'jsdoc/newline-after-description': 0, 39 | 'jsdoc/no-undefined-types': [1, { definedTypes: ['React', 'firebase'] }] 40 | } 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: prescottprue 5 | open_collective: react-redux-firebase 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Do you want to request a *feature* or report a *bug*?** 2 | 3 | (If this is a *usage question*, please **do not post it here**—post it on [gitter](https://gitter.im/redux-firebase/Lobby). If this is not a “feature” or a “bug”, or the phrase “How do I...?” applies, then it's probably a usage question.) 4 | 5 | 6 | **What is the current behavior?** 7 | 8 | 9 | 10 | **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via [codesandbox](https://codesandbox.io/) or similar.** 11 | 12 | 13 | 14 | **What is the expected behavior?** 15 | 16 | 17 | 18 | **Which versions of dependencies, and which browser and OS are affected by this issue? Did this work in previous versions or setups?** 19 | 20 | 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | 4 | ### Check List 5 | If not relevant to pull request, check off as complete 6 | 7 | - [ ] All tests passing 8 | - [ ] Docs updated with any changes or examples if applicable 9 | - [ ] Added tests to ensure new feature(s) work properly 10 | 11 | ### Relevant Issues 12 | 13 | -------------------------------------------------------------------------------- /.github/workflows/verify.yml: -------------------------------------------------------------------------------- 1 | name: Verify 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node-version: [10.x, 12.x, 14.x] 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | 21 | # Setup dependency caching 22 | - uses: actions/cache@v1 23 | with: 24 | path: ~/.npm 25 | key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} 26 | 27 | - name: Install Dependencies 28 | run: npm ci 29 | 30 | - name: Check For Lint 31 | run: npm run lint 32 | 33 | - name: Run Unit Tests + Coverage 34 | run: npm run test:cov 35 | 36 | - name: Run Build 37 | run: npm run build 38 | 39 | - name: Upload Coverage 40 | if: matrix.node-version == '10.x' 41 | env: 42 | CODE_COV: ${{ secrets.CODE_COV }} 43 | # Upload to codecov.io. Curl used in place of codecov/codecov-action 44 | # due to long build time. See https://github.com/codecov/codecov-action/issues/21 45 | run: curl -s https://codecov.io/bash | bash -s -- -t $CODE_COV 46 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | bin 3 | examples 4 | tests 5 | coverage 6 | .istanbul.yml 7 | _book 8 | .babelrc 9 | SUMMARY.md 10 | webpack.config.js 11 | .eslintignore 12 | .eslintrc 13 | book.json 14 | CNAME 15 | .github 16 | yarn.lock 17 | .travis.yml 18 | CODE_OF_CONDUCT.md 19 | CHANGELOG.md 20 | .codeclimate.yml 21 | PATRONS.md 22 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | semi: false 3 | trailingComma: 'none' 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | Every release, along with the migration instructions, is documented on the Github [Releases](https://github.com/prescottprue/react-redux-firebase/releases) page. 5 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | react-redux-firebase.com 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present Prescott Prue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PATRONS.md: -------------------------------------------------------------------------------- 1 | # Patrons 2 | 3 | Meet some of the outstanding companies and individuals that made it possible: 4 | 5 | * [Reside Network Inc.](https://github.com/reside-eng) 6 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitbook": ">=3.2.1", 3 | "title": "React Redux Firebase", 4 | "plugins": ["edit-link", "prism", "-highlight", "github", "anchorjs", "versions-select", "ga"], 5 | "pluginsConfig": { 6 | "edit-link": { 7 | "base": "https://github.com/prescottprue/react-redux-firebase/tree/master", 8 | "label": "Edit This Page" 9 | }, 10 | "github": { 11 | "url": "https://github.com/prescottprue/react-redux-firebase/" 12 | }, 13 | "theme-default": { 14 | "styles": { 15 | "website": "build/gitbook.css" 16 | } 17 | }, 18 | "ga": { 19 | "token": "UA-102355407-1" 20 | }, 21 | "versions": { 22 | "gitbookConfigURL": "https://storage.googleapis.com/docs.react-redux-firebase.com/book.json", 23 | "options": [ 24 | { 25 | "value": "http://react-redux-firebase.com/", 26 | "text": "Version 3.0.0", 27 | "selected": true 28 | }, 29 | { 30 | "value": "http://docs.react-redux-firebase.com/history/v2.0.0/", 31 | "text": "Version 2.0.0" 32 | }, 33 | { 34 | "value": "http://docs.react-redux-firebase.com/history/v1.5.0/", 35 | "text": "Version 1.5.0" 36 | }, 37 | { 38 | "value": "http://docs.react-redux-firebase.com/history/v1.4.0/", 39 | "text": "Version 1.4.0" 40 | } 41 | ] 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: true 4 | comment: 5 | behavior: default 6 | layout: header, diff 7 | require_changes: false 8 | coverage: 9 | precision: 2 10 | range: 11 | - 70.0 12 | - 100.0 13 | round: down 14 | status: 15 | changes: false 16 | patch: true 17 | project: true 18 | parsers: 19 | gcov: 20 | branch_detection: 21 | conditional: true 22 | loop: true 23 | macro: false 24 | method: false 25 | javascript: 26 | enable_partials: false 27 | -------------------------------------------------------------------------------- /docs/GLOSSARY.md: -------------------------------------------------------------------------------- 1 | # Glossary 2 | 3 | ## ## `profileDecorator` 4 | 5 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | Just like [redux](http://redux.js.org/docs/api/index.html), the react-redux-firebase API surface is intentionally as small as possible. 4 | 5 | ## Top-Level Exports 6 | * [useFirebase](/docs/api/useFirebase.md#usefirebase) 7 | * [useFirebaseConnect](/docs/api/useFirebaseConnect.md#usefirebaseconnect) 8 | * [useFirestore](/docs/api/useFirestore.md#usefirestore) 9 | * [useFirestoreConnect](/docs/api/useFirestoreConnect.md#usefirebaseconnect) 10 | * [firebaseConnect](/docs/api/firebaseConnect.md#firebaseconnect) 11 | * [withFirebase](/docs/api/withFirebase.md) 12 | * [firestoreConnect](/docs/api/firestoreConnect.md) 13 | * [withFirestore](/docs/api/withFirestore.md) 14 | * [reducer](/docs/api/reducer.md) (also exported as `firebaseReducer`) 15 | * [constants](/docs/api/constants.md) 16 | * [actionTypes](/docs/api/constants.md) 17 | * [isLoaded](/docs/api/helpers.md#isLoaded) 18 | * [isEmpty](/docs/api/helpers.md#isEmpty) 19 | * [populate](/docs/api/helpers.md#populate) 20 | 21 | ## Importing 22 | 23 | Every function described above is a top-level export. You can import any of them like this: 24 | 25 | ### ES6 26 | ```js 27 | import { firebaseConnect } from 'react-redux-firebase' 28 | ``` 29 | 30 | ### ES5 (CommonJS) 31 | ```js 32 | var firebaseConnect = require('react-redux-firebase').firebaseConnect 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/api/ReactReduxFirebaseContext.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Table of Contents 4 | 5 | - [ReactReduxFirebaseContext][1] 6 | 7 | ## ReactReduxFirebaseContext 8 | 9 | Context for extended firebase instance created 10 | by react-redux-firebase 11 | 12 | [1]: #reactreduxfirebasecontext 13 | -------------------------------------------------------------------------------- /docs/api/ReactReduxFirebaseProvider.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Table of Contents 4 | 5 | - [ReactReduxFirebaseProvider][1] 6 | - [Parameters][2] 7 | 8 | ## ReactReduxFirebaseProvider 9 | 10 | 11 | Provider for context containing extended firebase 12 | instance created by react-redux-firebase. 13 | 14 | ### Parameters 15 | 16 | - `props` **[object][4]** Component props (optional, default `{}`) 17 | - `props.config` **[object][4]** react-redux-firebase config 18 | - `props.dispatch` **[Function][5]** Redux's dispatch function 19 | - `props.firebase` **[object][4]** Firebase library 20 | - `props.initializeAuth` **[boolean][6]** Whether or not to initialize auth 21 | - `props.createFirestoreInstance` **[Function][5]** Function for creating 22 | extended firestore instance 23 | 24 | Returns **React.Context.Provider** Provider for react-redux-firebase context 25 | 26 | [1]: #reactreduxfirebaseprovider 27 | 28 | [2]: #parameters 29 | 30 | [3]: https://react-redux-firebase.com/api/docs/ReactReduxFirebaseProvider.html 31 | 32 | [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object 33 | 34 | [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function 35 | 36 | [6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean 37 | -------------------------------------------------------------------------------- /docs/api/ReduxFirestoreContext.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Table of Contents 4 | 5 | - [ReduxFirestoreContext][1] 6 | 7 | ## ReduxFirestoreContext 8 | 9 | Context for extended firebase instance created 10 | by react-redux-firebase 11 | 12 | [1]: #reduxfirestorecontext 13 | -------------------------------------------------------------------------------- /docs/api/ReduxFirestoreProvider.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Table of Contents 4 | 5 | - [ReduxFirestoreProvider][1] 6 | - [Parameters][2] 7 | 8 | ## ReduxFirestoreProvider 9 | 10 | 11 | Provider for context containing extended firestore instance created 12 | by react-redux-firebase 13 | 14 | ### Parameters 15 | 16 | - `props` **[object][4]** Component props (optional, default `{}`) 17 | - `props.config` **[object][4]** react-redux-firebase config 18 | - `props.dispatch` **[Function][5]** Redux's dispatch function 19 | - `props.firebase` **[object][4]** Firebase library 20 | - `props.initializeAuth` **[boolean][6]** Whether or not to initialize auth 21 | - `props.createFirestoreInstance` **[Function][5]** Function for creating 22 | extended firestore instance 23 | 24 | Returns **React.Context.Provider** Provider for redux-firestore context 25 | 26 | [1]: #reduxfirestoreprovider 27 | 28 | [2]: #parameters 29 | 30 | [3]: https://react-redux-firebase.com/docs/api/ReduxFirestoreProvider.html 31 | 32 | [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object 33 | 34 | [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function 35 | 36 | [6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean 37 | -------------------------------------------------------------------------------- /docs/api/getFirebase.md: -------------------------------------------------------------------------------- 1 | # getFirebase 2 | 3 | Expose [extended Firebase instance](/docs/api/firebaseInstance.md) created internally. Useful for 4 | integrations into external libraries such as redux-thunk and redux-observable. 5 | 6 | The methods which are available are documented in [firebaseInstance](/docs/api/firebaseInstance.md) 7 | 8 | **Examples** 9 | 10 | _redux-thunk integration_ 11 | 12 | ```javascript 13 | import { applyMiddleware, compose, createStore } from 'redux'; 14 | import thunk from 'redux-thunk'; 15 | import { getFirebase } from 'react-redux-firebase'; 16 | import makeRootReducer from './reducers'; 17 | 18 | const store = createStore( 19 | makeRootReducer(), 20 | initialState, 21 | compose( 22 | applyMiddleware([ 23 | // Pass getFirebase function as extra argument 24 | thunk.withExtraArgument(getFirebase) 25 | ]) 26 | ) 27 | ); 28 | 29 | // then later 30 | export function addTodo(newTodo) { 31 | return (dispatch, getState, getFirebase) => { 32 | const firebase = getFirebase() 33 | firebase 34 | .push('todos', newTodo) 35 | .then(() => { 36 | dispatch({ type: 'SOME_ACTION' }) 37 | }) 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/api/reducer.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Table of Contents 4 | 5 | - [reducer][1] 6 | - [Parameters][2] 7 | 8 | ## reducer 9 | 10 | ### Parameters 11 | 12 | - `state` **[object][3]** Current Firebase Redux State (state.firebase) 13 | - `action` **[object][3]** Action which will modify state 14 | - `action.type` **[string][4]** Type of Action being called 15 | - `action.path` **[string][4]** Path of action that was dispatched 16 | - `action.data` **[string][4]** Data associated with action 17 | 18 | Returns **[object][3]** Firebase redux state 19 | 20 | [1]: #reducer 21 | 22 | [2]: #parameters 23 | 24 | [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object 25 | 26 | [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String 27 | -------------------------------------------------------------------------------- /docs/api/useFirebase.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Table of Contents 4 | 5 | - [useFirebase][1] 6 | - [Examples][2] 7 | 8 | ## useFirebase 9 | 10 | 11 | React hook that provides `firebase` object. 12 | Firebase is gathered from ReactReduxFirebaseContext, which is 13 | set by createFirebaseInstance during setup. 14 | **NOTE**: This version of the Firebase library has extra methods, config, 15 | and functionality which give it it's capabilities such as dispatching 16 | actions. 17 | 18 | ### Examples 19 | 20 | _Basic_ 21 | 22 | ```javascript 23 | import { useFirebase } from 'react-redux-firebase' 24 | 25 | export default function AddData() { 26 | const firebase = useFirebase() 27 | 28 | function addTodo() { 29 | const exampleTodo = { done: false, text: 'Sample' } 30 | return firebase.push('todos', exampleTodo) 31 | } 32 | 33 | return ( 34 |
35 | 38 |
39 | ) 40 | } 41 | ``` 42 | 43 | Returns **[object][4]** Extended Firebase instance 44 | 45 | [1]: #usefirebase 46 | 47 | [2]: #examples 48 | 49 | [3]: https://react-redux-firebase.com/docs/api/useFirebase.html 50 | 51 | [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object 52 | -------------------------------------------------------------------------------- /docs/api/useFirestore.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Table of Contents 4 | 5 | - [useFirestore][1] 6 | - [Examples][2] 7 | 8 | ## useFirestore 9 | 10 | 11 | React hook that return firestore object. 12 | 13 | ### Examples 14 | 15 | _Basic_ 16 | 17 | ```javascript 18 | import React from 'react' 19 | import { useFirestore } from 'react-redux-firebase' 20 | 21 | export default function AddData() { 22 | const firestore = useFirestore() 23 | 24 | function addTodo() { 25 | const exampleTodo = { done: false, text: 'Sample' } 26 | return firestore.collection('todos').add(exampleTodo) 27 | } 28 | 29 | return ( 30 |
31 | 34 |
35 | ) 36 | } 37 | ``` 38 | 39 | Returns **[object][4]** Extended Firestore instance 40 | 41 | [1]: #usefirestore 42 | 43 | [2]: #examples 44 | 45 | [3]: https://react-redux-firebase.com/docs/api/useFirestore.html 46 | 47 | [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object 48 | -------------------------------------------------------------------------------- /docs/integrations/README.md: -------------------------------------------------------------------------------- 1 | # Integrations 2 | 3 | This section includes some integrations for using `react-redux-firebase` within other libraries. 4 | 5 | * [react-chrome-redux](/docs/integrations/react-chrome-redux.md) 6 | * [react-native](/docs/integrations/react-native.md) 7 | * [redux-form](/docs/integrations/redux-form.md) 8 | * [redux-observable](/docs/integrations/redux-observable.md) 9 | * [redux-persist](/docs/integrations/redux-persist.md) 10 | * [redux-saga](/docs/integrations/redux-saga.md) 11 | -------------------------------------------------------------------------------- /docs/integrations/react-chrome-redux.md: -------------------------------------------------------------------------------- 1 | # React Chrome Redux 2 | 3 | > Recipe for integrating with [`react-chrome-redux`](https://github.com/tshaddix/react-chrome-redux) 4 | 5 | **NOTE:** This recipe is based communications within [issue #157](https://github.com/prescottprue/react-redux-firebase/issues/157) and is not considered "completed". If you have suggestions, please post an issue or reach out over [gitter](https://gitter.im/redux-firebase/Lobby). 6 | 7 | Do not use `firebaseConnect` in your content/popup scripts. 8 | 9 | Use `react-redux-firebase` in the background script, and communicate with your content/popup using the proxy store and aliases. 10 | 11 | Following this pattern allows authenticating the user from the popup: 12 | 13 | ```js 14 | // in popup, display a login form (component named LoginForm) 15 | // ... 16 | const store = new Store({ 17 | portName: 'example' 18 | }); 19 | // ... 20 | onSubmit(e) { 21 | e.preventDefault(); 22 | if (this.isValid()) { 23 | // USER_LOGGING_IN is defined as an alias in react-chrome-redux 24 | store.dispatch({ type: 'USER_LOGGING_IN', data: {email: "test", password: "test"}}); 25 | } 26 | } 27 | // ... 28 | // Do not call firebaseConnect here 29 | export default connect(null, { login })(LoginForm); 30 | ``` 31 | 32 | Then, create your alias in the background script, import `react-redux-firebase` as well as `redux-thunk` to wait for Firebase's reply before updating the state (see reply in [issue #84 on react-chrome-redux](https://github.com/tshaddix/react-chrome-redux/issues/84)). 33 | 34 | ```js 35 | // in event (background script) 36 | // ... 37 | const store = createStore( 38 | rootReducer, 39 | {} 40 | ) 41 | 42 | wrapStore(store, { 43 | portName: 'example' 44 | }); 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/integrations/redux-saga.md: -------------------------------------------------------------------------------- 1 | # Redux Saga Recipes 2 | 3 | ### Example 4 | 5 | ```javascript 6 | import { applyMiddleware, compose, createStore } from 'redux' 7 | import { browserHistory } from 'react-router' 8 | import makeRootReducer from './reducers' 9 | import createSagaMiddleware from 'redux-saga' 10 | import firebase from 'firebase/app' 11 | import 'firebase/database' 12 | 13 | const firebaseConfig = {} // firebase configuration including databaseURL 14 | const reduxFirebase = { 15 | userProfile: 'users' 16 | } 17 | 18 | firebase.initializeApp(firebaseConfig) 19 | 20 | function* helloSaga() { 21 | try { 22 | yield firebase.ref('/some/path').push({ nice: 'work!' }) 23 | } catch (err) { 24 | console.log('Error in saga!:', err) 25 | } 26 | } 27 | 28 | export default (initialState = {}, history) => { 29 | const sagaMiddleware = createSagaMiddleware() // create middleware 30 | 31 | const middleware = [sagaMiddleware] 32 | 33 | const store = createStore( 34 | makeRootReducer(), 35 | {}, // initial state 36 | compose(applyMiddleware(...middleware)) 37 | ) 38 | 39 | return store 40 | } 41 | 42 | // when calling saga 43 | sagaMiddleware.run(helloSaga) 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/integrations/reselect.md: -------------------------------------------------------------------------------- 1 | # Reselect 2 | 3 | There are a number of reasons to use state selectors, as mentioned in the [relesect docs](https://github.com/reduxjs/reselect): 4 | 5 | > * Selectors can compute derived data, allowing Redux to store the minimal possible state. 6 | > * Selectors are efficient. A selector is not recomputed unless one of its arguments changes. 7 | > * Selectors are composable. They can be used as input to other selectors. 8 | 9 | For more information, about why this is important, checkout the [motivation for memoized selectors sections of the reselect docs](https://github.com/reduxjs/reselect#motivation-for-memoized-selectors) 10 | 11 | ## State Selectors 12 | 13 | Select only what you need from state in your selectors instead of the whole firebase/firestore state object: 14 | 15 | ```js 16 | import { createSelector } from 'reselect'; 17 | import { connect } from 'react-redux' 18 | import { get, sumBy } from 'lodash' 19 | 20 | const netTotalSelector = createSelector( 21 | state => get(state, 'firestore.data.products'), 22 | products => sumBy(products, 'price') 23 | ) 24 | 25 | connect((state) => ({ 26 | netTotal: netTotalSelector(state) 27 | }))(Component) 28 | ``` 29 | 30 | In this case Reselect will memoize the products object. That means that even if there's any update to other parts of redux state (including firebase/firestore), the memoized products object will stay the same until there is an update to the products themselves. 31 | 32 | See [issue #614](https://github.com/prescottprue/react-redux-firebase/issues/614) for more info. -------------------------------------------------------------------------------- /docs/recipes/populate.md: -------------------------------------------------------------------------------- 1 | # Populate 2 | 3 | ### Populate Key Within Items in a List 4 | 5 | Populate the owner of each item in a todos list from the 'users' root. 6 | 7 | #### Examples 8 | 9 | ##### Populate List of Items 10 | 11 | ```javascript 12 | import { compose } from 'redux' 13 | import { connect } from 'react-redux' 14 | import { firebaseConnect, populate } from 'react-redux-firebase' 15 | 16 | const populates = [ 17 | { child: 'owner', root: 'users' } 18 | ] 19 | 20 | const enhance = compose( 21 | firebaseConnect([ 22 | { path: 'todos', populates } 23 | ]), 24 | connect( 25 | ({ firebase }) => ({ 26 | todos: populate(firebase, 'todos', populates), 27 | }) 28 | ) 29 | ) 30 | 31 | export default enhance(SomeComponent) 32 | ``` 33 | 34 | ### Populate Profile Parameters 35 | 36 | To Populate parameters within profile/user object, include the `profileParamsToPopulate` parameter within your configuration as well as using `populate`. 37 | 38 | **NOTE** Using `profileParamsToPopulate` no longer automatically populates profile, you must use `populate`. Un-populated profile lives within state under `state.firebase.profile`. 39 | 40 | #### Examples 41 | 42 | ##### Populate Role 43 | 44 | Populating a user's role parameter from a list of roles (under `roles` collection). 45 | 46 | ```javascript 47 | export const profilePopulates = [{ child: 'role', root: 'roles' }] 48 | const config = { 49 | userProfile: 'users', 50 | profileParamsToPopulate: profilePopulates // populate list of todos from todos ref 51 | } 52 | 53 | // Wrapping some component 54 | connect( 55 | ({ firebase }) => ({ 56 | profile: firebase.profile, 57 | populatedProfile: populate(firebase, 'profile', profilePopulates), 58 | }) 59 | )(SomeComponent) 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/recipes/ssr.md: -------------------------------------------------------------------------------- 1 | # Server Side Rendering 2 | 3 | ## Preload Data 4 | Preloading data is a common step to in serverside rendering. How it is done differs based on whether you are using Real Time Database or Firestore. 5 | 6 | **Real Time Database** 7 | `promiseEvents`, which is similar to `firebaseConnect` expected it is presented as a function instead of a React Component. 8 | 9 | After creating your store: 10 | 11 | ```js 12 | store.firebase 13 | .promiseEvents([ 14 | { path: 'todos' }, 15 | { path: 'users' } 16 | ]) 17 | .then(() => { 18 | console.log('data is loaded into redux store') 19 | }) 20 | ``` 21 | 22 | **Firestore** 23 | Its just as simple as calling the built in get method with your query config: 24 | 25 | ```js 26 | store.firestore.get({ collection: 'todos' }) // or .get('todos') 27 | .then(() => { 28 | console.log('data is loaded into redux store') 29 | }) 30 | ``` 31 | 32 | ## Troubleshooting 33 | 34 | ### Include XMLHttpRequest 35 | 36 | ```js 37 | // needed to fix "Error: The XMLHttpRequest compatibility library was not found." 38 | global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest 39 | ``` 40 | 41 | If you find adding this extra code to be an annoyance or you would like to discuss a different way to do it, please feel free to open an issue/pull request or reach out on [gitter](https://gitter.im/redux-firebase/Lobby). 42 | 43 | 44 | 45 | ## Implement with Webpack 46 | 47 | ### Make Sure Loaders are setup appropriately 48 | Note, make sure that you have excluded `node_modules` in your webpack loaders 49 | 50 | ```js 51 | // webpack 1 52 | exclude: /node_modules/, 53 | // or webpack 2/3 54 | options: { presets: [ [ 'es2015', { modules: false } ] ] } 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | Please visit [the v3.0.0 Roadmap wiki doc](https://github.com/prescottprue/react-redux-firebase/wiki/v3.0.0-Roadmap) 4 | -------------------------------------------------------------------------------- /docs/static/BuildPhase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/docs/static/BuildPhase.png -------------------------------------------------------------------------------- /docs/static/FirebaseOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/docs/static/FirebaseOverview.png -------------------------------------------------------------------------------- /docs/static/PlistDownload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/docs/static/PlistDownload.png -------------------------------------------------------------------------------- /docs/static/RegisterApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/docs/static/RegisterApp.png -------------------------------------------------------------------------------- /docs/static/UrlTypes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/docs/static/UrlTypes.png -------------------------------------------------------------------------------- /docs/static/dataFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/docs/static/dataFlow.png -------------------------------------------------------------------------------- /examples/complete/firestore/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/complete/firestore/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': ['standard', 'standard-react', 'prettier', 'prettier/react'], 3 | root: true, 4 | parser: 'babel-eslint', 5 | plugins: ['import', 'react', 'react-hooks', 'prettier'], 6 | settings: { 7 | react: { 8 | version: '16.12' 9 | }, 10 | 'import/resolver': { 11 | node: { 12 | moduleDirectory: ['node_modules', '/'] 13 | } 14 | } 15 | }, 16 | rules: { 17 | semi: [ 18 | 2, 'never' 19 | ], 20 | 'no-console': 'error', 21 | 'react/forbid-prop-types': 0, 22 | 'react/require-default-props': 0, 23 | 'react/jsx-filename-extension': 0, 24 | 'import/no-named-as-default': 0, 25 | 'no-return-await': 2, 26 | 'react-hooks/rules-of-hooks': 'error', 27 | 'react-hooks/exhaustive-deps': 'warn', 28 | 'prettier/prettier': [ 29 | 'error', 30 | { 31 | singleQuote: true, 32 | trailingComma: 'none', 33 | semi: false, 34 | bracketSpacing: true, 35 | jsxBracketSameLine: true, 36 | printWidth: 80, 37 | tabWidth: 2, 38 | useTabs: false 39 | } 40 | ] 41 | }, 42 | overrides: [ 43 | { 44 | files: ['./src/**/*.test.js', './src/**/*.spec.js'], 45 | env: { 46 | jest: true 47 | } 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /examples/complete/firestore/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/complete/firestore/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/firestore/public/favicon.ico -------------------------------------------------------------------------------- /examples/complete/firestore/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/firestore/public/logo192.png -------------------------------------------------------------------------------- /examples/complete/firestore/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/firestore/public/logo512.png -------------------------------------------------------------------------------- /examples/complete/firestore/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Firestore Example", 3 | "name": "Firestore + react-redux-firebase example", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/complete/firestore/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 40vh; 19 | max-height: 50vh; 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | justify-content: center; 24 | font-size: calc(10px + 2vmin); 25 | color: white; 26 | } 27 | 28 | .App-link { 29 | color: #61dafb; 30 | } 31 | 32 | @keyframes App-logo-spin { 33 | from { 34 | transform: rotate(0deg); 35 | } 36 | to { 37 | transform: rotate(360deg); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux' 3 | import firebase from 'firebase/app' 4 | import 'firebase/auth' 5 | import 'firebase/database' 6 | import 'firebase/firestore' // make sure you add this for firestore 7 | import { ReactReduxFirebaseProvider } from 'react-redux-firebase' 8 | import { createFirestoreInstance } from 'redux-firestore' 9 | import Home from './Home' 10 | import configureStore from './store' 11 | import { firebase as fbConfig, rrfConfig } from './config' 12 | import './App.css' 13 | 14 | const initialState = window && window.__INITIAL_STATE__ // set initial state here 15 | const store = configureStore(initialState) 16 | // Initialize Firebase instance 17 | firebase.initializeApp(fbConfig) 18 | 19 | export default function App() { 20 | return ( 21 | 22 | 27 | 28 | 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from '@testing-library/react' 3 | import App from './App' 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render() 7 | const linkElement = getByText(/learn react/i) 8 | expect(linkElement).toBeInTheDocument() 9 | }) 10 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Todos from './Todos' 3 | import NewTodo from './NewTodo' 4 | import logo from './logo.svg' 5 | import './App.css' 6 | 7 | function Home() { 8 | return ( 9 |
10 |
11 |

firestore demo

12 | logo 13 |
14 |
15 |

16 | Loaded From
17 | 18 | 23 | redux-firebasev3.firebaseio.com 24 | 25 | 26 |

27 |

Todos List

28 | 29 | 30 |
31 |
32 | ) 33 | } 34 | 35 | export default Home 36 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/NewTodo.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useFirestore } from 'react-redux-firebase' 3 | 4 | function NewTodo() { 5 | const [inputVal, changeInput] = useState('') 6 | const firestore = useFirestore() 7 | 8 | function resetInput() { 9 | changeInput('') 10 | } 11 | function onInputChange(e) { 12 | return changeInput(e && e.target && e.target.value) 13 | } 14 | 15 | function addTodo() { 16 | return firestore 17 | .collection('todos') 18 | .add({ text: inputVal || 'sample', done: false }) 19 | } 20 | 21 | return ( 22 |
23 |

New Todo

24 | 25 | 26 | 27 |
28 | ) 29 | } 30 | 31 | export default NewTodo 32 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/Todo.css: -------------------------------------------------------------------------------- 1 | .Todo { 2 | margin: 1rem; 3 | 4 | } 5 | .Todo-Input { 6 | margin: 1rem; 7 | } 8 | 9 | .Todo-Button { 10 | margin: 1rem; 11 | } 12 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/TodoItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { useSelector } from 'react-redux' 4 | import { useFirestore } from 'react-redux-firebase' 5 | import './Todo.css' 6 | 7 | function TodoItem({ id }) { 8 | const todo = useSelector( 9 | ({ firestore: { data } }) => data.todos && data.todos[id] 10 | ) 11 | const firestore = useFirestore() 12 | 13 | function toggleDone() { 14 | firestore.update(`todos/${id}`, { done: !todo.done }) 15 | } 16 | 17 | function deleteTodo() { 18 | return firestore.delete(`todos/${id}`) 19 | } 20 | 21 | return ( 22 |
  • 23 | 29 | {todo.text || todo.name} 30 | 33 |
  • 34 | ) 35 | } 36 | 37 | TodoItem.propTypes = { 38 | id: PropTypes.string.isRequired 39 | } 40 | 41 | export default TodoItem 42 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/Todos.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux' 3 | import { useFirestoreConnect, isLoaded, isEmpty } from 'react-redux-firebase' 4 | import TodoItem from './TodoItem' 5 | 6 | const todosQuery = { 7 | collection: 'todos', 8 | limitTo: 10 9 | } 10 | 11 | function Todos() { 12 | // Attach todos listener 13 | useFirestoreConnect(() => [todosQuery]) 14 | 15 | // Get todos from redux state 16 | const todos = useSelector(({ firestore: { ordered } }) => ordered.todos) 17 | 18 | // Show a message while todos are loading 19 | if (!isLoaded(todos)) { 20 | return 'Loading' 21 | } 22 | 23 | // Show a message if there are no todos 24 | if (isEmpty(todos)) { 25 | return 'Todo list is empty' 26 | } 27 | 28 | return todos.map(({ id, ...todo }, ind) => ( 29 | 30 | )) 31 | } 32 | 33 | export default Todos 34 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/Todos.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render, unmountComponentAtNode } from 'react-dom' 3 | import { act } from 'react-dom/test-utils' 4 | import { useSelector } from 'react-redux' 5 | import Todos from './Todos' 6 | 7 | jest.mock('react-redux') 8 | 9 | describe('Should test the Todos Component', () => { 10 | let container = null 11 | 12 | beforeEach(() => { 13 | // setup a DOM element as a render target 14 | container = document.createElement('div') 15 | document.body.appendChild(container) 16 | }) 17 | 18 | afterEach(() => { 19 | // cleanup on exiting 20 | unmountComponentAtNode(container) 21 | container.remove() 22 | container = null 23 | }) 24 | 25 | it('show empty text when the todo list is empty', () => { 26 | const todos = [] 27 | useSelector.mockReturnValue(todos) 28 | 29 | act(() => { 30 | render(, container) 31 | }) 32 | 33 | container.querySelectorAll('li') 34 | expect(container.textContent).toBe('Todo list is empty') 35 | }) 36 | 37 | it('should have one TodoItem in the list', () => { 38 | const todos = [ 39 | { 40 | id: 'todoId', 41 | name: 'Sample todo', 42 | text: 'This is for testing todos' 43 | } 44 | ] 45 | 46 | useSelector.mockReturnValue(todos) 47 | act(() => { 48 | render(, container) 49 | }) 50 | 51 | const listItems = container.querySelectorAll('li') 52 | 53 | // this is not a real id, so only text which can be visible is Delete 54 | expect(listItems[0].textContent).toBe('Delete') 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/config.js: -------------------------------------------------------------------------------- 1 | export const firebase = { 2 | apiKey: 'AIzaSyCTUERDM-Pchn_UDTsfhVPiwM4TtNIxots', 3 | authDomain: 'redux-firebasev3.firebaseapp.com', 4 | databaseURL: 'https://redux-firebasev3.firebaseio.com', 5 | projectId: 'redux-firebasev3', 6 | storageBucket: 'redux-firebasev3.appspot.com', 7 | messagingSenderId: '823357791673' 8 | } 9 | 10 | export const rrfConfig = { 11 | userProfile: 'users', 12 | useFirestoreForProfile: true // Store in Firestore instead of Real Time DB 13 | } 14 | 15 | export default { firebase, rrfConfig } 16 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './index.css' 4 | import App from './App' 5 | import * as serviceWorker from './serviceWorker' 6 | 7 | ReactDOM.render(, document.getElementById('root')) 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister() 13 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { firebaseReducer } from 'react-redux-firebase' 3 | import { firestoreReducer } from 'redux-firestore' 4 | 5 | const rootReducer = combineReducers({ 6 | firebase: firebaseReducer, 7 | firestore: firestoreReducer 8 | }) 9 | 10 | export default rootReducer 11 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect' 6 | -------------------------------------------------------------------------------- /examples/complete/firestore/src/store.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, createStore, compose } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import rootReducer from './reducer' 4 | import { getFirebase } from 'react-redux-firebase' 5 | 6 | export default function configureStore(initialState, history) { 7 | const middleware = [thunk.withExtraArgument({ getFirebase })] 8 | const createStoreWithMiddleware = compose( 9 | applyMiddleware(...middleware), 10 | typeof window === 'object' && 11 | typeof window.devToolsExtension !== 'undefined' 12 | ? () => window.__REDUX_DEVTOOLS_EXTENSION__ 13 | : f => f 14 | )(createStore) 15 | const store = createStoreWithMiddleware(rootReducer) 16 | 17 | if (module.hot) { 18 | // Enable Webpack hot module replacement for reducers 19 | module.hot.accept('./reducer', () => { 20 | const nextRootReducer = require('./reducer') 21 | store.replaceReducer(nextRootReducer) 22 | }) 23 | } 24 | 25 | return store 26 | } 27 | -------------------------------------------------------------------------------- /examples/complete/material/.env.local: -------------------------------------------------------------------------------- 1 | CI=false 2 | # Needed to skip warnings from jest@beta in package.json 3 | SKIP_PREFLIGHT_CHECK=true 4 | -------------------------------------------------------------------------------- /examples/complete/material/.eslintignore: -------------------------------------------------------------------------------- 1 | **/coverage/** 2 | **/node_modules/** 3 | dist/** 4 | build/** 5 | src/index.html 6 | blueprints/** 7 | src/config.js 8 | -------------------------------------------------------------------------------- /examples/complete/material/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': ['airbnb', 'airbnb/hooks', 'prettier', 'prettier/react'], 3 | root: true, 4 | parser: 'babel-eslint', 5 | plugins: ['import', 'babel', 'react', 'react-hooks', 'prettier'], 6 | settings: { 7 | react: { 8 | version: '16.9' 9 | }, 10 | 'import/resolver': { 11 | node: { 12 | moduleDirectory: ['node_modules', '/'] 13 | } 14 | } 15 | }, 16 | rules: { 17 | semi: [ 18 | 2, 'never' 19 | ], 20 | 'no-console': 'error', 21 | 'react/forbid-prop-types': 0, 22 | 'react/require-default-props': 0, 23 | 'react/jsx-filename-extension': 0, 24 | 'import/no-named-as-default': 0, 25 | 'no-return-await': 2, 26 | 'react-hooks/rules-of-hooks': 'error', 27 | 'react-hooks/exhaustive-deps': 'warn', 28 | 'prettier/prettier': [ 29 | 'error', 30 | { 31 | singleQuote: true, 32 | trailingComma: 'none', 33 | semi: false, 34 | bracketSpacing: true, 35 | jsxBracketSameLine: true, 36 | printWidth: 80, 37 | tabWidth: 2, 38 | useTabs: false 39 | } 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/complete/material/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "redux-firebasev3", 4 | "master": "redux-firebasev3", 5 | "prod": "redux-firebasev3" 6 | }, 7 | "ci": { 8 | "createConfig": { 9 | "master": { 10 | "env": "staging", 11 | "firebase": { 12 | "apiKey": "${STAGE_FIREBASE_API_KEY}", 13 | "authDomain": "redux-firebasev3.firebaseapp.com", 14 | "databaseURL": "https://redux-firebasev3.firebaseio.com", 15 | "projectId": "redux-firebasev3", 16 | "storageBucket": "redux-firebasev3.appspot.com" 17 | } 18 | }, 19 | "prod": { 20 | "env": "production", 21 | "firebase": { 22 | "apiKey": "${PROD_FIREBASE_API_KEY}", 23 | "authDomain": "redux-firebasev3.firebaseapp.com", 24 | "databaseURL": "https://redux-firebasev3.firebaseio.com", 25 | "projectId": "redux-firebasev3", 26 | "storageBucket": "redux-firebasev3.appspot.com" 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/complete/material/.gitignore: -------------------------------------------------------------------------------- 1 | # OS 2 | *.log 3 | **/*.log 4 | .DS_Store 5 | **/.DS_Store 6 | 7 | # Dependencies 8 | **/node_modules 9 | 10 | # React App 11 | build 12 | src/config.js 13 | .env 14 | -------------------------------------------------------------------------------- /examples/complete/material/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-react-firebase": { 3 | "promptValues": { 4 | "githubUser": "prescottprue", 5 | "firebaseName": "redux-firebasev3", 6 | "firebaseKey": "AIzaSyCTUERDM-Pchn_UDTsfhVPiwM4TtNIxots", 7 | "includeRedux": true, 8 | "includeFirestore": false, 9 | "otherFeatures": [ 10 | "Config for Continuous Integration" 11 | ], 12 | "ciProvider": "gitlab", 13 | "deployTo": "firebase" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /examples/complete/material/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 - present prescottprue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/complete/material/database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | ".read": "auth !== null", 4 | ".write":"auth !== null", 5 | "users": { 6 | "$uid": { 7 | ".write": "auth !== null && $uid === auth.uid" 8 | } 9 | }, 10 | "projects": { 11 | ".indexOn": ["createdBy"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/complete/material/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | }, 5 | "storage": { 6 | "rules": "storage.rules" 7 | }, 8 | "hosting": { 9 | "public": "build", 10 | "ignore": [ 11 | "firebase.json", 12 | "**/.*", 13 | "**/node_modules/**", 14 | "jsconfig.json", 15 | "cypress/**" 16 | ], 17 | "rewrites": [ 18 | { 19 | "source": "**", 20 | "destination": "/index.html" 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/complete/material/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | // Example: 3 | // 4 | // "indexes": [ 5 | // { 6 | // "collectionId": "widgets", 7 | // "fields": [ 8 | // { "fieldPath": "foo", "mode": "ASCENDING" }, 9 | // { "fieldPath": "bar", "mode": "DESCENDING" } 10 | // ] 11 | // } 12 | // ] 13 | "indexes": [] 14 | } 15 | -------------------------------------------------------------------------------- /examples/complete/material/firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | function isAuthed() { 4 | return request.auth.uid != null 5 | } 6 | function isOwner(res) { 7 | return res.data.createdBy == request.auth.uid 8 | } 9 | 10 | // Private user profiles 11 | match /users/{userId} { 12 | allow read; 13 | allow write: if request.auth.uid == userId; 14 | } 15 | 16 | // Public user profiles 17 | match /users_public/{userId} { 18 | allow read; 19 | allow write: if false; // only written to by indexUser cloud function 20 | } 21 | 22 | // Projects 23 | match /projects/{projectId} { 24 | // Only projects you own can be viewed 25 | allow read, write: if isOwner(request.resource); 26 | // Rules apply to all child collections 27 | match /{allChildren=**} { 28 | allow read, write: if isOwner(get(/databases/$(database)/documents/projects/$(projectId))); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/complete/material/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "paths": { 5 | "utils/*": [ 6 | "utils/*" 7 | ] 8 | } 9 | }, 10 | "exclude": [ 11 | "node_modules", 12 | "dist" 13 | ] 14 | } -------------------------------------------------------------------------------- /examples/complete/material/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/material/public/favicon.ico -------------------------------------------------------------------------------- /examples/complete/material/public/humans.txt: -------------------------------------------------------------------------------- 1 | # Check it out: http://humanstxt.org/ 2 | 3 | # TEAM 4 | 5 | -- -- 6 | 7 | # THANKS 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/complete/material/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Material", 3 | "name": "Material", 4 | "start_url": "/login/", 5 | "background_color": "#2196f3", 6 | "theme_color": "#2196f3", 7 | "display": "standalone" 8 | } -------------------------------------------------------------------------------- /examples/complete/material/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /examples/complete/material/src/components/FormTextField/FormTextField.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import TextField from '@material-ui/core/TextField' 4 | 5 | function FormTextField({ 6 | label, 7 | input, 8 | meta: { touched, invalid, error }, 9 | ...custom 10 | }) { 11 | return ( 12 | 20 | ) 21 | } 22 | 23 | FormTextField.propTypes = { 24 | formTextField: PropTypes.object 25 | } 26 | 27 | export default FormTextField 28 | -------------------------------------------------------------------------------- /examples/complete/material/src/components/FormTextField/index.js: -------------------------------------------------------------------------------- 1 | import FormTextField from './FormTextField' 2 | 3 | export default FormTextField 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/components/LoadingSpinner/LoadingSpinner.enhancer.js: -------------------------------------------------------------------------------- 1 | import { withStyles } from '@material-ui/core/styles' 2 | import styles from './LoadingSpinner.styles' 3 | 4 | export default withStyles(styles) 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/components/LoadingSpinner/LoadingSpinner.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import CircularProgress from '@material-ui/core/CircularProgress' 4 | import { makeStyles } from '@material-ui/core/styles' 5 | import styles from './LoadingSpinner.styles' 6 | 7 | const useStyles = makeStyles(styles) 8 | 9 | function LoadingSpinner({ size }) { 10 | const classes = useStyles() 11 | 12 | return ( 13 |
    14 |
    15 | 16 |
    17 |
    18 | ) 19 | } 20 | 21 | LoadingSpinner.propTypes = { 22 | size: PropTypes.number 23 | } 24 | 25 | export default LoadingSpinner 26 | -------------------------------------------------------------------------------- /examples/complete/material/src/components/LoadingSpinner/LoadingSpinner.styles.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | root: { 3 | display: 'flex', 4 | alignItems: 'center', 5 | flexDirection: 'column', 6 | justifyContent: 'flex-start', 7 | paddingTop: '7rem', 8 | height: '100%' 9 | }, 10 | progress: { 11 | display: 'flex', 12 | justifyContent: 'center', 13 | alignItems: 'center', 14 | height: '50%' 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /examples/complete/material/src/components/LoadingSpinner/LoadingSpinner.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import renderer from 'react-test-renderer' 4 | import LoadingSpinner from './index' 5 | 6 | it('renders without crashing', () => { 7 | const div = document.createElement('div') 8 | ReactDOM.render(, div) 9 | ReactDOM.unmountComponentAtNode(div) 10 | }) 11 | 12 | it('renders a spinner', () => { 13 | const tree = renderer.create().toJSON() 14 | expect(tree).toMatchSnapshot() 15 | }) 16 | -------------------------------------------------------------------------------- /examples/complete/material/src/components/LoadingSpinner/index.js: -------------------------------------------------------------------------------- 1 | import LoadingSpinner from './LoadingSpinner' 2 | 3 | export default LoadingSpinner 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NOTE: This file is ignored from git tracking. In a CI environment it is 3 | * generated by firebase-ci based on config in .firebaserc (see .gitlab-ci.yaml). 4 | * This is done so that environment specific settings can be applied. 5 | */ 6 | 7 | export const env = 'local' 8 | 9 | // Config for firebase 10 | export const firebase = { 11 | apiKey: 'AIzaSyCTUERDM-Pchn_UDTsfhVPiwM4TtNIxots', 12 | authDomain: 'redux-firebasev3.firebaseapp.com', 13 | databaseURL: 'https://redux-firebasev3.firebaseio.com', 14 | projectId: 'redux-firebasev3', 15 | storageBucket: 'redux-firebasev3.appspot.com' 16 | } 17 | 18 | // Config to override default reduxFirebase config in store/createStore 19 | // which is not environment specific. 20 | // For more details, visit http://react-redux-firebase.com/docs/api/enhancer.html 21 | export const reduxFirebase = { 22 | enableLogging: false, // enable/disable Firebase Database Logging 23 | } 24 | 25 | export default { 26 | env, 27 | firebase, 28 | reduxFirebase 29 | } 30 | -------------------------------------------------------------------------------- /examples/complete/material/src/constants/formNames.js: -------------------------------------------------------------------------------- 1 | export const ACCOUNT_FORM_NAME = 'account' 2 | export const LOGIN_FORM_NAME = 'login' 3 | export const SIGNUP_FORM_NAME = 'signup' 4 | export const NEW_PROJECT_FORM_NAME = 'newProject' 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/constants/paths.js: -------------------------------------------------------------------------------- 1 | export const LIST_PATH = '/projects' 2 | export const ACCOUNT_PATH = '/account' 3 | export const LOGIN_PATH = '/login' 4 | export const SIGNUP_PATH = '/signup' 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/containers/App/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { BrowserRouter as Router } from 'react-router-dom' 4 | import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles' 5 | import firebase from 'firebase/app' 6 | import 'firebase/auth' 7 | import 'firebase/database' 8 | import 'firebase/firestore' // make sure you add this for firestore 9 | import { ReactReduxFirebaseProvider } from 'react-redux-firebase' 10 | import { createFirestoreInstance } from 'redux-firestore' 11 | import { Provider } from 'react-redux' 12 | import ThemeSettings from 'theme' 13 | import { firebase as fbConfig, reduxFirebase as rfConfig } from 'config' 14 | 15 | const theme = createMuiTheme(ThemeSettings) 16 | 17 | // Initialize Firebase instance 18 | firebase.initializeApp(fbConfig) 19 | 20 | function App({ routes, store }) { 21 | return ( 22 | 23 | 24 | 29 | {routes} 30 | 31 | 32 | 33 | ) 34 | } 35 | 36 | App.propTypes = { 37 | routes: PropTypes.object.isRequired, 38 | store: PropTypes.object.isRequired 39 | } 40 | 41 | export default App 42 | -------------------------------------------------------------------------------- /examples/complete/material/src/containers/App/index.js: -------------------------------------------------------------------------------- 1 | import App from './App' 2 | 3 | export default App 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/containers/Navbar/LoginMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import Button from '@material-ui/core/Button' 4 | import { LOGIN_PATH, SIGNUP_PATH } from 'constants/paths' 5 | 6 | const buttonStyle = { 7 | color: 'white', 8 | textDecoration: 'none', 9 | alignSelf: 'center' 10 | } 11 | 12 | function LoginMenu() { 13 | return ( 14 |
    15 | 18 | 21 |
    22 | ) 23 | } 24 | 25 | export default LoginMenu 26 | -------------------------------------------------------------------------------- /examples/complete/material/src/containers/Navbar/Navbar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import AppBar from '@material-ui/core/AppBar' 4 | import Toolbar from '@material-ui/core/Toolbar' 5 | import Typography from '@material-ui/core/Typography' 6 | import Button from '@material-ui/core/Button' 7 | import { makeStyles } from '@material-ui/core/styles' 8 | import { useSelector } from 'react-redux' 9 | import { isLoaded, isEmpty } from 'react-redux-firebase' 10 | import { LIST_PATH, LOGIN_PATH } from 'constants/paths' 11 | import AccountMenu from './AccountMenu' 12 | import styles from './Navbar.styles' 13 | 14 | const useStyles = makeStyles(styles) 15 | 16 | function Navbar() { 17 | const classes = useStyles() 18 | 19 | // Get auth from redux state 20 | const auth = useSelector(state => state.firebase.auth) 21 | const authExists = isLoaded(auth) && !isEmpty(auth) 22 | 23 | return ( 24 | 25 | 26 | 33 | material example 34 | 35 |
    36 | {authExists ? ( 37 | 38 | ) : ( 39 | 46 | )} 47 | 48 | 49 | ) 50 | } 51 | 52 | export default Navbar 53 | -------------------------------------------------------------------------------- /examples/complete/material/src/containers/Navbar/Navbar.styles.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | flex: { 3 | flexGrow: 1 4 | }, 5 | appBar: { 6 | // backgroundColor: theme.palette.primary1Color // Update this to change navbar color 7 | }, 8 | signIn: { 9 | color: 'white', 10 | textDecoration: 'none', 11 | alignSelf: 'center' 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /examples/complete/material/src/containers/Navbar/index.js: -------------------------------------------------------------------------------- 1 | import Navbar from './Navbar' 2 | 3 | export default Navbar 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | height: 100%; 10 | background-color: #F2F2F2; 11 | font-family: 'Roboto', 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 12 | } 13 | a { 14 | text-decoration: none !important; 15 | } 16 | *, 17 | *:before, 18 | *:after { 19 | box-sizing: inherit; 20 | } 21 | /* [type=button]{ 22 | -webkit-appearance: none !important; 23 | } */ 24 | -------------------------------------------------------------------------------- /examples/complete/material/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { initScripts } from './utils' 4 | import createStore from './store/createStore' 5 | import { version } from '../package.json' 6 | import { env } from './config' 7 | import App from './containers/App' 8 | import './index.css' 9 | 10 | // import * as serviceWorker from './serviceWorker' 11 | 12 | // Window Variables 13 | // ------------------------------------ 14 | window.version = version 15 | window.env = env 16 | initScripts() 17 | 18 | // Store Initialization 19 | // ------------------------------------ 20 | const initialState = window.___INITIAL_STATE__ || { 21 | firebase: { authError: null } 22 | } 23 | const store = createStore(initialState) 24 | const routes = require('./routes/index').default(store) 25 | 26 | ReactDOM.render( 27 | , 28 | document.getElementById('root') 29 | ) 30 | 31 | // If you want your app to work offline and load faster, you can change 32 | // unregister() to register() below. Note this comes with some pitfalls. 33 | // Learn more about service workers: http://bit.ly/CRA-PWA 34 | // serviceWorker.unregister() 35 | -------------------------------------------------------------------------------- /examples/complete/material/src/layouts/CoreLayout/CoreLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Navbar from 'containers/Navbar' 4 | import { Notifications } from 'modules/notification' 5 | import { makeStyles } from '@material-ui/core/styles' 6 | import styles from './CoreLayout.styles' 7 | 8 | const useStyles = makeStyles(styles) 9 | 10 | function CoreLayout({ children }) { 11 | const classes = useStyles() 12 | 13 | return ( 14 |
    15 | 16 |
    {children}
    17 | 18 |
    19 | ) 20 | } 21 | 22 | CoreLayout.propTypes = { 23 | children: PropTypes.element.isRequired 24 | } 25 | 26 | export default CoreLayout 27 | -------------------------------------------------------------------------------- /examples/complete/material/src/layouts/CoreLayout/CoreLayout.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({}) 2 | -------------------------------------------------------------------------------- /examples/complete/material/src/layouts/CoreLayout/index.js: -------------------------------------------------------------------------------- 1 | import CoreLayout from './CoreLayout' 2 | 3 | export default CoreLayout 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/modules/notification/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const NOTIFICATION_SHOW = 'NOTIFICATION_SHOW' 2 | export const NOTIFICATION_DISMISS = 'NOTIFICATION_DISMISS' 3 | export const NOTIFICATION_CLEAR = 'NOTIFICATION_CLEAR' 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/modules/notification/components/Notifications.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import Snackbar from '@material-ui/core/Snackbar' 5 | import IconButton from '@material-ui/core/IconButton' 6 | import CloseIcon from '@material-ui/icons/Close' 7 | import { makeStyles } from '@material-ui/core/styles' 8 | import * as actions from '../actions' 9 | 10 | const useStyles = makeStyles(() => ({ 11 | buttonRoot: { 12 | color: 'white' 13 | } 14 | })) 15 | 16 | function Notifications({ allIds, byId, dismissNotification }) { 17 | const classes = useStyles() 18 | 19 | // Only render if notifications exist 20 | if (!allIds || !Object.keys(allIds).length) { 21 | return null 22 | } 23 | 24 | return ( 25 |
    26 | {allIds.map(id => ( 27 | dismissNotification(id)} 33 | classes={{ root: classes.buttonRoot }}> 34 | 35 | 36 | } 37 | message={byId[id].message} 38 | /> 39 | ))} 40 |
    41 | ) 42 | } 43 | 44 | Notifications.propTypes = { 45 | allIds: PropTypes.array.isRequired, 46 | byId: PropTypes.object.isRequired, 47 | dismissNotification: PropTypes.func.isRequired 48 | } 49 | 50 | export default connect( 51 | ({ notifications: { allIds, byId } }) => ({ allIds, byId }), 52 | actions 53 | )(Notifications) 54 | -------------------------------------------------------------------------------- /examples/complete/material/src/modules/notification/components/useNotifications.js: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import { bindActionCreators } from 'redux' 3 | import { useDispatch } from 'react-redux' 4 | import * as actions from '../actions' 5 | 6 | export default function useNotifications() { 7 | const dispatch = useDispatch() 8 | return useMemo(() => { 9 | return bindActionCreators(actions, dispatch) 10 | }, [dispatch]) 11 | } -------------------------------------------------------------------------------- /examples/complete/material/src/modules/notification/index.js: -------------------------------------------------------------------------------- 1 | import * as actions from './actions' 2 | import reducer from './reducer' 3 | import useNotifications from './components/useNotifications' 4 | import Notifications from './components/Notifications' 5 | import * as actionTypes from './actionTypes' 6 | 7 | export default actions 8 | export { reducer, useNotifications, Notifications, actionTypes } 9 | -------------------------------------------------------------------------------- /examples/complete/material/src/modules/notification/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { without, omit } from 'lodash' 3 | import { NOTIFICATION_SHOW, NOTIFICATION_DISMISS } from './actionTypes' 4 | 5 | function notification(state = {}, action) { 6 | switch (action.type) { 7 | case NOTIFICATION_SHOW: 8 | return action.payload 9 | case NOTIFICATION_DISMISS: 10 | return undefined 11 | default: 12 | return state 13 | } 14 | } 15 | 16 | function allIds(state = [], action) { 17 | switch (action.type) { 18 | case NOTIFICATION_SHOW: 19 | return [...state, action.payload.id] 20 | case NOTIFICATION_DISMISS: 21 | return without(state, action.payload) 22 | default: 23 | return state 24 | } 25 | } 26 | 27 | function byId(state = {}, action) { 28 | switch (action.type) { 29 | case NOTIFICATION_SHOW: 30 | return { 31 | ...state, 32 | [action.payload.id]: notification(state[action.payload.id], action) 33 | } 34 | case NOTIFICATION_DISMISS: 35 | return omit(state, action.payload) 36 | default: 37 | return state 38 | } 39 | } 40 | 41 | export const notifications = combineReducers({ byId, allIds }) 42 | 43 | export default notifications 44 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Account/components/AccountForm/AccountForm.enhancer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { compose, setPropTypes } from 'recompose' 3 | import { reduxForm } from 'redux-form' 4 | import { ACCOUNT_FORM_NAME } from 'constants/formNames' 5 | 6 | export default compose( 7 | // set proptypes used in HOCs 8 | setPropTypes({ 9 | onSubmit: PropTypes.func.isRequired // used by reduxForm 10 | }), 11 | // Add form capabilities 12 | reduxForm({ form: ACCOUNT_FORM_NAME }) 13 | ) 14 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Account/components/AccountForm/AccountForm.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | ...theme.flexColumnCenter, 4 | justifyContent: 'flex-start', 5 | width: '100%', 6 | height: '100%' 7 | }, 8 | fields: { 9 | width: '60%' 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Account/components/AccountForm/index.js: -------------------------------------------------------------------------------- 1 | import AccountForm from './AccountForm' 2 | import enhance from './AccountForm.enhancer' 3 | 4 | export default enhance(AccountForm) 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Account/components/AccountPage/AccountPage.enhancer.js: -------------------------------------------------------------------------------- 1 | import { UserIsAuthenticated } from 'utils/router' 2 | 3 | export default UserIsAuthenticated // redirect to /login if user is not authenticated 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Account/components/AccountPage/AccountPage.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | ...theme.flexRowCenter, 4 | width: '100%', 5 | height: '100%', 6 | paddingTop: '1.5rem' 7 | }, 8 | pane: { 9 | ...theme.flexColumnCenter, 10 | justifyContent: 'space-around', 11 | flexBasis: '60%', 12 | padding: theme.spacing() 13 | }, 14 | settings: { 15 | ...theme.flexRowCenter 16 | }, 17 | avatarCurrent: { 18 | width: '100%', 19 | maxWidth: '13rem', 20 | marginTop: '3rem', 21 | height: 'auto', 22 | cursor: 'pointer' 23 | }, 24 | meta: { 25 | ...theme.flexColumnCenter, 26 | flexBasis: '60%', 27 | marginBottom: '3rem', 28 | marginTop: '2rem' 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Account/components/AccountPage/index.js: -------------------------------------------------------------------------------- 1 | import AccountPage from './AccountPage' 2 | import enhance from './AccountPage.enhancer' 3 | 4 | export default enhance(AccountPage) 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Account/components/ProviderDataForm/ProviderDataForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import List from '@material-ui/core/List' 4 | import ListItem from '@material-ui/core/ListItem' 5 | import ListItemIcon from '@material-ui/core/ListItemIcon' 6 | import ListItemText from '@material-ui/core/ListItemText' 7 | import ListSubheader from '@material-ui/core/ListSubheader' 8 | import AccountCircle from '@material-ui/icons/AccountCircle' 9 | 10 | function ProviderData({ providerData }) { 11 | return ( 12 | Accounts}> 13 | {providerData.map(providerAccount => ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | ))} 21 | 22 | ) 23 | } 24 | 25 | ProviderData.propTypes = { 26 | providerData: PropTypes.array.isRequired 27 | } 28 | 29 | export default ProviderData 30 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Account/components/ProviderDataForm/index.js: -------------------------------------------------------------------------------- 1 | import ProviderDataForm from './ProviderDataForm' 2 | 3 | export default ProviderDataForm 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Account/index.js: -------------------------------------------------------------------------------- 1 | import { Loadable } from 'utils/components' 2 | import { ACCOUNT_PATH as path } from 'constants/paths' 3 | 4 | export default { 5 | path, 6 | component: Loadable({ 7 | loader: () => 8 | import(/* webpackChunkName: 'Account' */ './components/AccountPage') 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Home/components/HomePage/HomePage.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | ...theme.flexColumnCenter 4 | }, 5 | section: { 6 | ...theme.flexColumnCenter 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Home/components/HomePage/index.js: -------------------------------------------------------------------------------- 1 | import HomePage from './HomePage' 2 | 3 | export default HomePage 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Home/index.js: -------------------------------------------------------------------------------- 1 | import HomePage from './components/HomePage' 2 | 3 | // Sync route definition 4 | export default { 5 | path: '/', 6 | component: HomePage 7 | } 8 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Login/components/LoginForm/LoginForm.enhancer.js: -------------------------------------------------------------------------------- 1 | import { reduxForm } from 'redux-form' 2 | import { LOGIN_FORM_NAME } from 'constants/formNames' 3 | 4 | // Add Form Capabilities 5 | export default reduxForm({ form: LOGIN_FORM_NAME }) 6 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Login/components/LoginForm/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { makeStyles } from '@material-ui/core/styles' 4 | import { Field } from 'redux-form' 5 | import TextField from 'components/FormTextField' 6 | import Button from '@material-ui/core/Button' 7 | import { required, validateEmail } from 'utils/form' 8 | import styles from './LoginForm.styles' 9 | 10 | const useStyles = makeStyles(styles) 11 | 12 | function LoginForm({ pristine, submitting, handleSubmit }) { 13 | const classes = useStyles() 14 | 15 | return ( 16 |
    17 | 24 | 32 |
    33 | 40 |
    41 | 42 | ) 43 | } 44 | 45 | LoginForm.propTypes = { 46 | pristine: PropTypes.bool.isRequired, // from enhancer (reduxForm) 47 | submitting: PropTypes.bool.isRequired, // from enhancer (reduxForm) 48 | handleSubmit: PropTypes.func.isRequired // from enhancer (reduxForm - calls onSubmit) 49 | } 50 | 51 | export default LoginForm 52 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Login/components/LoginForm/LoginForm.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | ...theme.flexColumnCenter, 4 | justifyContent: 'flex-start', 5 | flexGrow: 1, 6 | height: '100%', 7 | width: '100%', 8 | margin: '.2rem' 9 | }, 10 | submit: { 11 | ...theme.flexColumnCenter, 12 | justifyContent: 'center', 13 | flexGrow: 1, 14 | textAlign: 'center', 15 | padding: '1.25rem', 16 | minWidth: '192px', 17 | marginTop: '1.5rem' 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Login/components/LoginForm/index.js: -------------------------------------------------------------------------------- 1 | import LoginForm from './LoginForm' 2 | import enhance from './LoginForm.enhancer' 3 | 4 | export default enhance(LoginForm) 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Login/components/LoginPage/LoginPage.enhancer.js: -------------------------------------------------------------------------------- 1 | import { UserIsNotAuthenticated } from 'utils/router' 2 | 3 | // redirect to /projects if user is already authed 4 | export default UserIsNotAuthenticated 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Login/components/LoginPage/LoginPage.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | ...theme.flexColumnCenter, 4 | justifyContent: 'flex-start', 5 | height: '100%', 6 | width: '100%', 7 | fontWeight: 400, 8 | paddingTop: '1.5rem' 9 | }, 10 | panel: { 11 | ...theme.flexColumnCenter, 12 | justifyContent: 'center', 13 | flexGrow: 1, 14 | padding: '1.25rem', 15 | minWidth: '250px', 16 | minHeight: '270px' 17 | }, 18 | orLabel: { 19 | marginTop: '1rem', 20 | marginBottom: '.5rem' 21 | }, 22 | signup: { 23 | ...theme.flexColumnCenter, 24 | justifyContent: 'center', 25 | marginTop: '2rem' 26 | }, 27 | signupLabel: { 28 | fontSize: '1rem', 29 | fontWeight: 'bold' 30 | }, 31 | signupLink: { 32 | fontSize: '1.2rem' 33 | }, 34 | providers: { 35 | marginTop: '1rem' 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Login/components/LoginPage/index.js: -------------------------------------------------------------------------------- 1 | import LoginPage from './LoginPage' 2 | import enhance from './LoginPage.enhancer' 3 | 4 | export default enhance(LoginPage) 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Login/index.js: -------------------------------------------------------------------------------- 1 | import { Loadable } from 'utils/components' 2 | import { LOGIN_PATH as path } from 'constants/paths' 3 | 4 | export default { 5 | path, 6 | component: Loadable({ 7 | loader: () => 8 | import(/* webpackChunkName: 'Login' */ './components/LoginPage') 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/NotFound/components/NotFoundPage/NotFoundPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from '@material-ui/core/Typography' 3 | import { makeStyles } from '@material-ui/core/styles' 4 | import styles from './NotFoundPage.styles' 5 | 6 | const useStyles = makeStyles(styles) 7 | 8 | function NotFoundPage() { 9 | const classes = useStyles() 10 | 11 | return ( 12 |
    13 | Whoops! 404! 14 |

    This page was not found.

    15 |
    16 | ) 17 | } 18 | 19 | export default NotFoundPage 20 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/NotFound/components/NotFoundPage/NotFoundPage.styles.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | root: { 3 | display: 'flex', 4 | flexDirection: 'column', 5 | alignItems: 'center' 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/NotFound/components/NotFoundPage/index.js: -------------------------------------------------------------------------------- 1 | import NotFoundPage from './NotFoundPage' 2 | 3 | export default NotFoundPage 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/NotFound/index.js: -------------------------------------------------------------------------------- 1 | import Loadable from 'react-loadable' 2 | import LoadingSpinner from 'components/LoadingSpinner' 3 | 4 | export default { 5 | component: Loadable({ 6 | loader: () => 7 | import(/* webpackChunkName: 'NotFound' */ './components/NotFoundPage'), 8 | loading: LoadingSpinner 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/components/NewProjectDialog/NewProjectDialog.enhancer.js: -------------------------------------------------------------------------------- 1 | import { reduxForm } from 'redux-form' 2 | import { NEW_PROJECT_FORM_NAME } from 'constants/formNames' 3 | 4 | export default reduxForm({ 5 | form: NEW_PROJECT_FORM_NAME, 6 | // Clear the form for future use (creating another project) 7 | onSubmitSuccess: (result, dispatch, props) => props.reset() 8 | }) 9 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/components/NewProjectDialog/NewProjectDialog.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | padding: theme.spacing(2) 4 | }, 5 | inputs: { 6 | ...theme.flexColumnCenter 7 | }, 8 | buttons: { 9 | ...theme.flexColumnCenter 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/components/NewProjectDialog/index.js: -------------------------------------------------------------------------------- 1 | import NewProjectDialog from './NewProjectDialog' 2 | import enhance from './NewProjectDialog.enhancer' 3 | 4 | export default enhance(NewProjectDialog) 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/components/NewProjectTile/NewProjectTile.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import ContentAddCircle from '@material-ui/icons/AddCircle' 4 | import Paper from '@material-ui/core/Paper' 5 | import { makeStyles } from '@material-ui/core/styles' 6 | import styles from './NewProjectTile.styles' 7 | 8 | const useStyles = makeStyles(styles) 9 | 10 | function NewProjectTile({ onClick }) { 11 | const classes = useStyles() 12 | 13 | return ( 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | NewProjectTile.propTypes = { 21 | onClick: PropTypes.func 22 | } 23 | 24 | export default NewProjectTile 25 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/components/NewProjectTile/NewProjectTile.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | ...theme.flexRowCenter, 4 | alignItems: 'center', 5 | cursor: 'pointer', 6 | height: '200px', 7 | width: '300px', 8 | margin: theme.spacing(0.5), 9 | padding: theme.spacing(1.3), 10 | overflow: 'hidden' 11 | }, 12 | newIcon: { 13 | width: '6rem', 14 | height: '6rem', 15 | transition: 'all 800ms cubic-bezier(0.25,0.1,0.25,1) 0ms', 16 | '&:hover': { 17 | color: '#757575' 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/components/NewProjectTile/index.js: -------------------------------------------------------------------------------- 1 | import NewProjectTile from './NewProjectTile' 2 | 3 | export default NewProjectTile 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/components/ProjectTile/ProjectTile.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | display: 'flex', 4 | flexDirection: 'column', 5 | alignItems: 'flex-start', 6 | height: '200px', 7 | width: '300px', 8 | margin: theme.spacing(0.5), 9 | padding: theme.spacing(1.3) 10 | }, 11 | top: { 12 | display: 'flex', 13 | justifyContent: 'space-between', 14 | width: '100%' 15 | }, 16 | name: { 17 | fontSize: '1.5rem', 18 | cursor: 'pointer', 19 | textDecoration: 'none', 20 | transition: 'all 800ms cubic-bezier(0.25,0.1,0.25,1) 0ms', 21 | textOverflow: 'ellipsis', 22 | overflow: 'hidden', 23 | whiteSpace: 'nowrap', 24 | '&:hover': { 25 | color: '' 26 | }, 27 | '&:visited': { 28 | textDecoration: 'none' 29 | } 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/components/ProjectTile/index.js: -------------------------------------------------------------------------------- 1 | import ProjectTile from './ProjectTile' 2 | 3 | export default ProjectTile 4 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/components/ProjectsPage/ProjectsPage.enhancer.js: -------------------------------------------------------------------------------- 1 | import { UserIsAuthenticated } from 'utils/router' 2 | 3 | // redirect to /login if user is not logged in 4 | export default UserIsAuthenticated 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/components/ProjectsPage/ProjectsPage.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | ...theme.flexColumnCenter, 4 | paddingTop: theme.spacing(4), 5 | flexGrow: '2', 6 | boxSizing: 'border-box', 7 | overflowY: 'scroll' 8 | }, 9 | tiles: { 10 | display: 'flex', 11 | justifyContent: 'center', 12 | flexWrap: 'wrap', 13 | '-webkit-flex-flow': 'row wrap' 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/components/ProjectsPage/index.js: -------------------------------------------------------------------------------- 1 | import ProjectsPage from './ProjectsPage' 2 | import enhance from './ProjectsPage.enhancer' 3 | 4 | export default enhance(ProjectsPage) 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/index.js: -------------------------------------------------------------------------------- 1 | import { LIST_PATH as path } from 'constants/paths' 2 | import { Loadable } from 'utils/components' 3 | 4 | export default { 5 | path, 6 | component: Loadable({ 7 | loader: () => 8 | import(/* webpackChunkName: 'Projects' */ './components/ProjectsPage') 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/routes/Project/components/ProjectPage/ProjectPage.enhancer.js: -------------------------------------------------------------------------------- 1 | import { compose } from 'redux' 2 | import { withRouter } from 'react-router-dom' 3 | import { setDisplayName } from 'recompose' 4 | import { UserIsAuthenticated } from 'utils/router' 5 | 6 | export default compose( 7 | // Set component display name (more clear in dev/error tools) 8 | setDisplayName('EnhancedProjectPage'), 9 | // Add props.match 10 | withRouter, 11 | // Redirect to /login if user is not logged in 12 | UserIsAuthenticated, 13 | ) 14 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/routes/Project/components/ProjectPage/ProjectPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Card from '@material-ui/core/Card' 3 | import CardContent from '@material-ui/core/CardContent' 4 | import Typography from '@material-ui/core/Typography' 5 | import { makeStyles } from '@material-ui/core/styles' 6 | import { useFirebaseConnect, isLoaded } from 'react-redux-firebase' 7 | import { useParams } from 'react-router-dom' 8 | import { useSelector } from 'react-redux' 9 | import LoadingSpinner from 'components/LoadingSpinner' 10 | import styles from './ProjectPage.styles' 11 | 12 | const useStyles = makeStyles(styles) 13 | 14 | function ProjectPage() { 15 | const { projectId } = useParams() 16 | const classes = useStyles() 17 | 18 | // Create listener for projects 19 | useFirebaseConnect(() => [{ path: `projects/${projectId}` }]) 20 | 21 | // Get projects from redux state 22 | const project = useSelector(({ firebase: { data } }) => { 23 | return data.projects && data.projects[projectId] 24 | }) 25 | 26 | // Show loading spinner while project is loading 27 | if (!isLoaded(project)) { 28 | return 29 | } 30 | 31 | return ( 32 |
    33 | 34 | 35 | 36 | {project.name || 'Project'} 37 | 38 | {projectId} 39 |
    40 |
    {JSON.stringify(project, null, 2)}
    41 |
    42 |
    43 |
    44 |
    45 | ) 46 | } 47 | 48 | export default ProjectPage 49 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/routes/Project/components/ProjectPage/ProjectPage.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | padding: theme.spacing(2) 4 | }, 5 | progress: { 6 | ...theme.flexRowCenter, 7 | alignItems: 'center', 8 | paddingTop: theme.spacing(8) 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/routes/Project/components/ProjectPage/index.js: -------------------------------------------------------------------------------- 1 | import ProjectPage from './ProjectPage' 2 | import enhance from './ProjectPage.enhancer' 3 | 4 | export default enhance(ProjectPage) 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Projects/routes/Project/index.js: -------------------------------------------------------------------------------- 1 | import { Loadable } from 'utils/components' 2 | 3 | export default { 4 | path: ':projectId', 5 | component: Loadable({ 6 | loader: () => 7 | import(/* webpackChunkName: 'Project' */ './components/ProjectPage') 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Signup/components/SignupForm/SignupForm.enhancer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { reduxForm } from 'redux-form' 3 | import { setPropTypes, compose } from 'recompose' 4 | import { SIGNUP_FORM_NAME } from 'constants/formNames' 5 | 6 | export default compose( 7 | // Set prop-types used in HOCs 8 | setPropTypes({ 9 | onSubmit: PropTypes.func.isRequired // called by handleSubmit 10 | }), 11 | // Add form capabilities (handleSubmit, pristine, submitting) 12 | reduxForm({ 13 | form: SIGNUP_FORM_NAME 14 | }) 15 | ) 16 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Signup/components/SignupForm/SignupForm.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | ...theme.flexColumnCenter, 4 | justifyContent: 'flex-start', 5 | flexGrow: 1, 6 | height: '100%', 7 | width: '100%', 8 | margin: '.2rem', 9 | fontSize: '1.4rem' 10 | }, 11 | submit: { 12 | ...theme.flexColumnCenter, 13 | justifyContent: 'center', 14 | flexGrow: 1, 15 | textAlign: 'center', 16 | padding: '1.25rem', 17 | minWidth: '192px', 18 | marginTop: '1.5rem' 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Signup/components/SignupForm/index.js: -------------------------------------------------------------------------------- 1 | import SignupForm from './SignupForm' 2 | import enhance from './SignupForm.enhancer' 3 | 4 | export default enhance(SignupForm) 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Signup/components/SignupPage/SignupPage.enhancer.js: -------------------------------------------------------------------------------- 1 | import { UserIsNotAuthenticated } from 'utils/router' 2 | 3 | // Redirect to list page if logged in 4 | export default UserIsNotAuthenticated 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Signup/components/SignupPage/SignupPage.styles.js: -------------------------------------------------------------------------------- 1 | export default theme => ({ 2 | root: { 3 | ...theme.flexColumnCenter, 4 | justifyContent: 'flex-start', 5 | height: '100%', 6 | width: '100%', 7 | fontWeight: 400, 8 | paddingTop: '1.5rem' 9 | }, 10 | panel: { 11 | ...theme.flexColumnCenter, 12 | justifyContent: 'center', 13 | flexGrow: 1, 14 | padding: '1.25rem', 15 | minWidth: '250px', 16 | minHeight: '270px' 17 | }, 18 | orLabel: { 19 | marginTop: '1rem', 20 | marginBottom: '.5rem' 21 | }, 22 | login: { 23 | ...theme.flexColumnCenter, 24 | justifyContent: 'center', 25 | marginTop: '1.5rem' 26 | }, 27 | loginLabel: { 28 | fontSize: '1rem', 29 | fontWeight: 'bold' 30 | }, 31 | loginLink: { 32 | fontSize: '1.2rem' 33 | }, 34 | providers: { 35 | marginTop: '1rem' 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Signup/components/SignupPage/index.js: -------------------------------------------------------------------------------- 1 | import SignupPage from './SignupPage' 2 | import enhance from './SignupPage.enhancer' 3 | 4 | export default enhance(SignupPage) 5 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/Signup/index.js: -------------------------------------------------------------------------------- 1 | import { Loadable } from 'utils/components' 2 | import { SIGNUP_PATH as path } from 'constants/paths' 3 | 4 | export default { 5 | path, 6 | component: Loadable({ 7 | loader: () => 8 | import(/* webpackChunkName: 'Signup' */ './components/SignupPage') 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /examples/complete/material/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Switch, Route } from 'react-router-dom' 3 | import CoreLayout from '../layouts/CoreLayout' 4 | import Home from './Home' 5 | import LoginRoute from './Login' 6 | import SignupRoute from './Signup' 7 | import ProjectsRoute from './Projects' 8 | import AccountRoute from './Account' 9 | import NotFoundRoute from './NotFound' 10 | 11 | export default function createRoutes(store) { 12 | return ( 13 | 14 | 15 | } /> 16 | {/* Build Route components from routeSettings */ 17 | [ 18 | AccountRoute, 19 | ProjectsRoute, 20 | SignupRoute, 21 | LoginRoute 22 | /* Add More Routes Here */ 23 | ].map((settings, index) => ( 24 | 25 | ))} 26 | 27 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /examples/complete/material/src/static/User.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/material/src/static/User.png -------------------------------------------------------------------------------- /examples/complete/material/src/store/createStore.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, compose, createStore } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import { getFirebase } from 'react-redux-firebase' 4 | import makeRootReducer from './reducers' 5 | 6 | export default (initialState = {}) => { 7 | // ====================================================== 8 | // Store Enhancers 9 | // ====================================================== 10 | const enhancers = [] 11 | 12 | if (window && window.location && window.location.hostname === 'localhost') { 13 | const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION__ 14 | if (typeof devToolsExtension === 'function') { 15 | enhancers.push(devToolsExtension()) 16 | } 17 | } 18 | 19 | // ====================================================== 20 | // Middleware Configuration 21 | // ====================================================== 22 | const middleware = [ 23 | thunk.withExtraArgument(getFirebase) 24 | // This is where you add other middleware like redux-observable 25 | ] 26 | 27 | // ====================================================== 28 | // Store Instantiation and HMR Setup 29 | // ====================================================== 30 | const store = createStore( 31 | makeRootReducer(), 32 | initialState, 33 | compose( 34 | applyMiddleware(...middleware), 35 | ...enhancers 36 | ) 37 | ) 38 | 39 | store.asyncReducers = {} 40 | 41 | if (module.hot) { 42 | module.hot.accept('./reducers', () => { 43 | const reducers = require('./reducers').default // eslint-disable-line global-require 44 | store.replaceReducer(reducers(store.asyncReducers)) 45 | }) 46 | } 47 | 48 | return store 49 | } 50 | -------------------------------------------------------------------------------- /examples/complete/material/src/store/location.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const LOCATION_CHANGE = 'LOCATION_CHANGE' 5 | 6 | // ------------------------------------ 7 | // Actions 8 | // ------------------------------------ 9 | export function locationChange(location = '/') { 10 | return { 11 | type: LOCATION_CHANGE, 12 | payload: location 13 | } 14 | } 15 | 16 | // ------------------------------------ 17 | // Specialized Action Creator 18 | // ------------------------------------ 19 | export function updateLocation({ dispatch }) { 20 | return nextLocation => dispatch(locationChange(nextLocation)) 21 | } 22 | 23 | // ------------------------------------ 24 | // Reducer 25 | // ------------------------------------ 26 | const initialState = null 27 | export default function locationReducer(state = initialState, action) { 28 | return action.type === LOCATION_CHANGE ? action.payload : state 29 | } 30 | -------------------------------------------------------------------------------- /examples/complete/material/src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { reducer as firebase } from 'react-redux-firebase' 3 | import { reducer as form } from 'redux-form' 4 | import { reducer as notifications } from 'modules/notification' 5 | import locationReducer from './location' 6 | 7 | export function makeRootReducer(asyncReducers) { 8 | return combineReducers({ 9 | // Add sync reducers here 10 | firebase, 11 | form, 12 | notifications, 13 | location: locationReducer, 14 | ...asyncReducers 15 | }) 16 | } 17 | 18 | export function injectReducer(store, { key, reducer }) { 19 | store.asyncReducers[key] = reducer // eslint-disable-line no-param-reassign 20 | store.replaceReducer(makeRootReducer(store.asyncReducers)) 21 | } 22 | 23 | export default makeRootReducer 24 | -------------------------------------------------------------------------------- /examples/complete/material/src/theme.js: -------------------------------------------------------------------------------- 1 | export default { 2 | palette: { 3 | primary: { 4 | main: '#2196f3' 5 | } 6 | }, 7 | // Enable typography v2: https://material-ui.com/style/typography/#migration-to-typography-v2 8 | typography: { 9 | useNextVariants: true 10 | }, 11 | flexColumnCenter: { 12 | display: 'flex', 13 | flexDirection: 'column', 14 | alignItems: 'center' 15 | }, 16 | flexRowCenter: { 17 | display: 'flex', 18 | flexDirection: 'row', 19 | justifyContent: 'center' 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/complete/material/src/utils/form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns error message if value does not exist, otherwise returns 3 | * undefined 4 | * @param {string} value - Email to validate 5 | * @returns {string|undefined} Required string if value is undefined 6 | * @example Required Field 7 | * 14 | */ 15 | export function required(value) { 16 | return value ? undefined : 'Required' 17 | } 18 | 19 | /** 20 | * Returns error message if value is not a valid email, otherwise returns 21 | * undefined 22 | * @param {string} value - Email to validate 23 | * @returns {string|undefined} Required string if value is undefined 24 | * @example Basic 25 | * 31 | */ 32 | export function validateEmail(value) { 33 | return value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) 34 | ? 'Invalid email address' 35 | : undefined 36 | } 37 | -------------------------------------------------------------------------------- /examples/complete/material/src/utils/hooks.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux' 2 | import { useDispatch } from 'react-redux' 3 | import { useMemo } from 'react' 4 | 5 | export function useActions(actions, deps) { 6 | const dispatch = useDispatch() 7 | return useMemo(() => { 8 | if (Array.isArray(actions)) { 9 | return actions.map(a => bindActionCreators(a, dispatch)) 10 | } 11 | return bindActionCreators(actions, dispatch) 12 | }, deps ? [dispatch, ...deps] : [dispatch]) 13 | } -------------------------------------------------------------------------------- /examples/complete/material/src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Log a message and return data passed. Useful for logging 3 | * messages within functional programming flows. 4 | * @param message - Message to log along with data. 5 | * @example Basic 6 | * import { flow, map as fpMap } from 'lodash' 7 | * const original = [] 8 | * flow( 9 | * fpLog('Before Map'), 10 | * fpMap('name') 11 | * fpLog('After Map'), 12 | * )(original) 13 | * // => 'Before Map' [{ name: 'test' }] 14 | * // => 'After Map' ['test'] 15 | */ 16 | export function fpLog(message) { 17 | return existing => { 18 | console.log(message, existing) // eslint-disable-line no-console 19 | return existing 20 | } 21 | } 22 | 23 | /** 24 | * Initialize global scripts including analytics and error handling 25 | */ 26 | export function initScripts() { 27 | // Initialize global scripts here 28 | } 29 | -------------------------------------------------------------------------------- /examples/complete/material/storage.rules: -------------------------------------------------------------------------------- 1 | // Only authenticated users can read or write to the bucket 2 | service firebase.storage { 3 | match /b/{bucket}/o { 4 | match /{allPaths=**} { 5 | allow read, write: if request.auth != null; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "babel-preset-react-native-stage-0/decorator-support" 4 | ], 5 | "env": { 6 | "development": { 7 | "plugins": [ 8 | "transform-react-jsx-source" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | npm-debug.* 4 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | 4 | export default class App extends React.Component { 5 | render() { 6 | return ( 7 | 8 | Open up App.js to start working on your app! 9 | Changes you make will automatically reload. 10 | Shake your phone to open the developer menu. 11 | 12 | ); 13 | } 14 | } 15 | 16 | const styles = StyleSheet.create({ 17 | container: { 18 | flex: 1, 19 | backgroundColor: '#fff', 20 | alignItems: 'center', 21 | justifyContent: 'center', 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './App'; 3 | 4 | import renderer from 'react-test-renderer'; 5 | 6 | it('renders without crashing', () => { 7 | const rendered = renderer.create().toJSON(); 8 | expect(rendered).toBeTruthy(); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/README.md: -------------------------------------------------------------------------------- 1 | # React Native Firebase Example 2 | 3 | > Simple example of using native modules with react-redux-firebase by using react-native-firebase 4 | 5 | This project was bootstrapped with [Create React Native App](https://github.com/react-community/create-react-native-app). 6 | 7 | 8 | ## Get Started 9 | 10 | 1. `yarn install` 11 | 1. `react-native run-ios` 12 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "sdkVersion": "20.0.0" 4 | }, 5 | "name": "reactnativefirebase", 6 | "displayName": "RNF Example" 7 | } -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/index.android.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './src'; 3 | AppRegistry.registerComponent('reactnativefirebase', () => App); 4 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/index.ios.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './src'; 3 | AppRegistry.registerComponent('reactnativefirebase', () => App); 4 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/ios/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AD_UNIT_ID_FOR_BANNER_TEST 6 | ca-app-pub-3940256099942544/2934735716 7 | AD_UNIT_ID_FOR_INTERSTITIAL_TEST 8 | ca-app-pub-3940256099942544/4411468910 9 | CLIENT_ID 10 | 823357791673-bup2vc9puame4ufum2s6b9qu5pr1ui1m.apps.googleusercontent.com 11 | REVERSED_CLIENT_ID 12 | com.googleusercontent.apps.823357791673-bup2vc9puame4ufum2s6b9qu5pr1ui1m 13 | API_KEY 14 | AIzaSyCDBrha6FJ5KER3j7bE3SXSfkV6cezOoMk 15 | GCM_SENDER_ID 16 | 823357791673 17 | PLIST_VERSION 18 | 1 19 | BUNDLE_ID 20 | com.react-redux-firebase.example 21 | PROJECT_ID 22 | redux-firebasev3 23 | STORAGE_BUCKET 24 | redux-firebasev3.appspot.com 25 | IS_ADS_ENABLED 26 | 27 | IS_ANALYTICS_ENABLED 28 | 29 | IS_APPINVITE_ENABLED 30 | 31 | IS_GCM_ENABLED 32 | 33 | IS_SIGNIN_ENABLED 34 | 35 | GOOGLE_APP_ID 36 | 1:823357791673:ios:ba3aefbcea3dadaa 37 | DATABASE_URL 38 | https://redux-firebasev3.firebaseio.com 39 | 40 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '10.0' 3 | 4 | target 'reactnativefirebase' do 5 | # Uncomment the next line if you're using Swift or would like to use dynamic frameworks 6 | # use_frameworks! 7 | 8 | # Pods for reactnativefirebase 9 | # Required by RNFirebase 10 | pod 'Firebase/Core' 11 | pod 'RNFirebase', :path => '../node_modules/react-native-firebase' 12 | 13 | # [OPTIONAL PODS] - comment out pods for firebase products you won't be using. 14 | pod 'Firebase/AdMob' 15 | pod 'Firebase/Analytics' 16 | pod 'Firebase/Auth' 17 | pod 'Firebase/Crash' 18 | pod 'Firebase/Database' 19 | pod 'Firebase/DynamicLinks' 20 | pod 'Firebase/Messaging' 21 | pod 'Firebase/RemoteConfig' 22 | pod 'Firebase/Storage' 23 | pod "Yoga", :path => "../node_modules/react-native/ReactCommon/yoga" 24 | pod 'React', :path => '../node_modules/react-native', :subspecs => [ 25 | 'BatchedBridge', # Required For React Native 0.45.0+ 26 | 'Core', 27 | # Add any other subspecs you want to use in your project 28 | ] 29 | end 30 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/ios/reactnativefirebase.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/ios/reactnativefirebase/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/ios/reactnativefirebase/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | 12 | #import 13 | #import 14 | #import 15 | 16 | @implementation AppDelegate 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 19 | { 20 | NSURL *jsCodeLocation; 21 | 22 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 23 | 24 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 25 | moduleName:@"reactnativefirebase" 26 | initialProperties:nil 27 | launchOptions:launchOptions]; 28 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 29 | 30 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 31 | UIViewController *rootViewController = [UIViewController new]; 32 | rootViewController.view = rootView; 33 | self.window.rootViewController = rootViewController; 34 | [self.window makeKeyAndVisible]; 35 | [FIRApp configure]; 36 | return YES; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/ios/reactnativefirebase/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/ios/reactnativefirebase/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/ios/reactnativefirebaseTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-firebase-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "babel-preset-react-native-stage-0": "^1.0.1", 7 | "jest-expo": "~20.0.0", 8 | "react-test-renderer": "16.0.0-alpha.12" 9 | }, 10 | "scripts": { 11 | "start": "react-native start", 12 | "android": "react-native run-android", 13 | "ios": "react-native run-ios", 14 | "test": "node node_modules/jest/bin/jest.js --watch" 15 | }, 16 | "jest": { 17 | "preset": "jest-expo" 18 | }, 19 | "dependencies": { 20 | "react": "16.0.0-alpha.12", 21 | "react-native": "^0.47.0", 22 | "react-native-firebase": "^2.2.3", 23 | "react-redux": "^5.0.6", 24 | "react-redux-firebase": "next", 25 | "recompose": "^0.26.0", 26 | "redux": "^3.7.2", 27 | "redux-thunk": "^2.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/src/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { compose } from 'redux' 3 | import { connect } from 'react-redux' 4 | import { isLoaded, isEmpty, firebaseConnect } from 'react-redux-firebase' 5 | import { 6 | View, 7 | Text, 8 | StyleSheet, 9 | ActivityIndicator, 10 | TouchableOpacity 11 | } from 'react-native' 12 | import NewTodo from './NewTodo' 13 | import TodosList from './TodosList' 14 | 15 | const enhnace = compose( 16 | connect(({ firebase: { auth } }) => ({ 17 | uid: auth.uid 18 | })), 19 | withStateHandlers(({ initialText = '' }) => ({ newTodoText: initialText }), { 20 | onEmailChange: () => email => ({ email }), 21 | onPasswordChange: () => password => ({ password }), 22 | onNewTodoChange: () => newTodoText => ({ newTodoText }) 23 | }), 24 | withHandlers({ 25 | addTodo: props => () => { 26 | const newTodo = { text: newTodoText || 'Sample Text', owner: props.uid } 27 | return props.firebase.push('todos', newTodo) 28 | } 29 | }) 30 | ) 31 | 32 | const Home = ({ todos, addTodo, onNewTodoChange, newTodoText }) => ( 33 | 34 | Todos 35 | 40 | 41 | 42 | ) 43 | 44 | export default enhance(Home) 45 | 46 | const styles = StyleSheet.create({ 47 | container: { 48 | marginTop: 100, 49 | paddingLeft: 15, 50 | paddingRight: 15 51 | }, 52 | header: { 53 | alignSelf: 'center', 54 | marginBottom: 50 55 | } 56 | }) 57 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/src/NewTodo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | View, 4 | Text, 5 | StyleSheet, 6 | TextInput, 7 | TouchableOpacity 8 | } from 'react-native'; 9 | 10 | const NewTodo = ({ newValue, onInputChange, onNewTouch }) => ( 11 | 12 | 17 | 18 | Add Todo 19 | 20 | 21 | ) 22 | 23 | export default NewTodo; 24 | 25 | const styles = StyleSheet.create({ 26 | button: { 27 | height: 50, 28 | marginBottom: 40, 29 | padding: 10, 30 | borderWidth: 1, 31 | borderColor: 'black', 32 | backgroundColor: '#cccccc', 33 | alignItems: 'center', 34 | justifyContent: 'center', 35 | alignSelf: 'center', 36 | }, 37 | input: { 38 | height: 40, 39 | backgroundColor: '#fafafa', 40 | borderColor: 'grey', 41 | borderBottomWidth: 1, 42 | paddingLeft: 15, 43 | paddingRight: 15 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/src/createStore.js: -------------------------------------------------------------------------------- 1 | import { compose, createStore } from 'redux' 2 | import { reactReduxFirebase } from 'react-redux-firebase' 3 | import RNFirebase from 'react-native-firebase' 4 | import makeRootReducer from './reducers' 5 | 6 | const reactNativeFirebaseConfig = { 7 | debug: true 8 | } 9 | // for more config options, visit http://docs.react-redux-firebase.com/history/v2.0.0/docs/api/compose.html 10 | const reduxFirebaseConfig = { 11 | userProfile: 'users' // save users profiles to 'users' collection 12 | } 13 | 14 | export default (initialState = { firebase: {} }) => { 15 | // initialize firebase 16 | const firebase = RNFirebase.initializeApp(reactNativeFirebaseConfig) 17 | 18 | const store = createStore( 19 | makeRootReducer(), 20 | initialState, // initial state 21 | compose( 22 | reactReduxFirebase(firebase, reduxFirebaseConfig) // pass initialized react-native-firebase app instance 23 | ) 24 | ) 25 | return store 26 | } 27 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import createStore from './createStore'; 4 | import Home from './Home'; 5 | 6 | // Store Initialization 7 | const initialState = { firebase: {} }; 8 | const store = createStore(initialState); 9 | 10 | const Main = () => ( 11 | 12 | 13 | 14 | ); 15 | 16 | export default Main; 17 | -------------------------------------------------------------------------------- /examples/complete/react-native-firebase/src/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { firebaseStateReducer } from 'react-redux-firebase' 3 | 4 | export const makeRootReducer = (asyncReducers) => { 5 | return combineReducers({ 6 | // Add sync reducers here 7 | firebase: firebaseStateReducer, 8 | ...asyncReducers 9 | }) 10 | } 11 | 12 | export default makeRootReducer 13 | 14 | // Useful for injecting reducers as part of async routes 15 | export const injectReducer = (store, { key, reducer }) => { 16 | store.asyncReducers[key] = reducer 17 | store.replaceReducer(makeRootReducer(store.asyncReducers)) 18 | } 19 | -------------------------------------------------------------------------------- /examples/complete/react-native/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | __DEV__: false 4 | } 5 | } -------------------------------------------------------------------------------- /examples/complete/react-native/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p12 6 | *.key 7 | *.mobileprovision 8 | -------------------------------------------------------------------------------- /examples/complete/react-native/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/complete/react-native/__tests__/App-test.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import App from '../App'; 4 | import renderer from 'react-test-renderer'; 5 | import NavigationTestUtils from 'react-navigation/NavigationTestUtils'; 6 | 7 | describe('App snapshot', () => { 8 | jest.useFakeTimers(); 9 | beforeEach(() => { 10 | NavigationTestUtils.resetInternalState(); 11 | }); 12 | 13 | it('renders the loading screen', async () => { 14 | const tree = renderer.create().toJSON(); 15 | expect(tree).toMatchSnapshot(); 16 | }); 17 | 18 | it('renders the root without loading screen', async () => { 19 | const tree = renderer.create().toJSON(); 20 | expect(tree).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /examples/complete/react-native/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "react-redux-firebase example", 4 | "slug": "react-redux-firebase-example", 5 | "privacy": "public", 6 | "sdkVersion": "32.0.0", 7 | "platforms": [ 8 | "ios", 9 | "android" 10 | ], 11 | "version": "1.0.0", 12 | "orientation": "portrait", 13 | "icon": "./assets/images/icon.png", 14 | "splash": { 15 | "image": "./assets/images/splash.png", 16 | "resizeMode": "contain", 17 | "backgroundColor": "#ffffff" 18 | }, 19 | "updates": { 20 | "fallbackToCacheTimeout": 0 21 | }, 22 | "assetBundlePatterns": [ 23 | "**/*" 24 | ], 25 | "ios": { 26 | "supportsTablet": true 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /examples/complete/react-native/assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/react-native/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /examples/complete/react-native/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/react-native/assets/images/icon.png -------------------------------------------------------------------------------- /examples/complete/react-native/assets/images/robot-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/react-native/assets/images/robot-dev.png -------------------------------------------------------------------------------- /examples/complete/react-native/assets/images/robot-prod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/react-native/assets/images/robot-prod.png -------------------------------------------------------------------------------- /examples/complete/react-native/assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/react-native/assets/images/splash.png -------------------------------------------------------------------------------- /examples/complete/react-native/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /examples/complete/react-native/components/StyledText.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text } from 'react-native'; 3 | 4 | export class MonoText extends React.Component { 5 | render() { 6 | return ; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/complete/react-native/components/TabBarIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from 'expo'; 3 | 4 | import Colors from '../constants/Colors'; 5 | 6 | export default class TabBarIcon extends React.Component { 7 | render() { 8 | return ( 9 | 15 | ); 16 | } 17 | } -------------------------------------------------------------------------------- /examples/complete/react-native/components/Todo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { StyleSheet, Text, TouchableOpacity, Button } from 'react-native' 4 | import { compose, withHandlers } from 'recompose' 5 | import { withFirebase } from 'react-redux-firebase' 6 | import { connect } from 'react-redux' 7 | 8 | const enhance = compose( 9 | connect(({ firebase }, { todoKey }) => { 10 | const { text, done } = 11 | (firebase.data.todos && firebase.data.todos[todoKey]) || {} 12 | return { 13 | done, 14 | text 15 | } 16 | }), 17 | withFirebase, 18 | withHandlers({ 19 | toggleDone: props => () => 20 | props.firebase.update(`todos/${props.todoKey}`, { done: !props.done }) 21 | }) 22 | ) 23 | 24 | function Todo({ done, text, toggleDone }) { 25 | return ( 26 | 27 | 23 | 24 |
    25 | ) 26 | } 27 | 28 | export default NewTodo 29 | -------------------------------------------------------------------------------- /examples/complete/simple/src/Todo.css: -------------------------------------------------------------------------------- 1 | .Todo { 2 | margin: 1rem; 3 | 4 | } 5 | .Todo-Input { 6 | margin: 1rem; 7 | } 8 | 9 | .Todo-Button { 10 | margin: 1rem; 11 | } 12 | -------------------------------------------------------------------------------- /examples/complete/simple/src/TodoItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { useSelector } from 'react-redux' 4 | import { useFirebase } from 'react-redux-firebase' 5 | import './Todo.css' 6 | 7 | function TodoItem({ id }) { 8 | const todo = useSelector(state => state.firebase.data.todos[id]) 9 | const firebase = useFirebase() 10 | 11 | function toggleDone() { 12 | firebase.update(`todos/${id}`, { done: !todo.done }) 13 | } 14 | function deleteTodo() { 15 | return firebase.remove(`todos/${id}`) 16 | } 17 | 18 | return ( 19 |
  • 20 | 26 | {todo.text || todo.name} 27 | 30 |
  • 31 | ) 32 | } 33 | 34 | TodoItem.propTypes = { 35 | id: PropTypes.string.isRequired 36 | } 37 | 38 | export default TodoItem 39 | -------------------------------------------------------------------------------- /examples/complete/simple/src/Todos.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux' 3 | import { useFirebaseConnect, isLoaded, isEmpty } from 'react-redux-firebase' 4 | import TodoItem from './TodoItem' 5 | 6 | const todosQuery = { 7 | path: 'todos', 8 | queryParams: ['limitToLast=10'] 9 | } 10 | 11 | function Todos() { 12 | // Attach todos listener 13 | useFirebaseConnect(() => [ 14 | todosQuery 15 | ]) 16 | 17 | // Get todos from redux state 18 | const todos = useSelector(state => state.firebase.ordered.todos) 19 | 20 | // Show a message while todos are loading 21 | if (!isLoaded(todos)) { 22 | return 'Loading' 23 | } 24 | 25 | // Show a message if there are no todos 26 | if (isEmpty(todos)) { 27 | return 'Todo list is empty' 28 | } 29 | 30 | return todos.reverse().map(({ value: todo, key }, ind) => ( 31 | 36 | )) 37 | } 38 | 39 | export default Todos 40 | -------------------------------------------------------------------------------- /examples/complete/simple/src/config.js: -------------------------------------------------------------------------------- 1 | export const firebase = { 2 | apiKey: 'AIzaSyCTUERDM-Pchn_UDTsfhVPiwM4TtNIxots', 3 | authDomain: 'redux-firebasev3.firebaseapp.com', 4 | databaseURL: 'https://redux-firebasev3.firebaseio.com', 5 | storageBucket: 'redux-firebasev3.appspot.com', 6 | messagingSenderId: '823357791673', 7 | projectId: 'redux-firebasev3' 8 | } 9 | 10 | export const reduxFirebase = { 11 | userProfile: 'users', 12 | useFirestoreForProfile: true 13 | } 14 | 15 | export default { firebase, reduxFirebase } 16 | -------------------------------------------------------------------------------- /examples/complete/simple/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/simple/src/favicon.ico -------------------------------------------------------------------------------- /examples/complete/simple/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /examples/complete/simple/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') 9 | ) 10 | -------------------------------------------------------------------------------- /examples/complete/simple/src/initFirebase.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app' 2 | import 'firebase/auth' 3 | import 'firebase/database' 4 | import 'firebase/firestore' // make sure you add this for firestore 5 | import { firebase as fbConfig } from './config' 6 | 7 | let firebaseInstance 8 | 9 | export default function initFirebase(initialState, history) { 10 | if (firebaseInstance) { 11 | return firebaseInstance 12 | } 13 | 14 | // Initialize firebase instance if it doesn't already exist 15 | if (!firebaseInstance) { 16 | const shouldUseEmulator = process.env.REACT_APP_USE_DB_EMULATORS 17 | 18 | if (shouldUseEmulator) { // or window.location.hostname === 'localhost' if you want 19 | console.log('Using RTDB emulator') 20 | fbConfig.databaseURL = `http://localhost:9000?ns=${fbConfig.projectId}` 21 | } 22 | 23 | // Initialize Firebase instance 24 | firebase.initializeApp(fbConfig) 25 | 26 | if (shouldUseEmulator) { // or window.location.hostname === 'localhost' if you want 27 | console.log('Using Firestore emulator') 28 | firebase.firestore().settings({ 29 | host: 'localhost:8080', 30 | ssl: false 31 | }) 32 | } 33 | firebaseInstance = firebase 34 | } 35 | 36 | 37 | return firebaseInstance 38 | } 39 | -------------------------------------------------------------------------------- /examples/complete/simple/src/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { reducer as firebase } from 'react-redux-firebase' 3 | // import { reducer as firestore } from 'react-redux-firebase' 4 | 5 | const rootReducer = combineReducers({ 6 | firebase, 7 | // firestore // add this for firestore 8 | }) 9 | 10 | export default rootReducer 11 | -------------------------------------------------------------------------------- /examples/complete/simple/src/store.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, createStore, compose } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import rootReducer from './reducer' 4 | import { getFirebase } from 'react-redux-firebase' 5 | 6 | export default function configureStore (initialState, history) { 7 | const middleware = [ 8 | thunk.withExtraArgument({ getFirebase }) 9 | ] 10 | const createStoreWithMiddleware = compose( 11 | applyMiddleware(...middleware), 12 | typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? () => window.__REDUX_DEVTOOLS_EXTENSION__ : f => f 13 | )(createStore) 14 | const store = createStoreWithMiddleware(rootReducer) 15 | 16 | if (module.hot) { 17 | // Enable Webpack hot module replacement for reducers 18 | module.hot.accept('./reducer', () => { 19 | const nextRootReducer = require('./reducer') 20 | store.replaceReducer(nextRootReducer) 21 | }) 22 | } 23 | 24 | return store 25 | } 26 | -------------------------------------------------------------------------------- /examples/complete/typescript/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/complete/typescript/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | 'extends': [ 4 | 'airbnb-base', 5 | 'prettier', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'prettier/@typescript-eslint', 8 | 'react-app', 9 | 'prettier', 10 | 'prettier/react' 11 | ], 12 | root: true, 13 | plugins: ['@typescript-eslint', 'import', 'babel', 'react', 'react-hooks', 'prettier' ], 14 | settings: { 15 | 'import/resolver': { 16 | node: { 17 | moduleDirectory: ['node_modules', '/'], 18 | extensions: ['.js', '.jsx', '.ts', '.tsx'] 19 | } 20 | }, 21 | react: { 22 | version: '16.0' 23 | } 24 | }, 25 | env: { 26 | browser: true, 27 | node: true 28 | }, 29 | rules: { 30 | '@typescript-eslint/no-explicit-any': 0, 31 | 'import/prefer-default-export': 0, 32 | 'no-shadow': 0, 33 | 'consistent-return': 0, 34 | 'no-new': 0, 35 | 'new-cap': 0, 36 | 'no-return-await': 2, 37 | 'import/extensions': 0, 38 | 'react-hooks/rules-of-hooks': 'error', 39 | 'react-hooks/exhaustive-deps': 'warn', 40 | 'prettier/prettier': [ 41 | 'error', 42 | { 43 | singleQuote: true, 44 | trailingComma: 'none', 45 | semi: false, 46 | bracketSpacing: true, 47 | jsxBracketSameLine: true, 48 | printWidth: 80, 49 | tabWidth: 2, 50 | useTabs: false 51 | } 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/complete/typescript/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/complete/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-firebase-typescript-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "react-scripts start", 7 | "build": "react-scripts build", 8 | "test": "react-scripts test", 9 | "eject": "react-scripts eject", 10 | "lint": "eslint 'src/**/*.ts'", 11 | "lint:fix": "npm run lint -- --fix" 12 | }, 13 | "dependencies": { 14 | "react": "^16.10.0", 15 | "react-dom": "^16.10.0", 16 | "react-redux": "^7.1.0", 17 | "react-redux-firebase": "next", 18 | "redux": "^4.0.4" 19 | }, 20 | "devDependencies": { 21 | "@types/jest": "25.1.2", 22 | "@types/node": "13.7.0", 23 | "@types/react": "16.9.19", 24 | "@types/react-dom": "16.9.5", 25 | "@types/react-redux": "^7.1.7", 26 | "@typescript-eslint/eslint-plugin": "^2.19.0", 27 | "@typescript-eslint/parser": "^2.19.0", 28 | "eslint": "^6.8.0", 29 | "eslint-config-airbnb": "^18.0.1", 30 | "eslint-config-prettier": "^6.10.0", 31 | "eslint-plugin-babel": "^5.3.0", 32 | "eslint-plugin-import": "^2.20.1", 33 | "eslint-plugin-jsx-a11y": "^6.2.3", 34 | "eslint-plugin-prettier": "^3.1.2", 35 | "eslint-plugin-react": "^7.18.3", 36 | "eslint-plugin-react-hooks": "^2.3.0", 37 | "prettier": "^1.19.1", 38 | "react-scripts": "3.3.1", 39 | "typescript": "3.7.5" 40 | }, 41 | "eslintConfig": { 42 | "extends": "react-app" 43 | }, 44 | "browserslist": [] 45 | } 46 | -------------------------------------------------------------------------------- /examples/complete/typescript/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prescottprue/react-redux-firebase/befe3b962b449227d618c014956cfeefc6fe8044/examples/complete/typescript/public/favicon.ico -------------------------------------------------------------------------------- /examples/complete/typescript/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 |
    30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/complete/typescript/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/AddTodo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ExtendedFirebaseInstance, useFirebase } from "react-redux-firebase"; 3 | 4 | function AddTodo() { 5 | const firebase: ExtendedFirebaseInstance = useFirebase(); 6 | 7 | function handleAddClick() { 8 | firebase.push("public_todos", { done: false, text: "Example todo" }); 9 | } 10 | return ( 11 |
    12 | 13 |
    14 | ); 15 | } 16 | 17 | export default AddTodo; 18 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import firebase from "firebase/app"; 3 | import "firebase/auth"; 4 | import "firebase/database"; 5 | import "firebase/firestore"; // make sure you add this for firestore 6 | import { Provider } from "react-redux"; 7 | import { ReactReduxFirebaseProvider } from "react-redux-firebase"; 8 | import { firebase as fbConfig, reduxFirebase as rfConfig } from "./config"; 9 | import Home from "./Home"; 10 | import configureStore from "./store"; 11 | 12 | const store = configureStore(); 13 | // Initialize Firebase instance 14 | firebase.initializeApp(fbConfig); 15 | 16 | export default () => ( 17 | 18 | 22 | 23 | 24 | 25 | ); 26 | 27 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Todos from "./Todos"; 3 | import AddTodo from "./AddTodo"; 4 | import "./App.css"; 5 | 6 | function Home() { 7 | return ( 8 |
    9 |
    10 |

    11 | Edit src/App.tsx and save to reload. 12 |

    13 |
    14 | 15 | 16 |
    17 |
    18 |
    19 | ); 20 | } 21 | 22 | export default Home; 23 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/Todo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { AppState, TodoValue } from './reducer' 3 | import { useSelector } from "react-redux"; 4 | import { useFirebase } from 'react-redux-firebase' 5 | 6 | interface TodoProps { 7 | todoId: string 8 | } 9 | 10 | function Todo({ todoId }: TodoProps) { 11 | const todo: TodoValue = useSelector((state: AppState) => { 12 | return state.firebase.data.todos && state.firebase.data.todos[todoId] 13 | }) 14 | const firebase = useFirebase() 15 | function toggleDoneState() { 16 | firebase.update(`todos/${todoId}`, { done: !todo.done }) 17 | } 18 | 19 | return ( 20 |
    21 | 22 | {todo.text} 23 |
    24 | ); 25 | } 26 | 27 | export default Todo; 28 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/Todos.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { isLoaded, isEmpty, useFirebaseConnect } from "react-redux-firebase"; 3 | import { AppState } from './reducer' 4 | import Todo from './Todo' 5 | import { useSelector } from "react-redux"; 6 | 7 | function Todos() { 8 | useFirebaseConnect([{ path: 'public_todos', queryParams: ['limitToLast=10'], storeAs: 'todos' }]) 9 | const todos = useSelector((state: AppState) => { 10 | return state.firebase.ordered.todos 11 | }) 12 | 13 | if (!isLoaded(todos)) { 14 | return ( 15 |
    16 | Loading... 17 |
    18 | ); 19 | } 20 | 21 | if (isEmpty(todos)) { 22 | return ( 23 |
    24 | No Todos Found 25 |
    26 | ); 27 | } 28 | 29 | return ( 30 |
    31 | { 32 | todos && todos.map((todoItem) => { 33 | return 34 | }) 35 | } 36 |
    37 | ); 38 | } 39 | 40 | export default Todos; 41 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/config.ts: -------------------------------------------------------------------------------- 1 | export const firebase = { 2 | apiKey: 'AIzaSyCTUERDM-Pchn_UDTsfhVPiwM4TtNIxots', 3 | authDomain: 'redux-firebasev3.firebaseapp.com', 4 | databaseURL: 'https://redux-firebasev3.firebaseio.com', 5 | storageBucket: 'redux-firebasev3.appspot.com', 6 | messagingSenderId: '823357791673', 7 | projectId: 'redux-firebasev3' 8 | } 9 | 10 | export const reduxFirebase = { 11 | userProfile: 'users', 12 | useFirestoreForProfile: true 13 | } 14 | 15 | export default { firebase, reduxFirebase } 16 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | // / 2 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/reducer.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { firebaseReducer, FirebaseReducer } from 'react-redux-firebase' 3 | import { firestoreReducer, FirestoreReducer } from 'redux-firestore' 4 | 5 | interface UserProfile { 6 | email: string 7 | } 8 | 9 | export interface TodoValue { 10 | text: string 11 | done: boolean 12 | } 13 | 14 | // create schema for the DB 15 | interface DBSchema { 16 | todos: TodoValue 17 | [name: string]: any 18 | } 19 | interface RootState { 20 | firebase: FirebaseReducer.Reducer 21 | firestore: FirestoreReducer.Reducer; 22 | } 23 | 24 | const rootReducer = combineReducers({ 25 | firebase: firebaseReducer, 26 | firestore: firestoreReducer 27 | }) 28 | 29 | export type AppState = ReturnType 30 | 31 | export default rootReducer 32 | -------------------------------------------------------------------------------- /examples/complete/typescript/src/store.ts: -------------------------------------------------------------------------------- 1 | import { compose, createStore } from 'redux' 2 | import rootReducer from './reducer' 3 | 4 | export default function configureStore(): any { 5 | const createStoreWithMiddleware = compose( 6 | typeof window === 'object' && 7 | typeof (window as any).devToolsExtension !== 'undefined' 8 | ? (): any => (window as any).__REDUX_DEVTOOLS_EXTENSION__ // eslint-disable-line no-underscore-dangle 9 | : (f: any): any => f 10 | )(createStore) 11 | 12 | const store = createStoreWithMiddleware(rootReducer) 13 | 14 | if ((module as any).hot) { 15 | // Enable Webpack hot module replacement for reducers 16 | ;(module as any).hot.accept('./reducer', () => { 17 | const nextRootReducer = require('./reducer') // eslint-disable-line global-require, @typescript-eslint/no-var-requires 18 | store.replaceReducer(nextRootReducer) 19 | }) 20 | } 21 | 22 | return store 23 | } 24 | -------------------------------------------------------------------------------- /examples/complete/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "preserve" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /examples/complete/typescript/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "interface-name": false 9 | }, 10 | "rulesDirectory": [] 11 | } -------------------------------------------------------------------------------- /examples/snippets/decorators/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { firebaseConnect, isLoaded, isEmpty } from 'react-redux-firebase' 5 | import TodoItem from './TodoItem' 6 | 7 | @firebaseConnect([ 8 | 'todos' 9 | ]) 10 | @connect( 11 | ({ firebase: { ordered } }) => ({ 12 | todos: ordered.todos 13 | }) 14 | ) 15 | export default class App extends Component { 16 | static propTypes = { 17 | todos: PropTypes.array 18 | } 19 | 20 | render () { 21 | const { firebase, todos } = this.props 22 | 23 | const todosList = (!isLoaded(todos)) 24 | ? 'Loading' 25 | : (isEmpty(todos)) 26 | ? 'Todo list is empty' 27 | : todos.map((todo, id) => ( 28 | 29 | )) 30 | return ( 31 |
    32 |
    33 |

    react-redux-firebase decorators demo

    34 |
    35 |
    36 |

    Todos List

    37 | {todosList} 38 |
    39 |
    40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/snippets/decorators/README.md: -------------------------------------------------------------------------------- 1 | # Decorators Example 2 | 3 | Snippet that shows usage of decorators. Similar to the [simple example](https://github.com/prescottprue/react-redux-firebase/tree/master/examples/simple), this example shows syncing a list of todo items. 4 | 5 | ## Note 6 | 7 | This example is not a full application. For a full application please view the examples in the [Complete Folder](https://github.com/prescottprue/react-redux-firebase/tree/master/examples/complete) including the [material example](https://github.com/prescottprue/react-redux-firebase/tree/master/examples/complete/material). 8 | -------------------------------------------------------------------------------- /examples/snippets/decorators/TodoItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { withFirebase } from 'react-redux-firebase' 4 | 5 | import './Todo.css' 6 | 7 | @withFirebase // pass down props.firebase (firebaseConnect() can also be used) 8 | export default class TodoItem extends Component { 9 | static propTypes = { 10 | todo: PropTypes.object, 11 | id: PropTypes.string 12 | } 13 | 14 | toggleDone = () => firebase.set(`/todos/${id}/done`, !todo.done) 15 | 16 | delete = (event) => firebase.remove(`/todos/${id}`) 17 | 18 | render(){ 19 | const { todo, id } = this.props 20 | 21 | return ( 22 |
  • 23 | 29 | {todo.text} 30 | 33 |
  • 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/snippets/multipleQueries/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { map } from 'lodash' 4 | import { connect } from 'react-redux' 5 | import { compose } from 'redux' 6 | import { firebaseConnect, isLoaded, isEmpty } from 'react-redux-firebase' 7 | import TodoItem from './TodoItem' 8 | 9 | function renderList(list) { 10 | return !isLoaded(list) 11 | ? 'Loading' 12 | : isEmpty(list) 13 | ? 'Todo list is empty' 14 | : map(list, (todo, id) => ) 15 | } 16 | 17 | function Home({ incompleteTodos, completeTodos }) { 18 | return ( 19 |
    20 |
    21 |

    react-redux-firebase multiple queries demo

    22 |
    23 |
    24 |

    Incomplete Todos

    25 | {renderList(incompleteTodos)} 26 |

    Complete Todos

    27 | {renderList(completeTodos)} 28 |
    29 |
    30 | ) 31 | } 32 | 33 | Home.propTypes = { 34 | incompleteTodos: PropTypes.object, 35 | completeTodos: PropTypes.object 36 | } 37 | 38 | const enhance = compose( 39 | firebaseConnect([ 40 | { 41 | path: 'todos', 42 | storeAs: 'incompleteTodos', 43 | queryParams: ['orderByChild=done', 'equalTo=true'] 44 | }, 45 | { 46 | path: 'todos', 47 | storeAs: 'completeTodos', 48 | queryParams: ['orderByChild=done', 'equalTo=false'] 49 | } 50 | ]), 51 | connect(({ firebase: { data } }) => ({ 52 | incompleteTodos: data['incompleteTodos'], // path matches storeAs 53 | completeTodos: data['completeTodos'] // path matches storeAs 54 | })) 55 | ) 56 | 57 | export default enhance(Home) 58 | -------------------------------------------------------------------------------- /examples/snippets/multipleQueries/README.md: -------------------------------------------------------------------------------- 1 | # Multiple Queries Example 2 | 3 | Snippet that shows usage of `storeAs` to make multiple queries to the same location on Firebase. Similar to the [simple example](https://github.com/prescottprue/react-redux-firebase/tree/master/examples/simple), this example shows syncing a list of todo items. 4 | 5 | ## Note 6 | 7 | This example is not a full application. For a full application please view the examples in the [Complete Folder](https://github.com/prescottprue/react-redux-firebase/tree/master/examples/complete) including the [material example](https://github.com/prescottprue/react-redux-firebase/tree/master/examples/complete/material). 8 | -------------------------------------------------------------------------------- /examples/snippets/multipleQueries/TodoItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { firebaseConnect } from 'react-redux-firebase' 4 | import './Todo.css' 5 | 6 | const TodoItem = ({ firebase, todo, id }) => { 7 | const toggleDone = () => firebase.set(`todos/${id}/done`, !todo.done) 8 | const deleteTodo = () => firebase.remove(`todos/${id}`) 9 | return ( 10 |
  • 11 | 17 | {todo.text} 18 | 21 |
  • 22 | ) 23 | } 24 | 25 | TodoItem.propTypes = { 26 | todo: PropTypes.object, 27 | id: PropTypes.string 28 | } 29 | 30 | export default firebaseConnect()(TodoItem) 31 | -------------------------------------------------------------------------------- /examples/snippets/populates/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { map } from 'lodash' 4 | import { compose } from 'redux' 5 | import { connect } from 'react-redux' 6 | import { 7 | firebaseConnect, 8 | populate, 9 | isLoaded, 10 | isEmpty 11 | } from 'react-redux-firebase' 12 | 13 | // NOTE: In real application don't forget to use Provider from react-redux 14 | // or firebaseConnect/withFirebase will not work 15 | function Projects({ projects }) { 16 | return ( 17 |
    18 |

    react-redux-firebase populate snippet

    19 |
    20 |

    Projects List

    21 | {!isLoaded(projects) 22 | ? 'Loading' 23 | : isEmpty(projects) 24 | ? 'Todo list is empty' 25 | : map(projects, (project, id) => ( 26 |
    27 | Name: {project.name} 28 | Owner: {project.owner.displayName} 29 |
    30 | ))} 31 |
    32 |
    33 | ) 34 | } 35 | 36 | Projects.propTypes = { 37 | projects: PropTypes.object 38 | } 39 | 40 | const populates = [ 41 | { child: 'owner', root: 'users' } 42 | // or if you want a param of the populate child such as user's display name 43 | // { child: 'owner', root: 'users', childParam: 'displayName' } 44 | ] 45 | 46 | const enhance = compose( 47 | // gather projects and matching owners from firebase and place into redux 48 | firebaseConnect(() => [{ path: 'projects', populates }]), 49 | // projects with owner populated from redux into component props 50 | connect(state => ({ 51 | projects: populate(state.firebase, 'projects', populates) 52 | })) 53 | ) 54 | 55 | export default enhance(Projects) 56 | -------------------------------------------------------------------------------- /examples/snippets/populates/README.md: -------------------------------------------------------------------------------- 1 | # Populates Snippet 2 | 3 | This example shows using data from redux state to be used in queries. A good example of this is querying based on the current user's UID. 4 | 5 | **Note** Example does not use routing, which is what will commonly be used when creating a full application. For how to build with routing, please view [the routing recipes section of the docs.](https://react-redux-firebase.com/docs/recipes/routing.html) 6 | 7 | ## What It Does 8 | 9 | 1. Top level component uses `firebaseConnect` function to set a listener for the `projects` path on firebase. When the listener updates it also goes and gets the object from the `users` path that matches the owner from each project. This will be used in the next step, but for now the data has been loaded into redux. 10 | 11 | ```js 12 | const populates = [ 13 | { child: 'owner', root: 'users' }, 14 | ] 15 | 16 | firebaseConnect([ 17 | { path: 'projects', populates }, 18 | ]) 19 | ``` 20 | 21 | 1. Next is the `connect` HOC which allows us to grab from redux state and pass that data in as props to the component. In this case we are going to get projects with the owner parameter on each project replaced with the matching user: 22 | 23 | ```js 24 | connect((state) => ({ 25 | projects: populate(state.firebase, 'projects', populates), 26 | })) 27 | ``` 28 | 29 | 1. `isLoaded` can be used as usual to wait for data to be loaded. The projects list has the owner parameter "populated" from users. 30 | -------------------------------------------------------------------------------- /examples/snippets/stateBasedQuery/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { isLoaded, isEmpty } from 'react-redux-firebase' 5 | import Todos from './Todos' 6 | import LoginView from './LoginView' 7 | 8 | function Home({ auth }) { 9 | // handle initial loading of auth 10 | if (!isLoaded(auth)) { 11 | return
    Loading...
    12 | } 13 | 14 | // User is not logged in, show login view 15 | if (isEmpty(auth)) { 16 | return 17 | } 18 | 19 | return 20 | } 21 | 22 | Home.propTypes = { 23 | auth: PropTypes.object 24 | } 25 | 26 | export default connect(state => ({ 27 | auth: state.firebase.auth 28 | }))(Home) 29 | -------------------------------------------------------------------------------- /examples/snippets/stateBasedQuery/Todos.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { compose } from 'redux' 4 | import { connect } from 'react-redux' 5 | import { firebaseConnect, isLoaded, isEmpty } from 'react-redux-firebase' 6 | import TodoItem from './TodoItem' 7 | 8 | function Todos({ todos }) { 9 | return ( 10 |
    11 | {!isLoaded(todos) 12 | ? 'Loading' 13 | : isEmpty(todos) 14 | ? 'Todo list is empty' 15 | : todos.map(({ key }) => )} 16 |
    17 | ) 18 | } 19 | 20 | Todos.propTypes = { 21 | /* eslint-disable react/no-unused-prop-types */ 22 | auth: PropTypes.shape({ 23 | uid: PropTypes.string.isRequired 24 | }), 25 | /* eslint-enable react/no-unused-prop-types */ 26 | todos: PropTypes.object 27 | } 28 | 29 | const enhance = compose( 30 | firebaseConnect(props => [ 31 | // uid comes from props 32 | { 33 | path: 'todos', 34 | queryParams: ['orderByChild=uid', `equalTo=${props.uid}`] 35 | } 36 | ]), 37 | connect(state => ({ 38 | todos: state.firebase.ordered.todos 39 | })) 40 | ) 41 | 42 | export default enhance(Todos) 43 | -------------------------------------------------------------------------------- /examples/snippets/watchEvent/Basic.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { compose } from 'redux' 4 | import { connect } from 'react-redux' 5 | import { isLoaded, isEmpty, withFirebase } from 'react-redux-firebase' 6 | 7 | class SomeThing extends PureComponent { 8 | static propTypes = { 9 | todos: PropTypes.object 10 | } 11 | 12 | componentWillMount () { 13 | this.props.firebase.watchEvent('value', 'todos') 14 | } 15 | 16 | componentWillUnMount () { 17 | this.props.firebase.unWatchEvent('value', 'todos') 18 | } 19 | 20 | render () { 21 | const { todos } = this.props 22 | 23 | if (!isLoaded(todos)) { 24 | return
    Loading...
    25 | } 26 | 27 | if (isEmpty(todos)) { 28 | return
    No Todos Found
    29 | } 30 | 31 | return
    {JSON.stringify(todos, null, 2)}
    32 | } 33 | } 34 | 35 | export default compose( 36 | withFirebase, // add props.firebase 37 | connect(({ firebase: { data: { todos } } }) => ({ 38 | todos // map todos from redux state to props 39 | })), 40 | )(SomeThing) 41 | -------------------------------------------------------------------------------- /examples/snippets/watchEvent/README.md: -------------------------------------------------------------------------------- 1 | # watchEvent snippet 2 | 3 | This example shows creating a listener directly using `watchEvent`, which is handled automatically by `firebaseConnect` 4 | 5 | ## Whats Included 6 | 7 | - Basic snippet - Uses `watchEvent` directly 8 | - Recompose snippet - Uses `recompose` to simplify common patterns 9 | 10 | ## What It Does 11 | 12 | 1. Top level component uses `connect` function to bring `todos` from redux state into a prop name "todos": 13 | 14 | ```js 15 | export default compose( 16 | withFirebase, // add props.firebase 17 | connect(({ firebase: { data: { todos } } }) => ({ 18 | todos // map todos from redux state to props 19 | })) 20 | )(SomeThing) 21 | ``` 22 | 23 | 1. When the component mounts `watchEvent` is called to attach a listener for `todos` path of real time database 24 | 1. That component then uses `isLoaded` and `isEmpty` to show different views based on auth state (whether data is loaded or not): 25 | 26 | ```js 27 | render () { 28 | const { todos } = this.props 29 | 30 | if (!isLoaded(todos)) { 31 | return
    Loading...
    32 | } 33 | 34 | if (isEmpty(todos)) { 35 | return
    No Todos Found
    36 | } 37 | 38 | return
    {JSON.stringify(todos, null, 2)}
    39 | } 40 | ``` 41 | 42 | **NOTE**: This is simplified functionality of what is available through [`firebaseConnect`](https://react-redux-firebase.com/docs/api/firebaseConnect.html) and [`useFirebaseConnect`](https://react-redux-firebase.com/docs/api/useFirebaseConnect.html). 43 | -------------------------------------------------------------------------------- /examples/snippets/watchEvent/Recompose.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { isLoaded, isEmpty, withFirebase } from 'react-redux-firebase' 5 | import { compose, lifecycle, pure } from 'recompose' 6 | 7 | const SomeThing = ({ todos }) => { 8 | if (!isLoaded(todos)) { 9 | return
    Loading...
    10 | } 11 | 12 | if (isEmpty(todos)) { 13 | return
    No Todos Found
    14 | } 15 | 16 | return
    {JSON.stringify(todos, null, 2)}
    17 | } 18 | 19 | SomeThing.propTypes = { 20 | todos: PropTypes.object 21 | } 22 | 23 | // Create enhancer by composing HOCs 24 | const enhance = compose( 25 | withFirebase, // add props.firebase 26 | lifecycle({ 27 | componentWillMount () { 28 | this.props.firebase.watchEvent('value', 'todos') 29 | }, 30 | componentWillUnmount () { 31 | this.props.firebase.unWatchEvent('value', 'todos') 32 | } 33 | }), 34 | connect(({ firebase: { data: { todos } } }) => ({ 35 | todos // map todos from redux state to props 36 | })), 37 | pure // shallowEqual comparison of props for rendering optimization 38 | ) 39 | 40 | export default enhance(SomeThing) 41 | -------------------------------------------------------------------------------- /examples/snippets/webpack2/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { 4 | "es2015": { 5 | "loose": true, 6 | "modules": false 7 | } 8 | }], "react" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /examples/snippets/webpack2/README.md: -------------------------------------------------------------------------------- 1 | # Webpack 2 Example 2 | 3 | Snippet that shows usage `react-redux-firebase` in an application bundled with webpack 2/3 4 | 5 | ## Note 6 | 7 | This example is not a full application. For a full application please view the examples in the [Complete Folder](https://github.com/prescottprue/react-redux-firebase/tree/master/examples/complete) including the [material example](https://github.com/prescottprue/react-redux-firebase/tree/master/examples/complete/material). 8 | -------------------------------------------------------------------------------- /examples/snippets/webpack2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
    6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/snippets/webpack2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-firebase-webpack2-example", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "react": "15.4.1", 6 | "react-dom": "15.4.1", 7 | "react-redux": "^5.0.5", 8 | "react-redux-firebase": "^1.2.5" 9 | }, 10 | "devDependencies": { 11 | "babel-core": "6.18.2", 12 | "babel-loader": "6.2.8", 13 | "babel-preset-es2015": "6.18.0", 14 | "babel-preset-react": "6.16.0", 15 | "http-server": "0.9.0", 16 | "webpack": "^2.2.1" 17 | }, 18 | "scripts": { 19 | "build": "webpack --config webpack.config.js", 20 | "start": "http-server -a 0.0.0.0 -p 9000" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/snippets/webpack2/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux' 4 | import createStore from './store' 5 | 6 | const store = createStore() 7 | 8 | const Page = () => ( 9 |
    Hello World
    10 | ) 11 | 12 | const ConnectedPage = firebaseConnect()(Page) 13 | 14 | const App = () => ( 15 | 16 | 17 | 18 | ); 19 | 20 | ReactDOM.render(, document.querySelector('#app')); 21 | -------------------------------------------------------------------------------- /examples/snippets/webpack2/src/config.js: -------------------------------------------------------------------------------- 1 | export const firebase = { 2 | apiKey: 'AIzaSyCTUERDM-Pchn_UDTsfhVPiwM4TtNIxots', 3 | authDomain: 'redux-firebasev3.firebaseapp.com', 4 | databaseURL: 'https://redux-firebasev3.firebaseio.com', 5 | storageBucket: 'redux-firebasev3.appspot.com', 6 | messagingSenderId: '823357791673' 7 | } 8 | 9 | export default { firebase } 10 | -------------------------------------------------------------------------------- /examples/snippets/webpack2/src/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { firebaseStateReducer as firebase } from 'react-redux-firebase' 3 | 4 | const rootReducer = combineReducers({ 5 | firebase 6 | }) 7 | 8 | export default rootReducer 9 | -------------------------------------------------------------------------------- /examples/snippets/webpack2/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose } from 'redux' 2 | import rootReducer from './reducer' 3 | import { firebase as fbConfig } from './config' 4 | import { reactReduxFirebase } from 'react-redux-firebase' 5 | 6 | export default function configureStore (initialState, history) { 7 | const createStoreWithMiddleware = compose( 8 | reactReduxFirebase(fbConfig, 9 | { 10 | userProfile: 'users', 11 | } 12 | ), 13 | typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f 14 | )(createStore) 15 | const store = createStoreWithMiddleware(rootReducer) 16 | 17 | if (module.hot) { 18 | // Enable Webpack hot module replacement for reducers 19 | module.hot.accept('./reducer', () => { 20 | const nextRootReducer = require('./reducer') 21 | store.replaceReducer(nextRootReducer) 22 | }) 23 | } 24 | 25 | return store 26 | } 27 | -------------------------------------------------------------------------------- /examples/snippets/webpack2/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: { 6 | 'app': path.resolve(__dirname, 'src/app') 7 | }, 8 | output: { 9 | path: path.join(__dirname, 'dist'), 10 | filename: '[name].bundle.js' 11 | }, 12 | module: { 13 | rules: [{ 14 | test: /\.(js|jsx)$/, 15 | loader: 'babel-loader', 16 | query: { 17 | presets: [ 18 | [ 19 | "es2015", 20 | // { 21 | // "modules": false 22 | // } 23 | ], 24 | 'react' 25 | ], 26 | plugins: [] 27 | }, 28 | include: [ 29 | path.resolve(__dirname, 'src') 30 | ] 31 | }] 32 | }, 33 | plugins: [ 34 | // new webpack.DefinePlugin({ 35 | // 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') 36 | // }), 37 | ], 38 | resolve: { 39 | modules: [ 40 | 'node_modules' 41 | ], 42 | extensions: ['.js', '.json'] 43 | }, 44 | devtool: false 45 | }; 46 | -------------------------------------------------------------------------------- /src/ReactReduxFirebaseContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | 3 | /** 4 | * @description Context for extended firebase instance created 5 | * by react-redux-firebase 6 | */ 7 | const ReactReduxFirebaseContext = createContext(null) 8 | 9 | export default ReactReduxFirebaseContext 10 | -------------------------------------------------------------------------------- /src/ReduxFirestoreContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | 3 | /** 4 | * @description Context for extended firebase instance created 5 | * by react-redux-firebase 6 | */ 7 | const ReduxFirestoreContext = createContext(null) 8 | 9 | export default ReduxFirestoreContext 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import createFirebaseInstance, { getFirebase } from './createFirebaseInstance' 2 | import ReactReduxFirebaseProvider from './ReactReduxFirebaseProvider' 3 | import ReactReduxFirebaseContext from './ReactReduxFirebaseContext' 4 | import ReduxFirestoreProvider from './ReduxFirestoreProvider' 5 | import ReduxFirestoreContext from './ReduxFirestoreContext' 6 | import firebaseConnect from './firebaseConnect' 7 | import firestoreConnect from './firestoreConnect' 8 | import withFirebase from './withFirebase' 9 | import withFirestore from './withFirestore' 10 | import useFirebaseConnect from './useFirebaseConnect' 11 | import useFirestoreConnect from './useFirestoreConnect' 12 | import useFirebase from './useFirebase' 13 | import useFirestore from './useFirestore' 14 | import reducer from './reducer' 15 | import constants, { actionTypes } from './constants' 16 | import { authIsReady } from './utils/auth' 17 | import * as helpers from './helpers' 18 | 19 | export default { 20 | ReactReduxFirebaseProvider, 21 | ReactReduxFirebaseContext, 22 | ReduxFirestoreContext, 23 | ReduxFirestoreProvider, 24 | createFirebaseInstance, 25 | firebaseConnect, 26 | firestoreConnect, 27 | withFirebase, 28 | withFirestore, 29 | useFirebase, 30 | useFirebaseConnect, 31 | useFirestore, 32 | useFirestoreConnect, 33 | reducer, 34 | firebaseReducer: reducer, 35 | constants, 36 | actionTypes, 37 | getFirebase, 38 | authIsReady, 39 | ...helpers 40 | } 41 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from './utils/reducers' 2 | import { 3 | requestingReducer, 4 | requestedReducer, 5 | timestampsReducer, 6 | dataReducer, 7 | orderedReducer, 8 | authReducer, 9 | authErrorReducer, 10 | profileReducer, 11 | listenersReducer, 12 | isInitializingReducer, 13 | errorsReducer 14 | } from './reducers' 15 | 16 | /** 17 | * @name firebaseReducer 18 | * Main reducer for react-redux-firebase. This function is called 19 | * automatically by redux every time an action is fired. Based on which action 20 | * is called and its payload, the reducer will update redux state with relevant 21 | * changes. `firebaseReducer` is made up of multiple "slice reducers" 22 | * ([outlined in reducers docs](/docs/recipes/reducers.md)) combined using 23 | * [`combineReducers`](https://redux.js.org/docs/api/combineReducers.html) 24 | * following the patterns outlined in 25 | * [the redux docs](https://redux.js.org/docs/recipes/StructuringReducers.html). 26 | * @param {object} state - Current Firebase Redux State (state.firebase) 27 | * @param {object} action - Action which will modify state 28 | * @param {string} action.type - Type of Action being called 29 | * @param {string} action.path - Path of action that was dispatched 30 | * @param {string} action.data - Data associated with action 31 | * @returns {object} Firebase redux state 32 | */ 33 | export default combineReducers({ 34 | requesting: requestingReducer, 35 | requested: requestedReducer, 36 | timestamps: timestampsReducer, 37 | data: dataReducer, 38 | ordered: orderedReducer, 39 | auth: authReducer, 40 | authError: authErrorReducer, 41 | profile: profileReducer, 42 | listeners: listenersReducer, 43 | isInitializing: isInitializingReducer, 44 | errors: errorsReducer 45 | }) 46 | -------------------------------------------------------------------------------- /src/useFirebase.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import ReactReduxFirebaseContext from './ReactReduxFirebaseContext' 3 | 4 | /** 5 | * @name useFirebase 6 | * @description React hook that provides `firebase` object. 7 | * Firebase is gathered from ReactReduxFirebaseContext, which is 8 | * set by createFirebaseInstance during setup. 9 | * **NOTE**: This version of the Firebase library has extra methods, config, 10 | * and functionality which give it it's capabilities such as dispatching 11 | * actions. 12 | * @returns {object} - Extended Firebase instance 13 | * @see https://react-redux-firebase.com/docs/api/useFirebase.html 14 | * @example Basic 15 | * import { useFirebase } from 'react-redux-firebase' 16 | * 17 | * export default function AddData() { 18 | * const firebase = useFirebase() 19 | * 20 | * function addTodo() { 21 | * const exampleTodo = { done: false, text: 'Sample' } 22 | * return firebase.push('todos', exampleTodo) 23 | * } 24 | * 25 | * return ( 26 | *
    27 | * 30 | *
    31 | * ) 32 | * } 33 | */ 34 | export default function useFirebase() { 35 | return useContext(ReactReduxFirebaseContext) 36 | } 37 | -------------------------------------------------------------------------------- /src/useFirestore.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import ReduxFirestoreContext from './ReduxFirestoreContext' 3 | 4 | /** 5 | * @name useFirestore 6 | * @description React hook that return firestore object. 7 | * @returns {object} - Extended Firestore instance 8 | * @see https://react-redux-firebase.com/docs/api/useFirestore.html 9 | * @example Basic 10 | * import React from 'react' 11 | * import { useFirestore } from 'react-redux-firebase' 12 | * 13 | * export default function AddData() { 14 | * const firestore = useFirestore() 15 | * 16 | * function addTodo() { 17 | * const exampleTodo = { done: false, text: 'Sample' } 18 | * return firestore.collection('todos').add(exampleTodo) 19 | * } 20 | * 21 | * return ( 22 | *
    23 | * 26 | *
    27 | * ) 28 | * } 29 | */ 30 | export default function useFirestore() { 31 | return useContext(ReduxFirestoreContext) 32 | } 33 | -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: '../.eslintrc.js', 3 | 4 | globals: { 5 | sinon: true, 6 | expect: true, 7 | after: true, 8 | afterEach: true, 9 | before: true, 10 | beforeEach: true, 11 | it: true, 12 | describe: true, 13 | Firebase: true, 14 | firebase: true, 15 | fbConfig: true, 16 | uid: true, 17 | existingProfile: true 18 | }, 19 | 20 | rules: { 21 | 'no-unused-expressions': [0] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require @babel/register 2 | --require ./test/setup 3 | --recursive 4 | --colors 5 | --report lcov 6 | -------------------------------------------------------------------------------- /test/unit/library.spec.js: -------------------------------------------------------------------------------- 1 | import src from '../../src' 2 | 3 | describe('module', () => { 4 | describe('exports', () => { 5 | it('ReactReduxFirebaseContext', () => { 6 | expect(src).to.have.property('ReactReduxFirebaseContext') 7 | }) 8 | it('ReactReduxFirebaseProvider', () => { 9 | expect(src).to.respondTo('ReactReduxFirebaseProvider') 10 | }) 11 | it('ReduxFirestoreContext', () => { 12 | expect(src).to.have.property('ReduxFirestoreContext') 13 | }) 14 | it('ReduxFirestoreProvider', () => { 15 | expect(src).to.respondTo('ReduxFirestoreProvider') 16 | }) 17 | it('withFirestore', () => { 18 | expect(src).to.respondTo('withFirestore') 19 | }) 20 | it('withFirebase', () => { 21 | expect(src).to.respondTo('withFirebase') 22 | }) 23 | it('firebaseReducer', () => { 24 | expect(src).to.respondTo('firebaseReducer') 25 | }) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /test/unit/useFirebase.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TestUtils from 'react-dom/test-utils' 3 | import { firebaseWithConfig } from '../utils' 4 | import ReactReduxFirebaseProvider from '../../src/ReactReduxFirebaseProvider' 5 | import useFirebase from '../../src/useFirebase' 6 | 7 | describe('useFirebase', () => { 8 | it('return firebase object', () => { 9 | const spy = sinon.spy() 10 | const dispatchSpy = sinon.spy() 11 | const InnerComponent = ({ spy }) => { 12 | const firebase = useFirebase() 13 | spy(firebase) 14 | return null 15 | } 16 | TestUtils.renderIntoDocument( 17 | 21 | 22 | 23 | ) 24 | expect(spy).to.has.been.called 25 | expect(spy.lastCall.args[0]).to.respondTo('push') 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /test/unit/useFirestore.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TestUtils from 'react-dom/test-utils' 3 | import { firebaseWithConfig } from '../utils' 4 | import ReactReduxFirebaseProvider from '../../src/ReactReduxFirebaseProvider' 5 | import useFirestore from '../../src/useFirestore' 6 | import { createFirestoreInstance } from 'redux-firestore' 7 | 8 | describe('useFirestore', () => { 9 | it('return firestore object', () => { 10 | const spy = sinon.spy() 11 | const dispatchSpy = sinon.spy() 12 | const InnerComponent = ({ spy }) => { 13 | const firestore = useFirestore() 14 | spy(firestore) 15 | return null 16 | } 17 | TestUtils.renderIntoDocument( 18 | 23 | 24 | 25 | ) 26 | expect(spy).to.has.been.called 27 | expect(spy.lastCall.args[0]).to.respondTo('add') 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /test/unit/utils/actions.spec.js: -------------------------------------------------------------------------------- 1 | import { wrapInDispatch } from 'utils/actions' 2 | const method = () => Promise.resolve() 3 | const failMethod = () => Promise.reject(new Error('Some Error')) 4 | const dispatch = () => {} 5 | 6 | describe('Utils: Auth', () => { 7 | describe('wrapInDispatch', () => { 8 | // Skipped due to capatalize and auth provider function 9 | it('creates valid Auth Provider', () => { 10 | // TODO: Check that dispatch is called with actions 11 | expect( 12 | wrapInDispatch(dispatch, { method, args: ['arg1'], types: ['ACTION'] }) 13 | ).to.be.fulfilled 14 | }) 15 | it('handles string list of scopes', () => { 16 | expect( 17 | wrapInDispatch(dispatch, { 18 | method: failMethod, 19 | args: ['arg1'], 20 | types: ['ACTION'] 21 | }) 22 | ).to.be.rejectedWith('Failed') 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /test/unit/utils/index.spec.js: -------------------------------------------------------------------------------- 1 | import { createCallable } from 'utils' 2 | 3 | describe('Utils: Index', () => { 4 | describe('createCallable', () => { 5 | it.skip('calls a passed function', () => { 6 | const spy = sinon.spy() 7 | createCallable(spy) 8 | expect(spy).to.have.been.called.once 9 | }) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/unit/utils/storage.spec.js: -------------------------------------------------------------------------------- 1 | import { deleteFile } from 'utils/storage' 2 | import { fakeFirebase } from '../../utils' 3 | 4 | describe('Utils: Storage', () => { 5 | describe('deleteFile', () => { 6 | it('returns dbPath', () => 7 | expect( 8 | deleteFile(fakeFirebase, { path: 'some', dbPath: 'some' }) 9 | ).to.eventually.have.keys(['path', 'dbPath'])) 10 | it('returns dbPath', () => 11 | expect( 12 | deleteFile(fakeFirebase, { path: 'some' }) 13 | ).to.eventually.have.keys('path')) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /test/unit/withFirebase.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createContainer, TestLeaf } from '../utils' 3 | import withFirebase from '../../src/withFirebase' 4 | 5 | let wrapper 6 | let leaf 7 | 8 | describe('withFirebase', () => { 9 | beforeEach(() => { 10 | const container = createContainer({ hoc: withFirebase }) 11 | wrapper = container.wrapper 12 | leaf = container.leaf 13 | }) 14 | 15 | it('adds firebase as prop', () => { 16 | expect(leaf.prop('firebase')).to.exist 17 | expect(leaf.prop('firebase')).to.respondTo('push') 18 | }) 19 | 20 | it('adds dispatch as prop', () => { 21 | expect(leaf.prop('dispatch')).to.exist 22 | expect(leaf.prop('dispatch')).to.be.a.function 23 | }) 24 | 25 | describe('sets displayName static as', () => { 26 | /* eslint-disable no-template-curly-in-string */ 27 | describe('withFirebase(${WrappedComponentName}) for', () => { 28 | /* eslint-enable no-template-curly-in-string */ 29 | it('standard components', () => { 30 | const component = withFirebase(TestLeaf) 31 | expect(component.displayName).to.equal(`withFirebase(TestLeaf)`) 32 | }) 33 | 34 | it('string components', () => { 35 | const str = 'Test' 36 | const stringComp = withFirebase(str) 37 | expect(stringComp.displayName).to.equal(`withFirebase(${str})`) 38 | }) 39 | }) 40 | 41 | it('"Component" for all other types', () => { 42 | wrapper = withFirebase(() =>
    ) 43 | expect(wrapper.displayName).to.equal('withFirebase(Component)') 44 | }) 45 | }) 46 | 47 | it('sets WrappedComponent static as component which was wrapped', () => { 48 | expect(leaf).to.match(TestLeaf) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const webpack = require('webpack') 4 | const pkg = require('./package.json') 5 | const env = process.env.NODE_ENV 6 | const LodashModuleReplacementPlugin = require('lodash-webpack-plugin') 7 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') 8 | .BundleAnalyzerPlugin 9 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 10 | 11 | const config = { 12 | module: { 13 | rules: [ 14 | { test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/ } 15 | ] 16 | }, 17 | output: { 18 | library: 'ReactReduxFirebase', 19 | libraryTarget: 'umd' 20 | }, 21 | externals: { 22 | react: { 23 | commonjs: 'react', 24 | commonjs2: 'react', 25 | amd: 'react', 26 | root: 'React' 27 | }, 28 | firebase: { 29 | commonjs: 'firebase', 30 | commonjs2: 'firebase', 31 | amd: 'firebase', 32 | root: 'Firebase' 33 | }, 34 | 'prop-types': { 35 | commonjs: 'prop-types', 36 | commonjs2: 'prop-types', 37 | amd: 'prop-types', 38 | root: 'PropTypes' 39 | } 40 | }, 41 | plugins: [new LodashModuleReplacementPlugin()] 42 | } 43 | 44 | if (process.env.SIZE) { 45 | config.plugins.push(new BundleAnalyzerPlugin()) 46 | } 47 | 48 | if (env === 'production') { 49 | config.plugins.push(new UglifyJsPlugin()) 50 | } 51 | 52 | config.plugins.push( 53 | new webpack.BannerPlugin({ 54 | banner: `${pkg.name}${env === 'production' ? '.min' : ''}.js v${ 55 | pkg.version 56 | }`, 57 | raw: false, 58 | entryOnly: true 59 | }) 60 | ) 61 | 62 | module.exports = config 63 | --------------------------------------------------------------------------------