React Redux - Loading state too slow - how to solve it - reactjs

I'm trying to create a loading state for my Redux but it looks to "slow" to get updated.
First action fetchDB => setLoading: true => once over setLoading: false
Second action fetchCat => doesn't have the time to fire it that crashes
Really simple:
set loading action:
export const setLoading = () => {
return async (dispatch) => {
await dispatch({ type: SET_LOADING }); // no payload by default goes to true
};
};
set loading reducer:
import {
FETCH_DB,
SET_LOADING,
} from "../types"
const initalState = {
db: [],
loading: false,
}
export default (state = initalState, action) => {
switch (action.type) {
// this like the other cases sets loading to FALSE
case FETCH_DB:
return {
...state,
db: action.payload,
current: null,
loading: false,
}
case FETCH_CAT_FOOD:
return {
...state,
food: action.payload,
loading: false,
}
case FETCH_CAT_DESIGN:
return {
...state,
design: action.payload,
loading: false,
}
case SET_LOADING:
return {
...state,
loading: true,
}
default:
return state
}
}
then action I use that creates the problem:
export const fetchCat = kindof => {
return async dispatch => {
dispatch(setLoading()) // looks like that it doesn't get fired
const response = await axios
.get(`http://localhost:5000/api/categories/${kindof}`)
.then(results => results.data)
try {
await dispatch({ type: `FETCH_CAT_${kindof}`, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
and then the file (a custom component) that creates the problem.
It crashes cause categories.map is undefined.
It doesn't find loading: true so the loader doesn't stop.
import React, { useState, useEffect, Fragment } from "react"
import { Spinner } from "react-bootstrap"
import { connect, useDispatch, useSelector } from "react-redux"
import CatItem from "./CatItem" // custom component
import { fetchCat, setLoading } from "../../../store/actions/appActions"
const MapCat = ({ kindof, loading, categories }) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchCat(kindof)) // gives the category I want to fetch
// eslint-disable-next-line
}, [categories])
if (!loading) {
return (
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
)
} else {
return (
<Fragment>
<div>
{categories.map(item => (
<CatItem item={item} />
))}
</div>
</Fragment>
)
}
}
const mapStateToProps = (state, kindof) =>
({
loading: state.appDb.loading,
categories: state.appDb[kindof],
})
export default connect(mapStateToProps, { fetchCat, setLoading })(MapCat)
I think that it is supposed to work like this:
loading: false (by default) => true => time to fetch => false
But doesn't look like working. Any idea?

Firstly setLoading needs to return a plain object with type and payload
export const setLoading = () => ({ type: SET_LOADING });
In fetchCat the then is not required. Also async await for dispatch is not required.
export const fetchCat = (kindof) => {
return (dispatch) => {
dispatch(setLoading()); //<---this should now be ok.
const response = await axios.get(`http://localhost:5000/api/categories/${kindof}`)
// .then((results) => results.data); //<----- not required as you are using await
try {
dispatch({ type: `FETCH_CAT_${kindof}`, payload: response.data }); //<--- use response.data ...also async/await for dispatch is not rquired.
} catch (error) {
console.log("await error", error);
}
};
};
The 2nd arg of mapStateToProps is ownProps which is an object
const mapStateToProps = (state, ownProps) =>
({
loading: state.appDb.loading,
categories: state.appDb[ownProps.kindof],
})

You have quite a bit different way of calling dispatch. Let me list them out
dispatch(fetchCat(kindof)) // gives the category I want to fetch
await dispatch({ type: `FETCH_CAT_${kindof}`, payload: response })
You can see, await or not basically is the way you use async operation. However dispatch takes type and payload to function, which means you have to make sure what you send to dispatch is with the right object. Of course Redux does accept custom format via plugins, so maybe if you throw it a async as input, the reducer might understand it as well?
Please double check each dispatch first, for example, write a function that only dispatch one type of action. Only after you make each call working, don't move to assemble them together into a bundled call.

Related

Can't dispatch action in class component

// action
export const getEvents = () => async (dispatch) => {
try {
dispatch({ type: GET_EVENTS_REQUEST })
const data = await axios.get('http://localhost:5000/api/schedule').then((response) => response.data)
dispatch({ type: GET_EVENTS_SUCCESS, payload: data })
} catch (error) {
dispatch({
type: GET_EVENTS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
})
}
}
// reducer
export const getEventsReducer = (state = { event: [] }, action) => {
switch (action.type) {
case GET_EVENTS_REQUEST:
return { loading: true }
case GET_EVENTS_SUCCESS:
return { loading: false, event: action.payload }
case GET_EVENTS_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
}
// and this is how I'm trying to call my action:
import { getEvents } from '../../redux/actions/calendarActions'
class Calendar extends React.PureComponent {
componentDidMount() {
const { dispatch } = this.props
console.log(dispatch(getEvents()))
}
}
export default connect()(Calendar)
// component is much bigger, I only added relevant parts
Up until my reducer, if I console.log my data, it is correct, as well as in my redux dev tools tab: an array with a few entries. But when console.logging in my Calendar component, it returns a promise, with undefined result:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
What am I doing wrong?
Normally you want to have access to either the dispatch or the store of Redux within a component. you already have the dispatch function within the component, but if you need access to Redux state inside it:
first you need to define such function, which makes the redux store available in the component.
const mapStateToProps = (state) => ({
state: state // a "state" prop is available in the component which points to redux state,
})
or you can customize it if you only need certain properties of Redux state:
const mapStateToProps = (state) => ({
state: state.event //
})
and change the connect function like this:
connect(mapStateToProps)(Calendar)

Why is my dispatch/console.log not firing when I call the action?

I was working on my final project for Flatiron and I came across a bug while working on my frontend. I tried many things, but always came back to this one issue. My callback function inside my dispatch is not firing. So while I may be interested to know whether my code should be refactored/fixed of bugs, the biggest problem is that I cannot run my dispatches through an action.
Here is my generic container:
import { useEffect } from "react"
import { connect } from "react-redux"
import * as actions from "../../actions/index"
import Component from "./Component"
const Container = (props) => {
useEffect(() => {
actions.menuItemsFetchRandom(8)
}, [])
const menuItemComponents = props.menuItems.menuItems.map((menuItem) => {
return (
<Component key={menuItem.key} menuItem={menuItem} />
)
})
return (
<div className="container">
{
props.menuItems.fetching
? "loading..."
: (
props.menuItems.error === ""
? menuItemComponents
: props.menuItems.error
)
}
</div>
)
}
const mapStateToProps = (state) => {
return {
menuItems: state.menuItems
}
}
export default connect(mapStateToProps)(Container)
And my actions.menuItemsFetchRandom() comes from /actions/menuItems.js:
import * as common from "./common"
import * as reducers from "../reducers/index"
const MENU_ITEMS_URL = common.API_URL + "menu_items/"
export const menuItemsFetchMany = (options) => {
return (dispatch) => {
dispatch({
type: reducers.MENU_ITEMS_FETCH_REQUEST
})
fetch(MENU_ITEMS_URL + options).then((response) => {
return response.json()
}).then((menuItems) => {
dispatch({
type: reducers.MENU_ITEMS_FETCH_SUCCESS,
payload: menuItems
})
}).catch((error) => {
dispatch({
type: reducers.MENU_ITEMS_FETCH_ERROR,
payload: error
})
})
}
}
export const menuItemsFetchRandom = (numberOfItems) => {
console.log("hi")
return (dispatch) => {
console.log("Hello")
dispatch({
type: reducers.MENU_ITEMS_FETCH_REQUEST
})
fetch(MENU_ITEMS_URL).then((response) => {
return response.json()
}).then((menuItems) => {
const length = menuItems.length
if (numberOfItems > length) {
numberOfItems = length
}
dispatch({
type: reducers.MENU_ITEMS_FETCH_SUCCESS,
payload: (() => {
const result = []
while (result.length !== length) {
const choice = menuItems[common.getRandomInt(length)]
if (result.includes(choice)) {
continue
}
result.push(choice)
}
})()
})
}).catch((error) => {
dispatch({
type: reducers.MENU_ITEMS_FETCH_ERROR,
payload: error
})
})
}
}
My /reducers/menuItems.js looks like this:
export const MENU_ITEMS_FETCH_REQUEST = "MENU_ITEMS_FETCH_REQUEST"
export const MENU_ITEMS_FETCH_SUCCESS = "MENU_ITEMS_FETCH_SUCCESS"
export const MENU_ITEMS_FETCH_ERROR = "MENU_ITEMS_FETCH_ERROR"
const initialState = {
menuItems: [],
error: "",
fetching: false
}
const menuItems = (state=initialState, action) => {
switch (action.type) {
case MENU_ITEMS_FETCH_REQUEST: {
return {
...state,
error: "",
fetching: true
}
}
case MENU_ITEMS_FETCH_SUCCESS: {
return {
...state,
menuItems: action.payload,
error: "",
fetching: false
}
}
case MENU_ITEMS_FETCH_ERROR: {
return {
...state,
error: action.payload,
fetching: false
}
}
default: {
return state
}
}
}
export default menuItems
But that doesn't seem to matter as the console.log inside the callback function in menuItemsFetchRandom() does not fire. I get the console.log("hi"), but not the console.log("Hello"). Which to me tells me its either something wrong with my code, or something wrong with redux-thunk.
You need to actually dispatch that action, not just call the action creator.
const dispatch = useDispatch();
useEffect(() => {
dispatch(actions.menuItemsFetchRandom(8))
}, [])
PS: also, there is no need to use connect in function components. Using useSelector and useDispatch is much easier and the official recommendation. Additionally, you are writing a pretty outdated style of redux that makes you write a multitude of the code that is required with modern redux. You are likely following very outdated tutorials.
Please see the official tutorials at https://redux.js.org/tutorials/index

redux to dispatch response and a return message

In my action i am dispatching the type and the payload but what if i also want the res.status and a return JSON message to be included into my props. How would i do so in my action and reducer?
action
export const fetchUserPosts = () => (dispatch) => {
fetch(`${currentPort}/user/recipes`,
{
withCredentials: true,
credentials: 'include',
})
.then((res) => {
if (res.status !== 401) return res.json().then((data) => data);
return { message: { msgBody: 'UnAuthorized' }, msgError: true };
})
.then((posts) => dispatch({
type: FETCH_USER_POSTS,
payload: posts,
}));
};
reducer
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_USER_POSTS:
return {
...state,
fetchUsersPosts: action.payload,
};
default:
return state;
}
}
You can combine multiple items into a single payload. I would create different actions for success and error. Using the same action complicates the reducers logic. It's also easier to work with async/await then with nested promises.
This is a working example that uses SpaceX open API:
const FETCH_USER_POSTS_SUCCESS = 'FETCH_USER_POSTS_SUCCESS'
const FETCH_USER_POSTS_FAILED = 'FETCH_USER_POSTS_FAILURE'
const fetchPostSuccessAction = (payload) => ({
type: 'FETCH_USER_POSTS_SUCCESS',
payload,
})
const fetchPostFailureAction = (payload) => ({
type: 'FETCH_USER_POSTS_FAILURE',
payload,
})
const fetchUserPosts = () => async dispatch => {
const res = await fetch('https://api.spacexdata.com/v3/launches/latest');
if (res.status !== 401) {
const { ships: posts } = await res.json();
dispatch(fetchPostSuccessAction({
posts,
status: res.status,
}))
} else {
dispatch(fetchPostFailureAction({
message: { msgBody: 'UnAuthorized' },
}))
}
};
fetchUserPosts()(console.log)
The reducer can handle the object by destructuring it, and the properties to the new state in any way you need. You can also change other properties, for example changing errMsg to true or false according to the action's type:
export default function (state = initialState, { type, payload }) {
switch (type) {
case FETCH_USER_POSTS_SUCCESS: {
const { posts, status } = payload;
return {
...state,
status,
fetchUsersPosts: posts,
msgError: false,
message: null
};
}
case FETCH_USER_POSTS_FAILURE: {
const { message } = payload;
return {
...state,
status: 401,
fetchUsersPosts: null,
msgError: true,
message
};
}
default:
return state;
}
}
If I am following correctly you are using this action inside of a component to send a fetch. You don't have access to the components props with the reducer. You can send the http request in the component and use that to store the response in the state. Or use connect from 'react-redux' package to map the redux store to access the fetch result.
import { connect } from 'react-redux'
const component = props => {
//to access redux state in component use props.myprop
return <div>{props.myprops.title}</div>
}
const mapStateToProps = state => {
return{
myprop: state.fetchUsersPosts
}
}
export default connect(mapStateToProps)(component)
If this was what you were looking for you can learn more at https://react-redux.js.org/api/connect

