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).
Related
When i add columnext into useEffect dependencies it caused a infnate loop,but when i removed
columnext from denpendencies,the prop materialExtValues passed to my child Component MaterialForm is not the newest redux state but the previous state,my child component render the wrong data.I tried my solution on stackoverflow but can't get my except result,I am really confused,Who can help my out?
import React, { useCallback, useEffect, useState } from 'react';
import FormModal from '../../../../components/FormModal/FormModal';
import { FormType, ColumnExt } from '../../../../types';
import {
MaterialValues,
initialMaterialValues,
} from '../MaterialValues/MaterialValues';
import MaterialForm from './MaterialForm';
import { cvtNullToEmpty } from '../../../../helpers/cvtNullToEmpty';
import { useDispatch, useSelector } from 'react-redux';
import { selectColumnExtDataSelector } from '../../../../redux/columnext/columnext.selector';
import {
materialExtValues,
ExtValues,
EXT_KEYS,
} from '../MaterialValues/MaterialValues';
import { fetchColumnextsRequest } from '../../../../redux/columnext/columnext.action';
interface MaterialEditProps {
editItem: string;
initialValues: MaterialValues;
handleClose: () => void;
}
const MaterialEdit: React.FC<MaterialEditProps> = ({
editItem,
initialValues,
handleClose,
}) => {
const dispatch = useDispatch();
const columnexts: ColumnExt[] = useSelector(selectColumnExtDataSelector);
const [extValues, setExtValues] = useState<ExtValues>(materialExtValues);
//get newest extValues
const initExtValues = useCallback(() => {
const colextFormData = new FormData();
colextFormData.append('TableName', 'material');
colextFormData.append('ObjectId', editItem);
dispatch(fetchColumnextsRequest(colextFormData));
}, [editItem, dispatch]);
//combine newest extValues with old extValues
const mergeMaterialExtValues = useCallback(() => {
const materialExtMerge: ExtValues = {};
columnexts.forEach((item) => {
EXT_KEYS.forEach((key) => {
if (item[key] !== '') {
materialExtMerge[`${item.ColumnName}__${key}`] = item[key];
}
});
});
console.log('materialExtMerge', materialExtMerge);
const newExts = Object.assign(materialExtValues, materialExtMerge);
setExtValues((prev) => ({ ...prev, ...newExts }));
console.log('materialExtValues', materialExtValues);
}, [columnexts]);
useEffect(() => {
initExtValues();
}, [initExtValues, columnexts]);
useEffect(() => {
if (columnexts.length > 0 && columnexts[0].ObjectId === editItem) {
mergeMaterialExtValues();
}
}, [mergeMaterialExtValues, editItem, columnexts.length]);
return (
<>
<div className='material-edit'>
<FormModal
title='Edit Material'
iconSrc='/assets/images/icons/material.png'
handleClose={handleClose}
renderDataForm={() => (
<MaterialForm
formType={FormType.EDIT}
editItem={editItem}
materialExtValues={extValues}
initialValues={
(cvtNullToEmpty(initialValues) as MaterialValues) ||
initialMaterialValues
}
handleClose={handleClose}
/>
)}
/>
</div>
</>
);
};
export default MaterialEdit;
The code of selectColumnExtDataSelector is :
import { RootState } from "../rootReducer";
import { createSelector } from "reselect";
export const selectColumnExts = (state: RootState) =>
state.columnext
export const selectColumnExtDataSelector = createSelector(
[selectColumnExts],
columnexts => columnexts.data
)
And ColumnExtReducer code is:
import { ColumnExt } from "src/types"
import { AnyAction } from 'redux';
import { columnextActionType } from "./columnext.types";
export interface ColumnExtState {
data: ColumnExt[],
loading: boolean;
error: string | null;
}
const initialState: ColumnExtState = {
data: [],
loading: false,
error: null
}
const columnextReducer = (state: ColumnExtState = initialState,
action: AnyAction
) => {
switch (action.type) {
case columnextActionType.FETCH_COLUMNEXTS_REQUEST:
return { ...state, loading: true }
case columnextActionType.FETCH_COLUMNEXTS_SUCCESS:
return { ...state, loading: false, data: action.payload }
case columnextActionType.FETCH_COLUMNEXTS_FAILURE:
return { ...state, loading: true, error: action.payload }
default:
return state;
}
}
export default columnextReducer;
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.
I'm working on a react project. I have my own API to fetch information. I'm using the useEffect hook to fetch profile information from API. My problem is when page mounts for the first time i can fetch the data with no problem but if i refresh the page it doesn't work at all. I know i have to give a second parameter to useEffect. I tried to put profile as the second argument even dispatched the getCurrentProfile function but when i do that it constantly fires off fetch request. I would be glad if anyone can help me with that. Thanks.
Here is my Profile component:
export const Profile = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getCurrentProfile());
}, [])
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
const { user } = authReducer;
console.log("loading", loading)
console.log("profile", profile)
return loading && profile === null ? (
<div >
<Spinner />
</div>
) :
Here is my Profile action:
export const getCurrentProfile = () => async dispatch => {
try {
const res = await axios.get("/api/profile/me");
console.log(res);
dispatch({
type: "GET_PROFILE",
payload: res.data.data
})
} catch (err) {
dispatch({
type: "PROFILE_ERROR",
payload: { msg: err.response.statusText, status: err.response.status }
})
}
}
Here is my profile reducer:
export default (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case "GET_PROFILE":
return {
...state,
profile: payload,
loading: false
}
case "PROFILE_ERROR":
return {
...state,
error: payload,
profile: null
}
case "CLEAR_PROFILE":
return {
...state,
profile: null,
loading: false
}
default:
return state;
}
}
You might want to try adding conditional logic within the useEffect so you only trigger the dispatch if you don't already have a profile.
import "./styles.css";
import { useDispatch, useSelector } from "react-redux";
import { useEffect, useCallback } from "react";
import { getCurrentProfile } from "./action";
export const Profile = () => {
const dispatch = useDispatch();
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
// read more about this here: https://stackoverflow.com/questions/58624200/react-hook-useeffect-has-a-missing-dependency-dispatch
const stableDispatch = useCallback(dispatch, []);
useEffect(() => {
if (!profile) {
stableDispatch(getCurrentProfile());
}
}, [profile, stableDispatch]);
const { user } = authReducer;
console.log("loading", loading);
console.log("profile", profile);
return loading && profile === null ? <div>Spinner</div> : "Actual Profile";
};
export default Profile;
Also, it doesn't seem like you're currently doing anything with the loading piece of state–at least from what you've shared here. You might want to dispatch an action indicating that you're loading before you start the fetch and then it will be set to false when you get the response.
Check out this codesandbox for reference: https://codesandbox.io/s/focused-kilby-gd2nr?file=/src/App.js
Reducers:
const initialState = {
profile: null,
loading: false
};
export const profile = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case "LOADING_PROFILE":
return {
...state,
loading: true
};
case "GET_PROFILE":
return {
...state,
profile: payload,
loading: false
};
case "PROFILE_ERROR":
return {
...state,
error: payload,
profile: null
};
case "CLEAR_PROFILE":
return {
...state,
profile: null,
loading: false
};
default:
return state;
}
};
export const auth = (state = {}, action) => {
return state;
};
Action Creator:
import axios from "axios";
export const getCurrentProfile = () => async (dispatch) => {
try {
dispatch({ type: "LOADING_PROFILE" });
const res = await axios.get("https://jsonplaceholder.typicode.com/users/1");
console.log(res);
dispatch({
type: "GET_PROFILE",
payload: res.data.data
});
} catch (err) {
dispatch({
type: "PROFILE_ERROR",
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { profile, auth } from "./reducers";
import App from "./App";
import thunk from "redux-thunk";
const store = createStore(
combineReducers({
profile,
auth
}),
applyMiddleware(thunk)
);
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
);
Well i solved it by dispatching 'getCurrentProfile' not 'getCurrentProfile()' turns out using it like a function causes continuously firing off.
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
const dispatch = useDispatch();
useEffect(() => {
if (!profile) {
console.log("It worked")
dispatch(getCurrentProfile());
}
}, [dispatch(getCurrentProfile)])
I am passing value into the redux store through reducer. And I am displaying that value in the component.
But it says cannot read property name of undefined
Even whats weird is when I map the product, I can see product value in the console and when I don't map, I don't see the product value in the console. Please help me with this
Please find the code here
Component
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { listProductDetails } from "./actions/productActions";
const Playground = () => {
const dispatch = useDispatch();
const productDetails = useSelector((state) => state.productDetails);
const { product } = productDetails;
useEffect(() => {
dispatch(listProductDetails("pod2"));
}, [dispatch]);
return (
<div>
<h1>
<h1>{product[0].name}</h1>
</h1>
</div>
);
};
export default Playground;
Reducer
export const productDetailsReducers = (state = { product: [] }, action) => {
switch (action.type) {
case PRODUCT_DETAILS_REQUEST:
return { loading: true, ...state };
case PRODUCT_DETAILS_SUCCESS:
return {
loading: false,
product: action.payload,
};
case PRODUCT_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
Action
export const listProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST });
const { data } = await axios.get(`/api/products/${id}`);
console.log("this is the data");
console.log(data);
dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
Store
const reducer = combineReducers({
productDetails: productDetailsReducers,
});
It's always better to have a condition before accessing the data
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { listProductDetails } from "./actions/productActions";
const Playground = () => {
const dispatch = useDispatch();
const productDetails = useSelector((state) => state.productDetails);
const { product } = productDetails;
useEffect(() => {
dispatch(listProductDetails("pod2"));
}, [dispatch]);
return (
<div>
{
product &&
product.length ?
<h1>
<h1>{product[0].name || ""}</h1>
</h1>
: null
}
</div>
);
};
export default Playground;
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());
}
})