Redux | child component does NOT re-render parent component - reactjs

The code is long, but the idea is this:
I have 3 Components
Base App component, MapComponent , ShipComponent
App components calls out MapComponent using container, MapComponent calls out ShipComponent lso using the container.
Both Map and Ship containers are connected to store.
Map and Ship component use same multiarray data and is passed to mapStateToProps
In component Ship with the use of useEffect() an action is being called out to generate the data and put it inside the store.
The issue:
The child component (component Ship) re-renders just fine BUT the component Map (parent) does NOT even tho the parent Map component ALSO uses same shipLocation array. Both map and Ship component gets it from same redux store.
Why does child component after executing an action re-renders yet parent component doesn't? How do I fix it?
As a test, Instead of passing the action thru mapDispatchToProps to ShipComponent, I gave it to MapComponent and thru props passed it to ShipComponent for it to execute. Only then it updated the parent MapComponent. But I need ShipComponent to get the action and update MapComponent.
EDIT Added code example:
So root component (App)
import React from "react";
import { Provider } from "react-redux";
import Map from "../containers/Map";
import mainStore from "../store";
const store = mainStore();
function AppComponent(props) {
return (
<Provider store={store}>
<Map />
</Provider>
);
}
export default AppComponent;
that calls out container Map that passes all the state data from store to component MapComponent
import { connect } from "react-redux";
import MapComponent from "../components/MapComponent";
const mapStateToProps = ({ shipR }) => {
return {
shipLocation: [...shipR.shipLocation],
};
};
const mapDispatchToProps = (dispatch) => {
return {
};
};
const Map = connect(mapStateToProps, mapDispatchToProps)(MapComponent);
export default Map;
MapComponent:
import React from "react";
import { Provider } from "react-redux";
import mainStore from "../store";
import Ship from "../containers/Ship";
const store = mainStore();
function MapComponent(props) {
return (
<div id="__Map__">
{
<Provider store={store}>
<Ship />
</Provider>
}
</div>
);
}
export default MapComponent;
and this MapComponent calls out ShipComponent container
import { connect } from "react-redux";
import ShipComponent from "../components/ShipComponent";
import { generateDefaultShips, shipToggle } from "../actions/shipAction";
const mapStateToProps = ({ shipR }) => {
return {
shipLocation: [...shipR.shipLocation],
};
};
const mapDispatchToProps = (dispatch) => {
return {
genShips() {
dispatch(generateDefaultShips());
},
};
};
const Ship = connect(mapStateToProps, mapDispatchToProps)(ShipComponent);
export default Ship;
and the ShipComponent component looks like this:
import React, { useEffect } from "react";
function ShipComponent(props) {
useEffect(() => {
props.genShips();
}, []);
return (
<div className="ship"></div>
);
}
export default ShipComponent;
The genShips() is an action:
import { SHIP_GENERATION_RESULT } from "../constants";
export function generateDefaultShips() {
let arr = [];
for (let x = 0; x < 7; x++) {
arr[x] = [];
for (let y = 0; y < 11; y++) arr[x][y] = null;
}
return { type: SHIP_GENERATION_RESULT, ships: arr };
}
The MapComponent reducer:
const initiateState = {
};
function mapReducer(state = initiateState, action) {
return state;
}
export default mapReducer;
and ShipComponent reducer:
import {
SHIP_GENERATION_RESULT,
} from "../constants";
const initiateState = {
shipLocation: [],
};
function shipReducer(state = initiateState, action) {
switch (action.type) {
case SHIP_GENERATION_RESULT: {
return {
...state,
shipLocation: action.ships,
};
}
default:
return state;
}
}
export default shipReducer;
and constants index.js contains:
export const SHIP_GENERATION_RESULT = "SHIP_GENERATION_RESULT";
All of these containers, constants, components and actions are in different files
p.s. the child component ShipComponent gets the shipLocation multiarray data just fine, but since MapComponent (parent) does not re-render he does not.
O and the reduxers index.js:
import { combineReducers } from "redux";
import mapReducer from "./mapReducer";
import shipReducer from "./shipReducer";
const allReducers = combineReducers({
mapR: mapReducer,
shipR: shipReducer,
});
export default allReducers;
store index.js:
import { createStore, applyMiddleware } from "redux";
import thunkMiddleware from "redux-thunk";
import allReducers from "../reducers";
import { composeWithDevTools } from "redux-devtools-extension";
export default function mainStore(prevState) {
return createStore(
allReducers,
prevState,
composeWithDevTools(applyMiddleware(thunkMiddleware))
);
}

