Dispatching multiple actions using redux-thunk updates component only once - reactjs

I'm using redux-thunk for dispatching multiple action from one dispatch within a component.
export function fetchQuestions() {
return function(dispatch, getState) {
dispatch({type: "QUESTION_FETCH_PENDING"});
axios.get(`http://${API_ROOT}/api/questions/list`, {
headers: {'JWT': getState().users.token}
})
.then((response) => {
//if registration is successful tell it to reducer and authorize user
dispatch({type: "QUESTION_FETCH_SUCCESS", payload: response});
})
.catch((err) => {
if (err.response.data == "login") {
dispatch(unauthorizedRequest());
}
dispatch({
type: "QUESTION_FETCH_FAIL",
payload: err
});
});
}
}
The problem is that I want component to be updated on each dispatch that are inside of wrapping dispatch. They all proceed to reducer, and I can see them logged in console.
But that does not happen, component is only updated after first dispatch.
You can see, "FEED RENDER" message that is called each time render() called, and it is not called after remaining dispatches.
Thank you in advance!
UPDATE 1: Here is code for my reducer
export default function reducer(state={
questions: [],
questionPending: false,
questionSuccess: false,
questionFetching: false,
questionFetched: false,
error: null
}, action) {
switch(action.type) {
case "QUESTIONS_FETCH_PENDING": {
return {...state, questionFetching: true, questionFetched: false}
}
case "QUESTIONS_FETCH_SUCCESS": {
return {...state, questionFetching: false, questionFetched: true, questions: action.payload}
}
case "QUESTIONS_FETCH_FAIL": {
return {...state, questionFetching: false, questionFetched: false, error: action.payload}
}
case "QUESTION_ADD_PENDING": {
return {...state, questionPending: true, questionSuccess: false}
}
case "QUESTION_ADD_SUCCESS": {
return {...state, questionPending: false, questionSuccess: true}
}
case "QUESTION_ADD_FAIL": {
return {...state, questionPending: false, questionSuccess: false}
}
}
return state;
}
And for injecting store, I simply use #connect decorator:
#connect((store) => {
return {
questions: store.questions,
isAuth: store.users.isAuth
};
})
Problematic component:
export default class Feed extends React.Component {
componentWillMount() {
this.props.dispatch(fetchQuestions());
}
componentWillUpdate() {
console.log(this.props);
if (!this.props.isAuth) {
this.props.router.push('/auth');
}
}
logout() {
this.props.dispatch(logoutUser());
}
render() {
console.log("FEED RENDER!");
let questions = this.props.questions.questions.map((question) => {<questionFeed question={question}/>});
return (
<div>
<button onClick={this.logout.bind(this)}>LOGOUT</button>
THIS IS FEED!
</div>
);
}
}

The problem was simple TYPO: the names of actions didn't match to ones that are in reducers.
You can see I was dispatching QUESTION_FETCH_FAIL and QUESTION_FETCH_SUCCESS, but handling QUESTIONS_FETCH_FAIL and QUESTIONS_FETCH_SUCCESS.
SOLUTION: According to best practices of redux, you should always store action names in variables and share them with reducer.
NOTE: Don't fall to same dellusion as me, logged action in console does not mean it has reached the reducer
SPECIAL THANKS: To #OB3 for noting typo

Related

react props comes blank on first transaction

I am using redux promise middleware. I am trying to pass the value in Propsx to state. Props comes empty in useEffect. How can I transfer the contents of the props to state. Props value comes next.
action:
export function fetchBasket() {
return dispatch => {
dispatch({
type: 'GET_BASKET',
payload: axios.get('url', {
})
.then(response => response.data)
});
};
}
reducer:
const initialState = {
fetching: false,
error: {},
basket: []
};
export default (state = initialState, { type, payload }) => {
switch (type) {
case types.GET_BASKET_PENDING:
return {
fetching: true
};
case types.GET_BASKET_FULFILLED:
return {
...state,
fetching: false,
basket: payload.result,
};
case types.GET_BASKET_REJECTED:
return {
fetching: false,
error: payload.result
};
default:
return state;
}
};
use in Component
useEffect(() => {
props.fetchBasket();
console.log(props.basket); // null :/
}, []);
[enter link description here][1]If you want to have values in your first run(Mount). fetch here ==> useLayoutEffect and this will gives the values in useEffect()[]. [uselayouteffect]: https://reactjs.org/docs/hooks-reference.html#uselayouteffect
useEffect(() => {
props.fetchBasket();
console.log(props.basket); // null :/
}, []);
Your props will update only in the next event loop cycle, to use react hooks data updation inside useEffect you need to useReducer https://reactjs.org/docs/hooks-reference.html#usereducer

Next.js+redux+react - Header components is updated before Body component and it looks wierd

