Reducer: getting information about value changes at the store - reactjs

I don't understand how to get an updated value from the reducer store. For example, I have a React component. In this component after some actions, for example, after a few clicks on a button, I call the action from my reducer actions script like this.props.PostSomethingToServer(). Then the action sends some data to the node express server. The server makes some changes with data and then sends a response to the client store reducer. How can I get the updated data from the reducer store? I need to call another function in this React component with updated values.
By the way, I use mapStateToProps and export default connect() in the React component. As I know, mapStateToProps and export default connect() help to get data from the store before render(). But I still don't understand how to get updated data from the store after some actions.
A couple of code:
React component:
ChangeArrayData(){
let something = [];
// filling this array and modifying values
//then I call the action
this.props.postSomethingToServer(something);//something will be changed by server logic and will be returned with updated data by a response.
//then I wanna export the data to .excel, for example
this.ExportToExcel(); // here I have to get updated data from the reducer, but I don't have any information about changes in the reducer.
}
Reducer action:
export const postSomethingToServer= rankedElements => dispatch => {
axios
.post("/api/postData", elements)
.then(response => {
dispatch({
type: POST_SOMETHING_SUCCESSFUL,
status : "success",
payload: response.data
});
//... etc.
Reducer:
const initialState = {
something: {},
status: "",
error : ""
};
export default function(state = initialState, action) {
switch (action.type) {
case POST_SOMETHING:
return {
...state,
status: action.status,
}
case POST_SOMETHING_SUCCESSFUL:
return {
...state,
status: action.status,
something: action.payload
}
case GET_ERRORS:
return {
...state,
status: action.status,
error: action.error
}
default:
return state;
}
}

You Should assign reducer state values to some local state like following:
`const mapStateToProps = state => ({
contacts: state.data
});
export default connect(mapStateToProps, { actions })
(withStyles(contactStyle)(Contact));`
Here 'contacts' is a local state name we are using in the class and 'data' is a state name that we return from reducer after updating a state.
You can access the updated data using componentWillReceiveProps method like,
`componentWillReceiveProps(nextProps) {
if(nextProps.contacts !== undefined) {
//Handle updated states here
}
}`

After you dispatch the action, your reducer state something should have the data you expect. Given that you have mapped the data in your mapStateToProps function you can access it via props.
ChangeArrayData() {
let something = [];
this.props.postSomethingToServer(something);
this.ExportToExcel();
console.log(this.props.somethingReducerState);
}
const mapStateToProps = (state) => ({
somethingReducerState: state.yourReducerName.something,
});

I have the solution: I'm using componentWillReceiveProps(nextProps) and can receive a result from the reducer.
Thanks all for the answers.

Related

React-Redux: how to set the state?

I am trying to understand someone else their code but have difficulty understand the interaction between Redux and React.
On a React page, I invoke a Redux action called getSubscriptionPlan. Inside that Redux action, I see it is able to load the correct data (point 1 below). This uses a reducer, in which I can again confirm the correct data is there (point 2 below).
Then the logic returns to the React page (point 3 below). I now would expect to be able to find somewhere in the Redux store the previously mentioned data. However, I can't find that data listed anywhere... not in this.state (where I would expect it), nor in this.props. Did the reducer perhaps not update the store state...?
What am I doing wrong and how can I get the data to point 3 below?
React page:
import { connect } from "react-redux";
import { getSubscriptionPlan } from "../../../appRedux/actions/planAction";
async componentDidMount() {
let { planId } = this.state;
await this.props.getSubscriptionPlan(planId);
// 3. I can't find the data anywhere here: not inside this.state and not inside this.props.
this.setState({plan: this.state.plan});
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.payment.paymentData !== this.props.payment.paymentData) {
this.setState({
checkout: this.props.payment.paymentData,
plan: this.props.payment.paymentData.plan,
});
}
}
const mapStateToProps = (state) => {
return {
plan: state.plan,
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ getSubscriptionPlan }, dispatch
);
};
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Checkout)
);
Redux action:
export const getSubscriptionPlan = (id) => {
let token = getAuthToken();
return (dispatch) => {
axios
.get(`${url}/getSubscriptionPlan/${id}`, {
headers: { Authorization: `${token}` },
})
.then((res) => {
if (res.status === 200) {
// 1. From console.log(res.data) I know res.data correctly now contains the data
return dispatch({
type: GET_PLAN_SUCCESS,
payload: res.data,
});
})
};
};
Reducer:
export default function planReducer(state = initial_state, action) {
switch (action.type) {
case GET_PLAN_SUCCESS:
// 2. I know action.payload, at this point contains the correct data.
return { ...state, plan: action.payload };
default:
return state;
}
}
You are getting tripped up on how Redux works.
Redux does not use react component state. It manages state separately, and passes that state to components as props. When you call getSubscriptionPlan, you asynchronously dispatch an event to Redux, which handles the event and updates store state in the reducer. This state is the passed to the connected components mapStateToProps function, mapped to props, and then passed as props to your component. Passing new props triggers a componentDidUpdate and a rerender of the component.
A few key things here.
Redux does not interact with component state unless you explicitly set state with props passed from Redux.
Redux is asynchronous. That means that when you make a change to state via dispatch, the change is not immediately available in the component, but only available when new props are passed. It's event driven, not data binding. As a result, in your code you woun't see the plan prop in componentDidMount because at the time componentDidMount the call to getSubscriptionPlan hasn't happened.
You should see the prop populated in this.props in componentDidUpdate and in render before the didUpdate.
When working with react, it's best to think of components as basically functions of props with some extra lifecycle methods attached.

React Redux store update doesn't trigger component rerender

I'm new in Redux and have a problem with rerendering after the store changed. I have found many similar problems here on SO but still can't solve my issue.
I have a monthly task(event) calendar with multiple tasks. The Calendar is the main component and some level deeper there are multiple TaskItem components. At the first render, the calendar and the tasks are rendered fine (In this case without employee names). In the Calendar component I trigger loading employees with a useEffect hook. I can see the network request on my console. Besides this, the console logs in the action, and in the reducer also show the employee list. And the Redux devtool also shows the loaded employees. Still the mapStateToProps on TaskItem shows a completly empty state.
What I'm doing wrong?
Here is my related code:
Calendar:
const Calendar = ({startDay, tasks, loadEmployeesAction}) => {
useEffect(()=>{
loadEmployeesAction();
},[]);
...
}
export default connect(null, {loadEmployeesAction})(Calendar);
TaskItem:
const TaskItem = ({task, onTextEdit, onTaskView, saveTask, employees }) => {
...
}
const mapStateToProps = (state) => {
console.log('Actual state is: ', state);
return {
employees: state.employees
}
}
export default connect(mapStateToProps)(TaskItem);
Reducer:
export const employeeReducer = (state = [], action) => {
switch (action.type) {
case actionType.EMPLOYEES_LOADED:
console.log('Reducer - Employees loaded:', action );
return action.payload.employees;
default :
return state;
}
}
Actions:
const employeesLoaded = (employees) => {
return {type: actionType.EMPLOYEES_LOADED, payload: {
employees
}
}
}
export const loadEmployeesAction = () => {
return (dispatch) => {
return employeeApi.getAllEmployees().then(emps => {
console.log('Action - Employees loaded: ', emps);
dispatch(employeesLoaded(emps));
})
}
}
Root reducer:
export const rootReduxReducer = combineReducers({
employees: employeeReducer
});
I found the error. It was a very clumsy mistake.
All of my posted code was fine, but I put the store creation in a component that was rerendered again and again so my store was recreated again and again.
The reducer code seems to be not as the redux pattern. So usually the state object is not directly replaced with a different object. Instead only the part of the state that needs to be changed is only with some non-mutating operation like spread operator.
So I think the reducer code should be changed like
export const employeeReducer = (state = [], action) => {
switch (action.type) {
case actionType.EMPLOYEES_LOADED:
return {...state,employees:action.payload.employees}
default :
return state;
}
}
if the response from the API is in the form
[{"employee_name":"name","employee_age":24},.....]

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
};

