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.
Related
I'm trying to get initial data from a reducer by dispatching action from App.js component, it works fine but when I switch to another component and load it with useSelector it gets undefined.
I have tried this line of code in Headphones.js but the second one returns undefined
const allData = useSelector((state) => state.allDataReducer);
const { loading, error, data } = allData;
App.js
const dispatch = useDispatch();
useEffect(() => {
dispatch(welcomeAction());
dispatch(listAllData);
}, [dispatch]);
allDataReducer.js
import {
LIST_ALL_DATA_FAIL,
LIST_ALL_DATA_REQUEST,
LIST_ALL_DATA_SUCCESS,
} from "../constants/shared";
export const allDataReducer = (state = { loading: true, data: {} }, action) => {
switch (action.type) {
case LIST_ALL_DATA_REQUEST:
return { loading: true };
case LIST_ALL_DATA_SUCCESS:
return { loading: false, data: action.payload };
case LIST_ALL_DATA_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
shared.js
import {
LIST_ALL_DATA_FAIL,
LIST_ALL_DATA_REQUEST,
LIST_ALL_DATA_SUCCESS,
} from "../constants/shared";
import Axios from "axios";
export const listAllData = async (dispatch) => {
dispatch({
type: LIST_ALL_DATA_REQUEST,
});
try {
const { data } = await Axios.get("/all");
dispatch({ type: LIST_ALL_DATA_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: LIST_ALL_DATA_FAIL, payload: error.message });
}
};
Headphones.js
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { listheadphones } from "../actions/headphonesActions";
import BasicSection from "../components/BasicSection";
import Definer from "../components/Definer";
import LoadingBox from "../components/LoadingBox";
import MessageBox from "../components/MessageBox";
import ProductsCategories from "../components/ProductsCategories";
import BestAudioGear from "../components/BestAudioGear";
const Headphones = (props) => {
const dispatch = useDispatch();
const headphonesList = useSelector((state) => state.headphonesList);
const allData = useSelector((state) => state.allData);
const { loading, error, data } = allData; //undefined
//const { loading, error, headphones } = headphonesList;
console.log(headphonesList);
useEffect(() => {
dispatch(listheadphones());
}, [dispatch]);
return (
<div>
<Definer title="HEADPHONES" />
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
headphones.map((headphone) => (
<BasicSection
key={headphone.id}
name={headphone.headerName}
info={headphone.info}
mobile={headphone.mobile}
tablet={headphone.tablet}
desktop={headphone.desktop}
/>
))
)}
<ProductsCategories />
<BestAudioGear />
</div>
);
};
export default Headphones;
Github repo
Your description is still not specific enough, can't really pin down what the issue is. But here is some stuff I noticed:
dispatch(listAllData); somehow looks wrong to me, the action creator is usually a function that gets called: dispatch(listAllData());
Then where you define export const listAllData = async (dispatch) => { - this should be a function that returns a function if you're using the thunk middleware. You only defined a function.
After passing the product ID I'm being redirected to the product details page. In that page, I have details of the product such as name, destination, price and date etc. As I'm using redux and getting the data from mongodb the date is saved as 2021-11-23T18:00:00.000Z this format. So after fetching the data in my product details page I used flight?.data?.date.split("T")[0] to only show the date. But after clicking the button to go to the product details page it shows the following error TypeError: Cannot read properties of undefined (reading 'split'). But when I remove the split part from the code it is working fine. I guess the only reason is the code is running before the data is loaded. So for that I tried to show loading spinner before the data loads by dispatching loading action. But it isn't working. I've used redux thunk for other actions that is why I've added async in Action. Otherwise it gives me dispatch is not defined.
Details page
import { CircularProgress } from "#mui/material";
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useParams } from "react-router-dom";
import { getFlight, loadingStatus } from "../../actions/flights";
import "./Details.css";
const FlightDetails = () => {
const flight = useSelector((state) => state.flights);
console.log("flight", flight.loading);
const dispatch = useDispatch();
const { id } = useParams();
useEffect(() => {
dispatch(loadingStatus(true));
dispatch(getFlight(id));
dispatch(loadingStatus(false));
}, [id, dispatch]);
return (
<div className="details_parent">
{flight.loading ? (
<CircularProgress />
) : (
<div className="details_container">
<div className="form_data">
<span>From</span>
<h3>{flight?.data?.from}</h3>
</div>
<div className="form_data">
<span>To</span>
<h3>{flight?.data?.to}</h3>
</div>
<div className="form_data">
<span>Airline</span>
<h3>{flight?.data?.airline}</h3>
</div>
<div className="form_data">
<span>Trip type</span>
<h3>{flight?.data?.flightType}</h3>
</div>
<div className="form_data">
<span>Date(yyyy-mm-dd)</span>
<h3>{flight?.data?.date.split("T")[0]}</h3>
</div>
<div className="form_data">
<span>Price</span>
<h1>${flight?.data?.price}</h1>
</div>
</div>
)}
</div>
);
};
export default FlightDetails;
Reducer
const initialState = {
data: [],
loading: false,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "GET_FLIGHTS":
return { ...state, data: action.payload };
case "GET_FLIGHT":
return { ...state, data: action.payload };
case "CREATE_FLIGHT":
return { ...state, data: { ...action.payload } };
case "SEARCH":
return { ...state, data: action.payload };
case "LOADING":
return { ...state, loading: action.payload };
default:
return state;
}
};
export default reducer;
Actions
export const loadingStatus = (status) => async (dispatch) => {
dispatch({
type: "LOADING",
payload: status,
});
};
React runs a first render before running any useEffect calls. Change your initial loading state to true.
const initialState = {
data: [],
loading: true,
};
why you are not using flight?.data?.date?.split("T")[0]
but in this case you have to set loading in initial data of reducer to true
const initialState = {
data: [],
loading: true,
};
Rather than setting separate actions for change the loading state I've set the dispatching before getting the data in actions. And after the promise is returned I've again set the dispatch to false.
Actions to get the specific flight data
export const getFlight = (id) => async (dispatch) => {
try {
dispatch({
type: "LOADING",
payload: true,
});
const { data } = await api.fetchFlight(id);
dispatch({
type: "GET_FLIGHT",
payload: data,
});
dispatch({
type: "LOADING",
payload: false,
});
} catch (error) {
console.log(error);
}
};
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.
I have a problem. As I understood hook useEffect doen't run.
I have action that should take data from server.
export const getProducts = () => {
return dispatch => {
dispatch(getProductsStarted());
fetch('https://shopserver.firebaseapp.com/get-products')
.then(res => {
dispatch(getProductsSuccess(res.json()));
})
.catch(err => {
dispatch(getProductsFailure(err.message));
});
}
}
const getProductsSuccess = todo => ({
type: "ADD_TODO_SUCCESS",
payload: {
...todo
}
});
const getProductsStarted = () => ({
type: "ADD_TODO_STARTED"
});
const getProductsFailure = error => ({
type: "ADD_TODO_FAILURE",
payload: {
error
}
});
I have a reducer.
const initialState = {
loading: false,
products: [],
error: null
}
export const ProductReducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_TODO_SUCCESS":
return {
...state,
loading: false,
error: null,
todos: [...state.products, action.payload.products]
}
case "ADD_TODO_STARTED":
return {
...state,
loading: true
}
case "ADD_TODO_FAILURE":
return {
...state,
loading: false,
error: action.payload.error
}
default:
return state
}
}
And I have a Component where I want to render a result.
import React from 'react';
import { CardItem } from "./cardItem";
import { useSelector } from 'react-redux';
import { useEffect } from 'react';
import { getProducts } from '../Redux/Actions/productAction'
export const ProductCard = () => {
useEffect(() => {
getProducts();
console.log('111111')
})
const data = useSelector(state => state.ProductReducer.products);
return (
<div>
{data.map( element =>
CardItem (element)
)}
</div>
)
}
After rendering page nothing happens. ReduxDevTools shows that there was no send actions. Please, help me to fix it. Thank you.
I think you should be calling your async action like this :
import { useDispatch, useSelector } from 'react-redux';
[...]
export const ProductCard = () => {
const dispatch = useDispatch();
useEffect(() => {
// I guess getProducts is an async redux action using redux-thunk
dispatch(getProducts());
console.log('111111')
}, []);
[...]
}
I assume you want to load products only when component is born, so I pass an empty array as second argument for useEffect (https://reactjs.org/docs/hooks-reference.html#useeffect).
I am learning Redux so this may be a basic question and answer but here we go.
I am trying to fetch a list of items from my backend api (/api/items) and pass them into my ShoppingList.js component so that when the component loads the list of items will be displayed. I think that I have to fetch the data in the action, and then call that action in the useEffect hook in the react component, but I am not too sure as I can't seem to get it to work.
Can anyone help me figure this out?
Redux.js
import { createStore } from 'redux';
import axios from 'axios';
const initialState = {
items: [],
loading: false,
};
export const store = createStore(
reducer,
initialState,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
function reducer(state, action) {
switch (action.type) {
case 'GET_ITEMS':
return {
...state,
items: action.payload,
loading: false,
};
case 'ADD_ITEM':
return {
...state,
items: [...state.items, action.payload],
};
case 'DELETE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload),
};
case 'ITEMS_LOADING':
return {
...this.state,
loading: true,
};
default:
return state;
}
}
export const getItemsAction = () => ({
return(dispatch) {
console.log('here');
axios.get('api/items').then(response => {
console.log(response);
dispatch({ type: 'GET_ITEMS', payload: response.data });
});
},
});
export const addItemAction = item => ({
type: 'ADD_ITEM',
payload: item,
});
export const deleteItemAction = item => ({
type: 'DELETE_ITEM',
payload: item,
});
export const setItemsLoading = () => ({
type: 'ITEMS_LOADING',
});
ShoppingList.js
export default function ShoppingList() {
const items = useSelector(state => state.items);
const dispatch = useDispatch();
const addItem = name => dispatch(addItemAction(name));
const deleteItem = id => dispatch(deleteItemAction(id));
useEffect(() => {
//call get items dispatch?
getItemsAction();
});
return (
<div className="container mx-auto">
<button
onClick={() => {
const name = prompt('Enter Item');
if (name) {
// setItems([...items, { id: uuid(), name: name }]);
addItem({
id: uuid(),
name: name,
});
}
}}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mt-4"
>
Add Item
</button>
<ul className="mt-4">
<TransitionGroup className="shopping-list">
{items.map(({ id, name }) => (
<CSSTransition
key={id}
timeout={500}
classNames="fade"
style={{ marginBottom: '0.5rem' }}
>
<li>
{' '}
<button
className="bg-red-500 rounded px-2 mr-2 text-white"
onClick={deleteItem.bind(this, id)}
>
×
</button>
{name}
</li>
</CSSTransition>
))}
</TransitionGroup>
</ul>
</div>
);
}
You are close to where you need to be, the missing piece is that redux is synchronous, so you need to use something like redux-thunk or redux-saga to handle async actions such as network requests.
Once you have setup whatever library you want, you would call it similarly to calling a redux action, from a useEffect like you suggest.
So for example, if you use a thunk:
export const getItemsAction = () => ({
return (dispatch) => {
axios.get('api/items').then(response => {
console.log(response)
dispatch({ type: 'GET_ITEMS_SUCCESS', payload: response.data })
}).catch(err => dispatch({ type: 'GET_ITEMS_ERROR', error: err })
},
})
Then you could call it from your effect:
useEffect(() => {
dispatch(getItemsAction())
}, []);
Make sure you add an empty dependency array if you only want to call it once, otherwise it will get called every time your component refreshes.
Note that the thunk is able to dispatch other redux actions, and I changed a few of your actions types to make it more clear what is going on;
GET_ITEMS_SUCCESS is setting your successful response & GET_ITEMS_ERROR sets any error if there is one.