I'm struggling with a very strange behavior.
This is my Private page:
const Private = (props) => {
useEffect(() => {
if (!props.isAuthenticated) {
Router.push('/login');
}
}, [props.isAuthenticated]);
return (<Layout />);
};
The Layout component contains an Header component that renders also depending on props.isAuthenticated (has an if statement in the render() method).
I'm using redux-saga, so when the user logout from the system, a LOGOUT_SUCCESS action is being dispatched and the reducer sets props.isAuthenticated to false.
Till now everything fine, BUT, when this happening, what I see is that:
1) First the Header component is updating its display
2) After (let's say 2 seconds), I see the /login body.
This is very strange, when the user need to see an updated header but still with the previous body.
My guess is that the Header is updated before the Router.push (because props.isAuthenticated was changed) that takes time because it needs to render the /login page.
Am i right?
How can i fix it?
What is the best practice such that the whole page will change in once?
Maybe another good question to ask here, is where should the redirect happen? in the end of the saga? (yield call(Router.push, '/login')) or in the page useEffect authentication check?
Thanks!
EDIT:
The logout saga:
function* logoutFlow() {
try {
const res = yield call(sendLogoutRequest);
yield put({ type: LOGOUT_SUCCESS });
} catch (error) {
console.log(error)
}
}
export function* watchLogoutRequest() {
yield takeLatest(LOGOUT_REQUEST, logoutFlow)
}
The Header component:
const Header = (props) => {
if(props.isAuthenticated){
return <Button1 />;
} else {
return <Button2 />;
}
}
The login/logout reducer:
import { LOGIN_SUCCESS, LOGIN_FAILURE, LOGIN_REQUEST, LOGOUT_REQUEST, LOGOUT_SUCCESS } from './loginTypes';
const initialState = {
isAuthenticated: false,
loading: false,
error: ''
};
export default (state = initialState, action) => {
switch (action.type) {
case LOGIN_REQUEST:
return {
isAuthenticated: false,
loading: true,
error: ''
};
case LOGIN_SUCCESS:
return {
isAuthenticated: true,
loading: false,
error: ''
};
case LOGIN_FAILURE:
return {
isAuthenticated: false,
loading: false,
error: action.error
};
case LOGOUT_REQUEST:
return {
...state,
loading: true
};
case LOGOUT_SUCCESS:
return initialState;
default:
return state;
}
};

Getting updated state right after state is updated right after dispatch within a component

I'm making login auth call from Redux Saga, so in JSX I can wait for the state to update using conditional rendering but how can I wait for it in for ex. onSubmit function so that I can redirect if login is successful.
I'm using conditional statements to check if the user is logged in or not within onSubmit function right after dispatching the props.login(values) but I get the older state that has isLoggedIn set to false.
Login Component
<Formik
initialValues={{
email: '',
password: '',
}}
validationSchema={LoginSchema}
onSubmit={(
values,
{ setSubmitting, resetForm },
) => {
props.login(values);
setTimeout(() => {
if(props.isLoggedIn) {
props.history.push('/'); // Functional Component
} else {
resetForm();
}
setSubmitting(false);
}, 500);
}}
>
Initial State
const initialState = {
currentUser: {},
isLoggedIn: false,
errors: '',
propertyMessages: '',
};
Reducers
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case AUTH: {
if (action.payload.error) {
return {
...state,
errors: action.payload.error,
};
} else {
localStorage.setItem('access-token', action.payload);
const user = jwtDecode(action.payload);
return {
...state,
errors: '',
isLoggedIn: true,
currentUser: {
id: user.id,
name: user.name,
},
};
}
}
case LOGOUT: {
localStorage.removeItem('access-token');
return {
...state,
isLoggedIn: false,
currentUser: {},
errors: '',
};
}
default:
return state;
}
};
Redux Saga
function* loginSaga(payload) {
try {
const data = yield call(loginCall, payload); // Get token
yield put({ type: AUTH, payload: data });
} catch (e) {
console.log(e);
}
}
The isLoggedIn sets to true right after the props.login(values) dispatches but since the logic for redirection is right after the dispatcher if get's called with old state.
Expected results will happen if the redirection logic can be put on delay or incase of conditional rendering the component renders and cause redirects since it will have the updated value.
Found the solution
componentDidUpdate(prevProps) {
if (this.props.currentUser !== prevProps.currentUser) {
this.props.history.push('/');
}
}
As far as I understand your problem, you need to make props.login thenable i.e. a promise. Return a promise from the login function and put the if logic in then
something like this. More about this can be told if you share your login code.
onSubmit={(
values,
{ setSubmitting, resetForm },
) => {
props.login(values)
.then(() => {
if(props.isLoggedIn) {
props.history.push('/'); // Functional Component
} else {
resetForm();
}
setSubmitting(false);
})
}
}

Dispatching function does not "always" update the props

