Im getting double nested state after api call. Here is it.
Screenshot from redux devtools
Here are my actions
export const getUserSuccess = user => ({
type: GET_USER_SUCCESS,
user,
});
export const getUserFailure = error => ({
type: GET_USER_FAILURE,
error,
});
export const getUserRequest = () => (
dispatch => (
axios.get('./user.json')
.then(response => dispatch(getUserSuccess(response.data)))
.catch(error => dispatch(getUserFailure(error)))
)
);
here is my user reducer
export default function user(state = {}, action) {
switch (action.type) {
case GET_USER_SUCCESS:
return {
...state,
user: action.user,
};
case GET_USER_FAILURE:
return {
...state,
error: action.error,
};
default:
return state;
}
}
and here is my root reducer
export default combineReducers({
user,
});
You need to spread the data which comes for user, no need to assign it to user. Your reducer is in it already
case GET_USER_SUCCESS:
return {
...state,
...action.user
};
If this reducer should update the state with the complete representation of the new user (and discard old data) you should just return the new user as state.
Can you give the following a shot?
export default function user(state = {}, action) {
switch (action.type) {
case GET_USER_SUCCESS:
return action.user;
case GET_USER_FAILURE:
return {
...state,
error: action.error,
};
default:
return state;
}
}
Judging by this quote
If I'll try that with array of users then array will be destroyed
Why not use Object.assign()?
return Object.assign({}, state, {user: [...state.user, ...action.user]})
Assuming that having multiple users is a possibility, why not just maintain the user property in your state as an array?
Related
I am trying to implement an authentication system into my project and have been following this tutorial https://youtu.be/LKlO8vLvUao .
The system works however I am trying to display the error message on a failed login attempt and I cannot figure out how to get the error message from the reducer to the component where I'd like to display it (preferably using a [msg,setMsg] hook)
Here is my code from the action where I capture the error response and pass it to reducer:
export const signin = (formData, history) => async (dispatch) => {
try {
const { data } = await API.signIn(formData);
dispatch({ type: AUTH, data });
history.push('/')
}
catch (error){
const data = error.response.data;
dispatch({ type: "AUTH_FAIL", data })
}
}
Here is my reducer:
import { AUTH, LOGOUT } from '../constants/actionTypes'
const authReducer = (state = { authData: null }, action) => {
switch (action.type){
case AUTH:
localStorage.setItem('profile', JSON.stringify({ ...action?.data }))
return { ...state, authData: action?.data};
case LOGOUT:
localStorage.clear();
return { ...state, authData: null };
case "AUTH_FAIL":
return { ...state, authData: action?.data};
default:
return state;
}
}
export default authReducer;
In Reducer create initiateState object
const initiateState: {
authData: null,
error: null
}
as you did in action
catch (error){
**const data = data.response.error;**
dispatch({ type: "AUTH_FAIL", data })
}
also change reducer , and send the amount of action.data to error
const authReducer = (state = initiateState, action) => {
....//your code
case "AUTH_FAIL":
return { ...state, error: action.data};
default:
return state;
}
}
using Hooks
this equal to mapStateToProps
const error= useSelector((state) => state.error)
Where you are defining the state for this reducer, break it into two parts.one being authData as it is now, the second being error. When dispatching AUTH_FAIL, what you should return in the reducer, is the error instead of the authData. Now in your component you can call the state anyway you like and use the updated state. I know of 2 ways for doing this:
1-mapStatetoProps
2-store.subscribe() and store.getState()
I have a list of reducers in my combineReducer:
export default createRootReducer =>
combineReducers({
profile,
settings,
});
I want to get some state from profile, into settings, as I need the current user id, when making an API call.
My settings reducer:
export default (state = initialState, action) => {
switch (action.type) {
case Actions.SettingsSave:
api.patch("settingsAPI", { body: action.payload, params: { user_id } });
let finalPayload = Object.assign({}, state, action.payload);
return finalPayload;
default:
return state;
}
};
How would I get the data from profile into settings? Is there any way? I tried to get it directly from the store, but that's not allowed in a reducer that is in the middle of execution.
You could use a getState in the action creator and pass it in through the action. Something like
export default saveSettings(params){
const currState = store.getState()
return {
type: "SAVE_SETTINGS",
foo: params,
profileSlice: currState.settings
}
}
You would probably need to use thunk for accessing your global state. Here's an example:
export function updateProduct(product) {
return (dispatch, getState) => {
const { accountDetails } = getState();
dispatch({
type: UPDATE_PRODUCT,
stateOfResidenceId: accountDetails.stateOfResidenceId,
product,
});
};
}
Disclaimer: this question is targeting specific package reduxsauce
Takes classic redux action, by dispatching a single action, it will flow thru all the reducer and if we want to update the state, we catch the type in each and every reducer as we see fit
loginPage.js
this.props.memberLogin({ name: 'john' }); //{ type: MEMBER_LOGIN, payload: { name: 'john' } }
LoginAction.js
const memberLogin = member => {
return { type: MEMBER_LOGIN, payload: member }
}
authReducer.js
const INITIAL_STATE = { isLoggedIn: false }
switch(state = INITIAL_STATE, action) {
case MEMBER_LOGIN: return { ...state, isLoggedIn: true };
default: return state;
}
memberReducer.js
const INITIAL_STATE = { member: null }
switch(state = INITIAL_STATE, action) {
case MEMBER_LOGIN: return { ...state, member: action.payload };
default: return state;
}
Wondering by using reduxsauce, can we still achieve something similar as demonstrated above? (dispatch single action and update both reducer)
Yes, you can.
I created this Snack example to help you, but the gist is that you have to configure your reducers to listen to the same action.
Kinda like so:
const reduceA = (state, action) => ({
...state,
a: action.value,
});
const reduceB = (state, action) => ({
...state,
b: action.value,
});
const { Types, Creators: actionCreators } = createActions({
testAction: ['value'],
});
const HANDLERS_A = {
[Types.TEST_ACTION]: reduceA,
};
const HANDLERS_B = {
[Types.TEST_ACTION]: reduceB,
};
In the example both reducers A and B have their state values altered by the same action testAction.
this is simple redux application for show posts and todos, when i call dispatch action SHOW_POSTS works nice, but SHOW_TODOS return undefined, why ?
let initialState = {
todos : ['buy milk', 'write code'],
posts : ['weekly news']
}
function counter(state = initialState, action) {
switch (action.type) {
case 'SHOW_POSTS':
return state.posts
break;
case 'SHOW_TODOS':
return state.todos
break;
case 'ADD_TODO':
return {
todos: [...state, action.payload]
}
break;
default:
return state
}
}
let store = createStore(counter)
store.subscribe(() => console.log(store.getState()))
console.log('show posts:')
store.dispatch({ type: 'SHOW_POSTS' })
console.log('show todos:')
store.dispatch({ type: 'SHOW_TODOS' })
You're supposed to return the entire state on your reducers as the return statement will replace the next value of state. What you're essentially doing here is replacing your initialState with just the posts. You can see this by reverting those dispatch calls. You'll see that posts will be empty.
I have action creator to get data from API and have another action creator for loading status and want change loading status when data completely fetched.
Now, I wrote following codes but not working good, Loading status changes to false before data fetched completely.
My ActionCreator:
export const loadingStatus = (bool) => {
return {
type: Constants.LOADING_STATUS,
isLoading: bool
};
}
const allFlashCards = (action) => {
return{
type: Constants.ALL_CARD,
...action
}
};
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then((data)=>{
console.warn(data);
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
and my Reducer:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action};
break;
default:
return state;
}
};
export const Loading = (status= false, action) => {
switch (action.type) {
case Constants.LOADING_STATUS:
return action.isLoading;
break;
default:
return status;
}
}
and in my component:
componentDidMount() {
this.props.fetchCards();
}
render() {
return(
<div>
{this.props.loading ?
<Loading/> :
Object.keys(this.props.cards.data).map(this.renderCard)
}
</div>
);
}
const mapStateToProps = (state) => ({
cards: state.main,
loading: state.loading
});
const mapDispatchToProps = (dispatch) => ({
fetchCards: bindActionCreators(fetchAllFlashCards, dispatch)
});
and combineReducer is:
import { combineReducers } from 'redux';
import FlashCard , { Loading } from './FlashCard.js';
import { routerReducer } from "react-router-redux";
export default combineReducers({
main: FlashCard,
loading: Loading,
routing: routerReducer
});
In my page, I have an error in console and it's:
Uncaught TypeError: Cannot read property 'data' of undefined and if put my codes in timeout fixed my bug :/
What should i do?
Your default state is wrong here:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action};
break;
default:
return state;
}
};
It should be an empty object {} instead of an empty array [], since you're returning objects.
This code
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then((data)=>{
console.warn(data);
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
Looks completely fine. loadingStatus(false) should not be called before setting the flash cards. Your reducers and action creators are synchronous (as they should). So, nothing of note there.
I saw that you're using action.data on the Constants.ADD_CARD action case, but in your code you do not dispatch any actions with that type. Do you do it somewhere else? Maybe that's where the error is?
EDIT:
Another place that you're using .data is in your renderer: this.props.cards.data. What's the value of the state.main?
How are you creating your rootReducer? It should be something like this:
const rootReducer = combineReducers({
main: FlashCard,
loading: Loading,
});
Are you using main there? Or maybe cards?
Finally, I fixed my problem:
In my actionCreator change fetchAllFlashCards method to following:
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then(({data})=>{
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
and in reducer change FlashCard reducer to following:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action.data};
break;
default:
return state;
}
};