Redux UseSelect hook keeps re-rendering

I've created three useSelector hooks and for the first one I change the value by dispatching an action with an OnClick function. When I do this my other useSelects get re-rendered even though the reference hasn't changed. Does anyone know why this is happening? I believe this is happening because I put console.logs inside the useSelectors and see them get fired off every time I click the button. minedDiamond should be the only value to change when I click the button.
Minecraft.js
import React, { useEffect, useCallback } from "react";
import { connect, useSelector, useDispatch, shallowEqual } from "react-redux";
import { mineDiamond, fetchMinecraftItems } from "../redux/diamonds/actions";
const Minecraft = () => {
const loading = useSelector((state) => {
console.log("loading output");
return state.diamond.loading;
});
let minedDiamond = useSelector((state) => {
console.log("diamond output");
return state.diamond.minedDiamond;
});
const names = useSelector((state) => {
console.log("name rendered");
let data = state.diamond.minecraftData;
return data.map((i) => i.name);
});
const dispatch = useDispatch();
const handleClick = () => dispatch(mineDiamond((minedDiamond += 1)));
useEffect(() => {
dispatch(fetchMinecraftItems());
}, []);
console.log({ loading });
return (
<div className="wrapper">
<div className="wrapper__item">
<img src="/image/pickaxe.png" alt="diamond" />
<button onClick={handleClick} type="button" className="wrapper__button">
Mine
<span role="img" aria-label="cart">
🛒
</span>
</button>
</div>
<div className="wrapper__item">
<img src="/image/diamond.png" alt="axe" />
<span className="num">{minedDiamond}</span>
</div>
<div className="num">
{loading ? (
<p>loading...</p>
) : (
<h1 className="num">{names}</h1>
)}
</div>
</div>
);
};
export default Minecraft;
Action Creators
import * as Actions from "./actionTypes";
import axios from "axios";
//action creator
export const mineDiamond = (addDiamond) => ({
type: Actions.MINE_DIAMOND,
payload: addDiamond,
});
export function fetchMinecraftItems() {
return function (dispatch) {
dispatch({ type: Actions.MINECRAFT_DATA_FETCH });
return fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((json) => {
dispatch({ type: Actions.MINECRAFT_DATA_SUCCESS, payload: json });
})
.catch((err) =>
dispatch({ type: Actions.MINECRAFT_DATA_FAIL, payload: err })
);
};
}
Reducer
import * as Actions from "./actionTypes";
//reducer holds initial state
const initialState = {
minedDiamond: 0,
minecraftData: [],
loading: false,
error: null,
};
//reducer
const diamondReducer = (state = initialState, action) => {
switch (action.type) {
case Actions.MINE_DIAMOND:
return {
...state,
minedDiamond: action.payload,
};
case Actions.MINECRAFT_DATA_FETCH:
return {
...state,
loading: true,
};
case Actions.MINECRAFT_DATA_SUCCESS:
return {
...state,
loading: false,
minecraftData: action.payload,
};
case Actions.MINECRAFT_DATA_FAIL:
return {
...state,
error: action.payload,
};
default:
return state;
}
};
export default diamondReducer;
Your issue has nothing to do with the usage of console.log.
Would you kindly include the code for your action creators and reducers?
Assuming state.diamond.mincraftdata is an array, the line
return data.map((i) => i.name);
creates a new array every time the selector is run. As per the react-redux docs:
when an action is dispatched to the Redux store, useSelector() only forces a re-render if the selector result appears to be different than the last result. As of v7.1.0-alpha.5, the default comparison is a strict === reference comparison.

