React Context API - dispatch is not a function - javascript

Implementing a Log in system with React Context API. When submitted the form with user credentials, getting an error.
Error:
Unhandled Rejection (TypeError): dispatch is not a function
loginCall
src/apiCalls.js:4
1 | import axios from "axios";
2 |
3 | export const loginCall = async (userCredential, dispatch) => {
> 4 | dispatch({ type: "LOGIN_START" });
5 | try {
6 | const res = await axios.post("auth/login", userCredential);
7 | dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
Files:
Login.jsx
import React, { useContext, useRef } from "react";
import bgImg from "../assets/login/tw-bg.png";
import "./styles/login.css";
import TwitterIcon from "#mui/icons-material/Twitter";
import { loginCall } from "../apiCalls";
import {AuthContext} from '../context/AuthContext'
function Login() {
const email = useRef();
const password = useRef();
const context = useContext(AuthContext);
const handleSubmit = (e) => {
e.preventDefault();
loginCall(
{ email: email.current.value, password: password.current.value },
context.dispatch
);
};
console.log(context.user)
return (
<div className="login-container">
<div className="left">
<TwitterIcon className="left-tw-icon" style={{ fontSize: 250 }} />
<img src={bgImg} alt="background" className="login-background" />
</div>
<div className="right">
<TwitterIcon className="right-tw-icon" color="primary" />
<div className="main-title-container">
<span className="main-title-span">Şu anda olup bitenler</span>
</div>
<div className="secondary-title-container">
<span className="secondary-title-span">Twitter'a bugün katıl.</span>
</div>
<div className="form-container">
<form onSubmit={handleSubmit}>
<input type="email" placeholder="Username" ref={email} />
<input type="password" placeholder="Password" ref={password} />
<button type="submit">Log in</button>
</form>
</div>
</div>
</div>
);
}
export default Login;
apiCalls.js
import axios from "axios";
export const loginCall = async (userCredential, dispatch) => {
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("auth/login", userCredential);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
} catch (error) {
dispatch({ type: "LOGIN_FAILURE", payload: error });
}
};
AuthContext.js
import { Children, createContext, useReducer } from "react";
import AuthReducer from "./AuthReducer";
const INITIAL_STATE = {
user: null,
error: null,
isFetching: false,
};
export const AuthContext = createContext(INITIAL_STATE);
export const AuthContextProvider = ({children}) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
return (
<AuthContextProvider
value={{
user: state.user,
error: state.error,
isFetching: state.isFetching,
dispatch,
}}
>
{children}
</AuthContextProvider>
);
};
Any help appreciated.
Edit: AuthReducer and AuthActions added.
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
error: null,
isFetching: true,
};
case "LOGIN_FAILURE":
return {
user: null,
error: action.payload,
isFetching: false,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
error: null,
isFetching: false,
};
}
};
export default AuthReducer
```
export const LOGIN_START = (userCredentials) => {
type:"LOGIN_START"
}
export const LOGIN_SUCCESS = (user) => ({
type:"LOGIN_SUCCESS",
payload:user
})
export const LOGIN_FAILURE = (err) => ({
type:"LOGIN_FAILURE",
})
```
Some comment to handle "mostly code error." It seems all clear to me. But the problem still continues. If there is a point I am missing, it would be great to learn from you.
Thanks in advance.

