Redux and Firebase - firebase

I've struggled to implement react-redux-firebase and redux-firestore into my app after configuring the redux store (struggled with this too, even though redux-toolkit simplified some things). Is it possible that I can communicate with firebase without using those two packages above? If so, how do I use firebase in any of my slices? e.g., auth slice below.
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import firebase from 'firebase/app';
export const authSlice = createSlice({
name: 'authSlice',
initialState: {
currentUser: null,
isLoggedIn: false,
isLoading: false,
},
reducers: {
login: async (state, action) => {},
registerUser: (state, action) => {},
changeProfile: (state, action) => {},
logout: async (state, action) => {},
setCurrentUser: (state, action) => {},
},
});
// Action creators are generated for each case reducer function
export const {
login,
registerUser,
changeProfile,
logout,
setCurrentUser,
} = authSlice.actions;
export default authSlice.reducer;

This is the query in a separate file.
import firestore from '#react-native-firebase/firestore';
export const getPopularProducts = firestore()
.collection('POPULAR')
.orderBy('count', 'desc')
.limit(10)
.get()
.then(querySnapshot => {
const views = [];
querySnapshot.forEach(doc => {
views.push({
key: doc.id,
count: doc.data().count,
product: doc.data().product,
});
});
return views;
})
.catch(error => {
alert('Error getting popular products: ', error);
});
In the reducer/slice, import getPopularProducts.
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {getPopularProducts} from './../../lib/fetchData';
// Initial states
const initialState = {
products: [],
mainList: [],
popular: [],
};
// Get popular products from firebase
export const fetchPopularProducts = createAsyncThunk(
'prodSlice/fetchPopularProducts',
async () => {
const data = getPopularProducts;
const {_W} = data;
if (_W !== null) {
return _W;
}
},
);
export const productSlice = createSlice({
name: 'prodSlice',
initialState,
reducers: {
fetchData: (state, action) => {
state.isLoading = true;
state.mainList = action.payload;
state.products = action.payload;
}
},
extraReducers: {
[fetchPopularProducts.fulfilled]: (state, action) => {
state.popular = action.payload;
},
},
});
// Action creators are generated for each case reducer function
export const {fetchData} = productSlice.actions;
export const selectProducts = state => state.prodSlice;
export default productSlice.reducer;
Then you dispatch fetchPopularProducts inside the useEffect hook. I cases where I needed a parameter for the query, I'd put the query inside createAsyncThunk.

Related

using dispatch crash the store state

i want to moving to #reduxjs/toolkit, actually my app use the following packages:
"#reduxjs/toolkit": "^1.8.3",
"react-redux": "^7.2.2",
"#types/react-redux": "^7.1.20",
actually i use this type of action for my redux store :
export function action<T extends string, P>(type: T, payload?: Partial<P>) {
return { type, ...payload };
}
i change my configure store like this :
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), sagaMiddleware],
});
sagaMiddleware.run(rootSaga);
export default store;
type AppDispatch = typeof store.dispatch;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
i've created a simple button component like this :
interface FavoriteBtnProps {
className: string;
}
function FavoriteBtn({ className }: FavoriteBtnProps) {
const { windowDisplay } = useAppSelector(getFavoritePlacesState);
const dispatch = useDispatch();
const handleClick = () => {
dispatch(setWindowDisplay(!windowDisplay));
};
return (
<div className={className} onClick={handleClick}>
<FontAwesomeIcon icon={faBookmark} />
</div>
);
}
export default FavoriteBtn;
it work's fine but need to use generic redux dispatch (Dispatch()). the useAppDispatch created don't work with old actions
now i want to add in a parent component of FavoriteBtn
const { leftPanelDisplay } = useAppSelector(getPanelState);
const { windowDisplay } = useAppSelector(getFavoritePlacesState);
console.log(windowDisplay);
const dispatch = useDispatch();
const handleCollapseIcon = () => {
dispatch(
leftPanelDisplay
? panelsActions.leftPanel.collapse()
: panelsActions.leftPanel.uncollapse()
);
// dispatch(setWindowDisplay(!windowDisplay));
};
this code works fine the console log give me the good value, but if i uncomment
dispatch(setWindowDisplay(!windowDisplay));
i have this error :
Uncaught TypeError: Cannot destructure property 'windowDisplay' of
'Object(...)(...)' as it is undefined.
and i don't see favoriteAndHistoric state in the reduxDevTools
only the state created by the toolkit crashes.
here my slice
const favoritePlacesSlice = createSlice({
name: 'favoritePlaces',
initialState,
reducers: {
setWindowDisplay: (state, action: PayloadAction<boolean>) => {
state.windowDisplay = action.payload;
},
setFavorites: (state, action: PayloadAction<FavoriteSearch[]>) => {
state.favoritePlaces = action.payload;
},
},
extraReducers: (builder) => {
// GET ALL
builder.addCase(fetchFavoriteSearchs.pending, (state, action) => {
state.apiStatus = APIStatus.PENDING;
});
builder.addCase(fetchFavoriteSearchs.fulfilled, (state, action) => {
state.apiStatus = APIStatus.FULFILLED;
state.favoritePlaces = action.payload ?? [];
});
// ADD
builder.addCase(addFavoriteSearch.pending, (state, action) => {
state.apiStatus = APIStatus.PENDING;
});
builder.addCase(addFavoriteSearch.fulfilled, (state, action) => {
state.apiStatus = APIStatus.FULFILLED;
state.favoritePlaces = state.favoritePlaces.concat(action.payload);
});
// DELETE
builder.addCase(deleteFavoriteSearch.pending, (state, action) => {
state.apiStatus = APIStatus.PENDING;
});
builder.addCase(deleteFavoriteSearch.fulfilled, (state, action) => {
state.apiStatus = APIStatus.FULFILLED;
state.favoritePlaces = state.favoritePlaces.filter(
(f) => f.idIri !== action.payload
);
});
},
});
export const { setFavorites, setWindowDisplay } = favoritePlacesSlice.actions;
export default favoritePlacesSlice.reducer;
my rootReducer :
import { combineReducers } from 'redux';
...
import favoritePlacesReducer from '../features/favoritePlaces/favoritePlacesSlice';
const rootReducer = combineReducers({
...
favoritePlacesReducer,
});
export default rootReducer;
export type RootState = ReturnType<typeof rootReducer>;
i've allready tryed to reinstall #reduxjs/toolkit and react-redux from scratch but don't fix the problem and more, i have type errors on createAsyncThunk.
why it works on a componant and not on an other?
thx for help