dispatch of connect doesn't work in react native

nativeusingreact-redux,react-thunk,handleActionswithducks structure` and trying to dispatch action function to change state.
It worked actually until this morning, but it doesn't work anymore.
I have no idea what I changed. Even worse, I didn't commit because this project is for practicing react native, so I cannot undo my work.
If I'm right that I understood correctly, dispatch of connect in container component should call fetchName() in categoryImgListMod.js(action).
However, I guess dispatch never works here.
So state never changes.
If you give me any of advice, it would be very helpful for me, and I would appreciate you.
Here's my code
categoryListContainer.js
import React, {Component} from 'react';
import {View} from 'react-native';
import { connect } from 'react-redux';
import CategoryImgList from '../components/categoryImgList';
import * as CategoryImgActions from '../store/modules/categoryImgListMod';
class CategoryImgListContainer extends Component {
loadNames = async () => {
console.log(this.props);
const { CategoryImgActions } = this.props;
try {
await CategoryImgActions.fetchName();
} catch (e) {
console.log(e);
}
}
render() {
const {container} = styles;
const { loadNames } = this;
return (
<View style={container}>
<CategoryImgList names={loadNames}/>
</View>
);
}
}
const styles = {
container: {
height: '100%'
}
}
export default connect(
({categoryImgListMod}) => ({
name: categoryImgListMod.name
}),
(dispatch) => ({
fetchName: () => {
dispatch(CategoryImgActions.fetchName())
}
})
)(CategoryImgListContainer);
categoryImgListMod.js
import {handleActions} from 'redux-actions';
// firestore
import * as db from '../../shared';
// Action types
const GET_CATEGORY_NAME_PENDING = 'categoryImgList/GET_CATEGORY_NAME_PENDING';
const GET_CATEGORY_NAME_SUCCESS = 'categoryImgList/GET_CATEGORY_NAME_SUCCESS';
const GET_CATEGORY_NAME_FAILURE = 'categoryImgList/GET_CATEGORY_NAME_FAILURE';
// action creator
export const fetchName = () => async (dispatch) => {
dispatch({type: GET_CATEGORY_NAME_PENDING});
try {
const response = await db.getCategoryNames();
const arr = [];
response.docs.forEach(res => {
arr.push(res.id);
});
dispatch({type: GET_CATEGORY_NAME_SUCCESS, payload: arr});
return arr;
} catch (e) {
console.log(e);
dispatch({type: GET_CATEGORY_NAME_FAILURE, payload: e});
}
}
const initialState = {
fetching: false,
error: false,
name: []
};
// Reducer
export default handleActions({
[GET_CATEGORY_NAME_PENDING]: (state) => ({ ...state, fetching: true, error: false }),
[GET_CATEGORY_NAME_SUCCESS]: (state, action) => ({ ...state, fetching: false, name: action.payload }),
[GET_CATEGORY_NAME_FAILURE]: (state) => ({ ...state, fetching: false, error: true })
}, initialState);
I solved using bindActionCreators.
But, still I don't understand why it never dispatched.
(dispatch) => ({
CategoryImgActions: bindActionCreators(categoryImgActions, dispatch)
})
I believe its the curly braces which are the problem, try this:
(dispatch) => ({
fetchName: () => dispatch(CategoryImgActions.fetchName())
})
If you are using curly braces you need to explicitly return:
(dispatch) => ({
fetchName: () => {
return dispatch(CategoryImgActions.fetchName());
}
})

Resources