My Redux store is an array - reactjs

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!

Related

Redux useSelector returns nothing in child component

I am new to React Native and Redux, and was hoping someone could help out in my issue? I have a parent component that fetches some user data (their location) and dispatches to a redux store:
Parent
import { useDispatch } from 'react-redux'
import { setLocation } from './store/locationSlice'
const App = () => {
const dispatch = useDispatch()
const getLocation = () => {
const location = await fetchLoc()
dispatch(setLocation(location))
}
useEffect(() => {
getLocation()
},[])
}
My child component is intended to retrieve this data using the useSelector hook
Child
import { useSelector } from 'react-redux'
const HomeScreen = () => {
const location = useSelector(state => state.location)
useEffect(() => {
if (location) {
getEntitiesBasedOnLocation(location)
}
},[location])
}
However, in my case, useSelector never retrieves the up-to-date information that i have dispatched in the parent, with location returning undefined. I'm fairly certain there's a simple oversight here, but i'm at a loss as to what this could be. I was under the impression that useSelector subscribes to state changes, so why is it that that my dispatched action that causes a change of state is ignored? Using my debugger, I can see that my state is definitely updated with the correct data, but the child component doesn't pick this up..
Here's my location slice:
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
location: {
id: null,
name: null,
latitude: null,
longitude: null
}
}
const locationSlice = createSlice({
name: 'location',
initialState,
reducers: {
setLocation: (state, action) => {
const { id, name, latitude, longitude } = action.payload
state.location = { id, name, latitude, longitude }
}
}
})
export const { setLocation } = locationSlice.actions
export default locationSlice.reducer
UPDATE
The store is configured by wrapping the App.js component in a Provider component, with the store passed as its props as follows:
Root.js
import { configureStore } from '#reduxjs/toolkit'
import { Provider } from 'react-redux'
import locationReducer from './src/store/locationSlice'
import App from './src/App'
const Root = () => {
const store = configureStore({ reducer: locationReducer })
return (
<Provider store={store)>
<App />
</Provider>
)
}
The issue is in your selector. You've created the slice called 'location' and within that slice you've got your state { location: {...}}. So from the perspective of the selector (which accesses your global state, not just the location slice) the path to your data would be state.location.location. But your selector is trying to read out of state.location which only has a location prop. Anything else you tried to read out would be undefined.
It is common to export a custom selection function from the slice configuration. Remember that the selector must take exactly the data that you want to share in your component tree (locationSlice.state.location in this case). This is not mandatory, it is just to facilitate development.
// locationSlice
import { createSlice } from '#reduxjs/toolkit'
//...
export const { setLocation } = locationSlice.actions
export const selectLocation = (state) => state.location.location
export default locationSlice.reducer
// Child
import { useSelector } from 'react-redux'
import {selectLocation} from './src/store/locationSlice'
const HomeScreen = () => {
const location = useSelector(selectLocation)
//...
}
My workaround was to move my getLocation() function in the parent to the child component. useSelector now gets the state as expected. I feel that this work-around defeats the object of having global state access though, and i could probably just use local state rather than Redux.

Redux | child component does NOT re-render parent component

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.

Empty Redux store when attempting to dispatch from outside React component