How to add cases in ExtraReducer to match the actions created in currentReducer using createSlice() from #reduxjs/toolkit

Here below I have mentioned a redux slice. A fetchAllApps thunk function is created with createAsyncThunk for action 'allApps/allappsAdded/' which I dynamically got by allAppsAdded.type. When the fetchAllapps is dispatched it generated actions of type 'allApps/allappsAdded/pending', 'allApps/allappsAdded/fulfilled', 'allApps/allappsAdded/rejected' which I need to add in extraReducers to handle it by doing hardcode.Is there any way to add these action types like allAppsAdded.type programatically?. so that in future It makes easy for me to change these without redundant..
import {
configureStore,
createAsyncThunk,
createSlice
} from "#reduxjs/toolkit";
const initialState = {
apps: [],
categories: [],
loading: {
apps: false
}
};
const allappsSlice = createSlice({
name: "allapps",
initialState,
reducers: {
allappsAdded: (state, action) => {
state["apps"] = action.payload.apps;
state["categories"] = action.payload.categories;
}
},
extraReducers: {
}
});
export default () =>
configureStore({
reducer: allappsSlice.reducer
});
const { allappsAdded } = allappsSlice.actions;
const fetchAllApps = createAsyncThunk(allappsAdded.type, async () => {
console.log("ss");
setTimeout(() => ({ apps: [], categories: [] }), 2000);
});
export { allappsAdded, fetchAllApps };

React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhausCompiled with warnings

Error while dispatching an action!
React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhausCompiled with warnings.
App.js :-
function App() {
const user = null;
const dispatch = useDispatch();
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(userAuth => {
if (userAuth) {
dispatch(login({
uid: userAuth.uid,
email: userAuth.email
}));
} else {
dispatch(logout);
}
})
return unsubscribe;
}, []);
userSlice.js :-
export const userSlice = createSlice({
name: 'user',
initialState: {
user: null,
},
reducers: {
login: (state, action) => {
state.user = action.payload;
}
},
logout: (state) => {
state.user = null;
}
});
export const { login, logout } = userSlice.actions;
store.js :-
import { configureStore } from '#reduxjs/toolkit';
import userReducer from '../features/userSlice';
export const store = configureStore({
reducer: {
user: userReducer,
},
});

Redux toolkit: How to call another action from thunk(createAsyncThunk)

