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;
}
}
};
Related
After introducing Redux to my React Native Expo app, whenever I try to interact with the database my app stops working.
actions.js:
export const SET_SELECTED_PLAYERS = "SET_SELECTED_PLAYERS"
export const SET_PLAYERS = "SET_PLAYERS"
export const SET_SELECTED_COURSE = "SET_SELECTED_COURSE"
export const SET_COURSES = "SET_COURSES"
//Player actions
export const setPlayers = (players) => (
{ type: SET_PLAYERS, payload: players, }
)
export const setSelectedPlayers = (players) => (
({ type: SET_SELECTED_PLAYERS, payload: players, })
)
export const setSelectedCourse = (course) =>
({ type: SET_SELECTED_COURSE, payload: course, })
export const setCourses = (courses) =>
({ type: SET_COURSES, payload: courses, })
reducers.js:
import { SET_PLAYERS, SET_SELECTED_PLAYERS, SET_SELECTED_COURSE, SET_COURSES } from "./actions"
const initialState = {
players: [],
selectedPlayers: [],
courses: [],
selectedCourse: null,
round: {}
}
export const playerReducer = (state = initialState, action) => {
switch (action.type) {
case SET_PLAYERS:
return { ...state, players: action.payload }
case SET_SELECTED_PLAYERS:
return { ...state, selectedPlayers: action.payload }
default:
return state
}
}
export const courseReducer = (state = initialState, action) => {
switch (action.type) {
case SET_SELECTED_COURSE:
return { ...state, selectedCourse: action.payload }
case SET_COURSES:
return { ...state, courses: action.payload }
default:
return state
}
}
store.js:
import { createStore, combineReducers, applyMiddleware } from "redux";
import { courseReducer, playerReducer } from "./reducers";
const rootReducer = combineReducers({ playerReducer, courseReducer })
export const Store = createStore(rootReducer)
SQLite used in component :
const dispatch = useDispatch()
const db = SQLite.openDatabase("players.db")
useEffect(() => {
db.transaction(tx => {
tx.executeSql("SELECT * FROM Player", [], (trans, result) => {
dispatch(setPlayers(result.rows._array))
})
})
}, [])
Table for Player exists and app worked before I introduced Redux. It interacts with Firebase and when fetching data from cloud Redux has no problems. What problems could it have with SQLite?
Use sqlite query in redux action
export const getUsers = () => {
try {
return async dispatch => {
const result = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const json = await result.json();
if (json) {
dispatch({
type: GET_USERS,
payload: json
})
} else {
console.log('fetch user api error');
}
}
} catch (error) {
console.log('action error');
}
}
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
I am trying to make a call that changes redux state but i am having problems with dispatching the action. I am sure all imports are correct. I think the main problem is in mapStateToProps but just cant seem to find it.
Call
onClick={() => this.props.ethereum}
mapStateToProps and other...
const mapStateToProps = state => {
return({
depositMenu: state.depositMenu
})
}
const mapDispatchToProps = dispatch => {
return ( {
visa: () => dispatch(visa()),
bitcoin: () => dispatch(bitcoin()),
ethereum: () => dispatch(ethereum())
})
}
export default connect(
mapStateToProps,mapDispatchToProps
)(Deposit)
Actions
export const visa= () => {
return {
type: 'VISA'
}
}
export const bitcoin = () => {
return {
type: 'BITCOIN'
}
}
export const ethereum = () => {
return {
type: 'ETHEREUM'
}
}
Reducer
const MainPageDeposit = (state = 'visa', action) => {
switch (action.type) {
case 'VISA':
return state = 'visa';
case 'ETHEREUM':
return state = 'ethereum';
case 'BITCOIN':
return state = 'bitcoin';
default:
return state;
}
}
export default MainPageDeposit;
And combine reducers
import MainPageDeposit from './MainPageDeposit';
import { combineReducers } from 'redux';
const allReducers = combineReducers({
depositMenu: MainPageDeposit,
})
export default allReducers;
I think you should change onClick={() => this.props.ethereum} to onClick={this.props.ethereum}
I am like in a strange problem. The problem is that I am trying to make an API hit (in service file) which in turn provides some data (it is working), this data is to be updated in my reducer1.js and then returned. Now, my issue is though the value is coming in reducer file, but is not returned, so in turn, state is not changed, and in turn my end component is not rerendered.
Now, when my service file is successfully hitting and then returning data to my reducer1.js, why in the world the updated-state is not returned by "GET_List" action type? Can someone see any problem?
index.js (service file)
const global = {
getActressList: async function(){
const response = await fetch("http://localhost:2000/api/actressList");
const data = await response.json();
return data;
}
}
export default global;
reducer1.js
import global from '../../services/index';
const initialState = {
data: [
{
id: 1,
name: "Aishwarya Rai",
src: "/assets/img/aishwarya.png"
}
]
};
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
const data = global.getActressList();
data.then((res)=> {
return {
...state,
data: res
}
})
}
default:
return state;
}
}
export default reducer1;
Result:
You are returning from a promise not from a reducer function:
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
const data = global.getActressList();
data.then((res) => {
// here you are returning from a promise not from a reducer function
return {
...state,
data: res,
};
});
}
default:
return state;
}
}
The code in reducer should be sync like this:
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
return {
...state,
data: action.payload,
};
}
default:
return state;
}
}
And your data fetching should be moved to component effect like this:
function YourComponent() {
const dispatch = useDispatch();
const data = useSelector(state => state.data)
useEffect(() => {
const data = global.getActressList();
data.then((res) => {
dispatch({type: 'GET_LIST', payload: res});
});
}, [])
...
}
EDIT
If you use class components the fetching logic should be placed in componentDidMount lifecycle hook like this:
class YourComponent extends Component {
state = { data: [] };
componentDidMount() {
const data = global.getActressList();
data.then((res) => {
dispatchYourAction({type: 'GET_LIST', payload: res});
});
}
...
}
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.