Dispatching multiple actions in redux duplicates the state parameters - javascript

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

Related

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>

Redux action to reset - states are equal

I have the following initialState For React Redux:
const inistialStateRedux = {
configuredFilters: {
data: {
countries: [],
divisions: [],
companies: [],
locations: [],
fields: [],
search: '',
},
},
};
Now I want to create a RESET reducer.
It looks like that:
export const createReducer = (initialState, handlers) => (
state = initialState,
action
) => {
if (action.type in handlers) {
return handlers[action.type](state, action);
}
return state;
};
export const multiUse = (reducer, name = '') => (state = null, action) => {
if (action.name !== name) return state;
return reducer(state, action);
};
import {
createReducer
} from '../helper';
import * as Action from './actions';
import inistialStateRedux from '../inistialStateRedux';
export default createReducer({
data: {},
}, {
[Action.RESET_CONFIGURED_FILTERS]: (state) => ({
...state,
data: {
...inistialStateRedux.configuredFilters.data,
},
}),
});
But Redux Devtools shows, that the states are equal. What am I doing wrong ?
In the Actions you can use a dispatcher to reset the Form.
Like this:
import axios from 'axios';
import { ADD} from '../modelType';
import { reset } from 'redux-form';
export const add= formValues => async dispatch => {
const res = await axios.post('/api/model/', { ...formValues });
dispatch({
type: ADD,
payload: res.data
});
dispatch(reset('yourUniqueFormName'));
};

Redux state overwritten after next fetch

In my web app, I want to fetch urls from an API. Also, I want to fetch categories for these items.
index.js:
componentDidMount () {
this.props.fetchUrls();
this.props.fetchCategories();
}
Im fetching the urls first like that:
export const fetchUrlsSuccess = urls => ({
type: FETCH_URLS_SUCCESS,
payload: { urls }
});
export const fetchUrls = () => dispatch => {
dispatch(fetchUrlsBegin());
return fetch(`${api}/urls`)
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchUrlsSuccess(json));
return json.urls;
})
.catch(error => dispatch(fetchUrlsFailure(error)));
};
fetching categories:
export const fetchCategoriesSuccess = categories => ({
type: FETCH_CATEGORIES_SUCCESS,
payload: { categories }
});
export const fetchCategoriesFailure = error => ({
type: FETCH_CATEGORIES_FAILURE,
payload: { error }
});
export function fetchCategories() {
return dispatch => {
dispatch(fetchCategoriesBegin());
return fetch(`${api}/categories`)
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchCategoriesSuccess(json));
return json.categories;
})
.catch(error => dispatch(fetchCategoriesFailure(error)));
};
}
url reducer:
import {
FETCH_URLS_BEGIN,
FETCH_URLS_SUCCESS,
FETCH_URLS_FAILURE
} from "../actions/types";
export default function urlReducer(state = [], action) {
switch (action.type) {
case FETCH_URLS_BEGIN:
console.log("url fetch begin", state);
return {
...state,
loading: true,
error: null
};
case FETCH_URLS_SUCCESS:
console.log("url fetch success", state);
return {
...state,
loading: false,
items: action.payload.urls
};
case FETCH_URLS_FAILURE:
console.log("url fetch error", state);
return {
...state,
loading: false,
error: action.payload.error,
items: []
};
default:
return state;
}
}
categories reducer:
import {
FETCH_CATEGORIES_BEGIN,
FETCH_CATEGORIES_SUCCESS,
FETCH_CATEGORIES_FAILURE
} from "../actions/types";
export default function categoriesReducer(state = [], action) {
switch (action.type) {
case FETCH_CATEGORIES_BEGIN:
console.log("categories fetch begin", state);
return {
...state,
loading: true,
error: null
};
case FETCH_CATEGORIES_SUCCESS:
console.log("categories fetch success", state);
return {
...state,
loading: false,
items: action.payload.categories
};
case FETCH_CATEGORIES_FAILURE:
console.log("categories fetch fail", state);
return {
...state,
loading: false,
error: action.payload.error,
items: []
};
default:
return state;
}
}
combining reducers in index of reducers:
import { combineReducers } from "redux";
import urlReducer from "./urlReducer";
import categoriesReducer from "./categoriesReducer";
import modalReducer from "./modalReducer";
export default combineReducers({
urls: urlReducer,
modal: modalReducer,
categories: categoriesReducer
});
create store :
import { createStore, applyMiddleware, compose } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import thunk from "redux-thunk";
import storage from "redux-persist/lib/storage";
import rootReducer from "../reducers";
const persistConfig = {
key: "root",
storage
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const middleware = [thunk];
let store = createStore(
persistedReducer,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
let persistor = persistStore(store);
export { store, persistor };
For the categories, I do the same. Then I combine both reducers.
What happens is that the state.urls. Items get overwritten and state.categories.items holds state instead. I don't understand why.
output of the redux dev-tool after the second fetch:
I'm pretty new to redux and don't understand the state management...

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;
}
}
};

Why does mapping state to props give undefined?

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.

Categories