I am trying to call an action from a thunk created by createAsyncThunk.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
export const searchAction = createAsyncThunk(
'search/searchJoubun',
(request, thunkAPI) => {
thunkAPI.dispatch(setIsLoading(true)) // <- This doesn't trigger the setIsLoading action
// Call an API to search
},
);
const searchSlice = createSlice({
name: 'search',
initialState: {
isLoading: false,
searchResult: [],
},
reducers: {
setIsLoading(state, payload) {
state.isLoading = payload.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(searchAction.fulfilled, state => {
state.isLoading = false;
// Do something
})
.addCase(searchAction.rejected, state => {
state.isLoading = false;
// Do something
});
},
});
export default searchSlice;
export const { setIsLoading } = searchSlice.actions;
Inside searchAction(), I would like to mutate isLoading to true, before calling the API. Whet searchAction() is executed, it calls the API but the setIsLoading is not triggered.
Should I not call setIsLoading from the thunk and dispatch searchAction() and setIsLoading() separately from a component?
Versions
react-redux v7.2.5
reduxjs/toolkit v1.6.1
jest v27.0.6
ts-jest v27.0.5

Access redux state value on a React + Redux + Hooks + Typescript web app

I am trying to access the redux state to display its value on my website. I am using React redux hooks with functional components and Typescript.
Situation:
I have a store with two reducers: UI and user. The initial state is:
{
user: {
authenticated: false,
credentials: {}
},
UI: {
loading: false,
errors: null
}
}
When the user signs in, the signinUser action takes place and correctly changes the redux state. For example, for an invalid signin, the redux state is:
{
user: {
authenticated: false,
credentials: {}
},
UI: {
loading: false,
errors: {
general: 'wrong credentials, please try again'
}
}
}
Problem:
I am trying to acces the UI.errors so I can display them on my website. i have a function in my Signin component thnamed submitForm that calls the signinUser action that correctly dispatches the actions. My problem is that after that I want to retrieve the state.ui.errors and I can't figure out how to.
I have tried all this:
componentWillRecieveProps(nextProps) { ... } this solution is for class components and I am using functional components
useSelector((state: StoreState) => state.UI); If I do it inside submitForm is invalid because React Hooks don't allow to call inside a function. If I do it outside, it fetches the old state.
Here are my files (the parts related to this issue)
store.tsx
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
// Reducers
import userReducer from './reducers/userReducer';
import uiReducer from './reducers/uiReducer';
const initialState = {};
const middleware = [thunk];
const reducers = combineReducers({
user: userReducer,
UI: uiReducer
});
const store = createStore(
reducers,
initialState,
compose(
applyMiddleware(...middleware),
(window as any).__REDUX_DEVTOOLS_EXTENSION__ &&
(window as any).__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
userActions.tsx
import { SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI } from '../types';
import axios from 'axios';
// Interfaces
import { ISigninForm } from '../../utils/types';
// Redux
import { Dispatch } from 'redux';
import { useDispatch } from 'react-redux';
export const signinUser = (
userData: ISigninForm,
dispatch: Dispatch,
handleDialogClose: () => void
) => {
console.log('signinuser in userActions');
dispatch({ type: LOADING_UI });
axios
.post('/signin', userData)
.then((res) => {
const FBIdToken = `Bearer ${res.data.token}`;
localStorage.setItem('FBIdToken', FBIdToken);
axios.defaults.headers.common['Authorization'] = FBIdToken;
getUserData(dispatch);
dispatch({ type: CLEAR_ERRORS });
handleDialogClose();
// history.push("/profile"); // this will redirect to a page not built yet
})
.catch((err) => {
dispatch({
type: SET_ERRORS,
payload: err.response.data
});
});
};
export const getUserData = (dispatch: Dispatch) => {
console.log('getUserData');
axios
.get('/user')
.then((res) => {
console.log('/user', res);
dispatch({
type: SET_USER,
payload: res.data
});
})
.catch((err) => console.log('err', err));
};
uiReducer.tsx
import { SET_ERRORS, CLEAR_ERRORS, LOADING_UI, IAction } from '../types';
const initialState = {
loading: false,
errors: null
};
export default function (state = initialState, action: IAction) {
switch (action.type) {
case SET_ERRORS:
return {
...state,
loading: false,
errors: action.payload
};
case CLEAR_ERRORS:
return {
...state,
loading: false,
errors: null
};
case LOADING_UI:
return {
...state,
loading: true
};
default:
return state;
}
}
userReducer.tsx
import {
SET_USER,
SET_AUTHENTICATED,
SET_UNAUTHENTICATED,
IAction
} from '../types';
const initialState = {
authenticated: false,
credentials: {}
};
export default function (state = initialState, action: IAction) {
switch (action.type) {
case SET_AUTHENTICATED:
return {
...state,
authenticated: true
};
case SET_UNAUTHENTICATED:
return initialState;
case SET_USER:
console.log('SET_USER', action);
return {
authenticated: true,
...action.payload
};
default:
return state;
}
}
Signin.tsx
function Signin({ history }: RouteComponentProps): JSX.Element {
// States
const [dialogOpen, setDialogOpen] = React.useState(false);
const [errorsAPI, setErrorsAPI] = React.useState<ISigninErrors>({});
const [loading, setLoading] = React.useState(false);
// Dialog
const handleDialogOpen = () => {
setDialogOpen(true);
};
const handleDialogClose = () => {
setDialogOpen(false);
};
// Form
const { register, handleSubmit, errors } = useForm<ISigninForm>();
const submitForm = (data: ISigninForm) => {
signinUser(data, dispatch, handleDialogClose);
};
return (
// HTML content
);
}
export default withRouter(Signin);
Solution:
I had the solution in front of me this whole time, but I was not using the function in the right way.
const state = useSelector((state: StoreState) => state);
This is called inside the Signin function component. Then when I am returning the HTML object, I just call
{state.UI.errors !== null && 'general' in state.UI.errors && (
<p>{state.UI.errors.general}</p>
)}

Resources