Would you mind helping me to be clear about mapDispatchToProps.
I have a example code like this:
// ----------------------- not use mapDispatchToProps -----------------------------
//var onSubmit = (event) => {
// event.preventDefault()
// var email = event.target.elements[0].value
// var password = event.target.elements[1].value
// // const path = `/repos/${userName}/${repo}`
// store.dispatch(action.requestLogin({username:email,password:password}))
// // store.dispatch(action.receiveLogin({user{username:email,password:password,objectId:1,sessionToken:"asdfg"}}))
// }
// ----------------------- use mapDispatchToProps -----------------------------
const mapDispatchToProps = (dispatch) => {
return {
onSubmit: (event) => {
event.preventDefault()
var email = event.target.elements[0].value
var password = event.target.elements[1].value
dispatch(action.requestLogin({username:email,password:password}))
}
}
}
const mapStateToProps = state => ({
// onSubmit: onSubmit,
error: state.login.error
});
var LoginPage = ({ onSubmit,error }) => {
return (
`<div className="row">
<div className="col-md-12">
<LoginFormComponent className="account-form text-center" title="Log in to Portal" error={error !== null ? error : ""} onSubmit={onSubmit}/>
</div>
</div>`
)
}
export default connect(mapStateToProps,mapDispatchToProps)(LoginPage)
//-----------------------------and this is the reducer -------------------------------------
export default function login(state = {
logedAt: null,
isLogging: false,
error: null,
data: {},
}, action) {
switch (action.type) {
case types.LOGIN_REQUEST:
return update(state, {
isLogging: { $set: true },
error: { $set: null }
});
case types.LOGIN_SUCCESS:
return update(state, {
data: { $set: action.body },
isLogging: { $set: false },
logedAt: { $set: action.logedAt },
});
case types.LOGIN_FAILURE:
return update(state, {
logedAt: { $set: null },
error: { $set: action.error },
});
default:
return state;
}
}
//-----------------------------and the middleware -------------------------------------
export function login({dispatch, getState}){
return next => action => {
return callLogin().then(
response => dispatch(Object.assign({},{
body: response,
logedAt: Date.now(),
type: LOGIN_SUCCESS,
isFetching: false,
isAuthenticated: true,
// callLogin: callLogin,
})),
error => dispatch(Object.assign({} ,{
error: error.response.text,
type: LOGIN_FAILURE,
isFetching: false,
isAuthenticated: false,
// callLogin: callLogin,
}))
);
}
}
When I don't use the mapDispatchToProps, I just can dispatch the action for type:LOGIN_REQUEST but not the LOGIN_SUCCESS,LOGIN_FAILURE, when use mapDispatchToProps, it work. could you explain for me
Thanks a lot.
Related
I am writing a Jest/Testing Library unit test.
In test, I wrap my component in AuthProvider component:
const handlers: Record<string, (state: State, action: Action) => State> = {
INITIALIZE: (state: State, action: InitializeAction): State => {
const { isAuthenticated, permissions, user } = action.payload;
return {
...state,
isAuthenticated,
isInitialized: true,
permissions,
user,
};
},
LOGIN: (state: State): State => {
return {
...state,
isAuthenticated: true,
};
},
LOGOUT: (state: State): State => ({
...state,
isAuthenticated: false,
permissions: [],
}),
};
const reducer = (state: State, action: Action): State =>
handlers[action.type] ? handlers[action.type](state, action) : state;
const AuthContext = createContext<AuthContextValue>({
...initialState,
platform: 'JWT',
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
});
export const AuthProvider: FC<AuthProviderProps> = (props) => {
const { children } = props;
const [state, dispatch] = useReducer(reducer, initialState);
const router = useRouter();
const reduxDispatch = useDispatch();
useEffect(() => {
const initialize = async (): Promise<void> => {
try {
if (router.isReady) {
const { token, permissions, user, companyId } = router.query;
// TODO: Move all of this stuff from query and localstorage into session
const accessToken =
(token as string) || window.localStorage.getItem('accessToken');
const permsStorage = window.localStorage.getItem('perms');
const perms = (permissions as string) || permsStorage;
const userStorage = window.localStorage.getItem('user');
const selectedCompanyId =
(companyId as string) || window.localStorage.getItem('companyId');
const authUser = (user as string) || userStorage;
if (accessToken && perms) {
setSession(accessToken, perms, authUser);
try {
// check if user is admin by this perm, probably want to add a flag later
if (perms.includes('create:calcs')) {
if (!selectedCompanyId) {
const response = await reduxDispatch(getAllCompanies());
const companyId = response.payload[0].id;
reduxDispatch(companyActions.selectCompany(companyId));
reduxDispatch(getCurrentCompany({ companyId }));
} else {
reduxDispatch(
companyActions.selectCompany(selectedCompanyId),
);
await reduxDispatch(
getCurrentCompany({ companyId: selectedCompanyId }),
);
}
} else {
reduxDispatch(companyActions.selectCompany(selectedCompanyId));
await reduxDispatch(
getCurrentCompany({ companyId: selectedCompanyId }),
);
}
} catch (e) {
console.warn(e);
} finally {
dispatch({
type: 'INITIALIZE',
payload: {
isAuthenticated: true,
permissions: JSON.parse(perms),
user: JSON.parse(authUser),
},
});
}
if (token || permissions) {
router.replace(router.pathname, undefined, { shallow: true });
}
} else {
dispatch({
type: 'INITIALIZE',
payload: {
isAuthenticated: false,
permissions: [],
user: undefined,
},
});
setSession(undefined);
if (router.pathname !== '/client-landing') {
router.push('/login');
}
}
}
} catch (err) {
console.error(err);
dispatch({
type: 'INITIALIZE',
payload: {
isAuthenticated: false,
permissions: [],
user: undefined,
},
});
//router.push('/login');
}
};
initialize();
}, [router.isReady]);
const login = useCallback(async (): Promise<void> => {
const response = await axios.get('/auth/sign-in-with-intuit');
window.location = response.data;
}, []);
const logout = useCallback(async (): Promise<void> => {
const token = localStorage.getItem('accessToken');
// only logout if already logged in
if (token) {
dispatch({ type: 'LOGOUT' });
}
setSession(null);
router.push('/login');
}, [dispatch, router]);
return (
<AuthContext.Provider
value={{
...state,
platform: 'JWT',
login,
logout,
}}
>
{state.isInitialized && children}
</AuthContext.Provider>
);
};
AuthProvider.propTypes = {
children: PropTypes.node.isRequired,
};
export default AuthContext;
When I render the component wrapped with AuthProvider, it says
TypeError: Cannot read property 'isReady' of null
217 |
218 | initialize();
> 219 | }, [router.isReady]);
How can I make the router exist?
This is how I am rendering in Jest:
render(
<HelmetProvider>
<Provider store={mockStore(initState)}>
<AuthProvider>
<BenchmarksPage />
</AuthProvider>
,
</Provider>
</HelmetProvider>,
);
Note that I do not have to check if router works. I just have to make it run so that I can unit test the UI, no implementation details.
I am using Redux for state management, I have faced an issue in reducer function
here is the image of my console, You can see the Product Action is providing my data but the reducer is not passing on my function
here is my code of ProductAction:
export const getProductsbyFind = (myvariable) =>async (dispatch)=>{
try {
console.log(myvariable)
dispatch({type: ALL_PRODUCTS_REQUEST_BY_ID})
const{ data } = await axios.get(`/api/v1/product/${myvariable}`)
console.log(data)
dispatch({
type: ALL_PRODUCTS_SUCCESS_BY_ID,
payload: data
})
} catch (error) {
dispatch({
type:ALL_PRODUCTS_FAIL,
payload: error.response.data.message
})
}
}
here is the code of Reducer:
export const productReducersById = (state = { products: [] }, action) => {
switch (action.type) {
case ALL_PRODUCTS_REQUEST_BY_ID:
return {
loading: true,
products: []
}
case ALL_PRODUCTS_SUCCESS_BY_ID:
return {
loading: false,
products: action.payload.products,
productsCount: action.payload.productsCount
}
case UPDATE_QUANTITY_BY_ID:
const { index, quantity } = action.payload;
const prods = state.products.map((p, i) => {
if (i !== index)
return p;
return {
...p,
quantity
}
});
return {
loading: true,
products: prods
}
case ALL_PRODUCTS_FAIL_BY_ID:
return {
loading: false,
error: action.payload
}
case CLEAR_ERRORS_BY_ID:
return {
...state,
error: null
}
default:
return state
}
}
here is the code of my page where I want to get my data:
const { loading, products, error, productCount } = useSelector(state => state.products);
console.log(products)
useEffect(() => {
dispatch(getProductsbyFind(myvariable));
}, [dispatch])
You have a typo in your reducer:
case ALL_PRODUCTS_SUCCESS_BY_ID:
return {
loading: false,
- products: action.payload.products,
+ products: action.payload.product,
productsCount: action.payload.productsCount
}
(Also, productsCount does not exist in your payload, so that will become undefined.)
Store changes not immediately visible to component due to this error message not showing in component whenever request get failed. From reducer, state update take some time to return the update value to component. Hence, component always return as empty msg which is default value present in reducer
Api.js
export const createCategory = async (category, authtoken) => {
return await axios.post(
`${process.env.REACT_APP_API}/category/create`,
category,
{
headers: {
authtoken,
},
}
);
};
category.saga.js
export function* createCategoryAsync({ payload: { name, token } }) {
try {
yield delay(1000);
const response = yield call(createCategory, { name }, token);
yield delay(1000);
console.log("===response", response);
if (response.status === 200 && response.status < 300) {
yield put(createCategorySuccess(response.data.name));
}
console.log("===response", response);
} catch (error) {
yield put(createCategoryFail(error.response.data));
}
}
category.reducer.js
import CategoryActionTypes from "./category.types";
const INITIAL_STATE = {
categoryName: "",
categories: [],
error: false,
errorMsg: "",
};
const categoryReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case CategoryActionTypes.LOAD_CATEGORY_START:
return {
...state,
loading: true,
};
case CategoryActionTypes.LOAD_CATEGORY_SUCCESS:
return {
...state,
loading: false,
categories: action.payload,
};
case CategoryActionTypes.SET_CATEGORY_EMPTY:
return {
...state,
categoryName: "",
};
case CategoryActionTypes.CREATE_CATEGORY_START:
return {
...state,
loading: true,
};
case CategoryActionTypes.CREATE_CATEGORY_SUCCESS:
return {
...state,
loading: false,
categoryName: action.payload,
};
case CategoryActionTypes.LOAD_CATEGORY_FAIL:
case CategoryActionTypes.CREATE_CATEGORY_FAIL:
return {
...state,
loading: false,
error: true,
errorMsg: action.payload,
};
default:
return state;
}
};
export default categoryReducer;
Component.js
const Component = () => {
useEffect(() => {
loadCateories();
}, []);
const { categories, loading, categoryName, error, errorMsg } = useSelector(
(state) => ({
...state.category,
})
);
const loadCateories = () => {
dispatch(loadCategoryStart());
};
console.log("==errorMsg", errorMsg);
const {
user: { token },
} = useSelector((state) => ({ ...state }));
const handleSubmit = (e) => {
e.preventDefault();
// setLoading(true);
// dispatch(setCategoryEmpty());
dispatch(createCategoryStart({ name, token }));
if (categoryName) {
toast.success(`${name} is created`);
setName("");
loadCateories();
} else {
toast.error(errorMsg && errorMsg);
setName("");
}
};
}
I can't use default state value in redux
Hello,
I have a function to dispatch Login action,
I want in the initial to be false,
but when I put this value in the render method, it's given me an error
can't read property 'isLogin' of undefined
although in the reducer i add an initialState
let initialState = {
isLogin: false,
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case IS_LOGIN:
state = {
...state,
isLogin: true,
};
break;
default:
return state;
}
return state;
};
here's Store
const store = createStore(
combineReducers({
user: userReducer,
count: countPlayReducer,
}),
);
Action
export const isLoginFunc = isLogin => {
return {
type: IS_LOGIN,
payload: isLogin,
};
};
UI/Dispatching
import {isLoginFunc} from '../../redux/actions/isLoginAction';
signIn = async data => {
this.setState({loading: true}); // maybe here the wrong?
API.post('/login', data)
.then(async response => {
let {
data: {
data: {
response: {token},
},
},
} = response;
this.props.dispatch(isLoginFunc(true));
await saveToken('id_token', token);
this.setState({loading: false}, () =>
this.props.navigation.navigate({
key: 'TabHome',
routeName: 'TabHome',
}),
);
})
.catch(error => {
console.log(error);
this.setState({loading: false, error: error.response.data});
});
};
render(){
...
<Text>{this.props.user.isLogin}</Text>
...
}
mapStateToProps = state => {
return {
isLogin: state.user,
};
};
export default connect(mapStateToProps)(Login);
It looks like you're mapping redux state using the following:
mapStateToProps = state => {
return {
isLogin: state.user,
};
};
But then you're trying to access isLogin via:
this.props.user.isLogin
Looks like this should be changed to:
this.props.isLogin.isLogin
However you might want to alter your mapping to the following instead:
mapStateToProps = state => {
return {
isLogin: state.user.isLogin,
};
};
So that you can simply use:
this.props.isLogin
Preface: I'm new to react.
I'm creating a project based on React, Redux.
I want to set a loading status when I press the register button on the register component.
I did a lot of research for a possible solution, but wasn't able to find anything useful for my situation.
What's the best way to fix this?
Register reducer
const initialState = {
pending: false,
users: [],
error: null,
showModal: false,
loading: false
}
export function userReducer(state = initialState, action) {
switch (action.type) {
case 'TOGGLE_LOADING': return {
...state,
loading: !state.loading
}
case 'USER_ADD':
{
debugger;
state.users = state.users.concat(action.payload);
return {
...state,
loading: false,
users: state.users
}
}
case FETCH_USERS_PENDING:
return {
...state,
pending: true,
loading: false
}
case FETCH_USERS_SUCCESS:
return {
...state,
pending: false,
loading: false,
users: action.payload
}
case FETCH_USERS_ERROR:
return {
...state,
pending: false,
loading: false,
error: action.error
}
default:
return state;
}
}
export default userReducer;
Register action
export const userRegisterFetch = user => {
user.Username = user.Mobile;
return dispatch => {
dispatch({ type: 'TOGGLE_LOAD' })
return fetch(`${baseUrl}/users/Register`,
{
method: 'POST', body: JSON.stringify(user), headers: {
'Content-Type': 'application/json',
}
}
)
.then(resp => resp.json())
.then(data => {
if (data.result == undefined) {
return alert('error');
}
if (!data.result) {
alert(data.message);
}
else {
const location = {
pathname: '/Users/Confirm',
state: { mobile: user.Mobile }
}
history.push(location);
}
})
}
}
Register.js component
const { loading } = this.props
return(
{loading ? <Loading /> :
<Form>
...my form
</Form>
}
)
I think you only need 3 reducers to be honest, FETCH_USERS_INIT, FETCH_USERS_SUCCESS and FETCH_USERS_FAIL.
Register reducer
const initialState = {
users: [],
loading: false,
error: null
};
function userReducer(state = initialState, action) {
switch (action.type) {
case 'FETCH_USERS_INIT':
return {
...state,
loading: true
};
case 'FETCH_USERS_SUCCESS':
return {
...state,
loading: false,
error: null,
users: action.payload.users
};
case 'FETCH_USERS_FAIL':
return {
...state,
loading: false,
error: action.payload.error
};
default:
return initialState;
}
}
export default userReducer;
export const userRegisterFetch = user => {
user.Username = user.Mobile;
return async dispatch => {
dispatch({ type: 'FETCH_USERS_INIT' });
fetch(`${baseUrl}/users/Register`, {
method: 'POST',
body: JSON.stringify(user),
headers: {
'Content-Type': 'application/json'
}
}).then(response => {
/* dispatch 'FETCH_USERS_SUCCESS' with the list of users */
}).catch(error => {
/* dispatch 'FETCH_USERS_FAIL' with the corresponding error */
});
};
};
The action is not finished but i think it's clear how to finish it. Let me know if you have any doubt.