How to fetch updated state in Component while using Redux - reactjs

I am new to redux, I am getting confused about how to get State from Redux Store when we have multiple Reducer.
let Say this is my combineReducer
import apiCallInProgress from './ApiStattusReducer';
import login from './loginReducer';
const rootReducer = combineReducers({
login,
apiCallInProgress
});
export default rootReducer;
// below is my Login Reducer
const initialState = {
isAuthenticated: false,
user: {}
};
export function loginReducer(state = initialState, action = {}) {
console.log(action.user);
switch (action.type) {
case types.SET_CURRENT_USER:
return {
...state,
isAuthenticated: true,
user: action.user
}
default:
return state
}
}
export default loginReducer;
// now I want to access user from my component,
// I wrote as
function mapStateToProps(state) {
return {
login: state.login
}
}
componentDidMount() {
const { login } = this.props
console.log("set user didmount:" + login.user)
}
componentDidUpdate() {
const { login } = this.props
console.log("set user didupdate:" + login.user)
}
I am not able to get the state of user in the component but when I am pressing login button console.log(action.user) showing proper output in console .
The variable names we mentioned inside combineReducer, the same name do I need to use inside mapStateToProps func to fetch the state. I am very much confused. Someone, please explain.

I think you're doing everything right till you get to the component (although difficult to fully determine without actually testing the code). The main issue is, as far as I can see, you're not actually dispatching the action so login.user is never being set.
The connect method of react-redux has two function parameters - mapStateToProps, which you're using correctly, and mapDispatchToProps, which it doesn't look like you're using.
mapDispatchToProps is a method to pass Redux actions into props, which when invoked, will fire the action, which will in turn be picked up by the reducer, which will return a new state object. Create a new directory called actions in the root of your app. Inside it, create a file called loginActions.js, and within that, put something like the following:
export function setCurrentUser(user) {
return (dispatch) => {
dispatch({ type: 'SET_CURRENT_USER', user)
}
}
import this function into your component, then in your connect function, add mapDispatchToProps
import { connect } from 'react-redux'
import { setCurrentUser } from './actions/loginActions'
// component code
const mapDispatchToProps = {
setCurrentUser: setCurrentUser
}
const mapStateToProps = (state) => {
return {
login: state.login
}
}
connect(mapStateToProps, mapDispatchToProps)(YourComponentName)
In your componentDidMount method, you can now call this action:
componentDidMount() {
this.props.setCurrentUser('myUser')
}
in componentDidUpdate, the user should now be available:
componentDidUpdate(prevProps) {
if (prevProps.login !== this.props.login) {
console.log(this.props.login)
}
}
I hope this helps. Let me know if you need any more help.

Related

How middleware in react life cycle works?

I am new in react js. I have started doing a small product with react-redux. I am using saga middle-ware.
What i have done is as under.
This is the component
//all import work
import { activateAuthLayout, onLoad } from '../../../store/actions';
class EcommerceProductEdit extends Component {
constructor(props) {
super(props);
this.state = {
checked: false,
unselected_lists: [],
main_checked: false
}
//here I get the products props always null
console.log(this.props);
}
componentDidMount() {
this.props.activateAuthLayout();
//dispatching an action to fetch data from api, done in midddleware
if (this.props.user !== null && this.props.user.shop_id)
this.props.onLoad({
payload: this.props.user
});
}
render() {
//here I get the products props
console.log(this.props);
return (
//jsx work
);
}
}
const mapStatetoProps = state => {
const { user, is_logged_in } = state.Common;
const { products, is_loading } = state.Products;
return { user, is_logged_in, products, is_loading };
}
export default withRouter(connect(mapStatetoProps, { activateAuthLayout, onLoad })(EcommerceProductEdit));
Action is
import { FETCH_PRODUCT, FETCH_PRODUCT_SUCCESS } from './actionTypes';
export const onLoad = (action) => {
return {
type: FETCH_PRODUCT,
payload: action.payload
}
}
export const productFetched = (action) => {
return {
type: FETCH_PRODUCT_SUCCESS,
payload: action.payload
}
}
Reducer is
import { FETCH_PRODUCT_SUCCESS } from './actionTypes';
const initialState = {
products: null,
is_loading: true
}
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_PRODUCT_SUCCESS:
state = {
...state,
products: action.payload,
is_loading: false
}
break;
default:
state = { ...state };
break;
}
return state;
}
And saga is
import { takeEvery, put, call } from 'redux-saga/effects';
import { FETCH_PRODUCT } from './actionTypes';
import { productFetched } from './actions';
import agent from '../../agent';
function* fetchProduct(action) {
try {
let response = yield call(agent.Products.get, action.payload);
yield put(productFetched({ payload: response }));
} catch (error) {
if (error.message) {
console.log(error);
} else if (error.response.text === 'Unauthorized') {
console.log(error)
}
}
}
function* productSaga() {
yield takeEvery(FETCH_PRODUCT, fetchProduct)
}
export default productSaga;
I am being able to get the products props only in render function. How would i be able to get it it in constructor ?
I would be really grateful if anyone explained me about react life cycle a little bit more.
Thanks.
updated
a constructor is called during object instantiation. According to the docs "The constructor for a React component is called before it is mounted". So if the props passed to the component are being changed after the component has been mounted you can use componentWillReceiveProps life cycle methods.
componentWillReceiveProps is deprecated so you can use componentDidUpdate instead. Example from the docs.
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.userID !== prevProps.userID) {
// update your component state from here.
this.fetchData(this.props.userID);
}
}
MiddleWare: Middleware just comes in between the flow after the action has been dispatched and before it reaches the reducers, like in your case once you fire onLoad action and before it reaches the reducers, its caught in Saga middleware which executes it according to code written in it
Lifecycle in your case goes the following way:
In your compoenentDidMount method, you dispatch an action of onLoad. The action type in such a case becomes "FETCH_PRODUCT" and same action is now caught in Saga.
Since this is async call, the code in your component continues executing while the Saga perform its action in parallel. It calls API through this line of code: yield call(agent.Products.get, action.payload); . Once API call is completed, it dispatches an action 'productfetched' through this line of code yield put(productFetched({ payload: response }));.
Now this action reaches reducer and modify the state of "products". Since the product state in your redux is modified, your component EcommerceProductEdit re-renders and you get your product list in render method. The point to be noted is that the flow must have already finished executing inside componentDidMount method by this time, so no chance of having products their
Solution to your problem:
Once an action is dispatched and which has become async due to Saga, you won't be able to get value in constructor, if you use Saga. You can just directly call upon the API using axios/fetch library in componentDidMount and await their (Making it synchronous). Once you get response, you may proceed further
In case you have functional component, then you may use Effect hook and bind the dependency to products state. You can write your code in this block, what you want to be executed after API call is made and product list modifies.
React.useEffect(
() => {
// You code goes here
},
[products]
);
You just have to console props rather than doing this.props. You should not reference props with this inside the constructor.
Do this instead:
console.log(props)
Middleware is not related to react lifecycle at all, other than it updates and connected components "react" to props updating.
Check the constructor docs
https://reactjs.org/docs/react-component.html#constructor
Question: why are you trying to log props in the constructor anyway? If you want to know what the props are, use one of the lifecycle functions, componentDidMount/componentDidUpdate, don't use the render function to do side-effects like make asynchronous calls or console log.
componentDidMount() {
console.log(this.props);
}
If you must log props in the constructor though, access the props object that was passed as the component won't have a this.props populated yet.
constructor(props) {
super(props);
...
console.log(props);
}

