Why does mapping state to props give undefined? - javascript

I'm having a problem with my setup of Redux. I didn't have a problem with single file of posts actions and reducers, but as soon as added a searchQueries sections, it shows only undefined values for the searchQueries props.
I've tried copying it as far as I can and modifying it for the second set of actions/reducers, but I'm still ending up with undefined props in the case of searchQueries. I'm getting all the props, including the default values in the case of posts. Here's the code for each of these:
/actions/posts.js:
import axios from 'axios'
export function postsHasErrored(bool) {
return {
type: 'POSTS_HAS_ERRORED',
hasErrored: bool
}
}
export function postsIsLoading(bool) {
return {
type: 'POSTS_IS_LOADING',
isLoading: bool
}
}
export function postsFetchDataSuccess(posts) {
return {
type: 'POSTS_FETCH_DATA_SUCCESS',
posts
}
}
export function totalPagesFetchDataSuccess(totalPages) {
return {
type: 'TOTAL_PAGES_FETCH_DATA_SUCCESS',
totalPages
}
}
export function postsFetchData(url) {
return (dispatch) => {
dispatch(postsIsLoading(true))
axios.get(url)
.then(res => {
if (res.status !== 200) throw Error(res.statusText)
dispatch(postsIsLoading(false))
return res
})
.then(res => {
dispatch(postsFetchDataSuccess(res.data))
dispatch(totalPagesFetchDataSuccess(res.headers['x-wp-totalpages']))
})
.catch(() => dispatch(postsHasErrored(true)))
}
}
/actions/searchQueries.js:
const readLocation = (name) => {
let parameter = getParameterByName(name);
if (name === 'categories') {
if (parameter) {
parameter = parameter.split(',')
for (let i = 0; i < parameter.length; i++) parameter[i] = parseInt(parameter[i], 10)
}
else parameter = []
}
if (parameter === null) {
if (name === 'search') parameter = ''
if (name === 'page') parameter = 1
}
console.log(parameter)
return parameter
}
export function setSearchString(searchString) {
return {
type: 'SET_SEARCH_STRING',
searchString
}
}
export function setSearchCategories(searchCategories) {
return {
type: 'SET_SEARCH_CATEGORIES',
searchCategories
}
}
export function setSearchPage(searchPage) {
return {
type: 'SET_SEARCH_PAGE',
searchPage
}
}
export function searchQueriesSetting() {
return (dispatch) => {
dispatch(setSearchString(readLocation('search')))
dispatch(setSearchCategories(readLocation('categories')))
dispatch(setSearchPage(readLocation('page')))
}
}
/reducers/posts.js:
export function postsHasErrored(state = false, action) {
switch (action.type) {
case 'POSTS_HAS_ERRORED':
return action.hasErrored
default:
return state
}
}
export function postsIsLoading(state = false, action) {
switch (action.type) {
case 'POSTS_IS_LOADING':
return action.isLoading
default:
return state
}
}
export function posts(state = [], action) {
switch (action.type) {
case 'POSTS_FETCH_DATA_SUCCESS':
return action.posts
default:
return state
}
}
export function totalPages(state = 1, action) {
switch (action.type) {
case 'TOTAL_PAGES_FETCH_DATA_SUCCESS':
return parseInt(action.totalPages, 10)
default:
return state
}
}
/reducers/searchQueries.js:
export function searchString(state = '', action) {
switch (action.type) {
case 'SET_SEARCH_STRING':
return action.searchString
default:
return state
}
}
export function searchCategories(state = [], action) {
switch (action.type) {
case 'SET_SEARCH_CATEGORIES':
return action.searchCategories
default:
return state
}
}
export function searchPage(state = 1, action) {
switch (action.type) {
case 'SET_SEARCH_PAGE':
return action.searchPage
default:
return state
}
}
/reducers/index.js:
import { combineReducers } from 'redux'
import { posts, totalPages, postsHasErrored, postsIsLoading } from './posts'
import { searchString, searchCategories, searchPage } from './searchQueries'
export default combineReducers({
posts,
postsHasErrored,
postsIsLoading,
totalPages,
searchString,
searchCategories,
searchPage
})
/components/PostsList.js
// dependencies
import React, { Component } from 'react'
import axios from 'axios'
import { connect } from 'react-redux'
// components
import PostsListItem from './PostsListItem'
import PostsPages from './PostsPages'
// actions
import { postsFetchData } from '../actions/posts'
import { searchQueriesSetting } from '../actions/searchQueries'
// styles
import '../styles/css/postsList.css'
// shared modules
import { createSearchUrl } from '../sharedModules/sharedModules'
class PostsList extends Component {
componentWillReceiveProps(nextProps) {
if (nextProps.searchPage !== this.props.searchPage) this.componentDidMount()
}
componentDidMount() {
this.props.searchQueriesSetting()
this.props.fetchData(createSearchUrl(
'http://localhost/wordpress-api/wp-json/wp/v2/posts?per_page=1',
this.props.searchCategories,
this.props.searchString,
this.props.searchPage
))
}
render() {
console.log(this.props)
const { isLoading, hasErrored, posts } = this.props
if (isLoading) return <div className='posts-list'><h2 className='loading'>Loading...</h2></div>
const postsList = posts.map(post => <PostsListItem post={post} key={post.id} />)
return (
<div className='posts-list'>
{postsList}
<PostsPages />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
posts: state.posts,
hasErrored: state.postsHasErrored,
isLoading: state.postsIsLoading,
searchCategories: state.searchCategories,
searchString: state.searchString,
searchPage: state.searchPage
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (url) => dispatch(postsFetchData(url)),
searchQueriesSetting: () => dispatch(searchQueriesSetting())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsList)
/components/PostsPages.js
// dependencies
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
// actions
import { setSearchPage } from '../actions/searchQueries'
// shared modules
import { createSearchUrl } from '../sharedModules/sharedModules'
class PostsPages extends Component {
isLinkEdgy = (pageNumber) => {
if (parseInt(pageNumber, 10) <= 1) return ''
if (parseInt(pageNumber, 10) >= parseInt(this.props.totalPages, 10)) return this.props.totalPages
return pageNumber
}
render() {
const { totalPages, currentPage, searchCategories, searchString, setSearchPage } = this.props
const previousUrl = createSearchUrl('/blog', searchCategories, searchString, this.isLinkEdgy(parseInt(currentPage, 10) - 1))
const nextUrl = createSearchUrl('/blog', searchCategories, searchString, this.isLinkEdgy(parseInt(currentPage, 10) + 1))
return (
<div className='posts-pages'>
<ul className='posts-pages-list'>
<li><Link to={previousUrl} onClick={() => setSearchPage(this.isLinkEdgy(parseInt(currentPage, 10) - 1))}>Prev page</Link></li>
<li><Link to={nextUrl} onClick={() => setSearchPage(this.isLinkEdgy(parseInt(currentPage, 10) + 1))}>Next page</Link></li>
</ul>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
totalPages: state.totalPages,
currentPage: state.searchPage,
searchCategories: state.searchCategories,
searchString: state.searchString
}
}
const mapDispatchToProps = (dispatch) => {
return {
setSearchPage: (searchPage) => dispatch(setSearchPage(searchPage))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsPages)

That's because you're accessing the wrong par of state. Take a look at your combineReducers call:
export default combineReducers({
posts,
postsHasErrored,
postsIsLoading,
totalPages,
setSearchString,
setSearchCategories,
setSearchPage
})
Per the Redux documentation:
combineReducers(reducers)
The shape of the state object matches the keys of the passed reducers.
Thus your state object actually looks like this:
{
posts: ...,
postsHasErrored: ...,
postsIsLoading: ...,
totalPages: ...,
setSearchString: ...,
setSearchCategories: ...,
setSearchPage: ...
}
In your mapDispatchToProps, you're trying to access the wrong part of state:
currentPage: state.searchPage,
searchCategories: state.searchCategories,
searchString: state.searchString
Since state.searchPage and the other two don't exist in the state object, you get undefined. Instead, make sure you access the keys which have the same name as the reducers:
currentPage: state.setSearchPage,
searchCategories: state.setSearchCategories,
searchString: state.setSearchString
Or just rename your reducers (which would be preferable as they are misnomers right now). Get rid of the set prefix on the reducers, they are not actions.

Related

Dispatching multiple actions in redux duplicates the state parameters

I have used created two actions and their respective reducers. When i dispatch any single action, both actions initial states are being saved to state where the parameters of the states are duplicated.
actions/index.js
import { COUNTER_CHANGE, UPDATE_NAVIGATION } from "../constants";
export function changeCount(count) {
return {
type: COUNTER_CHANGE,
payload: count,
};
}
export function updateNavigation(obj) {
return {
type: UPDATE_NAVIGATION,
payload: obj,
};
}
reducers.js
import { COUNTER_CHANGE, UPDATE_NAVIGATION } from "../constants";
import logger from "redux-logger";
const initialState = {
count: 0,
navigation: {},
};
export const countReducer = (state = initialState, action) => {
switch (action.type) {
case COUNTER_CHANGE:
return {
...state,
count: action.payload,
};
default:
return state;
}
};
export const updateNavigation = (state = initialState, action) => {
switch (action.type) {
case UPDATE_NAVIGATION:
return {
...state,
navigation: action.payload,
};
default:
return state;
}
};
// export default countReducer;
reducer/index.js
import { countReducer, updateNavigation } from "../reducers/countReducer";
import { combineReducers } from "redux";
const allReducers = combineReducers({
countReducer,
updateNavigation,
});
export default allReducers;
Dispatching actions
componentDidMount = () => {
const { navigation } = this.props;
this.props.updateNavigation(navigation);
};
const mapDispatchToProps = (dispatch) => {
return { ...bindActionCreators({ changeCount, updateNavigation }, dispatch) };
};
As we can see here I have triggered only updateNavigation action. But it updates states with duplicate parameters in redux state as shown below
The expected o/p will be
countReducer : {count : 0}
updateNavigation : {navigation :{}}
The shape of state for each reducer is incorrect. See defining-state-shape docs and try this:
export const countReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case COUNTER_CHANGE:
return {
...state,
count: action.payload,
};
default:
return state;
}
};
export const updateNavigation = (state = { navigation: {} }, action) => {
switch (action.type) {
case UPDATE_NAVIGATION:
return {
...state,
navigation: action.payload,
};
default:
return state;
}
};
import { countReducer, updateNavigation } from "../reducers/countReducer";
import { combineReducers } from "redux";
const allReducers = combineReducers({
countReducer,
updateNavigation,
});
const store = createStore(allReducers);
console.log(store.getState());
Output:
{ countReducer: { count: 0 }, updateNavigation: { navigation: {} } }
In your action/index.js
import { COUNTER_CHANGE, UPDATE_NAVIGATION } from "../constants";
export function changeCount(count) {
dispatch( {
type: COUNTER_CHANGE,
payload: count,
});
}
export function updateNavigation(obj) {
dispatch({
type: UPDATE_NAVIGATION,
payload: obj,
});
}
Dispatch the data without returning it

React redux prop object property undefined

I am new to React Redux. I am not sure what is wrong on my code. There is no error on the terminal but when I take a look on the browser there is a TypeError. ItemsProduct was on the props. I was wondering why it returns an error when I am trying to access the properties.
productDescription.js
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import axios from "axios";
import {
fetchProductsRequests,
fetchProductsSuccess,
fetchProductError,
} from "../../actions/productActions";
class ProductDescription extends Component {
componentDidMount() {
this.props.fetchProducts();
}
render() {
return (
<>
<div className="grid grid-cols-3 gap-6 mb-10">
<div className="col-start-2 col-end-4">
<h4>{this.props.itemsProduct[0].name}</h4>
</div>
</div>
</>
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
itemsProduct: state.rootProduct.products.filter(
(prod) => prod.id == ownProps.match.params.id
),
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchProducts: () => {
dispatch(fetchProductsRequests());
axios
.get("http://localhost:3000/js/products.json")
.then((response) => {
dispatch(fetchProductsSuccess(response.data));
})
.catch((error) => {
dispatch(fetchProductError(error.message));
});
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductDescription);
productActions.js
export const FETCH_PRODUCTS_REQUESTS = "FETCH_PRODUCTS_REQUESTS";
export const FETCH_PRODUCTS_SUCCESS = "FETCH_PRODUCTS_SUCCESS";
export const FETCH_PRODUCTS_ERROR = "FETCH_PRODUCTS_ERROR";
export const fetchProductsRequests = () => {
return {
type: FETCH_PRODUCTS_REQUESTS,
};
};
export const fetchProductsSuccess = (product) => {
return {
type: FETCH_PRODUCTS_SUCCESS,
payload: product,
};
};
export const fetchProductError = (error) => {
return {
type: FETCH_PRODUCTS_ERROR,
payload: error,
};
};
productReducer.js
const initialState = {
loading: true,
products: [],
error: "",
};
const productReducer = (state = initialState, action) => {
switch (action.type) {
case "FETCH_PRODUCTS_REQUESTS":
return {
...state,
loading: true,
};
case "FETCH_PRODUCTS_SUCCESS":
return {
loading: false,
products: action.payload,
error: "",
};
case "FETCH_PRODUCTS_ERROR":
return {
loading: false,
products: [],
error: action.payload,
};
default:
return state;
}
};
export default productReducer;
Root Reducer
import { combineReducers } from "redux";
import productReducer from "./productReducer";
const rootReducer = combineReducers({
rootProduct: productReducer,
});
export default rootReducer;
You can do a quick check if there is data coming from your axios by doing this (it will prevent any undefined or null values)
dispatch(fetchProductsSuccess(response.data || 'no data'));
Also you should return your state in the reducer as follows:
case "FETCH_PRODUCTS_SUCCESS":
return {
...state,
loading: false,
products: action.payload,
error: "",
};
case "FETCH_PRODUCTS_ERROR":
return {
...state,
loading: false,
products: [],
error: action.payload,
};
Your
itemsProduct: state.rootProduct.products.filter(
(prod) => prod.id == ownProps.match.params.id
),
may return an empty array meaning you will not be able to retrieve that object in your view
<h4>{this.props.itemsProduct[0].name}</h4>

React-Redux - TypeError: state.menu is not iterable

I am working on a React application and I am using Redux to store the state. I have the following code:
menu.reducer.js:
import { INCREASE_CATEGORY_RANK, DECREASE_CATEGORY_RANK } from './menu.types';
const INITIAL_STATE = []
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case INCREASE_CATEGORY_RANK: {
console.log("Printing state");
console.log(state);
const menuArray = [...state.menu];
menuArray.sort((a,b) => (a.rank > b.rank) ? 1 : -1);
return {
...state,
menu: menuArray
}
}
case DECREASE_CATEGORY_RANK: {
return state.map(category => {
if (category._id === action.payload._id && category.rank !== -1) {
const oldrank = category.rank;
return {
...category,
rank: oldrank - 1
}
} else {
return category;
}
})
}
default:
return state;
}
}
menu.types.js:
export const INCREASE_CATEGORY_RANK = "INCREASE_CATEGORY_RANK";
export const DECREASE_CATEGORY_RANK = "DECREASE_CATEGORY_RANK";
menu.actions.js:
import { apiUrl, apiConfig } from '../../util/api';
import { INCREASE_CATEGORY_RANK, DECREASE_CATEGORY_RANK } from './menu.types';
export const getMenu = () => async dispatch => {
const response = await fetch(`${apiUrl}/menu`);
if (response.ok) {
const menuData = await response.json();
dispatch({ type: GET_MENU, payload: menuData })
}
}
export const increaseCategoryRank = category => dispatch => {
dispatch({ type: INCREASE_CATEGORY_RANK, payload: category })
}
export const decreaseCategoryRank = category => dispatch => {
dispatch({ type: DECREASE_CATEGORY_RANK, payload: category })
}
category-arrows.component.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increaseCategoryRank, decreaseCategoryRank } from '../../redux/menu/menu.actions';
import './category-arrows.styles.scss';
class CategoryArrows extends Component {
render() {
const { category, increaseCategoryRank, decreaseCategoryRank } = this.props;
return (
<div class="arrows-container">
<div class="up-arrow" onClick={() => this.props.increaseCategoryRank(category)}></div>
<div class="category-rank">
<p>{category.rank}</p>
</div>
<div class="down-arrow" onClick={() => this.props.decreaseCategoryRank(category)}></div>
</div>
)
}
}
export default connect(null, { increaseCategoryRank, decreaseCategoryRank } )(CategoryArrows);
For my Reducer function, the initial state is retrieved from a database. The Reducer code deals with the menu array, which is an array of objects:
I want to copy the menu array from the state in my Reducer function, so that I can sort it, and then reassign the sorted menu array to the state.
I have tried to console.log() the state to see what it is in my Reducer function. When I click on the up-arrow div in category-arrows.component.jsx, the INCREASE_CATEGORY_RANK action is dispatched. When I check the Console, I get the following:
However when I copy the menu array from the state in the INCREASE_CATEGORY_RANK case in my Reducer function, I get the following error:
I am not sure why I am getting the above error and how to resolve it. Any insights are appreciated.
It looks like the reducer expect an array not an object:
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case INCREASE_CATEGORY_RANK: {
return [...state].sort((a,b) => (a.rank > b.rank) ? 1 : -1);
}
}
}

