React-Redux mapStateToProps doesn't update component - reactjs

So I simplified my code but basically I have a straight-forward redux store:
/* store.js */
import { createStore } from 'redux';
const reducer = (state = {}, action) => {
if (action.type === 'action') state.data = data;
return state;
}
const store = createStore(reducer);
store.subscribe(() => {
console.log(store.getState()); // returns the right state, updates properly
});
export default store;
A Loader that that pulls the data from the server and dispatches it to the store:
/* Loader.js */
class Loader {
dispatch (allDocuments) {
store.dispatch({
type: 'action',
data: data
});
}
async fetchData () {
try {
const allDocuments = await ajaxCall('GET', '/fetchData');
this.dispatch(allDocuments);
return allDocuments;
} catch (e) {
console.error(e);
}
}
}
export default Loader;
And then this is my App.js file where I fire the Loader fetch method every 5 seconds and map the store state to a React component:
/* App.js */
import Loader from './Loader';
const loader = new Loader();
setInterval(async () => {
await loader.fetchData();
}, 5000);
const App = ({
data
}) => {
console.log(data); //doesn't update
return (
<div>
<p>{data}</p>
</div>
)
};
const mapStateToProps = state => ({data: state.data,})
export default connect(mapStateToProps)(App);
So the problem here is that the component does not update. Loader dispatches properly, and the redux store does get updated but the data prop in App remains an empty object, and doesn't refire the render method.
Why is mapStateToProps not updating the component when the store state changes?