In my sample todo list app, I am trying to update all todos after creating or removing one so I can re-render the list component after changes. It sometimes updates the todos, but sometimes does not. I could not figure out the reason.
I am using Redux and Thunk. When a button is clicked, I dispatch "Create requested", then make async request, then dispatch "Create received", then dispatch fetchAllTodos(), and then catch "Create failed".
In the HomePage container, I pass this.props.todos to TodoList component as props. The problem I have is that it sometimes re-renders the list component with the changes, but sometimes does not. I also figured out in componentWillReceiveProps method that I do not get the updated props. Could this be related to a server issue or is it something I am doing wrong?
Here is how I set up the store:
const middlewares = [thunk];
const store = createStore(
combineReducers({
...reducers,
}),
compose(applyMiddleware(...middlewares))
);
Here is the fetchAllTodos() method:
export function fetchAllTodos() {
return dispatch => {
dispatch({
type: ALL_TODOS_REQUESTED
});
console.log('todos requested');
return SuperFetch.get('/todos')
.then(todos => {
console.log('todos received ' + todos.length);
dispatch({
type: ALL_TODOS_RECEIVED,
payload: todos
})
})
.catch(error => dispatch({
type: ALL_TODOS_FAILED,
payload: error
}));
}
Here is createTodo() method:
export function createTodo(description) {
const body = {description: description};
return dispatch => {
dispatch({
type: CREATE_TODO_REQUESTED
});
return SuperFetch.post(`/todos`, body)
.then(todo => dispatch({
type: CREATE_TODO_RECEIVED,
payload: todo
}))
.then(dispatch(fetchAllTodos()))
.catch(error => dispatch({
type: CREATE_TODO_FAILED,
payload: error
}));
}
}
Here is the reducer:
export default function todoReducer(state = initialState, action) {
switch (action.type) {
case ALL_TODOS_REQUESTED:
state = Object.assign({}, state, {todosLoading: true, todosError: null});
break;
case ALL_TODOS_RECEIVED:
state = Object.assign({}, state, {todos: action.payload, todosLoading: false, todosError: null});
break;
case ALL_TODOS_FAILED:
state = Object.assign({}, state, {todos: null, todosLoading: false, todosError: action.payload});
break;
// rest of it
return state
In HomePage, I just map state to props, and then pass the todos if they exist:
render() {
const {todos, error, loading} = this.props;
// if error part
if (todos && !loading) {
return (
<React.Fragment>
<Container text>
<TodoInput/>
</Container>
<Container style={{marginTop: '1em'}} text>
<TodoList todos={todos}/>
</Container>
</React.Fragment>
);
}
Might the problem be related to the fact that I am dispatching the action in TodoInput component and trying to update the TodoList? If so, how can I fix the problem because I do not want to dispatch all actions on the same HomePage container.
The problem is the way you manage state in your reducers. What you are doing is directly mutating an existing state, by doing state = blah which is against redux-priniciples. In order for redux to effectively recognize that a change has been made to the reducer, you must return a brand-new state object. Only then will your connected components re-render with the updated reducer data.
export default function todoReducer(state = initialState, action) {
switch (action.type) {
case ALL_TODOS_REQUESTED:
return {
...state,
todosLoading: true,
todosError: null
}
case ALL_TODOS_RECEIVED:
return {
...state,
todos: action.payload,
todosLoading: false,
todosError: null
}
case ALL_TODOS_FAILED:
return {
...state,
todos: null,
todosLoading: false,
todosError: action.payload
}
default:
return state
}
}
Problems is how you are updating the state in your reducer :
if todos is Object
state = {...state, {todos: {...action.payload, ...state.todos }, todosLoading: false, todosError: null}}
if todos is list
state = {...state, {todos: state.todos.concat(action.payload) }, todosLoading: false, todosError: null}}

redux rejactjs Use one store for multiple actions

I am new in react and redux and I would like to know if it is possible to use one store for multiple actions.
I am trying this but the first action ges overwritten with the last action, why?
I am calling the action in two separate component and I call those two component in my app component.
reducer.js
const dataReducer = (state = {
fetching: false,
fetched: false,
data: {},
error: null
}, action) => {
switch (action.type) {
case 'FETCH_DATA_PENDING':
return {...state, fetching: true}
break;
case 'FETCH_DATA_FULFILLED':
return {...state, fetching: false, fetched: true, data: action.payload.data }
break;
case 'FETCH_DATA_REJECTED':
return {...state, fetching: false, error: action.payload }
break;
}
return state;
}
module.exports = dataReducer;
action.js
import axios from 'axios';
const apiUrl = 'https://swapi.co/api/';
//fetch categories
export function fetchCategories() {
return {
type: 'FETCH_DATA',
payload: axios.get(apiUrl)
}
}
//fetch films
export function fetchFilms() {
return {
type: 'FETCH_DATA',
payload: axios.get(apiUrl + 'films')
}
}
You should be able to do it this way, but the fact that both of your actions have the same type might be confusing in your reducers. It might be more helpful to have a type FETCH_FILMS and FETCH_CATEGORIES. That way the reducer can do separate things with them, unless of course, you always want every reducer to do the exact same thing with them.

Resources