Getting TypeError: _this2.props.handleClick is not a function when passing redux prop from container to presentational component in react

Pretty new to Redux. I'm trying to pass a handleClick event as a prop from a container component to a presentational component, the handleClick event is supposed to call upon an action which has been received as a prop with mapDispatchToProps.
Could someone tell me how to do this correctly please?
I'm building a calculator, just started, this only has three actions so far, add, Record_input_1 and Record_Input_2.
containers/ButtonsContainer.js:
import React, { Component } from 'react';
import { Buttons } from '../components/Buttons'
import { Record_Input_1 } from '../actions/sum-action';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class ButtonsContainer extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(num) {
return this.props.onRecordInput1(num)
}
render() {
return(
<Buttons handleClick={this.handleClick} />
)
}
mapStateToProps = (state) => {
return {
inputValue1: state.inputValue1,
inputValue2: state.inputValue2,
answer: state.answer
}
}
mapDispatchToProps = (dispatch) => {
return bindActionCreators({
onRecordInput1: Record_Input_1,
onRecordInput2: Record_Input_2
}, dispatch);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ButtonsContainer);
components/Buttons.js
import React, { Component } from 'react';
class Buttons extends Component {
render() {
const buttonMaker = (buttons, row) => {
for (let value of buttons) {
row.push(<button onClick={() => this.props.handleClick(value)} key={value}>
{value}
</button> )
}
}
let row1 = [];
let buttons1 = [1,2,3]
buttonMaker(buttons1, row1)
let row2 = [];
let buttons2 = [4,5,6]
buttonMaker(buttons2, row2)
let row3 = [];
let buttons3 = [7,8,9]
buttonMaker(buttons3, row3)
return (
<div>
<div>{row1}</div>
<br />
<div>{row2}</div>
<br />
<div>{row3}</div>
</div>
)
}
}
export default Buttons;
actions/sum-actions/js:
export const ADD = 'ADD';
export const RECORD_INPUT_1 = 'RECORD_INPUT_1';
export const RECORD_INPUT_2 = 'RECORD_INPUT_2';
export const add = (newInput1, newInput2) => {
return {
type: ADD,
newAnswer: newInput1 + newInput2
}
}
export const Record_Input_1 = (newInput1) => {
return {
type: RECORD_INPUT_1,
newInput1
}
}
export const Record_Input_2 = (newInput2) => {
return {
type: RECORD_INPUT_2,
newInput2
}
}
reducders/sum-reducer.js:
import { ADD, RECORD_INPUT_1, RECORD_INPUT_2 } from '../actions/sum-action'
export const initialState = {
inputValue1: '',
inputValue2: '',
answer: 0
}
export const sumReducer = (state = initialState, action) => {
switch (action.type) {
case ADD:
return [
...state,
{
answer: action.newAnswer
}
]
case RECORD_INPUT_1:
return [
...state,
{
inputValue1: action.newInput1
}
]
case RECORD_INPUT_2:
return [
...state,
{
inputValue2: action.newInput2
}
]
default:
return state;
}
}
store.js:
import { combineReducers, createStore } from 'redux';
import { initialState, sumReducer } from './reducers/sum-reducer';
const rootReducers = combineReducers({
sumReducer
})
export default createStore(rootReducers, initialState, window.devToolsExtension && window.devToolsExtension());
The buttons display ok, when I click on one I get this error:
TypeError: _this2.props.handleClick is not a function
for:
8 | render() {
9 | const buttonMaker = (buttons, row) => {
10 | for (let value of buttons) {
> 11 | row.push(<button onClick={() => this.props.handleClick(value)} key={value}
12 | {value}
13 | </button> )
14 | }
You are declaring mapStateToProps and mapDispatchToProps within ButtonsContainer. You are then passing those two methods to react-redux's connect as if they were declared outside of ButtonsContainer, hence they are undefined. Try moving them out of ButtonsContainer as shown here. It should look something like this:
class ButtonsContainer extends Component {
...
}
const mapStateToProps = (state) => {
return {
inputValue1: state.inputValue1,
inputValue2: state.inputValue2,
answer: state.answer
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
onRecordInput1: Record_Input_1,
onRecordInput2: Record_Input_2
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ButtonsContainer);

How to dispatch two actions (one is async), in redux-thunk?

Dispatch multiple actions in redux-thunk and receive infinite loop.
I am trying to turn on a spinner before a request goes to the back-end and stop spinner after request succeeds or fails.
Does anyone have any idea about where I've done mistake?
My code looks like this.
logic.js
import * as actions from "./actions";
export const getData = () => {
return dispatch => {
dispatch(actions.startSpinner());
setAuthorizationToken(getToken());
axiosInstance
.get("/data")
.then(response => {
dispatch(actions.stopSpinner()); //I guess this is problem ?
dispatch(actions.getData(response.data));
})
.catch(err => {
console.log(err);
dispatch(actions.stopSpinner());
});
};
};
And file actions.js
export const startSpinner = () => {
return { type: actionTypes.START_SPINNER };
};
export const stopSpinner = () => {
return { type: actionTypes.STOP_SPINNER };
};
export const getData = data => {
return {
type: actionTypes.GET_DATA,
payload: data
};
};
And reducer for it spinner.js
import actionTypes from "../actionTypes";
export default (state = false, action) => {
switch (action.type) {
case actionTypes.START_SPINNER: {
return (state = true);
}
case actionTypes.STOP_SPINNER: {
return (state = false);
}
default: {
return state;
}
}
};
And reducer for data dataReducer.js
import actionTypes from "../actionTypes";
const defaultState = [];
export default (state = defaultState, action) => {
switch (action.type) {
case actionTypes.GET_DATA: {
let newState = [...action.payload];
return newState;
}
default: {
return state;
}
}
};

Categories