you can try this:
import axios from "axios";
export const loginCall = (userCredential) => {
return async (dispatch, getState) => {
dispatch(LOGIN_START());
try {
const res = await axios.post("auth/login", userCredential);
dispatch(LOGIN_SUCCESS(res.data);
} catch (error) {
dispatch(LOGIN_FAILURE());
}
};
in your error you see this message:
dispatch is not a function
loginCall
src/apiCalls.js:4
it is quite straight forward. It tells you what the problem is which dispatch. it is loginCall and and loginCall is in apiCalls. try to focus on the error that you see.
dispatch basically fires the function that you want to call. In your syntax, you put an object in the dispatch({some stuff}), but it actually expects you to call a function there.
I am not sure if my solution will fix everything but in the worst case, you should at least get a different error. :)

I had the same problem recently working on a similar project.
npm install cors
then in your index.js file with your express declarations,
const cors = require('cors'),
then at the very top your code but below your imports,
app.use(cors())
That will probably fix it.

Related

localStorage persist data in react MERN Stack App

I am building a full-stack React App with mongodb and I am trying to persist user data when I log in using localStorage. However, the localStorage is only storing the null value. I have failed to figure out what I am doing wrong, a little would be golden at this point. I am using contextApi for this, and I have provided the code below.... Please save me, your help will be heavily appreciated. The login route works perfectly fine in the backend.
I expected the localstorage to save the user information when I login. The login in works, and as a result, the login proceeds to the home page as expected ,but the user information does not persist in the localstorage but rather returns a null value.
Here is the login page code
import React, { useState, useContext } from 'react'
import { AuthContext } from '../../context/AuthContext';
import axios from "axios";
import { useNavigate } from "react-router-dom";
import "./login.css";
const Login = () => {
const [credentials, setCredentials] = useState({
username: undefined,
password: undefined
});
const { user ,loading, error, dispatch } = useContext(AuthContext);
const navigate = useNavigate();
const handleChange = (e) => {
setCredentials((prev) => ({ ...prev, [e.target.id]: e.target.value}));
}
const handleClick = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/auth/login", credentials);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data })
navigate("/")
} catch(err) {
dispatch({type: "LOGIN_FAILURE", payload: err.response.data})
}
}
console.log(user);
return (
<div className='login'>
<div className="lContainer">
<input type="text" placeholder='username' id='username' onChange={handleChange} className="lInput" />
<input type="password" placeholder='password' id='password' onChange={handleChange} className="lInput" />
<button disabled={loading} onClick={handleClick} className="lButton">Login</button>
{error && <span>{error.message}</span>}
</div>
</div>
)
}
export default Login;
This is the contextApi code for the user-authentication
import { createContext, useReducer, useEffect } from "react";
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
loading: false,
error: null
};
export const AuthContext = createContext(INITIAL_STATE);
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
loading: true,
error: null
};
case "LOGIN_SUCCCES":
return {
user: action.payload,
loading: false,
error: null
};
case "LOGIN_FAILURE":
return {
user: null,
loading: false,
error: action.payload
};
case "LOGOUT":
return {
user: null,
loading: false,
error: null
}
default:
return state
}
};
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
/**
* ! save user to localstorage
*/
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
loading: state.loading,
error: state.error,
dispatch
}}
>
{children}
</AuthContext.Provider>
)
}
I expected the localstorage to save the user information when I login. The login in works, and as a result, the login proceeds to the home page as expected ,but the user information does not persist in the localstorage but rather returns a null value.

React context API authentication not working