mapStateToProps expects that you will not mutate the state. The problem is your reducer, which is mutating the state variable by assigning directly to state.data.
To avoid mutating the state, you'll want to return a new copy of the object whenever you change the data. Like this:
const reducer = (state = {}, action) => {
if (action.type === 'action') {
return {
...state,
data: action.payload
}
return state;
}
Of course if you only have one type of action than redux is not the right tool for the job.

Your reducer doesn't save the action.data payload. It also isn't returning a new state object reference.
const reducer = (state = {}, action) => {
if (action.type === 'action') state.data = data; // <-- mutation
return state;
}
When the action type matches then you should return a new state object reference with the action.data payload.
const reducer = (state = {}, action) => {
if (action.type === 'action') {
return {
...state,
data: action.data;
};
}
return state;
}

Related

Empty Array inside Redux Action map object

I'm getting an object from an action (using axios) and using a map function to iterate it.
I also need to get another action but inside the parent object mapped.
I see that the request/response are ok (with returned data), but the reducer variable still gets empty.
1: component gets data
componentDidMount() {
const { match: { params } } = this.props;
this.props.getSaleDetails(params.order_id);
}
2: defining mapStateToProps and mapDispatchToProps
const mapStateToPropos = state => ({
saleDetails: state.salesOrders.saleDetails,
saleDetailFurthers: state.salesOrders.saleDetailFurthers
});
const mapDispatchToProps = dispatch =>
bindActionCreators({ getSaleDetails, getDetailFurthers }, dispatch);
3: creating a const from the redux props
const detailsArray = saleDetails.data;
4: iterate array with map function
// getDetailFurthers is another action, getting data by passing "detail_id" and updating "saleDetailFurthers" props
{detailsArray && detailsArray.map((item) => {
const {getDetailFurthers, saleDetailFurthers} = this.props;
getDetailFurthers(item.detail_id)
console.log(saleDetailFurthers)
// empty array????
count++;
return (
<Paper className={classes.paper} key={item.detail_id}>
// ... next code lines
5: Actions
export function getDetailFurthers(detail_id){
return async dispatch => {
const request = await axios.get(`${BASE_URL}/salesorders/detail/furthers/${detail_id}`)
return {
type: "SALE_DETAIL_FURTHERS_FETCHED",
payload: request
}
}
}
6: Reducers
const INITIAL_STATE = {
//... others
saleDetailFurthers: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
///... other cases
case "SALE_DETAIL_FURTHERS_FETCHED":
return { ...state, saleDetailFurthers: action.payload }
default:
return state
}
};
I expect the "saleDetailFurthers" const be loaded with data from redux action.
You need to use dispatch instead of returning, like so:
dispatch({
type: "SALE_DETAIL_FURTHERS_FETCHED",
payload: request
});
export function getDetailFurthers(detail_id) => dispatch =>{
const request = await axios.get(`${BASE_URL}/salesorders/detail/furthers/${detail_id}`)
dispatch ({
type: "SALE_DETAIL_FURTHERS_FETCHED",
payload: request
})
}

how to use mapDispatchToProps in react redux

I am new in redux.
My code :
Home Screen
<Text> {{this.props.mycity}} </Text>
const mapStateToProps = function(state) {
return {
mycity: state.layersFlag.baseDistrictADhabi //consist true/false
}
}
export default connect(mapStateToProps)(HomeScreen);
Sidemenu Screen :
UI
<Switch onValueChange={(flag) => {
this.props.toggleCity();
} value={this.state.city} />
const mapDispatchToProps = dispatch => {
return {
toggleCity: () => {
dispatch({ type: "changeCity" })
}
};
};
export default connect(null, mapDispatchToProps)(SideMenuScreen);
Store and reducer setup :
const initialState = {
city : {
mycity: true
}
};
const reducer = (state = initialState, action)=>{
switch (action.type) {
case "changeCity":
return Object.assign({}, state, {
mycity: action.payload.mycity
})
default:
return state;
}
}
const Store = createStore(reducer);
I am stuck in sidemenu. How to dispach in mapDispatchToProps method:
How to pass action in mapDispatchToProps in sidemenu?
If my assumptions on what your Switch component does is correct, it would trigger the onValueChange event-listener when you pass in this.state.city to the value prop. You end up calling this.props.toggleCity() to dispatch your changeCity action. I think the set-up is correct for here...
However, it looks like your reducer is expecting an action.payload which you never passed in as part of the action.
const reducer = (state = initialState, action)=>{
switch (action.type) {
case "changeCity":
return Object.assign({}, state, {
mycity: action.payload.mycity
})
default:
return state;
}
}
So yes the dispatch is working correctly, but you are not passing all the necessary data for your reducer to return a new piece of state.
You need to update your mapDispatchToProps, your event-handler and your reducer to something like
<Switch onValueChange={(flag) => {
this.props.toggleCity(this.state.city);
} value={this.state.city} />
const mapDispatchToProps = dispatch => {
return {
toggleCity: (myCity) => {
dispatch({ type: "changeCity", payload: myCity })
}
};
};
export default connect(null, mapDispatchToProps)(SideMenuScreen);
Your reducer also seems to have an extra key, you don't need to access the mycity prop in payload if its already the payload. Update to:
const reducer = (state = initialState, action)=>{
switch (action.type) {
case "changeCity":
return Object.assign({}, state, {
mycity: action.payload
})
default:
return state;
}
}
Adding on, if you want your Hone component to re-render with the new data in your redux-state, you can do something like this.
In your HomeScreen component, make use of a state-variable to save your abudhabi or whatever city-value and call componentDidUpdate() to setState and re-render your component.
class HomeScreen extends React.Component{
state = {
abudhabi: false
}
//when the component gets the new redux state this will trigger
componentDidUpdate(prevProps){
if(this.props.abudhabi !== prevProps.abudhabi){
this.setState({
abudhabi: this.props.abudhabi
})
}
}
}

Redux Store populates in getInitialProps but is empty on client

I'm using the Redux Thunk example template. When I dispatch an action in getInitialProps, that populates my store, the data is loaded but after the page is rendered, the store is still empty.
static async getInitialProps({ reduxStore }) {
await reduxStore.dispatch(fetchCategories())
const categories = reduxStore.getState().programm.categories;
console.log('STATE!!!', categories)
return { categories }
}
The categories will load correctly but when I inspect my store, the categories state is empty.
Here is my store:
import db from '../../api/db'
// TYPES
export const actionTypes = {
FETCH_PROGRAMMS: 'FETCH_PROGRAMMS',
FETCH_CATEGORIES: 'FETCH_CATEGORIES'
}
// ACTIONS
export const fetchCategories = () => async dispatch => {
const categories = await db.fetchCategories();
console.log('loaded Cate', categories)
return dispatch({
type: actionTypes.FETCH_CATEGORIES,
payload: categories
})
}
// REDUCERS
const initialState = {
programms: [],
categories: []
}
export const programmReducers = (state = initialState, action) => {
switch (action.type) {
case actionTypes.FETCH_PROGRAMMS:
return Object.assign({}, state, {
programms: action.payload
})
case actionTypes.FETCH_CATEGORIES:
console.log('Payload!', action);
return Object.assign({}, state, {
categories: action.payload
})
default:
return state
}
}
How can I make the redux state loaded on the server (in getInitialProps) be carried over to the client?
After hours of searching for solution it seems like I found my problem. It seems like I need to pass an initialState when creating the store. So instead of this:
export function initializeStore() {
return createStore(
rootReducers,
composeWithDevTools(applyMiddleware(...middleware))
)
}
I'm doing this and it works now
const exampleInitialState = {}
export function initializeStore(initialState = exampleInitialState) {
return createStore(
rootReducers,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
)
}
If you do this:
return { categories }
in getInitialProps, categories should be available in component's props in client side.
It should be available in Redux as well, this could cause the problem:
return Object.assign({}, state, {
categories: action.payload
})
Take a look at this Object.assign, the function only takes 2 parameters.
My normal way of doing this:
return {
...state,
categories: action.payload,
};

How can I keep previous state and add a new state to the previous state in Redux reducers?

The below is one of my reducers. In the function applySetRandomImages, I want to access previous state of randomImages to add a new state of randomImages with the previous state.
How can I do that? does the reducer function in Redux provides some call back function for that? or should I implement that on my own?
// ACTIONS
const SET_RANDOM_IMAGES = "SET_RANDOM_IMAGES";
// ACTION CREATORS
function setRandomImages(randomImages) {
return {
type: SET_RANDOM_IMAGES,
randomImages
};
}
// API ACTIONS
function getRandomImages(page) {
return (dispatch, getState) => {
fetch(`/boutiques/random-images/?page=${page}`)
.then(response => response.json())
.then(json => dispatch(setRandomImages(json.results)))
.catch(err => console.log(err));
};
}
// INITIAL STATE
const initialState = {};
// REDUCER
function reducer(state = initialState, action) {
switch (action.type) {
case SET_RANDOM_IMAGES:
return applySetRandomImages(state, action);
default:
return state;
}
}
// REDUCER FUNCTIONS
function applySetRandomImages(state, action) {
const { randomImages } = action;
return {
...state,
randomImages <--- I need to merge the randomImages with a new state of randomImages
};
}
// EXPORTS
const actionCreators = {
getRandomImages,
};
export { actionCreators };
// DEFAULT REDUCER EXPORTS
export default reducer;
You can merge randomImages by spreading the old state and the new one into a new array:
function applySetRandomImages(state, action) {
const { randomImages } = action;
return {
...state,
randomImages: [...state.randomImages, ...randomImages],
};
}
Separate actions, reducers and types into their own folders.
types/index.js
export const SET_RANDOM_IMAGES = "SET_RANDOM_IMAGES";
actions/imageActions.js
import * as types from '../types';
export const getRandomImages = page => dispatch => (
fetch(`/boutiques/random-images/?page=${page}`)
.then(response => response.json())
.then(json => dispatch({ type: types.SET_RANDOM_IMAGES, payload: json.results })))
.catch(err => console.log(err))
)
From within a component, you will connect to redux state (state.images or state.images.collection) and dispatch the action (getRandomImages):
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getRandomImages } from '../actions/imageActions';
class Example extends Component {
componentDidMount = () => this.props.getRandomImages(); // dispatches action creator
render = () => (
<div>
{ /*
The props below are redux state passed into
the component via the connect function
*/ }
{this.props.images.map(image => (
<img src={image.src} alt="image.name" />
))}
{this.props.collection.map(image => (
<img src={image.src} alt="image.name" />
))}
</div>
)
}
export default connect(state => ({
images: state.images, // pulled from redux state (state.images => this.props.images)
collection: state.images.collection // pulled from redux state (state.images.collection => this.props.images.collection)
}), { getRandomImages})(Example)
It will then trigger the AJAX request, then return a type and payload to your reducer:
reducers/index.js
import * as types from '../types'
// overwrite images for each successful fetch request
const imageReducer(state={}, {type, payload}) {
switch (type) {
// spread out any previous state, then spread out the payload (json.results)
case types.SET_RANDOM_IMAGES: return { ...state, ...payload }
default: return state;
}
}
// or append images on each successful fetch request...
const imageReducer(state={}, {type, payload}) {
switch (type) {
case types.SET_RANDOM_IMAGES:
return {
...state, // spread out any previous state
collection: [
...state.collection, // then spread out any previous "collection" state,
...payload // then spread/append the payload (json.results)
]
}
default: return state;
}
}
export default combineReducers({
images: imageReducer
});
The reducer will then spread out any previous imageReducer state, then append the res.results to it via payload. Now it exists in redux as state.images or state.images.collection. This is then pulled from redux state and into the component above as this.props.images or this.props.images.collection.
I want to access previous state of randomImages to add a new state of randomImages with the previous state
return {
...state,
...randomImages
};
If previous state was:
{
a: 1,
b: 2,
c: 3
}
And randomImages is:
{
d: 4,
e: 5
}
Then the returned new state will be
{
a: 1,
b: 2,
c: 3,
d: 4,
e: 5
}

Reload render issue after change props using Redux?

I use the redux in my apps. there is only one state is define. I can change the state and render the screen. but whenever i change state props I can't reload the screen.
Code:
action-types.js
export const SET_NOTIFICATION = "SET_NOTIFICATION";
action.js
import {
SET_NOTIFICATION,
} from "./action-types";
let initialState = {
notyIndex: 0,
};
export const setNotyIndex = (notyIndex) => ({type: SET_NOTIFICATION, notyIndex});
reducer.js
import {
SET_NOTIFICATION,
} from "./action-types";
let initialState = {
notyIndex: 0,
};
export default reducer = (state = initialState, action) => {
switch (action.type) {
case SET_NOTIFICATION:
return Object.assign({}, state, {notyIndex: action.notyIndex});
break;
default:
return initialState;
break;
}
};
I connect the redux as below. DashBoard.js
import { setNotyIndex } from "./action";
import {connect} from "react-redux"
********* LIFE CYCLE START ************
componentWillMount(){
console.log('Call update');
console.log('Index is',this.props.notyIndex);
}
shouldComponentUpdate=()=>{
return true
}
componentDidUpdate=(prevProps, prevState, snapshot)=>{
console.log('Call update');
console.log('Index is',this.props.notyIndex);
}
componentDidMount() {
console.log('Call update');
console.log('Index is',this.props.notyIndex);
}
********* LIFE CYCLE END ************
const mapDispatchToProps = (dispatch) => {
return {
setNotyIndex: (notyIndex) => dispatch(setNotyIndex(notyIndex)),
}
};
const mapStateToProps = (state) => {
if (state === undefined) {
return {};
}
return {
notyIndex: state.notyIndex,
}
};
connect(mapStateToProps, mapDispatchToProps)(DashBoard);
value is set like.
setNotyIndex(1);
- As above code the no one lifecycle method called after set the value.
Thanks.
First when you use redux's Method you must call with
this.props.setNotyIndex(1);
and When you use redux's veriable in your component you must have you use
this.props.notyIndex
You can console in your mapStateToProps method to get changes like under
const mapStateToProps = (state) => {
console.log("State veriable : ", state)
if (state === undefined) {
return {};
}
return {
notyIndex: state.notyIndex,
}
};
When you change your redux veriable and if you use that veriable in your code then relative component rerender it selt. But if there some issue then you can call setState menually after redux method calling, like under
this.props.setNotyIndex(1);
this.setState({
});
I hope it work for you.......

Resources