callback function after dispatch function in react-redux - reactjs

I am trying to implement a simple login form, that gets username and password as input.
User.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
set,
reject,
verify,
isLoggedIn,
} from './userSlice';
export function User() {
const isUserLoggedIn = useSelector(isLoggedIn);
const dispatch = useDispatch();
const [loginError, setLoginError] = useState(false);
return (
<div>
<div> {loginError? 'Invalid credentials': ''}</div>
{/* Form elements here */}
<button
onClick={() => dispatch(verify())}
>
Verify User
</button>
</div>
);
}
userSlice.js
import { createSlice } from '#reduxjs/toolkit';
export const userSlice = createSlice({
name: 'user',
initialState: {
loggedIn: false,
email: '',
name: '',
token:''
},
reducers: {
set: (state, action) => {
return Object.assign({}, state, action.payload);
},
reject: (state, action) =>{
state.value = action.payload
}
},
});
export const { set, reject } = userSlice.actions;
export const verify = user => dispatch => { // For making an api call to verify the credentials are correct
axios.post('login', data).then(function(){
dispatch(set({loggedIn:true}))
}).catch(function(){
dispatch(reject({loggedIn:false}))
});
};
export const isLoggedIn = state => state.user.loggedIn;
export default userSlice.reducer;
All codes are working fine.
Now if the api call fails, i need to update the state loginError to true. How it can be done from userSlice.js file to User.js file.

Something like that I guess
User.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
set,
reject,
verify,
isLoggedIn,
isLoginError, //<-------------
} from './userSlice';
export function User() {
const isUserLoggedIn = useSelector(isLoggedIn);
const dispatch = useDispatch();
const isLoginError = useSelector(isLoginError); //<----------------
return (
<div>
<div> {isLoginError ? 'Invalid credentials': ''}</div> //<-------------
{/* Form elements here */}
<button
onClick={() => dispatch(verify())}
>
Verify User
</button>
</div>
);
}
userSlice.js
import { createSlice } from '#reduxjs/toolkit';
export const userSlice = createSlice({
name: 'user',
initialState: {
loggedIn: false,
email: '',
name: '',
token:''
},
reducers: {
set: (state, action) => {
return Object.assign({}, {...state, loginError:false}, action.payload); //<------
},
reject: (state, action) =>{
return Object.assign({}, {...state, loginError:true}, action.payload); //<-------
}
},
});
export const { set, reject } = userSlice.actions;
export const verify = user => dispatch => { // For making an api call to verify the credentials are correct
axios.post('login', data).then(function(){
dispatch(set({loggedIn:true}))
}).catch(function(){
dispatch(reject({loggedIn:false}))
});
};
export const isLoggedIn = state => state.user.loggedIn;
export const isLoginError = state => state.user.loginError; //<----------
export default userSlice.reducer;

Related

How to get Data from api using axios in redux-toolkit

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;

Updating redux slice with multiple values

I am using a redux slice in a react project to store information about the current user. However, I am getting the following error when loading the page:
TypeError: Cannot read properties of undefined (reading 'name')
Dashboard.js:
export function Dashboard() {
const currentUser = useSelector(state => state.currentUser);
const dispatch = useDispatch();
return (
<div>
<p>{currentUser.name}</p>
<p>{currentUser.description}</p>
</div>
<Button onClick={() => dispatch(setCurrentUser(name, description))}>Set user</Button>
);
}
currentUserSlice.js:
import { createSlice } from '#reduxjs/toolkit'
export const currentUser = createSlice({
name: 'currentUser',
initialState: {
name: "",
description: ""
},
reducers: {
setCurrentUser: (state, action) => {
return state = {
name: action.payload.name,
description: action.payload.description
}
}
}
})
// each case under reducers becomes an action
export const { setCurrentUser } = currentUserSlice.actions
export default currentUserSlice.reducer
store.js:
import { configureStore } from '#reduxjs/toolkit'
import currentUserReducer from './currentUserSlice'
export default configureStore({
reducer: {
currentUser: currentUserReducer,
}
})
Update
I did not properly import the reducer into store.js. However, the dispatch is not updating the data now that I have imported it
You are sending two values in your payload instead of an object. Please update your dispatched function to send an object instead:
export function Dashboard() {
const currentUser = useSelector(state => state.currentUser);
const dispatch = useDispatch();
return (
<div>
<p>{currentUser.name}</p>
<p>{currentUser.description}</p>
</div>
<Button onClick={() => dispatch(setCurrentUser({name, description}))}>Set user</Button>
);
}

