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;
Related
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.
I am facing an issue in my code base so I have made a sample code to demonstrate the issue.
link for the codesandbox code
App.js
import React, { Component } from 'react';
import './App.css';
import { connect } from 'react-redux';
import { handleDataInit, handlePageChange, handleDataAdded } from './appDataAction';
import First from './First';
import Second from './Second';
import { reduxStore } from "./store";
class App extends Component {
handleChange = (pageNumber, pageTitle) => {
let data = {
val1: "val1",
val2: "val2",
val3: "val3"
}
this.props.handleDataAdded(data);
console.log("app Data", this.props.appData);
console.log('app data in redux store ', reduxStore.getState().appData);
this.props.handlePageChange({ pageNumber, pageTitle });
}
render() {
return (
<div>
<button onClick={() => this.handleChange(1, "first_page")}>1</button>
<button onClick={() => this.handleChange(2, "second_page")}>2</button>
{
this.props.appData.pageNumber === 1 ?
<First />
:
<Second />
}
</div>
);
}
}
const mapStateToProps = (state) => {
console.log('map state to props state value is ', state);
return ({
appData: state && state.appData
})
}
const mapDispatchToProps = (dispatch) => {
return ({
handleDataInit: (data) => dispatch(handleDataInit(data)),
handlePageChange: (newPage) => dispatch(handlePageChange(newPage)),
handleDataAdded: (data) => dispatch(handleDataAdded(data))
})
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
screenshot for the two console.log
browser console log:
appDataAction.js
export const handleDataInit = (data) => {
return ({
type: "data_init",
payload: data
});
}
export const handlePageChange = (newPage) => {
return ({
type: "page_change",
payload: newPage
});
}
export const handleDataAdded = (data) => {
return ({
type: "data_added",
payload: data
});
}
appDataReducer.js
const initialState = {
pageNumber: 1,
pageTitle: "first_page",
}
export const appDataReducer = (state = initialState, action) => {
switch (action.type) {
case "data_init":
if (Object.keys(state).length > 2) {
return state
}
else {
let newState = Object.assign({}, state, action.payload);
// console.log("new state in init ", newState);
return newState;
}
case "page_change":
// console.log('action.payload', action.payload);
let newState2 = {
...state,
pageNumber: action.payload.pageNumber,
pageTitle: action.payload.pageTitle
}
// console.log('new state is ', newState2);
return newState2;
case "data_added":
let newState3 = Object.assign({}, state, action.payload);
// console.log("new state in data added ", newState3);
return newState3;
default:
return state;
}
}
From react-redux documentation
The first argument to a mapStateToProps function is the entire Redux store state (the same value returned by a call to store.getState()).
can somebody explain why there is difference in the two console's.
I have debugged and found out that after return from reducer mapStateToProps is called and it gets the updated value of state
then why is this.props.appData is not up to date in the handleChange function.
I believe it could be something related to dirty state but if it is proper for getState() in the function it should be for this.props.appData too.
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 trying to figure out why Redux-Devtools does not show state change because it shows that action={
type: 'GET_LOCATION_SUCCESS',
payload: {}
}, while debugger and console.log() shows that action.payload is n.
I am using
redux-observable
rxjs
typesafe-actions
This is my action creator:
import { createAsyncAction } from 'typesafe-actions';
import { GET_LOCATION_BEGIN, GET_LOCATION_FAILURE, GET_LOCATION_SUCCESS } from './constants';
export const getMyLocation = createAsyncAction(
GET_LOCATION_BEGIN,
GET_LOCATION_SUCCESS,
GET_LOCATION_FAILURE
)<undefined, Position, string>();
This is my reducer:
import { RootAction } from 'MyTypes';
import { combineReducers } from 'redux';
import { getType } from 'typesafe-actions';
import {getMyLocation} from '../actions/locationActions';
export type locationState = {
position: Position;
error: boolean;
};
export const locationReducer = combineReducers<locationState, RootAction>({
position: (state = {} as Position, action) => {
switch(action.type){
case getType(getMyLocation.success):
return action.payload;
default:
return state;
}
},
error: (state = false, action) => {
switch(action.type){
case getType(getMyLocation.failure):
return true;
default:
return state;
}
}
})
And this is my epic: [EDIT v.1]
export const getMyLocationEpic: Epic<RootAction, RootAction, RootState, Services> = (action$, state$, {geoLocation}) =>
action$.pipe(
filter(isActionOf(getMyLocation.request)),
switchMap( () => geoLocation.getGeoLocation(options).pipe(
take(1),
mergeMap( (data : Position) => of(
// Dispatch action with my location data and then dispatch action to request weather from API. It runs fetchWeatherEpic
getMyLocation.success(data),
fetchWeather.request(data)
)
),
catchError(err => of(getMyLocation.failure(err)))
)
)
);
I am wondering what might be the reason of not showing state update, it works as it should in console.log() but it doesn't work in devtools.
I am fairly new to redux, and I am running into a problem.
I am trying to implement flash messages to my login page, but redux's dispatch is not changing the UI State.
I want a flash message to appear on the login page after user successfully register.
//login.js
class Login extends Component{
renderMessage() {
if (this.props.flashMessageType== "registrationComplete"){
return (
<Message
style={{textAlign: "left"}}
success
icon="check circle"
header="Account Registration was Successful"
list={["You must verify your email before logging in"]}
/>
);
} else {
return (null);
}
}
render() {
return ({
this.renderMessage()
});
}
}
function mapStateToProps(state) {
return {
flashMessageType:state.flashMessage.flashType,
};
}
export default connect(mapStateToProps, actions)(Login);
Here is the reducer
const initialState = {
flashType: "",
};
export default function(state = {initialState}, action){
switch(action.type){
case USER_REGISTER:
return [
...state,
{
flashType:"registrationComplete"
}
];
default:
return initialState;
}
}
and here is the actions
export const submitForm = (values,history) => async dispatch => {
const res = await axios.post('/api/signup', values);
history.push('/');
dispatch({type: FETCH_USER, payload: res.data});
dispatch({type: USER_REGISTER});
};
I appreciate your help.
Thanks,
Vincent
As Amr Aly mentioned (and now soroush), you're essentially mutating the state when you do:
return[ ...state, { flashType:"registrationComplete" }]
What you really want is:
return { ...state, flashMessage: "registrationComplete" }
Also, some of your code is a bit redundant and/or missing some important instructions (like try/catch blocks).
What your code should look like:
FlashMessage.js
import React, { PureComponent } from 'react';
import Message from '../some/other/directory';
import actions from '../some/oter/directory':
class Login extends PureComponent {
render = () => (
this.props.flashMessage == "registrationComplete"
? <Message
style={{textAlign: "left"}}
success
icon="check circle"
header="Account Registration was Successful"
list={["You must verify your email before logging in"]}
/>
: null
)
}
export default connect(state => ({ flashMessage: state.auth.flashMessage }), actions)(Login)
reducers.js
import { routerReducer as routing } from 'react-router-redux';
import { combineReducers } from 'redux';
import { FETCH_USER, USER_REGISTER } from '../actions/types';
const authReducer = (state={}, ({ type, payload }) => {
switch(type){
case FETCH_USER: return { ...state, loggedinUser: payload };
case USER_REGISTER: return { ...state, flashMessage: "registrationComplete" }
default: return state;
}
}
export default = combineReducers({
auth: authReducer,
routing
});
actions.js
import { FETCH_USER, USER_REGISTER } from './types';
export const submitForm = (values,history) => async dispatch => {
try {
const {data} = await axios.post('/api/signup',values);
dispatch({ type:FETCH_USER, payload: data });
dispatch({ type:USER_REGISTER });
history.push('/');
catch (err) {
console.error("Error: ", err.toString());
}
};
Your reducer should be:
const initialState = {
flashType: "",
};
export default function(state = initialState, action){
switch(action.type){
case USER_REGISTER:
return {
...state,
flashType: "registrationComplete",
};
default:
return state;
}
}