I'm trying to set up a simple authentication system using react's context api. I have two react pages here using react router, Login.js and App.js.
Here's App.js, I want it to use the isAuthenticated boolean from the context api to decide which page to render:
App.js:
function App() {
const { isAuthenticated } = useContext(AuthContext);
return (
<div className="App">
<AuthContextProvider>
<Router>
<Routes>
<Route path="/" element={isAuthenticated ? <Home /> : <Login />} />
<Route path="/register" element={isAuthenticated ? <Home /> : <Register />} />
</Routes>
</Router>
</AuthContextProvider>
</div>
);
}
Here's how login.js authenticates the user:
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { dispatch } = useContext(AuthContext);
const handleClick = (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const credentials = {
email: email,
password: password,
}
//Make login request to the server, and use the userID it sends back to authenticate the user
//Currently has a bug where userID is blank on first call, but works on later calls
axios.post('/auth/login', credentials)
.then(response => {
dispatch({ type: "LOGIN_SUCCESS", payload: response.data });
// console.log(userID);
// console.log(isAuthenticated);
})
.catch(err => {
console.log(err)
dispatch({ type: "LOGIN_FAILURE", payload: err });
});
}
catch(err) {
console.log(err);
}
}
Now there are 2 issues here:
In login.js, when I click the login button that calls handleClick, and I do console.log(isAuthenticated), it logs false the first time. Any time after that it will log true.
In App.js, isAuthenticated never changes to true, even while login.js will console.log it as true, so the user is never brought to the home page.
I've been struggling with this for a while now, and I just can't figure out what's going wrong here. I think it may have something to do with App.js not rerendering after the user logs in but I'm not sure.
I also have three files handling the authorization context. I don't think these are causing the issue but I will provide the code just in case I'm missing something
AuthContext.js:
import { createContext, useReducer } from "react";
import AuthReducer from "./AuthReducer";
const initialState = {
userID: null,
isAuthenticated: false,
error: false,
}
export const AuthContext = createContext(initialState);
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, initialState);
return (
<AuthContext.Provider value={{
userID: state.userID,
isAuthenticated: state.isAuthenticated,
error: state.error,
dispatch,
}}>
{children}
</AuthContext.Provider>
)
}
AuthActions.js:
export const LoginStart = (userCredentials) => ({
type: "LOGIN_START",
});
export const LoginSuccess = (userID) => ({
type: "LOGIN_SUCCESS",
payload: userID,
isAuthenticated: true,
});
export const LoginFailure = (error) => ({
type: "LOGIN_FAILURE",
payload: error,
isAuthenticated: false,
});
AuthReducer.js:
const AuthReducer = (state, action) => {
switch(action.type) {
case "LOGIN_START":
return {
userID: null,
isAuthenticated: false,
error: false,
}
case "LOGIN_SUCCESS":
return {
userID: action.payload,
isAuthenticated: true,
error: false,
}
case "LOGIN_FAILURE":
return {
userID: null,
isAuthenticated: false,
error: action.payload,
}
default:
return state;
}
}
export default AuthReducer;
Any help is appreciated, thanks!
I figured this out, I simply had to move the AuthContext Provider out of App.js and into index.js

useEffect hook is turning into infinite loop even when the dependency is not changing all the time

Below is my component in reactjs.
import React, { useState, useEffect } from 'react';
import { Link, Redirect } from 'react-router-dom';
import { connect, useDispatch, useSelector } from 'react-redux';
import { loginUserAction } from '../actions/authenticationActions';
import { setCookie } from '../utils/cookies';
const LoginPage = () => {
const [isSuccess, setSuccess] = useState(false);
const [message, setMessage] = useState('');
const dispatch = useDispatch();
const login = useSelector(state => state.login.response);
console.log(login);
useEffect(() => {
if (login !== undefined) {
setSuccess(login.success);
setMessage(login.message);
if (isSuccess) {
setCookie('token', login.token, 1);
}
}
}, [login]);
const onHandleLogin = (event) => {
event.preventDefault();
const email = event.target.email.value;
const password = event.target.password.value;
dispatch(loginUserAction({
email, password,
}));
}
return (
<div>
<h3>Login Page</h3>
{!isSuccess ? <div>{message}</div> : <Redirect to='dashboard' />}
<form onSubmit={onHandleLogin}>
<div>
<label htmlFor="email">Email</label>
<input type="email" name="email" id="email" />
</div>
<div>
<label htmlFor="password">Password</label>
<input type="password" name="password" id="password" />
</div>
<div>
<button>Login</button>
</div>
</form>
Don't have account? <Link to='register'>Register here</Link>
</div>
);
};
export default LoginPage;
It logs user in. As you can see I am using hooks. When I console.log login from useSelector hook, it console's the updated state. Then the useEffect hook gets called. But the problem is the login is not updating all the time. But still useEffect goes into a loop. What am I missing and how can I fix this?
UPDATE
Below is my reducer
import * as types from '../actions';
export default function(state = [], action) {
const response = action.response;
switch(action.type) {
case types.LOGIN_USER_SUCCESS:
return { ...state, response };
case types.LOGIN_USER_ERROR:
return { ...state, response };
default:
return state;
}
};
Here is the action.
import * as types from './index';
export const loginUserAction = (user) => {
return {
type: types.LOGIN_USER,
user
}
};
A possible solution would be to destructure the object to make the comparison easier
const {message = '', success = false, token = ''} = useSelector(state => state.login.response || {}); //should prevent the error, of response is undefined
console.log(message, success);
useEffect(() => {
//there are other condition options like maybe if(message?.length)
if (message) {
setMessage(message);
}
// Can move setSuccess out of the if, to setSuccess even when it is falsy
if (success) { //note that using isSuccess here might not work cause the state might be the old one still
setSuccess(success)
setCookie('token', token, 1);
}
}, [message, success, token]); //having scalar values (string and boolean) will prevent the loop.
if (login && !isSuccess) { // Here
setSuccess(login.success);
setMessage(login.message);
if (isSuccess) {
setCookie('token', login.token, 1);
}
}
Try to add this and see if this works
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
loading: false,
error: null,
user: null,
isUserLogged: false,
};
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
userAuthStart(state, action) {
return {
...state,
loading: true,
error: null,
user: null,
isUserLogged: false,
};
},
userAuthSuccess(state, { payload }) {
return { ...state, loading: false, user: payload, isUserLogged: true };
},
userAuthFail(state, { payload }) {
return { ...state, loading: false, error: payload, isUserLogged: false };
},
userLogout(state) {
return {
...state,
loading: false,
error: null,
user: null,
isUserLogged: false,
};
},
},
});
export const {
userAuthStart,
userAuthSuccess,
userAuthFail,
userLogout,
} = authSlice.actions;
export default authSlice.reducer;
I use #reduxjs/toolkit for redux.
You can declare and update state of isUserLogged or something to true if user is logged successfully. Then, you can use useSelector to use it inside components.
How to use
const { isUserLogged } = useSelector(state => state.auth)