I have what I think is a fairly standard React-Redux setup with some persistence mixed in.
app.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import AppRouter from './routers/AppRouter';
import configureStore from './store/configureStore';
import config from 'cgConfig';
const { store, persistor } = configureStore();
const jsx = (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<AppRouter />
</PersistGate>
</Provider>
);
ReactDOM.render(jsx, document.getElementById(config.constants.element.rootId));
configureStore.js
import {
createStore,
combineReducers
} from 'redux';
import {
persistStore,
persistReducer
} from 'redux-persist';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import storage from 'redux-persist/lib/storage';
import config from 'cgConfig';
//...import reducers...
export default () => {
const persistConfig = {
key: 'root',
storage,
stateReconciler: autoMergeLevel2
};
let rootReducer = combineReducers({
//...all of my reducers...
});
let store = undefined;
let pr = persistReducer(persistConfig, rootReducer);
if (config.env.includeReduxDevTools) {
store = createStore(
pr,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
} else {
store = createStore(pr);
}
let persistor = persistStore(store);
return {
store,
persistor
};
};
However, the one thing that is a bit unconventional is that I need to update the store outside the context of a react component. As far as I understand (for example), this should not be too difficult to accomplish. Simply call configureStore() to get the store and run a store.dispatch(action). Problem is that I keep getting the initial state of the store back (IE empty), which isn't the same as the one I have already set up through the process of logging in etc. Thus when I run a dispatch, the wrong state is being updated.
Not sure what I am doing wrong, would appreciate any help.
EDIT to answer Uma's question about what the router looks like:
First some more context. The website I am working on will basically have shapes generated in something akin to a graph. These shapes can be manipulated after they are selected from various sources like contextual menus and a toolbar. It is in the context of this change event that I am working on.
When a change is made, the info of the selected item and what needs to be changed will be sent to a generic function which will determine which reducer/action to use to update the Redux store. All my reducers follow the same pattern and look like this:
const reducerInitialState = {};
const setSomeProperty= (state, action) => {
let newState = { ...state };
newState.some_property = action.some_update;
return newState;
};
export default (state = reducerInitialState, action) => {
switch (action.type) {
case 'UPDATE_SOME_PROPERTY':
return setSomeProperty(state, action);
case 'LOG_OUT':
return { ...reducerInitialState };
default:
return state;
}
};
Action for completeness:
export const setSomeProperty = update_data => ({
type: 'UPDATE_SOME_PROPERTY',
some_update: update_data
});
The generic function I have would look something like this:
import configureStore from './store/configureStore';
import { setSomeProperty} from './actions/SomeAction';
import API from './utilities/API';
export default async ({ newValue, selectedShape }) => {
if (!newValue || !selectedShape)
throw "Cannot update the server with missing parameters.";
await API()
.post(
`/api/update-shape`,
{
newValue,
shape: selectedShape
}
)
.then(response => {
updateValue({ selectedShape, newValue });
})
.catch(error => {
// handle error
});
}
const getAction = ({ shape }) => {
switch (shape.type) {
case 0:
return setSomeProperty;
default:
throw `Invalid type of ${shape.type} attempting to update.`;
}
}
const updateValue = ({ selectedShape, newValue }) => {
const action = getAction({ shape: selectedShape })
const { store } = configureStore();
store.dispatch(action(newValue))
}
Note: API is a wrapper for Axios.
Since posting this yesterday I have read that creating a second store like what I am doing with const { store } = configureStore(); is where one of my problems lie in that React/Redux can't have 2 of them. I have also come to realize that the problem most likely have to do with the initial state in my reducers, that somehow using configureStore() does not send the actual state to the reducers and thus all my reducers are showing their initial states when I look at them using console.log(store.getState());. If this is true, at least I know the problem and that is half the battle, but I am unsure how to proceed as I have tried to ReHydrate the state I get from configureStore() but nothing seems to work the way I expect it to.
As far as I can tell you end up in a weird loop where you call an action setSomeProperty then it gets you to the reducer, and in reducer you call that setSomeProperty action again.
In reducer I would expect to see something like this:
export default (state = reducerInitialState, action) => {
switch (action.type) {
case 'UPDATE_SOME_PROPERTY':
return {
...state, // preserve already set state properties if needed
some_update: action.some_update
}
case 'LOG_OUT':
return { ...reducerInitialState };
default:
return state;
}
};

React Redux fetching data from backend approach

I'm a bit confused and would love an answer that will help me to clear my thoughts.
Let's say I have a backend (nodejs, express etc..) where I store my users and their data, and sometimes I wanna fetch data from the backend, such as the user info after he logs in, or a list of products and save them in the state.
My approach so far and what I've seen, I fetch the data before the component loads and dispatch an action with the data from the response.
But I recently started digging a bit about this and I saw react-thunk library which I knew earlier and started to wonder if what is the best practice of fetching from backend/API?
Has React Hooks change anything about this topic? Is it important to know this?
I feel a bit dumb but couldn't find an article or video that talks exactly about this topic :)
To do this best practice, use the following method:
I used some packages and patterns for best practice:
redux-logger for log actions and states in console of browser.
reselect Selectors can compute derived data, allowing Redux to
store the minimal possible state and etc.
redux-thunk Thunks are the recommended middleware for basic
Redux side effects logic, including complex synchronous logic that
needs access to the store, and simple async logic like AJAX requests
and etc.
axios for work with api (Promise based HTTP client for the
browser and node.js)
create a directory by name redux or any name of you like in src folder and
then create two files store.js and rootReducer.js in redux directory. We assume fetch products from API.
To do this:
Create a new directory by name product in redux directory and then
create four files by names product.types.js, product.actions.js,
product.reducer.js, product.selector.js in redux/product directory
The structure of the project should be as follows
...
src
App.js
redux
product
product.types.js
product.actions.js
product.reducer.js
rootReducer.js
store.js
Index.js
package.json
...
store.js
In this file we do the redux configuration
// redux/store.js:
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import rootReducer from "./root-reducer";
const middlewares = [logger, thunk];
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
rootReducer.js
The combineReducers helper function turns an object whose values are
different reducing functions into a single reducing function you can
pass to createStore.
// redux/rootReducer.js
import { combineReducers } from "redux";
import productReducer from "./product/product.reducer";
const rootReducer = combineReducers({
shop: productReducer,
});
export default rootReducer;
product.types.js
In this file we define constants for manage types of actions.
export const ShopActionTypes = {
FETCH_PRODUCTS_START: "FETCH_PRODUCTS_START",
FETCH_PRODUCTS_SUCCESS: "FETCH_PRODUCTS_SUCCESS",
FETCH_PRODUCTS_FAILURE: "FETCH_PRODUCTS_FAILURE"
};
product.actions.js
In this file we create action creators for handle actions.
// redux/product/product.actions.js
import { ShopActionTypes } from "./product.types";
import axios from "axios";
export const fetchProductsStart = () => ({
type: ShopActionTypes.FETCH_PRODUCTS_START
});
export const fetchProductsSuccess = products => ({
type: ShopActionTypes.FETCH_PRODUCTS_SUCCESS,
payload: products
});
export const fetchProductsFailure = error => ({
type: ShopActionTypes.FETCH_PRODUCTS_FAILURE,
payload: error
});
export const fetchProductsStartAsync = () => {
return dispatch => {
dispatch(fetchProductsStart());
axios
.get(url)
.then(response => dispatch(fetchProductsSuccess(response.data.data)))
.catch(error => dispatch(fetchProductsFailure(error)));
};
};
product.reducer.js
In this file we create productReducer function for handle actions.
import { ShopActionTypes } from "./product.types";
const INITIAL_STATE = {
products: [],
isFetching: false,
errorMessage: undefined,
};
const productReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ShopActionTypes.FETCH_PRODUCTS_START:
return {
...state,
isFetching: true
};
case ShopActionTypes.FETCH_PRODUCTS_SUCCESS:
return {
...state,
products: action.payload,
isFetching: false
};
case ShopActionTypes.FETCH_PRODUCTS_FAILURE:
return {
...state,
isFetching: false,
errorMessage: action.payload
};
default:
return state;
}
};
export default productReducer;
product.selector.js
In this file we select products and isFetching from shop state.
import { createSelector } from "reselect";
const selectShop = state => state.shop;
export const selectProducts = createSelector(
[selectShop],
shop => shop.products
);
export const selectIsProductsFetching = createSelector(
[selectShop],
shop => shop.isFetching
);
Index.js
In this file wrapped whole app and components with Provider for access child components to the store and states.
// src/Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./redux/store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js class component
In this file we do connect to the store and states with class component
// src/App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectIsProductsFetching,
selectProducts
} from "./redux/product/product.selectors";
import { fetchProductsStartAsync } from "./redux/product/product.actions";
class App extends Component {
componentDidMount() {
const { fetchProductsStartAsync } = this.props;
fetchProductsStartAsync();
}
render() {
const { products, isProductsFetching } = this.props;
console.log('products', products);
console.log('isProductsFetching', isProductsFetching);
return (
<div className="App">Please see console in browser</div>
);
}
}
const mapStateToProps = createStructuredSelector({
products: selectProducts,
isProductsFetching: selectIsProductsFetching,
});
const mapDispatchToProps = dispatch => ({
fetchProductsStartAsync: () => dispatch(fetchProductsStartAsync())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
or App.js with functional component ( useEffect hook )
In this file we do connect to the store and states with functional component
// src/App.js
import React, { Component, useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectIsProductsFetching,
selectProducts
} from "./redux/product/product.selectors";
import { fetchProductsStartAsync } from "./redux/product/product.actions";
const App = ({ fetchProductsStartAsync, products, isProductsFetching}) => {
useEffect(() => {
fetchProductsStartAsync();
},[]);
console.log('products', products);
console.log('isProductsFetching', isProductsFetching);
return (
<div className="App">Please see console in browser</div>
);
}
const mapStateToProps = createStructuredSelector({
products: selectProducts,
isProductsFetching: selectIsProductsFetching,
});
const mapDispatchToProps = dispatch => ({
fetchProductsStartAsync: () => dispatch(fetchProductsStartAsync())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);

React component not getting latest redux state values

I'm having an issue with a React Native application that uses Redux, and I've struggling to figure out the cause.
When the Redux state is changed by a dispatch in component A, component B doesn't show the most up to date value of the Redux state. For example, say I click a button in component A to change a Redux value from 0 to 1, it will show 0 in component B, but when I click the button to add 1 to make it 2, component B will show 1. Below is an example of my code. When I click on the TouchableOpacity in component A, it should change this.props.screen from 1 (the initial state) to 0. In component B, I have a regular console.log of this.props.screen, and a console.log inside a setTimeout with 50 milliseconds. Inside the console, the console.log in the setTimeout has the correct value of 0 when hit, however the one outside it still shows 1. Similarly, the text rendered in component B will show 1 as well. If I click the button again, it will then show 0.
I've included the relevant action and reducer from my code. At first, I thought it might be a mutation, but it seemed that can only happen with objects and arrays (I'm only using a number). I would appreciate some help figuring out how to have the text rendered in component B reflect the most current value. Thanks in advance!
Component A
import { connect } from "react-redux";
import { setScreen } from "../redux/Actions";
class Header extends Component {
componentWillReceiveProps(nextProps){
setTimeout(() => { this.logoHide() },10);
this.props.scrollLocation < 10 ? this.changeTransparency(0) : this.changeTransparency(.9);
}
setScreen(screen){
this.props.setScreen(screen);
}
render() {
var {height, width} = Dimensions.get('window');
return (
<View>
<TouchableOpacity onPress={() => this.setScreen(0)}>
<Text>Click Me</Text>
</TouchableOpacity>
</View>
);
}
}
const mapStateToProps = state => {
return {
height: state.height,
platform: state.platform,
screen: state.screen,
scrollLocation: state.scrollLocation
};
};
const mapDispatchToProps = dispatch => {
return {
setScreen: (value) => dispatch(setScreen(value))
};
};
export default connect(mapStateToProps,mapDispatchToProps)(Header);
Redux Action
import { SET_SCREEN } from './Constants';
export const setScreenDispatcher = (value) => ({ type: SET_SCREEN, screen: value});
export const setScreen = (value) => {
return (dispatch) => {
dispatch(setScreenDispatcher(value));
}
}
Redux Reducer
import { combineReducers } from 'redux';
import { SET_SCREEN } from "./Constants";
const initialState = []
const screen = (state = 1, action) => {
switch (action.type) {
case SET_SCREEN:
return action.screen;
default:
return state;
}
};
// COMBINE REDUCERS //
export default combineReducers({
screen
});
Component B
import { connect } from "react-redux";
class VisibleMenus extends Component {
componentWillUpdate(){
console.log(this.props.screen);
setTimeout(() => {console.log(this.props.screen)},50);
}
}
render() {
return (
<View>
<Text>{this.props.screen}</Text>
</View>
);
}
}
const mapStateToProps = state => {
return {
screen: state.screen
};
};
const mapDispatchToProps = dispatch => {
return {
};
};
export default connect(mapStateToProps,mapDispatchToProps)(VisibleMenus);
App.js
import React, {Component} from 'react';
import { Provider } from "react-redux";
import VisibleMenus from './VisibleMenus';
import { Store } from "./redux/Store";
const store = Store();
export default class App extends Component {
render() {
return (
<Provider store={store}>
<VisibleMenus />
</Provider>
);
}
}
Store.js
// REDUX STORE //
import { createStore, applyMiddleware } from "redux";
import rootReducer from "./Reducers";
import ReduxThunk from 'redux-thunk'
export const Store = (initialState) => {
return createStore(
rootReducer,
initialState,
applyMiddleware(ReduxThunk)
);
}
For anyone who runs into this, I thought I'd share how I fixed it.
I researched mutations, and I definitely wasn't mutating the state, yet my components would not update when the Redux store changed.
I tried using both componentWillUpdate() and componentWillReceiveProps() but both didn't change anything. However, I was doing a comparison between this.props.screen and this.state.screen which ended up being my issue.
I should have been doing a comparison with nextProps.screen and this.state.screen inside a componentWillReceiveProps(nextProps) which ended up fixing everything.
I do want to thank Hashith for his help.

Resources