Redux MapDispatchToProps not functioning

So I'm new to Redux and I'm trying to get this base model working so I can quickly work on a small personal project, I set everything up and have no errors but I'm trying to test and my function doesn't work so I was hoping someone could point out what I've missed.
I've followed multiple different tutorials and each has a different approach so that has me lost a bit so I apologize for that.
My store.js looks like so
import rootReducer from "./reducers";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
I've used a combineReducers in my index.js in reducers folder and the auth: points to the authReducer.js file, which is this
const INIT_STATE = {
email: "",
password: "",
isLoggedIn: "false"
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
isLoggedIn: action.value
};
default:
return state;
}
};
Now What I'm aiming for is to have a button that changes that "IsLoggedIn" initial state to a true string instead of a false, I've went into my actions folder and made an authActions.js which looks like so
import { IS_LOGGED_IN_CHANGE } from "../actions/types";
import store from "../store";
export const isLoggedInChange = value => {
return dispatch => {
dispatch({
type: IS_LOGGED_IN_CHANGE,
value
});
};
};
And Finally I want to show you my component page which is showing all this, It's looking like so
import { connect } from "react-redux";
import styles from "./Landing.module.css";
import { isLoggedInChange } from "../../actions/authActions";
class Landing extends Component {
makeTrue = () => {
isLoggedInChange("true");
};
constructor(props) {
super(props);
this.state = {
email: "",
password: ""
};
}
render() {
return (
<div className={styles.background}>
<button onClick={this.makeTrue}>MAKE TRUE</button>
{this.props.isLoggedIn}
</div>
);
}
}
const mapStateToProps = state => ({
isLoggedIn: state.auth.isLoggedIn
});
const mapDispatchToProps = dispatch => ({
isLoggedInChange: value => dispatch(isLoggedInChange(value))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Landing);
Can you tell if I dropped anything making this? why is the button not changing the store state? TIA
Two problems here. You're calling your action creator directly not props.isLoggedInChange
makeTrue = () => {
this.props.isLoggedInChange("true");
};
And you need to spread the old state inside your action
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
...state,
isLoggedIn: action.value
};
Isn't the point of my mapDispatchToProps to be able to use the function right away as I was doing
Yes, the problem is mapDispatchToProps inject a function (or multiple functions) wrapped in dispatch into your props.
import { actionCreator } from './actions
const mapDispatchToProps = dispatch =>({
actionCreator : () => dispatch(actionCreator)
})
Now you have two actionCreator, one globally available in the scope (which is your action creator) and props.actionCreator which is the original action creator wrapped in dispatch. So when you call actionCreator() from inside your component it won't throw any errors (cause there is a function named actionCreator in the scope, but you will be calling the wrong function, the right one is located at props.actionCreator.
Why do I need to spread the state?
A reducer is a pure function which receives a state and action and returns the new state. When you just return
return {
isLoggedIn : true
}
You're actually overwriting the original state (which contains other properties), so first you need to spread the original state to maintain it's structural integrity and them overwrite the properties you want
return{
...state,
isLoggedIn : !state.isLoggedIn
}
Redux state is immutable so you need to return a brand new instance of state, change your reducer state to the below.
export default (state = INIT_STATE, action) => {
switch (action.type) {
case IS_LOGGED_IN_CHANGE:
console.log(action);
return Object.assign({}, state, {
isLoggedIn: action.value
});
default:
return state;
}
};
The key difference there being the
return Object.assign({}, state, {
isLoggedIn: action.value
});
Object.assign in the way I'm using it here combines the state object into a brand new object. Check out immutability within redux reducers and I'd recommend adding redux-immutable-state-invariant as a dev package, it can detect when you're directly modifying state and help point out errors like this
Return the state with the new value for isLoggedIn. Use the reducer like this:
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
...state,
isLoggedIn: action.value
};