REACT/REDUX Action not getting dispatched

What I am tying to do is when the user clicks on sign in button my action gets dispatch with email and password.
But, my action is not getting dispatched. Like when I checked my redux-dev-tools it is not showing anything:
There are no error message in console. I checked other answer's but nothing helped.
Here is the source code:
LoginScreen.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import ErrorMessage from "../../components/ErrorMessage/ErrorMessage";
import Loader from "../../components/Loader/Loader";
import { login } from "../../redux/actions/userActions";
import "./LoginScreen.scss";
const LoginScreen = ({ location, history }) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const dispatch = useDispatch();
const userLogin = useSelector((state) => state.userLogin);
const { loading, error, userInfo } = userLogin;
const redirect = location.search ? location.search.split("=")[1] : "/";
useEffect(() => {
if (userInfo) {
history.push(redirect);
}
}, [history, userInfo, redirect]);
const submitHandler = (e) => {
e.preventDefault();
dispatch(login(email, password));
};
return (
<>
<div className="login-container">
<div className="login-form">
<h1>Login</h1>
{loading ? (
<Loader />
) : error ? (
<ErrorMessage error={error} />
) : (
<form onSubmit={submitHandler}>
<div className="login-form-items">
<input
className="login-input"
type="email"
placeholder="Email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
className="login-input"
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit" value="submit">
Login
</button>
<h4>OR</h4>
<div className="login-form-social">
<button className="social">
<img
className="googleLogo"
src="/logo/google.svg"
alt="G"
/>{" "}
Login with Google
</button>
<button className="social social-github">
<img
className="githubLogo"
src="/logo/github.svg"
alt="GH"
/>{" "}
Login with GitHub
</button>
</div>
</div>
</form>
)}
</div>
</div>
</>
);
};
export default LoginScreen;
userAction.js
import axios from "axios";
import {
USER_LOGIN_FAIL,
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
} from "../constants/userConstants";
export const login = () => (email, password) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const config = {
headers: {
"Content-Type": "appllication/json",
},
};
const { data } = await axios.post(
"/api/users/login",
{ email, password },
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
userReducer.js
import {
USER_LOGIN_FAIL,
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
USER_LOGOUT,
} from "../constants/userConstants";
export const userLoginReducer = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return { loading: true };
case USER_LOGIN_SUCCESS:
return { loading: false, userInfo: action.payload };
case USER_LOGIN_FAIL:
return { loading: false, error: action.payload };
case USER_LOGOUT:
return {};
default:
return state;
}
};
store.js
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
// reducers
import { userLoginReducer } from "./reducers/userReducers";
const reducer = combineReducers({
userLogin: userLoginReducer,
});
const userInfoFromStorage = localStorage.getItem("userInfo")
? JSON.parse(localStorage.getItem("userInfo"))
: null;
const initialState = {
userLogin: { userInfo: userInfoFromStorage },
};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
You've defined your action wrong. With redux-thunk you define your actions like this:
export const login = (email, password) => async (dispatch) => {
// your action code
};
// The above code is equivalent to
export const login = (email, password) => {
return async (dispatch) => {
// your action code
}
}
Not like this:
export const login = () => (email, password) => async (dispatch) => {
// your action code
};
// The above code is equivalent to
export const login = () => {
return (email, password) => {
return async (dispatch) => { // this is wrong
}
}
}
So your action is returning a function which then returns another function.
The way you use it caught my attention. Out of general use. Generally, api operations are done with packages such as saga or thunk. Action is only used as a hyperlink. I suggest you review the article below. I think this build will solve your problem.
https://blog.devgenius.io/reactjs-simple-understanding-redux-with-redux-saga-f635e273e24a