React/redux toolkit fetchapi issue -- I am trying to get userlist and its showing undefined

in below react/ redux toolkit app , in userslice file I am trying to export my entities piece of state and import in main file , when I try to console in comes undefined , not sure why its undefined ,
but When I trying to pull the {entities} directly form state its working fine, would like to know why its showing undefined in console, if anyone knows please check ?
below is the state part which I am getting undefined
export const { SelectUserList } = (state) => state.userslist.entities;
below is the console which shows undefiend
console.log(SelectUserList);
my slice file is below
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
export const fetchuserlist = createAsyncThunk(
"userslist/fetchusers",
async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
return users;
}
);
const userSlice = createSlice({
name: "userslist",
initialState: {
entities: [],
loading: false,
},
reducers: {
// userAdded(state, action) {
// state.entities.push(action.payload);
// },
},
extraReducers: {
[fetchuserlist.pending]: (state, action) => {
state.loading = true;
},
[fetchuserlist.fulfilled]: (state, action) => {
state.entities = [...state.entities, ...action.payload];
state.loading = false;
},
[fetchuserlist.rejected]: (state, action) => {
state.loading = false;
},
},
});
export const { userAdded, userUpdated, userDeleted } = userSlice.actions;
export const { SelectUserList } = (state) => state.userslist.entities;
export default userSlice.reducer;
me component file is below
import React from "react";
import { fetchuserlist, SelectUserList } from "./features/userSlice";
import { useDispatch, useSelector } from "react-redux";
const Mainlist = () => {
const dispatch = useDispatch();
const { entities } = useSelector((state) => state.users);
console.log(SelectUserList);
return (
<div>
<button onClick={() => dispatch(fetchuserlist())}>Load list</button>
{entities?.map((s) => (
<div className="user_list" key={s.id}>
<h4>{s.id}</h4>
<h6>{s.email}</h6>
<button>delete</button>
<button>edit</button>
</div>
))}
</div>
);
};
export default Mainlist;
In your slice you are declaring the function in the wrong way. You should declare the SelectUserList function like this:
export const SelectUserList = (state) => state.userslist.entities;
In your component file you should access the entities returned in SelectUserList with a useSelector. Like this:
const usersListEntities = useSelector(SelectUserList);

react/redux app - TypeError: Cannot read property 'user' of undefined

I am creating a react app, when getting data from redux. I am facing the below error message in browser. please check and let me know what am I missing.
I am using create-react-app redux-toolkit setup template to create the app
Here is my app.js:
import React from "react";
import { useSelector } from "react-redux";
import "./App.css";
import { selectUser } from "./features/userSlice";
import Header from "./components/Header";
import Sidebar from "./components/Sidebar";
import Feed from "./components/Feed";
import Login from "./components/Login";
function App() {
const user = useSelector(selectUser);
return (
<div className="App">
<Header />
{ !user ? (
<Login />
) : (
<div className="main_content">
<Sidebar />
<Feed />
</div>
)}
</div>
);
}
export default App;
below you can find the redux reducer and actions
import { createSlice } from '#reduxjs/toolkit';
export const userSlice = createSlice({
name: 'user',
initialState: {
user: null,
},
reducers: {
login: (state, action) => {
state.value = action.payload
},
logout: (state, action) => {
state.user = null
}
},
});
export const { login, logout } = userSlice.actions;
export const selectUser = (state) => state.user.user;
export default userSlice.reducer;
below is the screenshot of error which. I'am getting when running the app
Working example for you, be sure you configured your store correctly. You should separate this into responding files.
import React from "react";
import { combineReducers, createStore, createSlice } from "#reduxjs/toolkit";
import { connect, Provider, useDispatch, useSelector } from "react-redux";
// your part
const userSlice = createSlice({
name: "user",
initialState: {
user: null
},
reducers: {
login: (state, action) => {
state.user = action.payload;
},
logout: (state, action) => {
state.user = null;
}
}
});
const { login, logout } = userSlice.actions
const selectUser = (state) => state.user.user;
// what I added
const reducer = combineReducers({
user: userSlice.reducer
});
const store = createStore(reducer);
const Main = (props) => {
const dispatch = useDispatch() // I used this to check if reducers work
const user = useSelector( selectUser )
return (
<div onClick={ () => { dispatch(login({name: "Adam"})) }}>
{ !user ? "LOGIN" : "DASHBOARD "}
</div>
)
}
const mapStateToProps = (state) => ({
user: state.user
});
const Container = connect(mapStateToProps, { login, logout })(Main);
function App() {
return (
<Provider store={store}>
<Container/>
</Provider>
);
}
export default App;