Class object not working properly in reducer

I have a user model (es6 class) and I'm creating a object using the new keyboard and passing that to the initialState to my userReducer function. How can I update the model based on action.
E.g. If I try to dispatch an action to change the isLogging in userModel then the prevState and nextState is same in logger.
https://i.ibb.co/0CBSZ5v/Screenshot-from-2019-04-19-19-07-44.png
User Reducer
import { USER } from '../constants'
import type { IUserInitialState, IUserAction } from '../types'
import { UserModel } from '../models'
const initialState: IUserInitialState = new UserModel()
export const userReducer = (state: IUserInitialState = initialState, action: IUserAction): Object => {
console.log(state)
switch (action.type) {
case USER.LOGIN_REQUEST:
console.log(state)
initialState.userIsLogging = action.payload
return initialState
default:
return state
}
}
------------------------------
User Action
export const loginRequest = (type: boolean): Object => {
return {
type: USER.LOGIN_REQUEST,
payload: type
}
}
User Model
export class UserModel {
user: IUserModel = {
username: '',
password: '',
isLogging: false
}
set userModel(userObject: IUserModel) {
this.user = userObject
}
set userIsLogging(logging: boolean) {
this.user.isLogging = logging
}
get userIsLogging() {
return this.user.isLogging
}
}
[1]: https://i.ibb.co/0CBSZ5v/Screenshot-from-2019-04-19-19-07-44.png
You are using reducer wrong.
1- When you create a state, make sure it's just a primitive type without any methods.
2- A reducer is responsible of creating a new state on any action. But you are only returning initial state. You should have something like
case USER.LOGIN_REQUEST:
console.log(state)
initialState.userIsLogging = action.payload
return {
...state,
userIsLogging: action.payload,
}
3- You might want to check sagas. You don't have to handle all this async logic yourself
A reducer must be a pure function. It should returns a new state based on a previous state and an action.
Please do not mutate the previous state. Instead, create a new instance, make the changes you want there and finally returns this new instance.
I would highly recommend you to watch the videos from the redux github page. No better explanation that the one from the redux author.

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>);

Resources