In a MERN stack app, I'm trying to fetch data from two Mongodb databases and store them in context state, but I can only ever get one or the other depending on the order they are in.
App.js. In this case, the second useEffect hook works but not the first.
import { useEffect } from 'react';
import { useClosetContext } from './hooks/useClosetContext';
function App() {
const { dispatch } = useClosetContext()
useEffect(() => {
const fetchCloset = async () => {
const response = await fetch('/api/closet')
const json = await response.json()
if (response.ok) {
dispatch({type:'SET_CLOSET', payload: json})
}
}
fetchCloset()
}, [dispatch])
useEffect(() => {
const fetchSavedLists = async () => {
const response = await fetch('/api/checklist')
const json = await response.json()
if (response.ok) {
dispatch({type:'SET_CHECKLISTS', payload: json})
}
}
fetchSavedLists()
}, [dispatch])
return (
<div className="App">...</div>
);
}
export default App;
I have tried putting both the fetchCloset() and fetchSavedLists() functions in the same useEffect hook, but I get the same results.
I also tried this, but didn't get anything:
useEffect(() => {
const fetchData = () => {
Promise.all([
fetch('/api/closet'),
fetch('/api/checklist')
]).then(([closet, checklist]) => {
dispatch({type:'SET_CLOSET', payload: closet})
dispatch({type:'SET_CHECKLISTS', payload: checklist})
}).catch(err => {
console.log(err)
})
}
fetchData()
}, [dispatch])
Here is my Context file:
import { createContext, useReducer } from "react";
export const ClosetContext = createContext()
export const closetReducer = (state, action) => {
switch (action.type) {
case 'SET_CLOSET':
return {
closet: action.payload
}
case 'CREATE_GEAR':
return {
closet: [action.payload, ...state.closet]
}
case 'SET_CHECKLISTS':
return {
checklists: action.payload
}
case 'CREATE_CHECKLIST':
return {
checklists: [action.payload, ...state.checklists]
}
default:
return state
}
}
export const ClosetContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(closetReducer, {
closet: null,
checklists: null
})
return (
<ClosetContext.Provider value={{...state, dispatch}}>
{ children }
</ClosetContext.Provider>
);
}
Context Hook:
import { useContext } from "react";
import { ClosetContext } from "../context/ClosetContext";
export const useClosetContext = () => {
const context = useContext(ClosetContext)
if (!context) {
throw Error('error')
}
return context
}
I don't think there is anything wrong with the backend because I can fetch the data separately. Is there any way I can fetch both databases and set them to Context state?
The problem is in your reducer:
case 'SET_CLOSET':
return {
closet: action.payload
}
case 'SET_CHECKLISTS':
return {
checklists: action.payload
}
You may want something like:
case 'SET_CLOSET':
return {
...state,
closet: action.payload
}
case 'SET_CHECKLISTS':
return {
...state,
checklists: action.payload
}
Otherwise SET_CLOSET will erase checklists and SET_CHECKLISTS will erase closet.
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.
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;
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
Using react-redux to get items from my database. My reducer is receiving action.type but not action.payload from action. As shown in the redux developer tool here: The response from my database api is working and I have already applied my redux-thunk into my store using applyMiddleware.
Home.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { getAvailableItems } from '../Redux/Actions/ItemAction'
componentDidMount() {
this.props.getAvailableItems()
this.props.fetchPosts()
}
render() {
console.log(this.props)
return (<div></div>)
}
const mapStateToProps = state => {
return {
item : state.item.items
}
}
const mapActionsToProps = {
getAvailableItems,
fetchPosts
}
export default connect(mapStateToProps, mapActionsToProps)(Home)
ItemAction.js
export const getAvailableItems = () => dispatch => {
console.log("running itemAction")
fetch('https://us-central1-firebaselink.cloudfunctions.net/api/items')
.then(
(res) => {
//console.log(res.json())
res.json()
})
.then(data => dispatch(
{
type : 'GET_ALL_AVAILABLE_ITEMS',
payload : data
//console.log(data)
}
))
.catch((err) => console.log(err));
}
itemReducer.jsx
const initState = {
items : []
}
const itemReducers = (state = initState, action) => {
//return state;
console.log(action.type)
switch(action.type){
case 'GET_ALL_AVAILABLE_ITEMS':
console.log("this is the payload : "+action.payload)
return{
...state,
items: action.payload
}
default:
return state;
}
}
export default itemReducers;
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).