How to access a specific reducer in react-testing-library

I've been trying to implement unit testing with react using the react-testing-library
I want to test my login component that use useSelector and useDispatch hooks from react-redux
The problem is that when I use this function to pass the store to in my login.test.js is not recognizing the reducer and show me this error:
An error occurred while selecting the store state: Cannot read property 'isLoading' of undefined.
const isLoadingAuth = useSelector(state => state.Auth.isLoading);
I use combineReducers in my store (the app has a lot of reducers) in order to access in that specific reducer "Auth" but I don't know how to use them in my login.test.js
How can I access to my Auth reducer in my login.test.js file?
This is my login.jsx
const LoginForm = () => {
const [values, setValues] = useState({ email: "", password: "" });
const dispatch = useDispatch();
function handleChange(e) {
const { name, value } = e.target;
setValues({ ...values, [name]: value });
}
function submitData(e) {
e.preventDefault();
dispatch(actions.AuthUser(values));
}
const isLoadingAuth = useSelector(state => state.Auth.isLoading);
const error = useSelector(state => state.Auth.err);
const isAuthSucess = useSelector(state => state.Auth.isAuthSuccess);
if (isAuthSuccess) {
<Redirect to="/dashboard" />;
}
return (
<>
<div>
<form onSubmit={submitData}>
<Input
label="Email"
name="email"
value={values.email}
change={handleChange}
/>
<Input
label="Password"
name="password"
type="password"
value={values.password}
change={handleChange}
/>
<div>
<button>Entrar</button>
</div>
</form>
</div>
</>
);
};
My AuthReducer.js
import * as actionTypes from "../actions/Auth/types";
import { updateObject } from "../store/utility";
export const initalState = {
authData: null,
isLoading: false,
isAuthSuccess: null,
err: null
};
const authStart = state => {
return updateObject(state, {
isLoading: true,
err: null
});
};
const authFail = (state, action) => {
return updateObject(state, {
isLoading: false,
err: action.err
});
};
const auth = (state, action) => {
return updateObject(state, {
isLoading: false,
authData: action.authData,
isAuthSuccess: true
});
};
export function reducer(state = initalState, action) {
switch (action.type) {
case actionTypes.START_AUTH_REQ: {
return authStart(state, action);
}
case actionTypes.FAIL_AUTH_REQ: {
return authFail(state, action);
}
case actionTypes.AUTH: {
return auth(state, action);
}
default:
return state;
}
}
export default reducer;
And my Login.test.js
import React from "react";
import { createStore, combineReducers } from "redux";
import { Provider } from "react-redux";
import { render, cleanup, fireEvent } from "#testing-library/react";
import rootReducer from "../../../../reducers";
import "#testing-library/jest-dom/extend-expect";
import LoginForm from "./Form";
function renderWithRedux(
ui,
{
initialState,
store = createStore(combineReducers(rootReducer, initialState))
} = {}
) {
return {
...render(<Provider store={store}>{ui}</Provider>),
// adding `store` to the returned utilities to allow us
// to reference it in our tests (just try to avoid using
// this to test implementation details).
store
};
}
test("can render with redux with custom initial state", () => {
const { getByTestId, getByText } = renderWithRedux(<LoginForm />, {
initialState: { isLoading: false }
});
});
Your initial state is for you entire store so needs to match the structure of your root reducer:
test("can render with redux with custom initial state", () => {
const { getByTestId, getByText } = renderWithRedux(<LoginForm />, {
initialState: { Auth: { isLoading: false } }
});
});
I know it is a late reply but might help someone.
The problem with the above code is that it is using combineReducer correctly but passing state of AuthReducer only.
The combineReducer is expecting a consolidated state. For example:
const state = {
auth: initialState,
app: {
temp: {
// Some state
}
}
}
function renderWithRedux(ui: any, state: any) {
const store = createStore(rootReducer, state)
return {
...render(<Provider store={ store } > { ui } < /Provider>),
store,
}
}
test('can render with redux with custom initial state', () => {
const { getByTestId, getByText } = renderWithRedux(<LoginForm />, {
...state,
auth: {
...initialState, loading: true
}
});
});

Resources