Redux store updating but mapStateToProps not updating component props - react-redux

When I click on a pointer on my Google Maps component, I can see my store being updated in Redux Devtools but mapStateToProps does not seem to update my component props. Therefore, my Google Maps pointers <InfoWindow> does not open.
If I navigate to another Link(using react-router) from my NavBar and then navigate back to this page, the component receives the updated props from mapStateToProps and the previously clicked Google Maps pointer has the <InfoWindow> open.
I have been trying to debug this for the past 1 week, tried converting components/ClassSchedule/Map/Pure.jsx to a class component but it did not work.
components/ClassSchedule/Map/index.js
import { connect } from 'react-redux';
import { selectClass } from '../../../actions/index';
import Pure from './Pure';
const mapStateToProps = state => ({
selectedClass: state.classMapTable.selectedClass,
});
const mapDispatchToProps = dispatch => ({
selectClass: id => dispatch(selectClass(id)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Pure);
components/ClassSchedule/Map/Pure.jsx
import React from 'react';
import MapContent from './MapContent';
const Map = props => {
return (
<MapContent
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=googleMapsKeyHere`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `550px` }} />}
mapElement={<div style={{ height: `100%` }} />}
{...props}
/>
);
};
export default Map;
components/ClassSchedule/Map/MapContent.jsx
import React from 'react';
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
InfoWindow,
} from 'react-google-maps';
import { defaultPosition } from '../../../data/mapData';
import { classes } from '../../../data/classData';
const { zoom, centre } = defaultPosition;
const MapContent = withScriptjs(
withGoogleMap(({ selectedClass, selectClass }) => (
<GoogleMap defaultZoom={zoom} defaultCenter={centre}>
{classes.map(c => (
<Marker
key={`map${c.id}`}
icon={
'https://mt.google.com/vt/icon?psize=30&font=fonts/arialuni_t.ttf&color=ff304C13&name=icons/spotlight/spotlight-waypoint-a.png&ax=43&ay=48&text=%E2%80%A2'
}
position={c.coordinates}
onClick={() => selectClass(c.id)}
>
{selectedClass === c.id && (
<InfoWindow>
<React.Fragment>
<div>{c.area}</div>
<div>{`${c.level} ${c.subject}`}</div>
<div>{`${c.day}, ${c.time}`}</div>
</React.Fragment>
</InfoWindow>
)}
</Marker>
))}
</GoogleMap>
))
);
export default MapContent;
reducers/index.js
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
import classMapTable from './classMapTable';
export default history =>
combineReducers({
router: connectRouter(history),
classMapTable,
});
reducers/classMapTable.js
const classMapTable = (state = {}, action) => {
switch (action.type) {
case 'SELECT_CLASS':
return { ...state, selectedClass: action.classId };
default:
return state;
}
};
export default classMapTable;
store/index.js
import { createBrowserHistory } from 'history';
import { createStore, applyMiddleware } from 'redux';
import { routerMiddleware } from 'connected-react-router';
import { composeWithDevTools } from 'redux-devtools-extension';
import createRootReducer from '../reducers';
export const history = createBrowserHistory();
export default function configureStore(preloadedState) {
const store = createStore(
createRootReducer(history),
preloadedState,
composeWithDevTools(applyMiddleware(routerMiddleware(history)))
);
return store;
}
actions/index.js
export const selectClass = classId => ({
type: 'SELECT_CLASS',
classId,
});

After debugging for about 2 weeks, I randomly decided to run npm update. Turns out there wasn't any issue with my code, my npm packages were just outdated/not compatible. I have no idea how I had different versions of react and react-dom. EVERYTHING WORKS NOW.
This was in my package.json:
"react": "^16.7.0",
"react-dev-utils": "^7.0.0",
"react-dom": "^16.4.2",
After updating my package.json:
"react": "^16.8.1",
"react-dev-utils": "^7.0.1",
"react-dom": "^16.8.1",
Moral of the story: KEEP YOUR PACKAGES UP TO DATE.

Related

How to display fetched data in functional react-redux component?

Why I see always "loading..."?
I used redux-toolkit and createSlice and fetch data by axios.
I have not any problem by fetching data and my data is in State.
My problem is displaying fetched data.
My Component code is:
import React, {useEffect, useState} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {fetchTrackers} from 'dashboard/dashboardSlice';
export default function TrackerManagerDashboard() {
const [trackersList, setTrackersList] = useState(useSelector(state => state.trackersData));
const [activeTracker, setActiveTracker] = useState(useSelector(state => state.activeTracker));
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchTrackers);
}, []);
if(!trackersList)
return (
<div>loading...</div>
)
return (
<div className="TrackerManagerDashboard">
...
</div>
)
}
and reducer Slice file is:
import { createSlice } from '#reduxjs/toolkit'
import * as env from "../../environments";
import axios from 'axios';
const initialState = {
trackersData: {},
activeTracker: {},
}
const dashboardSlice = createSlice({
name: 'dashboard',
initialState,
reducers: {
setInitialState(state, action) {
state.trackersData = action.payload.data;
state.activeTracker = state.trackersData[Object.keys(state.trackersData)[0]];
},
},
})
export async function fetchTrackers (dispatch, getState) {
try {
const { data } = await axios.get(env.APP_URL + '/fetch/trackers.json');
dispatch(setInitialState({type: 'setInitialState', data }));
} catch(error) {
console.log(error);
}
}
export const { setInitialState} = dashboardSlice.actions
export default dashboardSlice.reducer
Never use useState(useSelector.
That means "create a component-local state with the initial value that the return value of useSelector at the time of first render has". If the Redux state changes later, you will never get to see it, as your useState is already initialized and any change there will not be reflected in your trackersList variable.
Instead, just call useSelector:
const trackersList = useSelector(state => state.trackersData);
const setActiveTracker = useSelector(state => state.activeTracker);

Error: An error occured while selecting the store state: Cannot read property 'counter' of undefined

App.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
export const App = () => {
const x = useSelector(state => state.reduserDD.counter);
const dispatch = useDispatch();
console.log(x);
return (
<div>
<div onClick={() => dispatch({ type: "increment" })}>+</div>
<div onClick={() => dispatch({ type: "decrement" })}>-</div>
<div>{x}</div>
</div>
);
};
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import { App } from "./App";
import { reduserDD } from "./reduser";
const store = createStore(reduserDD);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Reducer
const initialState = {
counter: 0
};
export const reduserDD = (state = initialState, { type }) => {
switch (type) {
case "increment":
return { counter: state.counter + 1 };
case "decrement":
return { counter: state.counter - 1 };
default:
return state;
}
};
I use Redux Hook and get this error.
Error: An error occured while selecting the store state: Cannot read >property 'counter' of undefined.
What is the problem and how can I fix it?
You pass directly your reduserDD to createStore. So state.reduserDD is not existed. state is your reduserDD.
You only need to get state.counter instead of state.reduserDD.counter
const x = useSelector(state => state.counter);

Redux state changed but component props is not updating

I am using redux to control an Ant Design Modal component with a boolean state. Basically it has a button that dispatch action to change the state, and the component will read the state value.
The state is changed properly but the component props value is not updating accordingly. Not sure why it is not working.
I have tried different approaches in reducer like creating a new boolean object to avoid mutating the state but no luck.
myAction.js
export const modalVisibilityOn = () => ({
type: 'MODAL_ON'
})
export const modalVisibilityOff = () => ({
type: 'MODAL_OFF'
})
myReducer.js
const modalVisibility = (state = false, action) => {
switch (action.type){
case 'MODAL_ON':
return true
case 'MODAL_OFF':
return false
default:
return state
}
}
export default modalVisibility
myRootReducer.js
import { combineReducers } from 'redux'
import modalVisibility from './signPage/myReducer'
export default combineReducers({
modalVisibility
})
myModal.js
import React from "react";
import PropTypes from 'prop-types'
import { Modal, Input } from 'antd';
import { connect } from 'react-redux'
import { modalVisibilityOff } from '../../reducers/signPage/myAction'
class myModal extends React.Component {
render() {
const { visibility, handleOk, handleCancel } = this.props;
myModal.propTypes = {
visibility: PropTypes.bool.isRequired,
handleOk: PropTypes.func.isRequired,
handleCancel: PropTypes.func.isRequired,
}
return (
<Modal
title="Sign"
visible={visibility}
onOk={handleOk}
onCancel={handleCancel}
closable={false}
>
<p>Please input your staff ID</p>
<Input addonBefore="Staff ID" />
</Modal>
);
}
}
const mapStateToProps = state => {
return {
visibility: state.modalVisibility
}
}
const mapDispatchToProps = dispatch => ({
handleOk: () => dispatch(modalVisibilityOff()),
handleCancel: () => dispatch(modalVisibilityOff()),
})
export default connect(
mapStateToProps,mapDispatchToProps
)(myModal)
myModalContainer.js
import React from "react";
import { Input } from "antd";
import { Button } from 'antd';
import { Row, Col } from 'antd';
import { Typography } from 'antd';
import PropTypes from 'prop-types'
import myModal from '../../dialogs/signPage/myModal';
import { connect } from 'react-redux'
import { modalVisibilityOn } from '../../reducers/signPage/myAction'
class myModalContainer extends React.Component {
render() {
const { Title } = Typography;
const { onClick } = this.props;
myModalContainer.propTypes = {
onClick: PropTypes.func.isRequired
}
return (
<div className="search-container-parent">
<Row className="search-container">
<Col className="search-col1" xs={24} sm={12}>
<Input size="large" style={{width:'40%'}} id="issueReturnNo" placeholder="QR code here"/>
<Button size="large">SEARCH</Button>
<div className="signBtn-div">
<Button size="large" type="primary" onClick={onClick} >SIGN</Button>
<myModal />
</div>
</Col>
<Col xs={24} sm={12}>
<Title className="issueLog-title" level={3} style={{color:"#F08080"}}>Issue</Title>
</Col>
</Row>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(modalVisibilityOn())
})
export default connect(
null, mapDispatchToProps
)(myModalContainer);
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import rootReducer from './myRootReducer'
const store = createStore(rootReducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
serviceWorker.unregister();
I expect the visibility props on myModal.js would be true when the sign button on myModalContainer.js is clicked, but the it keep showing false.
Any help would be appreciated. Thanks!
After lots of researches and trials, It turns out that my code has no problem..
The reason why it is not working as expected, is due to the redux and react-redux version. After switching package.json dependencies versions back to the one that redux official tutorial are using, the application is running without any problem.
In case anyone have the same problem, here is the version I am using now for my app in npm:
redux: ^3.5.2
react-redux: ^5.0.7
Update:
Just found out that the cause of the problem comes from conflicts between older version modules and newer version modules.
Therefore by updating all the dependencies in package.json to the latest version, the app can also run smoothly. It is not necessary to downgrade react-redux and redux.

React Redux mapStateToProps not firing on update

I have a connected component that maintains a display "state" along with a few other things that are needed for communication between a couple of components. I have two connected components that are children of this over-arching component. Depending on a flag that is within the "state" component one or the other child components will render. It might be better to just show the code:
EditorState Component:
import React from 'react';
import {connect} from 'react-redux';
import Library from '../library/index';
import Editor from '../editor/index';
import {
initialize,
editorState
} from './actions';
class EditorState extends React.Component {
componentWillMount() {
const {dispatch} = this.props;
dispatch(initialize());
}
render() {
const {state} = this.props;
switch(state) {
case editorState.Library:
return <Library />
case editorState.Editor:
return <Editor />
default:
return null;
}
}
}
export default connect(state => {
return state.EditorStateReducer;
})(EditorState);
EditorState Actions:
export const EDITOR_STATE_INITIALIZE = 'EDITOR_STATE_INITIALIZE';
export const editorState = {
Library: 'library',
Editor: 'editor'
}
export const initialize = ({
type: EDITOR_STATE_INITIALIZE,
state: editorState.Library
});
EditorState Reducer:
import {
EDITOR_STATE_INITIALIZE
} from './actions';
const init = () => ({
state: null
});
export default (state = init(), action) => {
switch(action.type) {
case EDITOR_STATE_INITIALIZE:
return {
...state,
state: action.state
}
default:
return {...state}
}
}
Library Component:
import React from 'react';
import {connect} from 'react-redux';
import {Page} from '../../../components/page/index';
import LibraryItem from '../../../components/library-item/library-item';
import {
initialize
} from './actions';
class Library extends React.Component {
componentWillMount() {
const {dispatch} = this.props;
dispatch(initialize());
}
render() {
const {templates} = this.props;
const editorTemplates = templates.map(template =>
<LibraryItem template={template} />
);
return (
<Page>
<div className="card-flex library-table">
{editorTemplates}
</div>
</Page>
)
}
}
export default connect(state => {
return state.LibraryReducer;
})(Library);
Library Actions:
import {
client,
serviceUrl
} from '../../../common/client';
export const LIBRARY_INITIALIZE = 'LIBRARY_INITIALIZE';
export const initialize = () => dispatch => {
client.get(`${serviceUrl}/templates`).then(resp => {
dispatch({
type: LIBRARY_INITIALIZE,
templates: resp.templates
});
});
}
Library Reducer:
import {
LIBRARY_INITIALIZE
} from './actions';
const init = () => ({
templates: []
});
export default (state = init(), action) => {
switch(action.type) {
case LIBRARY_INITIALIZE:
return {
...state,
templates: action.templates
}
default:
return {...state}
}
}
The problem that I am having is that the mapStateToProps in the Library Component is not being called upon the dispatch of LIBRARY_INITIALIZE. I have breakpoints in both mapStateToProps in the EditorState and Library, and a breakpoint in the LIBRARY_INITIALIZE switch in the Library reducer. Debugging page load goes like this:
EditorState mapStateToProps - state.EditorStateReducer.state is null
EditorState mapStateToProps - state.EditorStateReducer.state == editorState.Library
Library mapStateToProps - state.LibraryReducer.templates == []
Library Reducer Initialize - action.templates == [{template1}, {template2}, etc]
EditorState mapStateToProps - state.LibraryReducer.templates == [{template1}, {template2}, etc]
Then nothing. I would expect the Library mapStateToProps to fire as well after this so that the Library can re-render with the templates. However, this is not happening. Why is this not happening? I am ready to pull my hair out over this one...
You cannot be 100% sure that the updated state is rendered right after the dispatch call. mapStatetoProps is called when the component is about to re-render, which depends on whether React batches the updates or not. By default, React batches updates from event handlers.
You can refer https://github.com/reactjs/react-redux/issues/291

Using redux-connected component as screen in StackNavigator

I'm creating an react native app using create-react-native-app, react-navigation and react-redux. I'm trying to add a redux-connected component as a screen into a nested StackNavigator (though the nesting seems to not make a difference, it doesn't work either way) and consistently am getting an error message saying Route 'MilkStash' should declare a screen. When I remove the redux connection from the MilkStash.js file, everything works fine. Any idea how to get this working?
App.js
import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './src/reducers';
import AppWithNavigation from './src/AppWithNavigation';
export default () => (
<Provider store = {createStore(rootReducer)}>
<AppWithNavigation />
</Provider>
);
AppWithNavigation.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { StyleSheet, Text, View, Image, Button } from 'react-native';
import { DrawerNavigator, StackNavigator } from 'react-navigation';
import MilkStash from './screens'
import { StatsScreen, FAQScreen, AddMilk, AccountScreen } from './screens';
export default class AppWithNavigation extends React.Component {
render() {
return (
<MenuNavigator />
);
}
}
const MilkNavigator = StackNavigator(
{ Milk: { screen: MilkStash},
AddMilk: { screen: AddMilk }
},
);
const AccountNavigator = StackNavigator(
{ Account: {screen: AccountScreen}}
);
const StatsNavigator = StackNavigator(
{ Stats: {screen: StatsScreen }}
);
const FAQNavigator = StackNavigator(
{ FAQ: {screen: FAQScreen}}
)
const MenuNavigator = DrawerNavigator({
Milk: { screen: MilkNavigator},
Account: {screen: AccountNavigator},
Stats: {screen: StatsNavigator},
FAQ: {screen: FAQNavigator},
}
);
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#ecf0f1',
}
});
MilkStash.js
import React, {Component} from 'react';
import { StyleSheet, Text, View} from 'react-native';
import { StackNavigator } from 'react-navigation';
import { connect } from 'react-redux';
import { Milk } from '../../core/models/milk';
import styles from './styles.js';
export class MilkStash extends Component {
constructor(props){
super(props);
}
render() {
return (
<View style={styles.container}>
....displaying data goes here
</View>
)
}
}
function mapStateToProps(state){
return{
milkStash: state.milkStash
}
}
function mapDispatchToProps(dispatch){
return {
addMilk: (milk) => dispatch(addMilk(milk)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MilkStash);
milk-reducer.js
import {ADD_MILK} from '../constants';
const milkReducer = (state = {milkStash: []}, action = {}) => {
switch(action.type){
case ADD_MILK:
var item = action.payload;
return state
.update('milkStash', (milkStash) =>
{
var milkStashCopy = JSON.parse(JSON.stringify(milkStash));
milkStashCopy.concat(item);
return milkStashCopy;
});
default:
return state;
}
}
export default milkReducer;
reducers.js
export * from './milk.js';
import milkReducer from './milk';
import { combineReducers } from 'redux';
export default rootReducer = combineReducers({
milk: milkReducer
});
I figured out the answer and thought I would help prevent someone else struggling with this for 3 days. The issue had to do with the way I was importing the exports from MilkStash.js. Apparently using import MilkStash from './screens' will cause the error but changing it to import MilkStashContainer from './screens/MilkStash/MilkStash.js will fix the problem.

Resources