Redux: How to set default state of errorCode inside Redux store after clicking button wrapped in <Link> tag

I am trying to handle API error using Redux dispatch, by creating action, reducer & connect(mapStateToProps) at the end.
So when I enter wrong url, I get error message on my view.
But my button links wrapped inside tag are clicked then my next view shows a same the error message instead on rendering the new view.
// Actions
export const API_ERROR = "API_ERROR"
export const resetErrorCode = () => ({
type: RESET_ERROR_CODE
})
// Reducers
import { API_ERROR, RESET_ERROR_CODE } from "actions"
const api = (state = { errorCode: null }, action) => {
switch (action.type) {
case API_ERROR:
return { ...state, errorCode: action.errorCode }
case RESET_ERROR_CODE:
return { ...state, errorCode: action.errorCode }
default:
return { errorCode: null }
}
}
export default api
// Combiner reducers
import { combineReducers } from 'redux'
import API from "./api"
export default combineReducers({
API
});
// test.jsx
import { connect } from 'react-redux'
import API from 'api.js'
import { resetErrorCode } from 'actions'
import { Link } from 'react-router-dom'
class Test extends Component {
componentDidMount() {
API.call()
}
render() {
switch (this.props.errorCode) {
case 400:
case 404:
return (
<div>
Error Page
</div>
)
default:
return (
<div>
<Link to={{pathname: '/nextPage'}}> <button> Next Page </button> </Link>
</div>
)
}
}
componentWillUnmount() {
this.props.errorCode()
}
}
const mapStateToProps = (state) => {
return {
errorCode: state.apiStatus.errorCode,
}
}
const mapDispatchToProps = (dispatch) => {
return {
errorCode: () => dispatch(resetErrorCode())
}
}
export default connect(mapStateToProps)(Test)
// api.js
class API () {
responseErrorHandler(statusCode) {
console.log('responseErrorHandler() called')
store.dispatch(notifyAPIError(statusCode))
}
call = () => {
axios.get(url)
.then(...)
.catch((error) => this.responseErrorHandler(error.response.status))
}
}
I monitored my redux state while correct url redux state shows {errorCode: null} and when I enter wrong url redux state changes to {erroCode: 404}. Now If click my button, new view is rendered but the error code dont changes. Redux state is still {erroCode: 404}.
I tried to use componentWillUnmount to change the status of errorCode to null but it's not working.
Is there any solution to this problem and I'm not sure about my approach of componentWillUnmount().
I would appreciate the help.
I don't see much value on having the error on the Redux store if you're using it only inside a single component, and clearing it on unmount: a simple state would make things easier on this case.
If you're sure you want to go on that route, the right moment to clear the previous error is just when a new request is made, which happens on mounting of the component. This means that call() should reset the error before making the new call (which is now "loading"), and only set it again after it ends in an error.
Clearing at unmount would probably not be needed, as only pages which make the call need to check if an error happened, and those will reset it on mount.
Finally, some code bugs you might have:
Make sure your reducer is right: you don't usually modify the state on the "default" of the switch, as any other actions from other components would cause the state to get lost.
You're using the same prop name for the mapStateToProps and mapDispatchToProps, which is a problem. It doesn't fail because you're not using the mapDispatchToProps function on connect, so you might want to remove it entirely.

