Reducer 1 code is as below. I want to call another reducer method after successful authetication of user. so its based of response of reducer 1 , I want to call method/action of reducer 2.
const LOGIN = 'redux-example/auth/LOGIN';
const LOGIN_SUCCESS = 'redux-example/auth/LOGIN_SUCCESS';
const LOGIN_FAIL = 'redux-example/auth/LOGIN_FAIL';
import { browserHistory } from 'react-router';
import { apiurl } from '../../Constants';
import {savedata} from '../../redux/modules/new';
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case LOGIN:
return {
...state,
loggingIn: true
};
case LOGIN_SUCCESS:
return {
...state,
loggingIn: false,
user: action.result
};
case LOGIN_FAIL:
return {
...state,
loggingIn: false,
user: null,
loginError: action.error
};
default:
return state;
}
}
export function login(page,email,password) {
var querystring = require('querystring');
if(action == undefined) action = null;
var data = querystring.stringify({
email: email,
password: password
});
return {
types: [LOGIN, LOGIN_SUCCESS, LOGIN_FAIL],
promise: (client) => client.post(apiurl + 'ajax/login', {
data: data
}).then(response => {
//console.log(response);
switch(page){
case 'signin':
if(response.auth == 'true') {
redirectuser(response);
}
break;
default:
break;
}
return response;
})
.catch( error => Promise.reject(error))
};
}
export function redirectuser(response) {
console.log('response is as below');
console.log(response);
if(response.action == 'action1'){
savedata();
// here I want call another reducer method save data
}
}
When I call action save data of reducer 2 from reducer 1 , it does not work. How to dispatch action of reducer 2 from reducer 1.
Edit 1: my middleware code is as below
export default function clientMiddleware(client) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
const { promise, types, ...rest } = action; // eslint-disable-line no-redeclare
if (!promise) {
return next(action);
}
const [REQUEST, SUCCESS, FAILURE] = types;
next({ ...rest, type: REQUEST });
const actionPromise = promise(client, dispatch);
actionPromise.then(
result => next({ ...rest, result, type: SUCCESS }),
error => next({ ...rest, error, type: FAILURE })
).catch(error => {
next({ ...rest, error, type: FAILURE });
});
return actionPromise;
};
}
Dispatching an action inside a reducer is not a good move. As i understand, you have to do some update synchronously. One way is, once the first reducer is updated, where ever your are consuming that reducer go and inside componentWillReceiveProps or componentDidUpdate do something like.
NOTE: before dispatching you have to import the configurestore and create a const dispatch from store.
componentWillReceiveProps(nextProps)
{
//only if user was not there previously and now user is there
if(!this.props.user && nextProps.user)
{
dispatch({type: SECOND_ACTION, payLoad})
}
}
Related
In my action i am dispatching the type and the payload but what if i also want the res.status and a return JSON message to be included into my props. How would i do so in my action and reducer?
action
export const fetchUserPosts = () => (dispatch) => {
fetch(`${currentPort}/user/recipes`,
{
withCredentials: true,
credentials: 'include',
})
.then((res) => {
if (res.status !== 401) return res.json().then((data) => data);
return { message: { msgBody: 'UnAuthorized' }, msgError: true };
})
.then((posts) => dispatch({
type: FETCH_USER_POSTS,
payload: posts,
}));
};
reducer
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_USER_POSTS:
return {
...state,
fetchUsersPosts: action.payload,
};
default:
return state;
}
}
You can combine multiple items into a single payload. I would create different actions for success and error. Using the same action complicates the reducers logic. It's also easier to work with async/await then with nested promises.
This is a working example that uses SpaceX open API:
const FETCH_USER_POSTS_SUCCESS = 'FETCH_USER_POSTS_SUCCESS'
const FETCH_USER_POSTS_FAILED = 'FETCH_USER_POSTS_FAILURE'
const fetchPostSuccessAction = (payload) => ({
type: 'FETCH_USER_POSTS_SUCCESS',
payload,
})
const fetchPostFailureAction = (payload) => ({
type: 'FETCH_USER_POSTS_FAILURE',
payload,
})
const fetchUserPosts = () => async dispatch => {
const res = await fetch('https://api.spacexdata.com/v3/launches/latest');
if (res.status !== 401) {
const { ships: posts } = await res.json();
dispatch(fetchPostSuccessAction({
posts,
status: res.status,
}))
} else {
dispatch(fetchPostFailureAction({
message: { msgBody: 'UnAuthorized' },
}))
}
};
fetchUserPosts()(console.log)
The reducer can handle the object by destructuring it, and the properties to the new state in any way you need. You can also change other properties, for example changing errMsg to true or false according to the action's type:
export default function (state = initialState, { type, payload }) {
switch (type) {
case FETCH_USER_POSTS_SUCCESS: {
const { posts, status } = payload;
return {
...state,
status,
fetchUsersPosts: posts,
msgError: false,
message: null
};
}
case FETCH_USER_POSTS_FAILURE: {
const { message } = payload;
return {
...state,
status: 401,
fetchUsersPosts: null,
msgError: true,
message
};
}
default:
return state;
}
}
If I am following correctly you are using this action inside of a component to send a fetch. You don't have access to the components props with the reducer. You can send the http request in the component and use that to store the response in the state. Or use connect from 'react-redux' package to map the redux store to access the fetch result.
import { connect } from 'react-redux'
const component = props => {
//to access redux state in component use props.myprop
return <div>{props.myprops.title}</div>
}
const mapStateToProps = state => {
return{
myprop: state.fetchUsersPosts
}
}
export default connect(mapStateToProps)(component)
If this was what you were looking for you can learn more at https://react-redux.js.org/api/connect
isAuthenticated is undefined when i run this code. how can is use isAuthenticated with mapStateProps. if i am use Token `(Token '5302f4340a76cd80a855286c6d9e0e48d2f519cb'} like this then it's working fine but i want Authorized it with props.isAuthenticated anybody know how can i solve this issue?
authAction.js
import axios from 'axios';
import * as actionTypes from './actionTypes';
export const authStart = () => {
return {
type: actionTypes.AUTH_START
}
}
export const authSuccess = token => {
return {
type: actionTypes.AUTH_SUCCESS,
token: token
}
}
export const authFail = error => {
return {
type: actionTypes.AUTH_FAIL,
error: error
}
}
export const logout = () => {
localStorage.removeItem('token');
return {
type: actionTypes.AUTH_LOGOUT
};
}
export const authLogin = (userData) => {
return dispatch => {
dispatch(authStart());
axios.post('http://localhost:8000/rest-auth/login/', userData)
.then(res => {
const token = res.data.key;
localStorage.setItem('token', token);
dispatch(authSuccess(token));
})
.catch(err => {
dispatch(authFail(err))
})
}
}
authReducer.js
import * as actionTypes from '../actions/actionTypes';
import { updateObject } from '../utility';
const initialState = {
isAuthenticated: null,
token: null,
error: null,
loading: false
}
const authStart = (state, action) => {
return updateObject(state, {
isAuthenticated: false,
error: null,
loading: true
});
}
const authSuccess = (state, action) => {
return updateObject(state, {
isAuthenticated: true,
token: action.token,
error: null,
loading: false
});
}
const authFail = (state, action) => {
return updateObject(state, {
error: action.error,
loading: false
});
}
const authLogout = (state, action) => {
return updateObject(state, {
token: null
});
}
export default function (state = initialState, action) {
switch (action.type) {
case actionTypes.AUTH_START: return authStart(state, action);
case actionTypes.AUTH_SUCCESS: return authSuccess(state, action);
case actionTypes.AUTH_FAIL: return authFail(state, action);
case actionTypes.AUTH_LOGOUT: return authLogout(state, action);
default:
return state;
}
}
index.js
import { combineReducers } from 'redux';
import auth from './authReducer'
export default combineReducers({
auth: auth
});
articleList.js
const NewsList = (props) => {
// ...
const fetchItems = async () => {
try {
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Token ${props.isAuthenticated}`
}
}
const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/`, config);
setItems(res.data)
setLoading(false);
}
catch (err) {
console.log(`😱 Axios request failed: ${err}`);
}
}
fetchItems()
})
}, [items]);
// ...
}
const mapStateToProps = (state) => {
return {
isAuthenticated: state.auth.token
}
}
export default connect(mapStateToProps)(NewsList)
You need to debug your code. Start by connecting the dots: The output tells you that props.isAuthenticated is undefined. You pass this in from state.auth.token in mapStateToProps():
const mapStateToProps = (state) => {
return {
isAuthenticated: state.auth.token
}
}
So state.auth.token must be undefined also. That's as far as I can get from what you have shown me. You will need to debug further to figure out why. You can use the React Dev Tools to inspect props of your components. You can use Redux Dev Tools to inspect and manipulate the redux state. Check what the value of auth.token is in state. Look where it is supposed to be set and find out why it isn't getting set to a valid value.
Be sure to check this article for tips on how to debug your code.
I'm having an issue trying to use Redux Thunk for implementing JWT Authentication in my app: when I perform login and retrieve the promise from the action, I do not get the updated store (I still have the previous value).
Here is my code:
const Login = ({ doLogin, token }) => {
const submitForm = (e) => {
doLogin(email, password).then(function () {
console.log(token);
});
}
};
return (
// Some JSX that calls submitForm()
);
};
Login.propTypes = {
token: PropTypes.string.isRequired,
doLogin: PropTypes.func.isRequired,
};
function mapStateToProps(state) {
console.log(state);
return {
token: state.auth.access_token,
};
}
const mapDispatchToProps = { doLogin };
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Here is my action creator:
export function doLogin(email, password) {
return (dispatch) => {
dispatch(start());
return axios
.post(config.api.url + "/login", {
email,
password,
})
.then((success_rsp) => {
if (success_rsp.data.success) {
dispatch(success(success_rsp.data.access_token));
} else {
dispatch(error());
}
})
.catch((error_rsp) => dispatch(error()));
};
function start() {
return { type: types.AUTH_LOGIN };
}
function success(access_token) {
return {
type: types.AUTH_LOGIN_SUCCESS,
is_authenticated: true,
access_token: access_token
};
}
function error() {
return {
type: types.AUTH_LOGIN_ERROR,
};
}
}
And here is my reducer:
const authReducer = (state = initialState.auth, action) => {
switch (action.type) {
case types.AUTH_LOGIN:
return {
...state,
is_loading: true,
};
case types.AUTH_LOGIN_SUCCESS:
return {
...state,
is_loading: false,
is_authenticated: action.is_authenticated,
access_token: action.access_token
};
case types.AUTH_LOGIN_ERROR:
return {
...state,
is_loading: false,
is_authenticated: false,
access_token: "",
};
default:
return state;
}
};
The problem is that when I get in the Promise of my Login component, it logs the previous value of the token, and not the one received by the API.
However, when I log the state in the mapStateToProps function, I see that the function is called 3 times in total (one when arriving on the page, one when starting the doLogin, and one on the success), and it logs the right value for the token.
Do you know why the token I get is not updated? And how I could get an updated one?
Thank you!
I am trying to figure out how redux-form works with redux-saga.
Expected Flow:
I have a redux-form inside a selectbox, which includes two options. Selecting an option will trigger an API call to request data. handleOptionSaga will handle this data and will show it inside container.
Actual Flow:
The data isn't passed inside the container after I select an option.
Here is my code;
//ACTÄ°ONS
import FILTER_ITEMS from './FilterOptions.constants';
export const formFilterRequest = values => ({
type: FILTER_ITEMS.FORM_FILTER_REQUEST,
values
});
export const formFilterSuccess = data => ({
type: FILTER_ITEMS.FORM_FILTER_SUCCESS,
jsonData: data
});
export const formFilterFail = () => ({
type: FILTER_ITEMS.FORM_FILTER_FAILURE
});
// REDUCER
const JsonDataReducer = (state : Object = initialState, action : Object) => {
switch (action.type) {
case FILTER_ITEMS.FORM_FILTER_REQUEST:
return {
...state
};
case FILTER_ITEMS.FORM_FILTER_SUCCESS:
return {
...state,
jsonData: action.data
};
case FILTER_ITEMS.FORM_FILTER_FAILURE:
return {
...state
};
default:
return state;
}
};
//REDUX SAGA
function* handleOptionSaga({ values }) {
yield put(startSubmit('optionFilterForm'));
console.log('values ', values);
yield delay(2000);
if (values && values.selectedOption === '1') {
const { data } = yield call(
axios.get,
'https://jsonplaceholder.typicode.com/posts/10'
);
if (data) {
yield put(getOptionActions.formFilterSuccess(data));
}
} else if (values && values.selectedOption === '2') {
const { data } = yield call(
axios.get,
'https://jsonplaceholder.typicode.com/posts/20'
);
if (data) {
yield put(getOptionActions.formFilterSuccess(data));
}
} else {
return false;
}
yield put(setSubmitSucceeded('optionFilterForm'));
yield put(stopSubmit('optionFilterForm'));
}
export default function* flightSearchFormSaga() {
yield takeEvery(FILTER_ITEMS.FORM_FILTER_REQUEST, handleOptionSaga);
yield takeEvery(FILTER_ITEMS.FORM_FILTER_SUCCESS, handleOptionSaga);
}
There is something weird in your FORM_FILTER_SUCCESS flow.
Here, you set jsonData to hold the data from the API call:
export const formFilterSuccess = (data) => ({
type: FILTER_ITEMS.FORM_FILTER_SUCCESS,
jsonData:data
});
But here, you are trying to access action.data instead of action.jsonData:
case FILTER_ITEMS.FORM_FILTER_SUCCESS:
return {
...state,
jsonData: action.data <--- Try to use action.jsonData here
};
Welcome to Stack Overflow!
I am new to redux and I am trying to make it work with my application, but I have problems with understanding how to work with async actions in it. I have action that is api call. This action should be called as soon as my other state is not empty. I do not get any mistakes but do not think that my action is called since the data is empty. Can anybody help to understand what I am doing wrong?
Here is my actions.js. The wordsFetchData is the action I need to call:
export function wordsFetchDataSuccess(items){
return{
type: 'WORDS_FETCH_DATA_SUCCESS',
items
};
}
export function wordsAreFetching(bool){
return{
type: 'WORDS_ARE_FETCHING',
areFetching: bool
}
}
export function wordsHasErrored(bool) {
return {
type: 'WORDS_HAS_ERRORED',
hasErrored: bool
};
}
export function wordsFetchData(parsed) {
return (dispatch) => {
dispatch(wordsAreFetching(true));
fetch('URL', {
method: "POST",
headers: {
"Content-type": "application/json"
},body: JSON.stringify({
words: parsed
})
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(wordsAreFetching(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(wordsFetchDataSuccess(items)))
.catch(() => dispatch(wordsHasErrored(true)));
};
}
Here are my reducers:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
export function wordsAreFetching(state = false, action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return action.areFetching;
default:
return state;
}
}
export function wordsFetchHasErrored(state = false, action) {
switch (action.type) {
case 'WORDS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
This is my componentDidMount function:
componentDidMount = (state) => {
this.props.fetchData(state);
};
This is the function after terminating which the action should be called:
parseInput = async () => {
console.log(this.state.textInput);
let tempArray = this.state.textInput.split(" "); // `convert
string into array`
let newArray = tempArray.filter(word => word.endsWith("*"));
let filterArray = newArray.map(word => word.replace('*', ''));
await this.setState({filterArray: filterArray});
await this.props.updateData(this.state.filterArray);
if (this.state.projectID === "" && this.state.entity === "")
this.dialog.current.handleClickOpen();
else
if (this.state.filterArray.length !== 0)
this.componentDidMount(this.state.filterArray);
};
These are the mapStateToProps and mapDispatchToProps functions.
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.wordsFetchHasErrored,
areFetching: state.wordsAreFetching
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: wordsFetchData
};
};
You only need one action for executing fetching (i.e WORDS_ARE_FETCHING), the rest of the cases (i.e WORDS_HAS_ERRORED & WORDS_FETCH_DATA_SUCCESS) can be handled inside your reducer.
Your action:
export function wordsAreFetching(){
return{
type: 'WORDS_ARE_FETCHING',
}
}
Your new reducer:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return {...state, error: false, areFetching: true};
case 'WORDS_FETCH_DATA_SUCCESS':
return {...state, items: action.payload , areFetching: false};
case 'WORDS_HAS_ERRORED':
return {...state, error: true, areFetching: false};
default:
return state;
}
Then you can trigger WORDS_FETCH_DATA_SUCCESS after you get the data from here:
export function wordsFetchData() {
try {
const response = await axios.get(YOUR_URL);
return dispatch({ type: WORDS_FETCH_DATA_SUCCESS, payload: response.data });
} catch (err) {
return dispatch({ type: WORDS_HAS_ERRORED });
}
}
Take a look at this example, it uses axios that can help you with async calls.
A couple of things:
No need to pass state into your componentDidMount, your mapDispatchToProps is not using it.
Here is a suggestion to structure those functions. They are a bit more concise and readable.
const mapStateToProps = ({items, wordsAreFetching, wordsFetchHasError}) => ({
items,
hasErrored: wordsFetchHasErrored,
areFetching: wordsAreFetching,
});
const mapDispatchToProps = () => ({
fetchData: wordsFetchData(),
});
Other notes and helpful things:
If you're using thunk, you'll have access to your entire redux store in here as a second argument. For example:
return (dispatch, getState) => {
dispatch(wordsAreFetching(true));
console.log('getState', getState());
const { words } = getState().items;
// This is a great place to do some checks to see if you _need_ to fetch any data!
// Maybe you already have it in your state?
if (!words.length) {
fetch('URL', {
method: "POST",
headers: {
......
}
})
I hope this helps, if you need anything else feel free to ask.