So I have been creating an application where a user needs to log into firebase using google authentication. I am using redux, react-redux, react-redux-firebase, redux-firestore, and redux-thunk. I am able to successfully log the user into firebase with the google authentication. I now want to use firestore in order to have a collection of all the users. I have looked at the documentation for redux-firestore and the method of getting/manipulating is a little different. I have tried using the documentation, but I cannot get the functions to work with redux-firestore.
Here is the action
export const signIn = () => (
dispatch,
getState,
{getFirebase, getFirestore}) => {
const firebase = getFirebase();
const firestore = getFirestore();
firebase.auth().signInWithPopup(provider).then(function(result) {
if(result.credential) {
firestore.get({collection: 'users', doc: result.user.uid}).then(function(doc) {
if(!doc.exists){
console.log("new!")
firestore.add(
{collection: 'users', doc: result.user.uid},
{name: firebase.auth.currentUser.displayName});
} else{
console.log("old!")
}
})
}
}).catch((err) => {
})
};
And here is my setup in index.js for the src folder
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {BrowserRouter} from 'react-router-dom';
import {createStore, applyMiddleware, compose} from 'redux';
import {Provider} from 'react-redux';
import allReducers from './reducers';
import thunk from 'redux-thunk';
import firebase from './Firebase';
import {firebaseConfig} from './Firebase'
import {createFirestoreInstance, getFirestore, reduxFirestore} from 'redux-firestore';
import {ReactReduxFirebaseProvider, getFirebase} from 'react-redux-firebase';
const store = createStore(
allReducers,
compose(
applyMiddleware(thunk.withExtraArgument({getFirebase, getFirestore})),
reduxFirestore(firebaseConfig)
));
const rrfConfig = {
userProfile: 'users',
useFirestoreForProfile: true
};
const rrfProps = {
firebase,
config: rrfConfig,
dispatch: store.dispatch,
}
ReactDOM.render(
<Provider store={store}>
<ReactReduxFirebaseProvider {...rrfProps}>
<BrowserRouter>
<App />
</BrowserRouter>
</ReactReduxFirebaseProvider>
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
I know that I have not used createFirestoreInstance in this code, but I was playing around with it.
If anyone could tell me how to get this working, I would appreciate it.
Thanks!
Quick update:
I have figured out how to at least write to firestore using this code
const userRef = firebase.firestore().collection('users').doc(result.user.uid);
userRef.get().then(function(doc) {
if(!doc.exists){
userRef.set({name: result.user.displayName});
}
})
This is not the best (or maybe the right solution), but it does work. It is not using redux-firestore, but is there a better way?
If you're using React, use react-redux-firebase. There's no need for these many complication and the code looks much neater and simpler. Authentication, firestore and all other firebase features works out of the box with just small amount of code. They also comes with React hooks like useFirebase() and useFirestore() instead of you needing to write them on your own.
react-redux-firebase is built on top of redux-firebase and provides all the things you would need in React.
If your app only uses firebase, I would even recommend you use just plain Redux without Redux Thunk or Redux Saga.
Related
I'm currently trying to use React + Typescript + Redux and I'm running into an issue. I'm trying to test the Redux Store setup via chrome devTools. I know I butchered the code (very new to Typescript) and I'm getting this error 'Uncaught TypeError: Cannot read properties of undefined (reading 'dispatch')' every time I test it. I tried declaring a global window state, installed redux-dev-tools, but still very lost.
This is what my store/index.tsx file look like:
import {
legacy_createStore as createStore,
combineReducers,
applyMiddleware,
compose,
StoreEnhancer,
} from "redux";
import { devToolsEnhancer } from "redux-devtools-extension";
import thunk from "redux-thunk";
const rootReducer = combineReducers({});
let enhancer;
if (process.env.NODE_ENV === "production") {
enhancer = applyMiddleware(thunk);
} else {
const logger = require("redux-logger").default;
const composeEnhancers =
(window && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
enhancer = composeEnhancers(applyMiddleware(thunk, logger));
}
const configureStore = () => {
return createStore(rootReducer, devToolsEnhancer({}));
};
export default configureStore;
and my types/index.d.ts:
import { StoreEnhancer } from 'redux'
export {};
declare global {
interface Window {
store: {};
__REDUX_DEVTOOLS_EXTENSION__?: () => StoreEnhancer;
}
}
And finally my src/index.tsx:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import configureStore from "./store";
const store = configureStore();
if (process.env.NODE_ENV !== "production") {
window.store = store;
};
function Root() {
return (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
}
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<Root />
</React.StrictMode>
);
I will also attach screenshots of my file setup:
And my console error:
I am open to all suggestions, thank you!
The first issue here is that the store setup code is using outdated patterns and a lot of handwritten code. Today, you should be using our official Redux Toolkit package to write your Redux apps, and as part of that, RTK's configureStore API. It does all that same work with just a few lines:
import { configureStore } from "#reduxjs/toolkit";
const store = configureStore({
reducer: {
posts: postsReducer,
comments: commentsReducer
}
})
That automatically combines reducers, adds the Redux DevTools extension setup, and adds the thunk middleware.
See our docs for guidance on setup:
https://redux.js.org/tutorials/quick-start
https://redux.js.org/tutorials/typescript-quick-start
https://redux.js.org/introduction/why-rtk-is-redux-today
https://redux.js.org/tutorials/essentials/part-2-app-structure
As for the specific error message you're seeing... the code seems like it would run. My guess is that something is wrong with the process.env.NODE_ENV check you added and so it's not assigning window.store.
RTK also works much better with TypeScript than legacy Redux code does.
I have followed countless tutorials on redux-persist and believe I am implementing it correct in my redux-toolkit app. However, I think there's a disconnect in fundamental understanding on my end. Can I use persist on an application that uses REST API or do I need to setup my on backend server for it to work? And if you cant use persist for an app that uses REST API, how would I go about getting state to persist on an app that uses Redux-Toolkit? In Application in my browser's devtools, it shows that my state has been saved but when I close the browser window and open it back up, I find that my shopping cart is empty. Here's my code just in case I'm missing something:
Store js
import cartReducer from "./features/Cart/cartSlice"
import modalReducer from "./features/Modal/modalSlice"
//Persist
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
const persistConfig = {
key: "persist-key",
storage
}
const persistedReducer = persistReducer(persistConfig, cartReducer)
const store = configureStore({
reducer: {
persistedReducer: persistedReducer,
cart: cartReducer,
modal: modalReducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
serializableCheck: false
})
})
const persistor = persistStore(store)
export default store;
export { persistor }
Index js
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store, {persistor} from "./store"
import { PersistGate } from 'redux-persist/integration/react';
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<PersistGate persistor={persistor}>
<App />
</PersistGate>
</Provider>
</React.StrictMode>
);
Any help will be appreciated.
Yes you can use redux-persist to able keep data at client side,
but keep on mind, you should never keep sensible data. And use it only keep needful data such as session data settings, etc.
You can retrieve all such as normal redux data, handler as useSelector from react-redux library will return your current state.
So your backend not implements nothing to warranty your redux flow, it is entirety responsibility of frontend.
Problems setting up firebase/firestore with react and redux. Been reading through all the docs on http://docs.react-redux-firebase.com/history/v3.0.0/docs/getting_started.html and cannot render the application and obtaining the error "Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports."
Removing ReactReduxFirebaseProvider allows my application to render however it won't be connected to the database. I have checked all my default and named exports however still cannot find the problem.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import App from './App';
import { createFirestoreInstance } from 'redux-firestore';
import { createStore, applyMiddleware, compose } from 'redux';
import { firebase} from './config/fbConfig';
import { Provider } from 'react-redux';
import { ReactReduxFirebaseProvider} from 'react-redux-firebase';
import rootReducer from './store/reducers/rootReducer';
import thunk from 'redux-thunk';
// react-redux-firebase config
const rrfConfig = {
userProfile: 'users',
useFirestoreForProfile: true
};
const store = createStore(
rootReducer,
compose(
applyMiddleware(thunk)
));
const rrfProps = {
firebase,
config: rrfConfig,
dispatch: store.dispatch,
createFirestoreInstance
};
ReactDOM.render(<Provider store={store}><ReactReduxFirebaseProvider {...rrfProps}><App/></ReactReduxFirebaseProvider></Provider>, document.getElementById('root'));
serviceWorker.unregister();
fbConfig
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
const fbConfig = {}; // object containing my Firebase config
// Initialize Firebase
firebase.initializeApp(fbConfig);
const firestore = firebase.firestore();
const auth = firebase.auth();
export {
firebase,
firestore,
auth
};
I am new to working with firebase and fairly new to react and redux so have probably made a simple mistake however after hours of staring at this error I cannot find a solution. Any help would be appreciated.
Solution for me was to open terminal in root directory of project and run:
npm install react-redux-firebase#next
When I navigate through the app the UI is stuck although the url changes.
I would like to integrate redux-persist on my current app but it eventually drove me to a strange bug to me.
Note: I use also the redux-saga as middleware on creating the store.
store.js
import { createStore, applyMiddleware, compose } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web and AsyncStorage for react-native
import createSagaMiddleware from 'redux-saga'
import rootReducer from "../reducers/index";
import rootSaga from '../sagas/index'
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const sagaMiddleware = createSagaMiddleware()
const middleware = applyMiddleware(sagaMiddleware)
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
persistedReducer,
{},
composeEnhancers(middleware)
)
export const persistor = persistStore(store)
sagaMiddleware.run(rootSaga)
export default store
window.store = store
When I comment in the Persist Gate component then the navigation works as intended.
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter as Router } from "react-router-dom";
import { Provider } from 'react-redux'
import registerServiceWorker from "./js/registerServiceWorker";
import { PersistGate } from 'redux-persist/integration/react'
import store, { persistor } from './js/store';
ReactDOM.render(
<Router>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
</Router >,
document.getElementById("root")
);
registerServiceWorker();
I hope I made myself clear!
Try wrapping your Router with the PersistGate. The order of these higher order components matters for React Router. The way you have it now, when you change the url it's not triggering a re-render, so swapping the order should fix the issue.
Updating this question to use connected-react-router instead of react-router-redux since it is not compatible with react-router v4.
I can't seem to get my routing working when dispatching an action. I suspect it's because I'm using sagas which aren't being configured properly.
I have a saga:
import { call } from 'redux-saga/effects'
import { push } from 'connected-react-router'
//...
yield call(push, '/dashboard')
The push function doesn't redirect the browser to the specified path despite the redux logs in webdev tools showing that the action was successfully dispatched.
The top level index.js file looks like:
import createSagaMiddleware from 'redux-saga'
import rootSaga from './redux/sagas'
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import logger from 'redux-logger'
import App from './App'
import registerServiceWorker from './registerServiceWorker'
import rootReducer from './redux/modules'
import { applyMiddleware, compose, createStore } from 'redux'
import { createBrowserHistory } from 'history'
import { routerMiddleware, connectRouter } from 'connected-react-router'
const history = createBrowserHistory()
const sagaMiddleware = createSagaMiddleware()
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
connectRouter(history)(rootReducer),
composeEnhancer(
applyMiddleware(
sagaMiddleware,
routerMiddleware(history),
logger
)
)
)
sagaMiddleware.run(rootSaga)
const render = () => {
ReactDOM.render(
<Provider store={store}>
<App history={history} />
</Provider>,
document.getElementById('root')
)
}
render()
registerServiceWorker()
The App.js file containing the root component has:
import { ConnectedRouter } from 'connected-react-router'
import { Route, Switch, Redirect } from 'react-router-dom'
const App = ({ history }) => {
return (
<ConnectedRouter history={history}>
<Switch>
{ routes }
</Switch>
</ConnectedRouter>
)
}
export default App
What's missing from this setup to make it work?
Dependency versions:
"react-redux": "^5.0.7",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"connected-react-router": "^4.3.0"
Unlike history's push method (which is an impure function), connected-react-router's push is an action creator and its result (action) must be dispatched to trigger a navigation.
To do so in redux-saga you have to use put, not call.
call creates a call effect.
When yielded, it simply executes given function with given arguments and returns a result. It is a good fit for (but not limited by) impure function calls (e.g. network request), by decoupling us from a direct execution of a function.
put creates a dispatch effect.
When yielded, it dispatches passed in action object. Thus, decoupling your code only from a direct call of dispatch, not action creator (which should be pure by design).
So, in your case, the solution would look like:
yield put(push('/dashboard'))
P.S: the same applies to react-router-redux's push
you need to wire up the router's middleware, e.g.:
import { browserHistory } from 'react-router'
import { routerMiddleware } from 'react-router-redux'
const sagaMw = createSagaMiddleware()
const routerMw = routerMiddleware(browserHistory)
const middleware = applyMiddleware(sagaMw, routerMw, logger)
Sagas are implemented as Generator functions that yield objects to the
redux-saga middleware
So your Saga should export a Generator function:
import { call } from 'redux-saga/effects'
import { push } from 'connected-react-router'
//...
export function* rootSaga() {
return yield call(push, '/dashboard')
}
And rootSaga should be registered with the sagaMiddleware:
import { rootSaga } from './redux/sagas';
...
sagaMiddleware.run(rootSaga)
...
Reference: https://redux-saga.js.org/docs/introduction/BeginnerTutorial.html
What seems to work for me is to use withRoter (dont know if it is correct way or not):
export default withRouter(compose(withConnect)(App)); //wrapped in withRouter
Or
export default compose(
withRouter,
withConnect,
)(App);
And in redux-saga:
yield put(push('/newpage'));
I use react-boilerplate, https://github.com/react-boilerplate/react-boilerplate.
I don't know if it is correct or not but this way the route changes in url and I get to the new route. If i don't use withRouter the route changes in url and nothing more happens...
I found solution here https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md
Would like to have someone opinion on that. Folks at https://github.com/react-boilerplate/react-boilerplate maybe?