React useEffect Hook does not get called, Redux action does not get fired

Whenever I go to my EditProduct page I am calling useEffect to get the product that has the ID of match.params.id. The problem is the hook is not getting called. I do not know honestly why that is, or why it would be at all, since i am calling it how it should be called. I am also getting a type error saying it can not read prop name of undefined, obvsiously if the product is null it can not read the name of it.
I am also seeing in my redux devtools also that no actions get fired, although loadUser is an action that should always get fired when the site re renders/mount, which i did set up in app.js.
I will paste some of my codes below and a github repo.
https://github.com/tigerabrodi/eBuy
editProduct component
import React, {Fragment, useState, useEffect} from 'react';
import {withRouter} from "react-router-dom";
import {connect} from "react-redux";
import {editProduct, getProduct} from '../../redux/product/product.actions';
import Spinner from '../layout/Spinner';
const EditProduct = ({history, editProduct, match, product: {loading, product}}) => {
useEffect(() => {
getProduct(match.params.id);
}, [getProduct, match.params.id]);
const [formData,
setFormData] = useState({name: product.name, description: product.description, price: product.price, image: ""});
const [showImage, setShowImage] = useState(false);
const [imageName, setImageName] = useState("");
const onChangeImage = e => {
setFormData({...formData, image: e.target.files[0]});
setShowImage(true);
setImageName(e.target.files[0].name);
}
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const onSubmit = e => {
e.preventDefault();
editProduct(formData, history, match.params.id);
}
const {name, description, price} = formData;
return (
<Fragment>
<div className="container">
<div className="row">
{loading && (
<Spinner />
)}
<div className="col text-info font-weight-bold m-2">
*- All Fields Requried!
<form onSubmit={e => onSubmit(e)}>
<div className="form-group m-2">
<label htmlFor="name">Name</label>
<input type="text" placeholder="Enter Products Name" name="name" value={name} onChange={e => onChange(e)} className="form-control" required/>
</div>
<div className="form-group m-2">
<label htmlFor="price">Price</label>
<input type="number" name="price" placeholder="Enter Products Price" value={price} onChange={e => onChange(e)} className="form-control" required/>
</div>
<div class="custom-file m-2">
<input type="file" onChange={e => onChangeImage(e)} class="custom-file-input bg-info" required/>
<label class="custom-file-label">{showImage ? imageName : "Upload Image"}</label>
</div>
<div className="form-group m-2">
<label htmlFor="title">Description</label>
<textarea name="description" onChange={e => onChange(e)} placeholder="Enter Products description" value={description} className="form-control" required/>
</div>
<input type="submit" value="Add Product" className="btn btn-block btn-info"/>
</form>
</div>
</div>
</div>
</Fragment>
);
}
const mapStateToProps = state => ({
product: state.product,
auth: state.auth
});
export default connect(mapStateToProps, {editProduct})(withRouter(EditProduct));
app.js
import './App.css';
import React, {Fragment, useEffect} from 'react';
import {Provider} from "react-redux";
import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
import store from "./redux/store";
import setAuthToken from './utils/setAuthToken';
import { loadUser } from './redux/auth/auth.actions';
import Navbar from './components/layout/Navbar';
import Landing from './components/layout/Landing';
import Alert from './components/layout/Alert';
import Register from './components/auth/Register';
import Login from './components/auth/Login';
import PrivateRoute from './components/routing/PrivateRoute';
import Dashboard from './components/dashboard/Dashboard';
import CreateProduct from './components/product-forms/CreateProduct';
import Products from './components/products/Products';
import EditProduct from './components/product-forms/EditProduct';
if (localStorage.token) {
setAuthToken(localStorage.token)
}
const App = () => {
useEffect(() => {
store.dispatch(loadUser());
}, []);
return (
<Provider store={store}>
<Router>
<Fragment>
<Navbar />
<Alert />
<Route exact path="/" component={Landing} />
<Switch>
<Route exact path="/register" component={Register} />
<Route exact path="/login" component={Login} />
<PrivateRoute exact path="/dashboard" component={Dashboard} />
<PrivateRoute exact path="/add-product" component={CreateProduct} />
<PrivateRoute exact path="/products" component={Products} />
<PrivateRoute exact path="/products/edit/:id" component={EditProduct} />
</Switch>
</Fragment>
</Router>
</Provider>
);
}
export default App;
product actions
import {ProductActionTypes} from "./product.types"
import {setAlert} from "../alert/alert.actions"
import axios from "axios"
// Add A Product
export const addProduct = (productData, history) => async dispatch => {
const formData = new FormData();
formData.append("name", productData.name);
formData.append("description", productData.description);
formData.append("price", productData.price);
formData.append("image", productData.image);
try {
const res = await axios.post("/products", formData);
dispatch({
type: ProductActionTypes.ADD_PRODUCT,
payload: res.data
});
history.push("/dashboard");
dispatch(setAlert("Product created successfully", "success"))
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
})
}
}
// Get all products
export const getAllProducts = page => async dispatch => {
try {
const res = await axios.get(`/products?page=${page}`);
dispatch({
type: ProductActionTypes.GET_PRODUCTS,
payload: {products: res.data.products, totalItems: res.data.totalItems}
})
} catch (err) {
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: err
})
}
}
// Delete a product
export const deleteSingleProduct = id => async dispatch => {
try {
await axios.delete(`/products/${id}`);
dispatch({
type: ProductActionTypes.DELETE_PRODUCT,
payload: id
});
dispatch(setAlert("Product deleted successfully", "success"))
} catch (err) {
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
})
}
}
// Get A Single users products
export const getUserProducts = (id, page) => async dispatch => {
try {
const res = await axios.get(`/products/${id}?page=${page}`);
dispatch({
type: ProductActionTypes.GET_PRODUCTS,
payload: res.data
})
} catch (err) {
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
})
}
}
// Edit a Product
export const editProduct = (productData, history, id) => async dispatch => {
const formData = new FormData();
formData.append("name", productData.name);
formData.append("description", productData.description);
formData.append("price", productData.price);
formData.append("image", productData.image);
try {
const res = await axios.put(`/products/edit/${id}`, formData);
dispatch({
type: ProductActionTypes.UPDATE_PRODUCT,
payload: res.data
});
dispatch(setAlert("Product updated successfully", "success"))
history.push("/dashboard")
} catch (err) {
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
})
}
}
// Get a single product by ID
export const getProduct = id => async dispatch => {
try {
const res = await axios.get(`/products/product/${id}`);
dispatch({
type: ProductActionTypes.GET_PRODUCT,
payload: res.data
});
} catch (err) {
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
});
}
}
product reducer
import {ProductActionTypes} from "./product.types";
const initialState = {
products: [],
totalProducts: null,
product: null,
loading: true,
error: {}
}
const productReducer = (state = initialState, action) => {
const {payload, type} = action;
switch(type) {
case ProductActionTypes.GET_PRODUCTS:
return {
...state,
products: payload.products,
totalProducts: payload.totalItems,
loading: false
}
case ProductActionTypes.GET_PRODUCT:
return {
...state,
product: payload,
loading: false
}
case ProductActionTypes.ADD_PRODUCT:
return {
...state,
products: [payload, ...state.products],
loading: false
}
case ProductActionTypes.UPDATE_PRODUCT:
return {
...state,
products: state.products.map(product => product._id === payload.id ? {product: payload.product} : product),
loading: false
}
case ProductActionTypes.DELETE_PRODUCT:
return {
...state,
products: state.products.filter(product => product._id !== payload),
loading: false
}
case ProductActionTypes.PRODUCT_ERROR:
return {
...state,
error: payload,
loading: false
}
default:
return state;
}
}
export default productReducer
auth actions
import axios from "axios";
import {setAlert} from "../alert/alert.actions"
import {AuthActionTypes} from "./auth.types"
import setAuthToken from "../../utils/setAuthToken"
// Load User
export const loadUser = () => async dispatch => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get('/auth');
dispatch({
type: AuthActionTypes.USER_LOADED,
payload: res.data
});
} catch (err) {
dispatch({
type: AuthActionTypes.AUTH_ERROR
});
}
};
// Register User
export const register = ({ name, email, password }) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ name, email, password });
try {
const res = await axios.post('/auth/signup', body, config);
dispatch({
type: AuthActionTypes.REGISTER_SUCCESS,
payload: res.data
});
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: AuthActionTypes.REGISTER_FAIL
});
}
};
// Login User
export const login = (email, password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post('/auth/signin', body, config);
dispatch({
type: AuthActionTypes.LOGIN_SUCCESS,
payload: res.data
});
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: AuthActionTypes.LOGIN_FAIL
});
}
};
// Logout / Clear Profile
export const logout = () => dispatch => {
dispatch({ type: AuthActionTypes.LOGOUT });
};
auth reducer
import {AuthActionTypes} from "./auth.types";
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: null,
loading: true,
user: null
}
const authReducer = (state = initialState, action) => {
const {type, payload} = action;
switch (type) {
case AuthActionTypes.USER_LOADED:
return {
...state,
isAuthenticated: true,
loading: false,
user: payload
};
case AuthActionTypes.REGISTER_SUCCESS:
case AuthActionTypes.LOGIN_SUCCESS:
localStorage.setItem('token', payload.token);
return {
...state,
...payload,
isAuthenticated: true,
loading: false
};
case AuthActionTypes.REGISTER_FAIL:
case AuthActionTypes.AUTH_ERROR:
case AuthActionTypes.LOGIN_FAIL:
case AuthActionTypes.LOGOUT:
case AuthActionTypes.ACCOUNT_DELETED:
case AuthActionTypes.USER_ERROR:
localStorage.removeItem('token');
return {
...state,
token: null,
isAuthenticated: false,
loading: false
};
default:
return state;
}
}
export default authReducer
I'm pretty sure the useEffect is firing, you should put a log in there and I guarantee you'll see it.
What it looks to me, with the code you posted is that you're taking in the action creator, getProduct and calling it in the hook -- and you're not seeing it dispatch in the store and you're not seeing the work it's doing fire. Based on those issues, it's because you're not dispatching the action.
You need to use react-redux's connect to connect the component to either get dispatch, or pre-bind the action creator to be dispatched, or use useDispatch to get a reference to dispatch and then you need to wrap that dispatch(getProduct(...)).
Unrelated, if getDispatch is a static function (meaning the definition never changes) you would not need, nor should you, pass it as a dependency to useEffect. That dependency array for hooks is not for all the things you reference in the hook, but for all the values that, when they change, should cause the hook to re-run. In other words, if you just boil it down to the product ID then anytime the component is rendered with a new product ID that effect hook would rerun the function. And since getProduct is imported and never reassigned or changed, then useEffect will never re-trigger on that value so you can remove it from the dependency array.
To follow that I do recommend putting dispatch in the dependency array because react-redux does not note in their documentation that the reference to dispatch is static therefore you should not assume that it will not change.
I think the problem is that the withRouter HOC is "blocked" by the connect HOC and doesn't pass the router's data to your component.
You should change the order of HOCS (withRouter first):
withRouter(connect(mapStateToProps)(EditProduct))

Categories

Resources