Here is the code I'm playing with
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import axios from 'axios'
const initialState = {
user: {},
requesting: false,
err: null
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'REQ_USER_INIT': return { ...state, requesting: true }
case 'REQ_USER_DATA': return { ...state, requesting: false, user: action.user }
case 'REQ_USER_ERR': return { ...state, requesting: false, err: action.err }
}
return state;
}
const logger = (store) => (next) => (action) => {
let previous = JSON.stringify(store.getState())
next(action)
console.log(
'action: ' + JSON.stringify(action) +
'\n\tprevious: ' + previous +
'\n\tcurrent: ' + JSON.stringify(store.getState())
)
}
const store = createStore(reducer, applyMiddleware(logger, thunk))
store.dispatch((dispatch) => {
dispatch({ type: 'REQ_USER_INIT' })
// Fake Online REST API for Testing and Prototyping
// break url to get an error response
let usersEndpoint = 'https://jsonplaceholder.typicode.com/users/1'
axios.get(usersEndpoint)
.then((response) => {
dispatch({
type: 'REQ_USER_DATA',
user: {
id: response.data.id,
username: response.data.username,
email: response.data.email,
}
})
})
.catch((error) => {
dispatch({
type: 'REQ_USER_ERR',
err: error.message
})
})
})
I believe it is pretty straightforward, right? I dispatch REQ_USER_INIT and then REQ_USER_DATA once the response is received. I should log two actions, however I get 3. Second action is undefined and I am strugling to figure out what causes it. Is it a bug with redux-thunk or am I doing something wrong?
Here is the output from my console:
action: {"type":"REQ_USER_INIT"}
·previous: {"user":{},"requesting":false,"err":null}
·current: {"user":{},"requesting":true,"err":null}
action: undefined
·previous: {"user":{},"requesting":false,"err":null}
·current: {"user":{},"requesting":true,"err":null}
action: {"type":"REQ_USER_DATA","user":{"id":1,"username":"Bret","email":"Sincere#april.biz"}}
·previous: {"user":{},"requesting":true,"err":null}
·current: {"user":{"id":1,"username":"Bret","email":"Sincere#april.biz"},"requesting":false,"err":null}
The order of middlewares matters. Try making logger last
const store = createStore(reducer, applyMiddleware(thunk, logger))
Related
Don't know if I followed a wrong tutorial.
But when I try to launch a fetch action it is always return null array on the first few seconds.(based on my initial state) probably because Endpoint provided a response a little delayed.
or I'm not calling the stored valued correctly
Here the logs, you can see upon firing a OnPressIN two times and calling a function
LOG Running "uCon" with {"rootTag":1}
LOG [] <--- First onPress
LOG [] <--- Second onPress
LOG [{"member_id": 14987, "number": "(03) xxxx x495", "status_id": 3}, {"member_id": 14988, "number": "(03) xxxx x123", "status_id": 3}, {"member_id": 14990, "number": "(03) xxxx x125", "status_id": 3}] <--- Third onPress
Here's the action
export const setEmail = email => dispatch => {
dispatch({
type: SET_EMAIL,
payload: email,
});
};
export const getMemberEmail = (emailParam) => {
//It probably return first before waiting for the dispatch below to finish?
return (dispatch) => {
fetch(API_URL + "/query-email", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: emailParam
})
})
.then((res) => {
// If response was successful parse the json and dispatch an update
if (res.ok) {
res.json().then((emailCheck) => {
dispatch({
type: GET_MEMBER_EMAIL,
payload: emailCheck
});
});
} else {
// response wasn't successful so dispatch an error
console.log("Unable to fetch")
}
})
.catch((err) => {
// Runs if there is a general JavaScript error.
console.log(err);
});
};
};
Here's the reducer
import { SET_EMAIL, GET_MEMBER_EMAIL, SET_LOGIN_WARNING_1, SET_LOGIN_WARNING_2, SET_EMAIL_MODAL, SET_SERVICES_MODAL } from "../actions";
const initialState = {
memberEmailCheck: [],
email: '',
loginWarning1: false,
loginWarning2: false,
emailModal: true,
servicesModal: false,
}
function loginReducer(state = initialState, action) {
switch (action.type) {
case GET_MEMBER_EMAIL:
return { ...state, memberEmailCheck: action.payload };
case SET_EMAIL:
return { ...state, email: action.payload };
case SET_LOGIN_WARNING_1:
return { ...state, loginWarning1: action.payload }
case SET_LOGIN_WARNING_2:
return { ...state, loginWarning2: action.payload }
case SET_EMAIL_MODAL:
return { ...state, emailModal: action.payload }
case SET_SERVICES_MODAL:
return { ...state, servicesModal: action.payload }
default:
return state;
}
}
export default loginReducer
and lastly here'sthe store
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import modalReducer from "./reducers/modalReducers";
import loginReducer from "./reducers/loginReducers";
const rootReducer = combineReducers({ modalReducer, loginReducer });
export const Store = createStore(rootReducer, applyMiddleware(thunk));
Now the component
import { getMemberEmail, setEmail, setLoginWarning1, setLoginWarning2, setEmailModal, setServicesModal } from '../redux/actions';
import { useDispatch, useSelector } from 'react-redux';
export default function LogIn({ navigation, route }) {
const backgroundImage = "../../assets/images/background.png";
const logoHeader = "../../assets/images/uConnectedHeader.png";
const { memberEmailCheck, email, loginWarning1, loginWarning2, emailModal, servicesModal } = useSelector(state => state.loginReducer);
const dispatch = useDispatch();
// const [welcomeModal, setWelcomeModal] = useState(true);
const checkEMail = async () => {
await dispatch(getMemberEmail(email));
console.log(memberEmailCheck); <-- Here's the console that I used to track the result
// if (memberEmailCheck.message === 'The email address provided was not found to match a service') {
// dispatch(setLoginWarning1(true));
// } else {
// // dispatch(setEmailModal(false));
// // dispatch(setServicesModal(true));
// validateMemberEmail();
// console.log(memberEmailCheck);
// }
};
and this function is called via an onPressIn
<TextInput
style={styles.modalInput}
placeholder="Email"
onChangeText={(value) => dispatch(setEmail(value))} />
<Pressable
style={({ pressed }) => [
{
backgroundColor: "#FD6B89",
borderRadius: 10,
margin: 10,
opacity: pressed
? 0.5
: 1,
}]}
onPressIn={() => { checkEMail() }}
>
I highly suspect that I'm calling the state when it is not being stored properly.
But I exhausted on trying to find a way make the action to wait for dispatch GET_MEMBER_EMAIL to finish before returning to the component.
I tried async await, promise or I'm really knowledgeable on this one.
Hope you can help me.
Thank you!
I am facing this error when I dispatch the action. I have used this method before it worked fine this time it constantly shows this error. Please Help I am unable to find any solutions on this. Thanks in advance.
Action:
export const listPackages = () => async (dispatch) => {
try {
dispatch({ type: PACKAGE_LIST_REQUEST })
const {data} = await axios.get('/api/packages/all')
dispatch({
type: PACKAGE_LIST_SUCCESS,
payload: data
})
} catch (error) {
dispatch({
type: PACKAGE_LIST_FAIL,
payload: error.message && error.response.data.message ? error.response.data.message : error.message
})
}
}
My Reducer:
import {
PACKAGE_LIST_REQUEST,
PACKAGE_LIST_SUCCESS,
PACKAGE_LIST_FAIL
} from "../constants/packageConstants"
export const packageListReducer = (state = {packages: []}, action) => {
switch(action.type){
case PACKAGE_LIST_REQUEST:
return {loading: true, packages: []}
case PACKAGE_LIST_SUCCESS:
return {loading: false, packages: action.payload}
case PACKAGE_LIST_FAIL:
return {loading: false, error: action.payload}
default:
return state
}
}
My Store:
import {createStore, combineReducers, applyMiddleware} from "redux"
import thunk from "redux-thunk"
import {composeWithDevTools} from "redux-devtools-extension"
import {packageListReducer} from "./reducers/packageReducers";
const reducer = combineReducers({
packageList: packageListReducer,
})
const initialState = {}
const middleware = [thunk]
const store = createStore(reducer,initialState,composeWithDevTools(applyMiddleware(...middleware)))
export default store
Component From Where I dispatched the action:
import {listPackages} from "../actions/packageActions"
export default function HomeScreen1() {
const dispatch = useDispatch()
const packages = []
useEffect(() => {
dispatch(listPackages())
}, [dispatch])
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
Although I have successfully fetched users and profiles but I don't know why this error below occurs:
Cannot read property 'content' of undefined
{content: "Hello world", status: 2}
Initially, I use Firestore and this time fetch 2 docs from different collections, users and profiles.
redux code:
export function fetchUser(id) {
return dispatch => {
usersRef
.doc(id)
.get()
.then(doc => {
profilesRef
.where("uid", "==", id)
.where("status", "==", doc.data().current_status)
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(snapshot) {
const profileItems = { ...doc.data(), profiles: snapshot.data() };
dispatch({
type: FETCH_USER,
payload: profileItems
});
});
});
});
};
}
In container:
import { FETCH_USER } from "../actions";
const initialState = {};
export default (state = initialState, action) => {
switch (action.type) {
console.log("action", action.payload)
// successfully fetched, user info and profiles: profiles info
case FETCH_USER:
return action.payload;
default:
return state;
}
};
In view:
function mapStateToProps({ users }) {
console.log("content", user.profiles.content)
// Cannot read property 'content' of undefined
return { user: users, profiles: user.profiles };
}
reducer file:
import { createStore, applyMiddleware, combineReducers } from "redux";
import UsersReducer from "./reducer_users.js";
const rootReducer = combineReducers({
users: UsersReducer,
});
const store = createStore(rootReducer, applyMiddleware(reduxThunk));
export default store;
Here you are making mistack you are accessing user instead of users
function mapStateToProps({ users , content }) {
console.log("content", user.profiles.content) // change user to users
console.log("content",content) // you can directly access content here
return { user: users, profiles: user.profiles };
}
you can initialize default state here
add one more type FETCH_USER_COMPLETE
import { FETCH_USER } from "../actions";
const initialState = {
content : '',
users : ''
};
export default (state = initialState, action) => {
switch (action.type) {
console.log("action", action.payload)
// successfully fetched, user info and profiles: profiles info
case FETCH_USER:
return { ...state , action.payload };
case FETCH_USER_COMPLETE :
return { ...state , users : action.payload.users , content : action.payload.users.profiles.content };
default:
return state;
}
};
I think it might not be best practice but I have solved like this:
from:
querySnapshot.forEach(function(snapshot) {
const profileItems = { ...doc.data(), profiles: snapshot.data() };
dispatch({
type: FETCH_USER,
payload: profileItems
});
});
to:
querySnapshot.forEach(function(snapshot) {
const userItems = { ...doc.data(), ...snapshot.data() };
dispatch({
type: FETCH_USER,
payload: userItems
});
});
I can get content like {user.content}.
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})
}
}