I am trying to auth session by random user with http get request and createAsyncThunk.
fetching the user data in App.js on mount hook.
I can see the get request in my network and the new fetched state in redux dev tool,
but my TopBar.js useSelector return the initial state before the fetch.
TopBar.js user log:
App.js:
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchRandomUserData())
}, [dispatch]);
authSlice.js:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
isLoggedIn: true,
user: {},
loading: false,
error: null,
};
export const fetchRandomUserData = createAsyncThunk(
'auth/fetchRandomUser',
async () => {
try {
const response = await fetch('https://randomuser.me/api/');
const data = await response.json();
return data.results[0];
} catch (error) {
throw Error(error);
}
}
);
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logout(state, action) {
state.isLoggedIn = false;
},
},
extraReducers: {
[fetchRandomUserData.pending]: (state, action) => {
state.loading = true;
state.error = null;
},
[fetchRandomUserData.fulfilled]: (state, action) => {
console.log("action.payload",action.payload);
state.user = action.payload;
state.loading = false;
},
[fetchRandomUserData.rejected]: (state, action) => {
state.error = action.error.message;
state.loading = false;
},
},
});
export const { logout } = authSlice.actions;
export default authSlice.reducer;
store.js
import { configureStore } from '#reduxjs/toolkit';
// import booksReducer from './reducers/booksReducer';
import booksReducer from './slices/bookSlice';
import authReducer from './slices/authSlice';
const store = configureStore({
reducer: { books: booksReducer, auth: authReducer },
});
export default store;
TopBat.js:
export default function TopBar(): JSX.Element {
const user = useSelector((state: any) => state.auth);
console.log("topbar",user); // returns the initial state
//....
Please make sure that you update react-redux to version 8 if you are using react 18.
There are known cases where components just stop updating if you are using react-redux 7 and lower with react 18.
Related
I'm fetching data from a mongoDB database and then fetch that data from the server and finally render the data to the UI in a specified component. I'm using redux-toolkit for state management.
The problem is when fetching the data from the server it is not visible in the store. Why is the empty array in the initial state still empty after fetching the data? I'm using createSlice Api that generates the action creators and action types and createAsyncThunk Api for the asynchronous task of fetching the data from the server.
Slice reducer
import { createSlice, createAsyncThunk} from '#reduxjs/toolkit'
import axios from 'axios'
const initialState = {
realestate: [],
isSuccess: false,
isLoading: false,
message: '',
}
export const getRealEstates = createAsyncThunk(
'realestate/getRealEstates', async () => {
try {
const response = await axios.get('castles')
return response.data
} catch (error) {
console.log(error)
}
}
)
export const estateSlice = createSlice({
name: 'realestate',
initialState,
reducers: {
reset: (state) => initialState,
},
extrareducers: (builder) => {
builder.addCase(getRealEstates.pending, (state) => {
state.isLoading = true
})
builder.addCase(getRealEstates.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.realestate = action.payload
})
builder.addCase(getRealEstates.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
}
})
export const { reset } = estateSlice.actions
export default estateSlice.reducer
Store
export const store = configureStore({
reducer: {
realestate: realestateReducer,
registered: registerReducer,
},
});
Component
const realestates = useSelector(state => state.realestate)
const { isLoading, realestate, isError, message, isSuccess} = realestates
const dispatch = useDispatch()
useEffect(() => {
dispatch(getRealEstates())
if(realestate){
setShow(true)
}else{
console.log('No data retrieved')
}
}, [dispatch, isError, realestate, message])
It's extraReducers with an uppercase R, your code contains extrareducers.
I am new in redux and redux-toolkit. I am trying to get data from api.
I get error and can not receive data from api. I am using redux-toolkit library.
This is my App.js:
function App() {
const companies = useSelector(state => state.companyList);
console.log(companies)
return (
<div className="App">
<header className="App-header">
{companies.map(company => {
return(
<h1>{company.name}</h1>
)
})}
<h1>hello</h1>
</header>
</div>
);
}
export default App;
This is createSlice.js
const getCompanies = axios.get(
"https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8"
).then((response) => {
console.log(response.data)
return response.data;
}).catch((ex) => {
console.log(ex)
})
export const companySlice = createSlice({
name: "companyList",
initialState: {value: getCompanies},
reducers: {
addCompnay: (state, action) => {},
},
});
export default companySlice.reducer;
Here is store.js
import { configureStore } from "#reduxjs/toolkit";
import companyReducer from "./features/companySlice/compnayList";
export const store = configureStore({
reducer:{
companyList: companyReducer,
}
})
In the browser, I receive this error:
enter image description here
You are making a lot of mistakes here so be sure to check out some tutorials and docs: https://redux-toolkit.js.org/tutorials/quick-start
You need to use createAsyncThunk and handle the response in extraReducers: https://redux-toolkit.js.org/rtk-query/usage/migrating-to-rtk-query#implementation-using-createslice--createasyncthunk
In companySlice:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
export const getCompanies = createAsyncThunk(
"companyList/getCompanies",
async () => {
try {
const response = await axios.get(
"https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8"
);
return response.data;
} catch (error) {
console.error(error);
}
});
const companySlice = createSlice({
name: "companyList",
initialState: {
company: {},
isLoading: false,
hasError: false
},
extraReducers: (builder) => {
builder
.addCase(getCompanies.pending, (state, action) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(getCompanies.fulfilled, (state, action) => {
state.company = action.payload;
state.isLoading = false;
state.hasError = false
})
.addCase(getCompanies.rejected, (state, action) => {
state.hasError = true
state.isLoading = false;
})
}
});
// Selectors
export const selectCompanies = state => state.companyList.company;
export const selectLoadingState = state => state.companyList.isLoading;
export const selectErrorState = state => state.companyList.hasError;
export default companySlice.reducer;
Then you import selectCompanies wherever you want to use it and access it with useSelector.
In App.js:
import { useSelector } from "react-redux";
import { selectCompanies } from "WHEREEVER selectCompanies IS EXPORTED FROM";
function App() {
// Company list state
const companies = useSelector(selectCompanies);
// Rest of the code
.....
.....
.....
.....
}
export default App;
so I have auth reducer and loading reducer. I'd like to set the state in loading reducer whenever the createAsynchThunk in auth reducer is pending. the code look like this:
//auth reducer
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import {callPOSTSignInUserAccount} from "api"
export const signInRequest = createAsyncThunk(
"auth/login",
async (userData: UserDataLogin, thunkAPI) => {
try {
const result = await callPOSTSignInUserAccount(
userData.email,
userData.password
);
const auth = result.data.AuthenticationResult;
const user = result.data.user;
catch(err) {
const result = {
alert: {
type: "error",
message: errMsg
}
}
return thunkAPI.rejectWithValue(result)
}
}
//state
const authState = {
isAuthenticated = true,
errorSignIn = "",
auth: {},
};
//slice for auth
const sliceAuth = createSlice({
name: "auth",
initialState: authState,
reducers: {},
extraReducers: (builder) => {
//Sign in request
.addCase(signInRequest.pending, (state, action) => {
//set loading reducer state from here
})
.addCase(signInRequest.fulfilled, (state, action:any) => {
if (action.payload?.auth !== undefined) {
state.isAuthenticated = true
state.errorSignIn = ""
state.auth = action.payload.auth
}
})
.addCase(signInRequest.rejected, (state, action:any) => {
//also set alert here
})
}
const authReducer = sliceAuth.reducer
export default authReducer
//loading reducer
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const loadingState = {
appLoading: false,
};
const sliceLoading = createSlice({
name: "loading",
initialState: loadingState,
reducers: {
setLoading: (state, action) => {
state.apploading = action.payload
}
})
const reducerLoading = sliceLoading.reducer
export default reducerLoading
from what I read I can't dispatch an action in reducer because it's anti-pattern. I want to change the loading state in loading reducer from the auth reducer.
I can add loading in the auth reducer initial state but it become hard to manage whenever I have more than one reducer in a react component.
I am trying to implement Redux Toolkit and Redux Saga with Firebase. Currently I am stuck with the problem of fetching the data from the Firestore. I have configured the store and everything that is needed for the Redux Saga and Toolkit, now the only problem I have is when I try to fetch the data from the Firestore. I have made also the helper functions which can and already did help me, but just without the Redux involved.
This is what I mean by this:
store.js
import createSagaMiddleware from "redux-saga";
import { configureStore } from "#reduxjs/toolkit";
import allQuestionsReducer from "./allQuestionsState";
import allQuestionsSaga from "./redux-saga/allQuestionsSaga";
const saga = createSagaMiddleware();
const store = configureStore({
reducer: {
allQuestions: allQuestionsReducer,
},
middleware: [saga],
});
saga.run(allQuestionsSaga);
export { store };
AllQuestionsState.js
import { createSlice } from "#reduxjs/toolkit";
export const allQuestionSlice = createSlice({
name: "allQuestions",
initialState: {
allQuestions: [],
isLoading: false,
error: null,
},
reducers: {
getAllQuestionsFetch: (state, action) => {
state.isLoading = true;
},
getAllQuestionsSuccess: (state, action) => {
state.allQuestions = action.payload;
state.isLoading = false;
},
getAllQuestionsError: (state, action) => {
state.error = action.payload;
state.isLoading = false;
},
},
});
export const {
getAllQuestionsFetch,
getAllQuestionsSuccess,
getAllQuestionsError,
} = allQuestionSlice.actions;
export default allQuestionSlice.reducer;
AllQuestionsSaga.js
import { call, put, takeEvery } from "redux-saga/effects";
import { getQuestions } from "../../utils/firebase-functions/firebase-functions";
import { getAllQuestionsSuccess } from "../allQuestionsState";
function* workGetAllQuestions() {
const response = yield new Promise(getQuestions);
yield put(getAllQuestionsSuccess(response));
}
function* allQuestionsSaga() {
yield takeEvery("allQuestions/getAllQuestionsFetch", workGetAllQuestions);
}
export default allQuestionsSaga;
Helper Function
export const getQuestions = async (formData) => {
let error;
try {
const success = await onSnapshot(collection(firebaseDatabase, "questions"));
return success.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
} catch (error) {
error = error.message;
return error;
}
};
Where the action is dispatched
const dispatch = useDispatch();
const data = useSelector(
(state) => state.allQuestions.allQuestions
);
useEffect(() => {
dispatch(getAllQuestionsFetch());
}, []);
console.log(data);
Does anyone has any clue on how to fix this. I am getting the [] even though I have data in Firestore.
I am trying to create a notification Component.
My notification Component is at root level and when ever user tries to login the process of the the async function is relayed to him i.e. pending fulfilled or rejected.
The Problem is that I don't know how to call notification reducer from userSlice or even if its possible is this a good way or not.
User Slice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const initialUserState = {
currentUser:null
}
export const getUser = createAsyncThunk(
'user/getUser',
async (endpoint, data) => {
return(
await axios.post(endpoint, data)
.then(res =>{
return res.data.user
})
.catch(error =>{
throw Error(error.response.data)
})
)
}
)
const userSlice = createSlice({
name: 'user',
initialState: initialUserState,
reducers:{
currentUser(state, action){
state.currentUser = action.payload
}
},
extraReducers:
(builder) => {
builder.addCase(getUser.pending, ()=>{
console.log("authing")
})
builder.addCase(getUser.fulfilled, (state, action)=>{
state.currentUser = action.payload
console.log("fulfilled")
})
builder.addCase(getUser.rejected, (state, action)=>{
console.log("failed")
alert(action.error.message)
})
}
})
export const userActions = userSlice.actions;
export default userSlice.reducer;
notificationSlice
import React from 'react'
import { useSelector } from 'react-redux'
function Notification() {
const toast = useSelector(state => state.notification)
console.log(toast)
return (
toast.active &&
<div className="notification" style={{backgroundColor:toast.backgroundColor}} >
{toast.message}
</div>
)
}
export default Notification
I want to change notification state when ever one of the extra reducer in userSlice is called
I think you are thinking about this almost exactly backwards. What you want is NOT to "call notification reducer from userSlice," but to LISTEN for userSlice actions in a notificationSlice.
I have done something like the following, which I think would work well for you:
import { createEntityAdapter, createSlice, isAnyOf } from '#reduxjs/toolkit'
const notificationsAdapter = createEntityAdapter()
const initialState = notificationsAdapter.getInitialState({
error: null,
success: null,
})
const notificationsSlice = createSlice({
name: 'notifications',
initialState,
reducers: {
clearNotifications: state => {
state.error = null
state.success = null
},
setError: (state, action) => {
state.success = null
state.error = action.payload
},
setSuccess: (state, action) => {
state.success = action.payload
state.error = null
},
},
extraReducers: builder => {
builder
.addMatcher(
isAnyOf(
getUser.fulfilled,
),
(state, action) => {
state.error = null
state.success = action.payload.message
}
)
.addMatcher(
isAnyOf(
getUser.rejected
// can add as many imported actions
// as you like to these
),
(state, action) => {
state.error = action?.payload
state.success = null
}
)
// reset all messages on pending
.addMatcher(
isAnyOf(
getUser.pending
),
(state, action) => {
state.error = null
state.success = null
}
)
},
})
export const { clearNotifications, setError, setSuccess } = notificationsSlice.actions
export default notificationsSlice.reducer
export const getErrorMsg = state => state.notifications.error
export const getSuccessMsg = state => state.notifications.success
Having added the above, you can now create a notification component that listens for
const error = useSelector(getErrorMsg)
const success = useSelector(getSuccessMsg)
and shows the messages accordingly.
Caveat:
My notificationSlice code assumes that when an action completes, there will exist a "message" object on the success payload. So, on my async thunks, if my api does not return this I must add this explicitly to the result.