You are using different store for App and Map component, remove the store and its provider in MapComponent should be helped. There should only 1 redux store in all app to make all data is consistent.

Related

mapStateToProps method is not invoked

For some reason my "secondComponent" isn't actually having mapStateToProps invoked at all, even with a console.log() in it and I am actually quite confused as to why this might be. Redux is working perfectly with my "App" component, but without mapStateToProps being called in the child component it's left in the dark.
Hoping someone could help me here!
1: Index.tsx, where I do make sure to have a Provider for the store.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import configureStore from './store';
const store = configureStore;
ReactDOM.render(
<Provider store={store()}>
<App />
</Provider>,
document.getElementById('root')
);
2: My "App" component that works with the redux store without issue.
import * as React from 'react';
import { connect } from 'react-redux';
import { addItemToCart, removeItemFromCart } from './store/cart/actions';
import { Button, Grid } from 'semantic-ui-react';
import { RootState } from './store';
import { SecondComponent } from './components/SecondComponent';
export interface IAppProps {
addItemToCart: typeof addItemToCart,
removeItemFromCart: typeof removeItemFromCart,
userId: number,
selectedItems: number[]
}
export class App extends React.Component<IAppProps> {
onClickAdd() {
this.props.addItemToCart(5);
}
public render() {
return (
<Grid centered>
<SecondComponent/>
</Grid>
);
}
}
const mapStateToProps = (state: RootState) => {
return {
userId: state.cart.userId,
selectedItems: state.cart.selectedItems
};
}
export default connect(
mapStateToProps,
{ addItemToCart, removeItemFromCart }
)(App);
3: The second component that doesn't have "mapStateToProps" invoked at all. :(
import * as React from 'react';
import { RootState } from '../store';
import { connect } from 'react-redux';
import { addItemToInventory, removeItemFromInventory } from '../store/inventory/actions';
import { Item, ItemTypesAvaliable } from '../store/inventory/types';
import { Fragment } from 'react';
import ItemTypeSection from './ItemTypeSection';
import { Grid } from 'semantic-ui-react';
export interface ISecondComponentProps{
removeItemFromInventory?: typeof removeItemFromInventory,
addItemToInventory?: typeof addItemToInventory,
items?: Item[],
itemTypesAvaliable?: ItemTypesAvaliable[]
}
export class SecondComponentextends React.Component<ISecondComponentProps> {
public render() {
let { itemTypesAvaliable } = this.props;
console.log(this.props);
return (
<Grid>
ok?
{itemTypesAvaliable != null ? itemTypesAvaliable.map(individualItemType => {
return <ItemTypeSection itemType={individualItemType} />
}) : <h1>doesn't work</h1>}
</Grid>
);
}
}
const mapStateToProps = (state: RootState) => {
console.log(state);
console.log("no???")
return {
// items: state.inventory.items,
// itemTypesAvaliable: state.inventory.itemTypesAvaliable
};
}
export default connect(
mapStateToProps,
{ addItemToInventory, removeItemFromInventory }
)(SecondComponent);```
Please make sure that you are importing the required instance. In your case, you need the redux connected instance of SecondComponent with the code
Right:
import SecondComponent from <pathToSecondComponentFile>
which is generating through
export default connect(
mapStateToProps,
{ addItemToInventory, removeItemFromInventory }
)(SecondComponent);
if you are importing the component like this
Wrong:
import {SecondComponent} from "<pathToSecondComponentFile>"
then please remove the braces of SecondComponent to get the default instance of export from file.

How to fix Uncaught TypeError: Cannot read property 'getState' of undefined?

I am trying to use React with Redux for the frontend part with django rest framework in the backend. Got the issue getState in Provider tag in App component because of issue in store. And when i try to use the map function in the Words.js, I get error of undefined use of map. And I believe this is because of value of the array is null. Hence to fixed this error of getState.
Got this error even on including the store in Provider of App component when a reducers was not defined.
When I load a static array it does get rendered properly in the specific component.
This is Redux Store in the filename:store.js
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers'
const initialState = {};
const middleware = [thunk];
const enhancer = composeWithDevTools(applyMiddleware(...middleware));
const store = createStore(
rootReducer,
initialState,
enhancer
);
export default store;
The index.js file is below
import App from './components/App'
import ReactDOM from 'react-dom';
import React from 'react';
ReactDOM.render(<App />, document.getElementById("app"));
They action types file types.js using django rest_framework to create the data.
export const GET_WORDS = "GET_WORDS";
The action file words.js
import { GET_WORDS } from "./types";
import axios from 'axios';
export const getWords = () => dispatch => {
axios.get('/api/words/')
.then(res => {
dispatch({
type: GET_WORDS,
payload: res.data
});
}).catch(err => console.log(err));
}
combined reducer file
import { combineReducers } from "redux";
import words from './words';
export default combineReducers({
words
});
The reducer file word.js
import { GET_WORDS } from '../actions/types';[enter image description here][1]
const initialState = {
words: []
}
export default function (state = initialState, action) {
switch (action.type) {
case GET_WORDS:
return {
...state,
words: action.payload
}
default:
return state;
}
}
The Component in which the words list will be called: Words.js
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getWords } from "../../../actions/words";
export class Words extends Component {
static propTypes = {
words: PropTypes.array.isRequired,
getWords: PropTypes.func.isRequired
};
componentDidMount() {
this.props.getWords();
}
render() {
return (
<Fragment>
Hi
</Fragment>
)
}
}
const mapStateToProps = state => ({
words: state.words.words
});
export default connect(mapStateToProps, { getWords })(Words);
And finally the App component
import React, { Component, Fragment } from 'react';
import Footer from './Layout/Footer/Footer';
import Header from './Layout/Header/Header';
import WordsDashboard from './Content/Words/WordsDashboard';
import { store } from '../store';
import { Provider } from "react-redux";
import { Words } from './Content/Words/Words';
export class App extends Component {
render() {
return (
<Provider store={store}>
<Fragment>
<Header />
React Buddy
<Words />
<Footer />
</Fragment>
</Provider>
)
}
}
export default App;
Your initialState has only words prop, so when mapping it to props you have one extra words level. Try changing it to:
const mapStateToProps = state => ({
words: state.words
});
Also you need to use mapDispatchToProps for getWords, since in your current code you're missing dispatch wrapper:
const mapDispatchToProps = dispatch => ({
getWords: () => dispatch(getWords())
})
export default connect(mapStateToProps, mapDispatchToProps)(Words);

Next.js and redux. Populating store on server side does not take effect

I connected redux to Next.js app just like in the docs (not sure what mapDispatchToProps does in the example though):
Init store method:
import { createStore, applyMiddleware } from 'redux';
import { createLogger } from 'redux-logger';
import axios from 'axios';
import axiosMiddleware from 'redux-axios-middleware';
import tokenMiddleware from './tokenMiddleware';
import getReducer from './combineReducers';
const logger = createLogger({ collapsed: true, diff: true });
const axiosMw = axiosMiddleware(axios.create(), { successSuffix: '_SUCCESS', errorSuffix: '_FAILURE' });
export default function initStore(logActions) {
return function init() {
const middleware = [tokenMiddleware, axiosMw];
if (logActions) middleware.push(logger);
return createStore(getReducer(), applyMiddleware(...middleware));
};
}
HOC which I use to connect pages:
import 'isomorphic-fetch';
import React from 'react';
import withRedux from 'next-redux-wrapper';
import { setUser } from 'lib/publisher/redux/actions/userActions';
import PublisherApp from './PublisherApp';
import initStore from '../redux/initStore';
export default Component => withRedux(initStore(), state => ({ state }))(
class extends React.Component {
static async getInitialProps({ store, isServer, req }) {
const cookies = req ? req.cookies : null;
if (cookies && cookies.user) {
store.dispatch(setUser(cookies.user));
}
return { isServer };
}
render() {
console.log(this.props.state);
return (
<PublisherApp {...this.props}>
<Component {...this.props} />
</PublisherApp>
);
}
}
);
The problem I'm having is that dispatched action
store.dispatch(setUser(cookies.user));
seems to work fine on server (I've debugged reducer and I know this user object from cookies is indeed handled by reducer) but when I do console.log(this.props.state) I get reducer with initial state - without user data.
You are missing second parameter inside createStore call. Try this:
export default function initStore(logActions) {
return function init(initData) {
const middleware = [tokenMiddleware, axiosMw];
if (logActions) middleware.push(logger);
return createStore(getReducer(), initData, applyMiddleware(...middleware));
};
}
Notice added initData parameter and it's usage.

How to get a resolved promise to my component with Redux Promise?

I'm making a request in my action - pulling from an API that needs to load in some data into my component. I have it start that request when the component will mount, but I can't seem to get Redux-Promise to work correctly because it just keeps returning:
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
in my dev tools when I try to console.log the value inside of my componentWillMount method.
Here's my code below:
Store & Router
import React from 'react';
import { render } from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import { Provider } from 'react-redux';
import { Router, hashHistory } from 'react-router';
import routes from './routes';
import rootReducer from './reducers';
const store = createStore(
rootReducer,
applyMiddleware(promiseMiddleware)
);
render(
<Provider store={store}>
<Router history={hashHistory} routes={routes} />
</Provider>,
document.getElementById('root')
);
Action
import axios from 'axios';
export const FETCH_REVIEWS = 'FETCH_REVIEWS';
export const REQUEST_URL = 'http://www.example.com/api';
export function fetchReviews() {
const request = axios.get(REQUEST_URL);
return {
type: FETCH_REVIEWS,
payload: request
};
};
Reviews Reducer
import { FETCH_REVIEWS } from '../actions/reviewActions';
const INITIAL_STATE = {
all: []
};
export default function reviewsReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case FETCH_REVIEWS:
return {
...state,
all: action.payload.data
}
default:
return state;
}
}
Root Reducer
import { combineReducers } from 'redux';
import reviewsReducer from './reviewsReducer';
const rootReducer = combineReducers({
reviews: reviewsReducer
});
export default rootReducer;
Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchReviews } from '../../actions/reviewActions';
class Home extends Component {
componentWillMount() {
console.log(this.props.fetchReviews());
}
render() {
return (
<div>List of Reviews will appear below:</div>
);
}
}
export default connect(null, { fetchReviews })(Home);
Any and all help is greatly appreciated. Thank you.
Redux-promise returns a proper Promise object, so you may change your code a little bit to avoid immediate execution.
class Home extends Component {
componentWillMount() {
this.props.fetchReviews().then((whatever) => { console.log('resolved')})
}
// ...
}

My Redux store is an array

I am integrating Redux into my React Native app.
I've been having trouble passing the state down into my components, and realised its because the initial state begins with a key of '0', e.g.
{
'0': {
key: 'value'
}
}
I.e. it seems to be an array.
So that if I have in my connect export:
export default connect(state => ({
key: state.key,
reduxState: state
}),
(dispatch) => ({
actions: bindActionCreators(MyActions, dispatch)
})
)(MyReduxApp);
key is always undefined, however, reduxState gets passed down successfully. (Of course it is not advised to send the whole state down)
The root component:
import React, { Component } from 'react-native';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import DataReducer from '../Reducers/Data';
import MyRedApp from './JustTunerApp';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers([DataReducer]); // ready for more reducers..
// const store = createStoreWithMiddleware(reducer);
const store = createStore(reducer);
export default class App extends Component {
render() {
console.log ("store.getState:")
console.log (store.getState())
return (
<Provider store={store}>
<MyReduxApp />
</Provider>
);
}
}
My reducer looks like:
const initialState = {
key : "value"
};
export default function key(state = initialState, action = {}) {
switch (action.type) {
// ...
default:
return state;
}
}
Is the store returned as an array? How should I pass it into the connect method?
If I pass it in as key: state[0].key then it works, but this doesn't seem right according to all the examples I've seen..
I should have posted the root component earlier... that held the clue.
const reducer = combineReducers([DataReducer])
should have been
const reducer = combineReducers({DataReducer})
the lesson here is don't rush and look at the docs more closely!

Resources