How to get the value as props in a different component

In my react application, I have three parallel components. In my first component, I am doing an API call and based on the response I am routing the flow to Validated or Non-Validated Component.
Once the user is routed to validated component, there is a button on click of which user should be redirected to another component which should display the data in API response (first component) as key value pair. I am using Redux for state management.
The issue I am facing is the data is dispatched as an empty object from the store. I am not sure where I am going wrong but when I am debugging the app, I see the the action is not getting dispatched to the store and it's always returning me the initial state.
action.js-
export const setPoiData = (poiData) => dispatch => {
console.log('inside actions');
dispatch({
type: SET_POI_DATA,
payload: poiData
})
}
Reducer.js-
const initialState = {
poiData: {},
}
const reducerFunc = (state = initialState, action) => {
switch (action.type) {
case SET_POI_DATA:
console.log('inside poi reducers');
return {...state,poiData: action.payload}
default: return {...state}
}
}
Component 1-
//API call
Detail Component-
To get the data from store I am doing something like below-
componentDidMount() {
console.log(this.props.poiData)
}
function mapStateToProps(state) {
return {
poiData: state.poiData,
}
}
const mapDispatchToProps = dispatch => ({
setPoiData(data) {
dispatch(setPoiData(data));
}
})
I am not sure where I am going wrong. Can someone suggest me how to proceed ahead on this?
inside componentDidMount() you must call action like this this.props.setPoiData(<your data here>);

React call logger action from reducers

Trying to call an action from actions/reducers with no success. I know this is an anti-pattern, but I want to custom log (will have a log view) of what is happening in the app. A view will list these items.
When running this I get the error:
Reducers may not dispatch actions
If I understand it correctly I should use actors but can't find good examples.
Any suggestions on how make a custom logger that can be triggered from reducers and actions (works fine from Components)
actions/log.js
import {appError} from './index';
import moment from 'moment'
import {store} from '../containers/app';
// ------- LOG WHATS HAPPENING -------
export const LOG_ADD = 'LOG_ADD';
export function logAdd(item){
return {
type: LOG_ADD,
item: item
}
}
export function triggerLog(log){
return function (dispatch, getState) {
dispatch(logAdd(log));
}
}
export const LOG_ITEM_ERROR = "logerror";
export const LOG_ITEM_NOTIFY = "lognotify";
export class Log {
constructor(type,title,msg1,msg2) {
this.date = moment().unix();
this.type = type;
this.title = title;
this.msg1 = msg1;
this.msg2 = msg2;
}
static error(title,msg1,msg2){
dispatch( triggerLog( new Log(LOG_ITEM_ERROR,title,msg1,msg2) ) );
}
static notify(title,msg1,msg2){
store.dispatch( triggerLog( new Log(LOG_ITEM_NOTIFY,title,msg1,msg2) ) );
}
}
reducers/version.js
export default function version(state = initialVersion,action){
switch(action.type){
case VERSION_RESPONSE:
if(action.json.UpdateRequired){
console.log("FORCE UPDATE");
Log.error('Version','Force update');
//#TODO make sure app gets updated
return;
}
Log.notify('Version','Check complete');
return Object.assign({}, state, {
isDone: true,
isFetching: false,
isValid: true
})
SOLUTION
Upgraded to react-native 0.30
Do not use the dispatch in action:
export function getVersion() {
return function (dispatch, getState) {
dispatch(mycall()); // <-- do not do this
Now the code example works.
If you want to dispatch non-primitive actions you need redux-thunk
Redux has also have nice example which is using this thunkMiddleware.
If you only want to log state changes you can use Chrome Redux plugin on use this simple redux-logger middleware.
Or write your own custom middleware

Resources