I am trying to add a reducer to my react native app.
Here is the store constant:
export const USER_PROFILE = 'USER_PROFILE';
Here is the action.js
import {USER_PROFILE} from '../constants/index';
export function userProfile(userReducer) {
return {
type: USER_PROFILE,
payload: {
email: '',
},
};
}
This the userReducer that is causing the error. I keep getting an error of customerReducer is not a function.
import {USER_PROFILE} from '../constants/index';
const initialState = {
userProfile: '',
};
const customerReducer = (state = initialState, action) => {
switch (action.type) {
case USER_PROFILE:
return {
...state,
userProfile: action.payload,
};
default:
return state;
}
};
export default customerReducer;
And I have declared the email as a state... const [email, setEmail] useState('')
calling the reducer here. const customerReducer = useSelector(state => state.userProfile);
Now dispatching it with the useDispatch method.
const dispatch = useDispatch();
...
dispatch(customerReducer(email));
Your action.js should look somthing like this:
import {USER_PROFILE} from '../constants/index';
export function userProfile(userReducer) {
return {
type: USER_PROFILE,
payload: {
email: userReducer.email,
},
};
and when you dispatch you should use the action "userProfile" not the reducer "customerReducer". Your dispatch should look somthing like this:
dispatch(userProfile({email}));
make sure to use curly brackets. beacause you should pass an object not a string.
Related
Error says that it reads isAuthenticated as undefined, even though in my global state I have the variable under state.authReducer.isAuthenticated.
I'm using redux and it appears that I can't access the global state (I think the issue lies in store.js but I really don't know what exactly is the issue). Some fellow learner has posted a similiar (maybe identical) issue, but the answers did not help me as it still reads isAuthenticated as undefined.
store.js:
const initialState = {};
const middleware = [thunk];
const store = legacy_createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
authReducer:
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: null,
loading: true,
user: null,
};
const authReducer = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case LOGIN_SUCCESS:
localStorage.setItem('token', payload.token);
return {
...state,
...payload,
isAuthenticated: true,
loading: false,
};
case LOGIN_FAIL:
localStorage.removeItem('token');
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
};
default:
return state;
}
};
Login.js component:
const Login = ({ loginUser, isAuthenticated }) => {
const [formData, setFormData] = useState({
email: '',
password: '',
});
const { email, password } = formData;
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const onSubmit = async (e) => {
e.preventDefault();
loginUser(email, password);
};
// Redirect if logged in
if (isAuthenticated) {
return <Navigate to='/dashboard' />;
}
return(some JSX form)
Login.propTypes = {
login: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool,
};
const mapStateToProps = (state) => ({
isAuthenticated: state.auth.isAuthenticated,
});
export default connect(null, { loginUser })(Login);
Edit: I found out that if I set connect() function first parameter to null, the component renders, but if I set the parameter to mapStateToProps it doesn't render (inside the component). Still, my issue is the same: isAuthenticated is undefined.
How are you defining rootReducer?
My guess, without looking, is that you're either treating all of the auth reducer as rootReducer, or calling combineReducers({authReducer}). In either case, there won't be a state.auth field, because your store configuration did not define one.
The short fix here is:
const rootReducer = combineReducers({
auth: authReducer
})
The better answer is to use our official Redux Toolkit package and its configureStore API, instead of the legacy createStore API:
const store = configureStore({
reducer: {
auth: authReducer
}
})
// this added `state.auth`, _and_ the thunk middleware,
// _and_ the Redux DevTools, in one function call!
You should also be using RTK's createSlice instead of writing reducers by hand.
first questioner here!
I'm new to React and find it confusing to manage state with redux. From the redux-logger output, it seems that I am successfully changing the redux state regarding a user sign-in but I don't really know how to set it to props, and as such, I'm getting an undefined value for currentUser (which is the prop I want to manage across all my pages). I'm using both withRouter and Redux in an effort to pass user properties to app.js.
It starts with an API call to the backend to see if the user can login, if success then returns an object {isAdmin: "", uId: ""}.
import React from "react";
import { withRouter } from "react-router-dom";
import { setCurrentUser } from "../../redux/user/user-actions";
import { connect } from "react-redux";
// sign-in.jsx
class Login extends React.Component {
constructor(props) {
super(props);
}
onSubmitClick = async (e) => {
e.preventDefault();
fetch("/api/login", {
method: "post",
body: JSON.stringify({
email: "",
password: "",
}),
})
.then((res) => res.json())
.then((user) => {
if (user.error) {
this.setState({ error: user.error });
} else {
// Set the user in redux too:
this.props.dispatch(setCurrentUser(user));
// Redirect to main page after login
this.props.history.push({
pathname: "/",
search: "?uid=" + user.key + "?admin=" + user.admin,
state: { userId: user.key, isAdmin: user.admin },
});
}
});
};
render() {
return (...)
}
const mapStateToProps = ({ user }) => ({
currentUser: user.currentUser,
});
export default connect(mapStateToProps)(withRouter(Login));
The line with code: this.props.dispatch(setCurrentUser(user)); successfully changed the state but not the props value.
Here is the redux stuff:
// user-actions.js --------------------------------------------------------------------------------------
export const setCurrentUser = (user) => ({
type: "SET_CURRENT_USER",
payload: user,
});
// user-reducer.js --------------------------------------------------------------------------------------
// The initial state is basically a null user (ID)
const initialState = {
user: null,
};
/*
This is essentially a function that takes the current state
and action as an argument and returns a new state result.
i.e. (state, action) => newState
*/
const userReducer = (state = initialState, action) => {
// Conditional for the current action type
if (action.type.localeCompare("SET_CURRENT_USER") === 0) {
// Return a new state object
return {
// Which has the existing data but also..
...state,
// The new user object (just an ID at this point)
user: action.payload,
};
} else {
// Otherwise we return the state unchanged
// (usually when the reducer doesnt pick up the certain action)
return state;
}
};
export default userReducer;
// store.js --------------------------------------------------------------------------------------
import { createStore, applyMiddleware } from "redux";
/*
Useful for debugging redux --> logger
Is a logger middleware that console.logs the actions fired and change of state
*/
import logger from "redux-logger";
import rootReducer from "./root-reducer";
const middlewares = [logger];
const store = createStore(rootReducer, applyMiddleware(...middlewares));
export default store;
// root-reducer.js --------------------------------------------------------------------------------------
import { combineReducers } from "redux";
import userReducer from "./user/user-reducer";
export default combineReducers({
user: userReducer,
});
And finally, the App.js relevant code
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
...props,
u_id: null,
};
}
unsubscribeFromAuth = null;
componentDidMount() {
const { setCurrentUser } = this.props;[enter image description here][1]
const userState = this.props.location;
console.log(this.props);
// Make sure that state for a user isnt undefined
if (userState.state) {
this.unsubscribeFromAuth = true;
const user = userState.state.userId;
this.props.dispatch(setCurrentUser(user));
}
console.log(this.props);
}
componentWillUnmount() {
this.unsubscribeFromAuth = false;
}
render() {
return (...)
}
}
const mapStateToProps = (state) => ({
currentUser: state.currentUser,
});
//Access the state and dispatch function from our store
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: (user) => dispatch(setCurrentUser(user)),
dispatch,
});
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(App));
Console output with redux-logger:
https://i.stack.imgur.com/r9JyV.png
As you can see, currentUser is undefined but all props in the location are there, I'm probably making some really dumb mistake when setting currentUser with the setCurrentUser action, both in the login and then again in the componentDidMount in the app.jsx
I'll add more detail upon request
Any help would be appreciated GREATLY! :)
You are saving the user in redux under user but you are trying to access it in the mapStateToPRops via currentUser:
const mapStateToProps = (state) => ({ currentUser: state.currentUser, });
Change it to const mapStateToProps = (state) => ({ currentUser: state.user, });
and it should work.
Also this:
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: (user) => dispatch(setCurrentUser(user)),
dispatch,
});
is equivalente to:
const mapDispatchToProps = ({
setCurrentUser
});
https://react-redux.js.org/using-react-redux/connect-mapdispatch#defining-mapdispatchtoprops-as-an-object
Currently, I'm using functional components with hooks but still dispatching my actions with the connect HOC.
I read through the documentation with useDispatch but I'm unsure how to incorporate it in my code. From the examples, they are passing the the action types and payloads inside the component. Would I have to move myOfferActions functions back to the component in order to useDispatch?
MyOffers component
import React, { useEffect } from "react";
import { connect, useSelector } from "react-redux";
import "./MyOffers.scss";
import MyOfferCard from "../../components/MyOfferCard/MyOfferCard";
import { fetchMyOffers } from "../../store/actions/myOffersActions";
const MyOffers = (props) => {
const myOffers = useSelector((state) => state.myOffers.myOffers);
useEffect(() => {
props.fetchMyOffers();
}, []);
return (
<div className="my-offers-main">
<h1>My offers</h1>
{myOffers && (
<div className="my-offer-container">
{myOffers.map((offer) => (
<MyOfferCard key={offer.id} offer={offer} />
))}
</div>
)}
</div>
);
};
export default connect(null, { fetchMyOffers })(MyOffers);
offerActions
export const fetchMyOffers = () => async (dispatch) => {
const userId = localStorage.getItem("userId");
try {
const result = await axiosWithAuth().get(`/offers/${userId}`);
let updatedData = result.data.map((offer) => {
//doing some stuff
};
});
dispatch(updateAction(FETCH_MY_OFFERS, updatedData));
} catch (error) {
console.log(error);
}
};
offerReducer
import * as types from "../actions/myOffersActions";
const initialState = {
offerForm: {},
myOffers: [],
};
function myOffersReducer(state = initialState, action) {
switch (action.type) {
case types.FETCH_MY_OFFERS:
return {
...state,
myOffers: action.payload,
};
default:
return state;
}
}
export default myOffersReducer;
I don't think you need connect when using the redux hooks.
You just need to call useDispatch like:
const dispatch = useDispatch();
And use the function by providing the object identifying the action:
dispatch({ type: 'SOME_ACTION', payload: 'my payload'});
It should be working with redux-thunk too (I guess this is what you're using): dispatch(fetchMyOffers())
I'm using global hooks with useContext to store signed up user information. I don't know why useContext render my component twice and state is undefined in the second time.
Provider is global, in the index.js
Any idea to prevent render twice the component?
store.js:
import React, { createContext, useReducer } from 'react';
const initialState = {
accessToken: '',
expirationDate: '',
fullName: ''
};
const store = createContext(initialState);
const { Provider } = store;
const StateProvider = ({ children }) => {
const [state, dispatch] = useReducer((state, action) => {
const { type, payload } = action;
switch (type) {
case 'login':
const newState = {
...state,
...payload
};
return newState;
default:
throw new Error();
}
}, initialState);
return <Provider value={{ state, dispatch }}>{children}</Provider>;
};
export { store, StateProvider };
when i use useContext(store) i get this result:
useContext result
Don't default to error
Always return state in your reducer, not undefined. This is the first issue I see.
default:
throw new Error();
}
to
default:
return state;
}
I am using the React Hooks implementation of React-Redux. Below is the flow of my code. For some reason any values that I pass in my dispatch(fuction(value)) is not detected in my reducer. I can't figure it out.
src/components/categories/selectCategory.tsx
import React from 'react';
import {useDispatch} from 'react-redux';
import {setCategory} from '../../store/actions';
const selectCategory = (name: string) => {
dispatch(setCategory(name)); // ex: name = 'algebra'
};
store/actions/index.ts
export const setCategory = (name) => ({
type: 'SET_CATEGORY',
name: name
});
store/reducers/index.ts
import {combineReducers} from 'redux';
import category from './category';
const app = combineReducers({
category
});
export default app;
store/reducers/category.ts
const initialState = {
name: 'calculus'
};
const category = (state = initialState, action) => {
switch (action.type) {
case 'SET_CATEGORY':
return {name: state.name}; // outputs 'calculus'
default:
return state;
}
};
export default category;
I'm sure there is some small detail I am missing.
My issue was fixed by returning the action.name property instead of state.name.
store/reducers/category.ts
const initialState = {
name: 'calculus'
};
const category = (state = initialState, action) => {
switch (action.type) {
case 'SET_CATEGORY':
return {name: action.name};
default:
return state;
}
};
export default category;