React Redux Component rendering before state is ready - javascript

I have a react component that fetches from API with createAsyncThunk and cannot understand a behaviour that happens on Mount in this part:
if(isLoading===true) return <div>Loading...</div>
if(failedToLoad===true) return <div>Error loading feed</div>
if(feedResponse) return(
<div className={styles.feed}>
{feedResponse.map(({id}) => {
return(
<Link to={`/thread=${id}`} key={id}>
<Thread key={id} id={id}/>
</Link>
)
})}
</div>
)
If I remove if(feedResponse) next to the return(...) the component will crash because it will try to render before feedResponse status has data. Why isn't that covered by the first two IFs?
if(isLoading===true) return <div>Loading...</div>
if(failedToLoad===true) return <div>Error loading feed</div>
It is my understanding that there is no scenario where we have an inLoading = false & feedResponse = null
Here is the complete code if needed:
Feed.js
export const Feed = () => {
const dispatch = useDispatch()
const feedResponse = useSelector(selectFeedResponse)
const isLoading = useSelector(isLoadingFeed)
const failedToLoad = useSelector(failedToLoadFeed)
const location = useLocation()
useUpdateEffect(() => {
dispatch(SearchThunk(searchTerm))
}, searchTerm)
useUpdateEffect(() => {
dispatch(homeThunk(location.pathname+'.json'))
}, location)
if(isLoading===true) return <div>Loading...</div>
if(failedToLoad===true) return <div>Error loading feed</div>
if(feedResponse) return(
<div className={styles.feed}>
{feedResponse.map(({id}) => {
return(
<Link to={`/thread=${id}`} key={id}>
<Thread key={id} id={id}/>
</Link>
)
})}
</div>
)
}
feedSlice.js
export const homeThunk = createAsyncThunk(
'feed/homeThunk',
async (homePath) => {
const response = await fetch(`https://www.reddit.com${homePath}`)
const json = await response.json()
const threads = json.data.children.map(thread => {
return {
id: thread.data.id,
subreddit: thread.data.subreddit,
title: thread.data.title,
author: thread.data.author,
thumbnail: thread.data.thumbnail,
created: thread.data.created,
score: thread.data.score,
num_comments: thread.data.num_comments
}
})
return threads
}
)
export const feedSlice = createSlice({
name: 'feed',
initialState: {
feedResponse: '',
isLoadingFeed: false,
failedToLoadFeed: false
},
extraReducers: (builder) => {
builder
.addCase(homeThunk.pending, (state) => {
state.isLoadingFeed = true
state.failedToLoadFeed = false
})
.addCase(homeThunk.fulfilled, (state, action) => {
state.isLoadingFeed = false
state.failedToLoadFeed = false
state.feedResponse = action.payload
})
.addCase(homeThunk.rejected, (state) => {
state.isLoadingFeed = false
state.failedToLoadFeed = true
})
}
})
export const selectFeedResponse = state => state.feed.feedResponse
export const isLoadingFeed = state => state.feed.isLoadingFeed
export const failedToLoadFeed = state => state.feed.failedToLoadFeed
export default feedSlice.reducer
feedUtilities.js
import { useEffect, useRef } from "react";
export const useUpdateEffect = (effect, deps = []) => {
const isFirstMount = useRef(true);
useEffect(() => {
if(!isFirstMount.current) effect()
else isFirstMount.current = false
}, [deps]);
}

Related

asynchrone api calls with redux

I have a react application and I try to implement redux within this application.
So my files look like this:
reducer.js
import {
CHANGE_SEARCH_FIELD,
REQUEST_ROBOTS_FAILED,
REQUEST_ROBOTS_PENDING,
REQUEST_ROBOTS_SUCCESS
} from './constants';
const initialState = {
searchField: ''
}
export const searchRobots = (state = initialState, action = {}) => {
console.log(action.type);
switch (action.type) {
case CHANGE_SEARCH_FIELD:
return Object.assign({}, state, { searchField: action.payload });
default:
return state;
}
}
const initialStateRobots = {
robots: [],
isPending: true
}
export const requestRobots = (state = initialStateRobots, action = {}) => {
switch (action.type) {
case REQUEST_ROBOTS_PENDING:
return Object.assign({}, state, { isPending: true })
case REQUEST_ROBOTS_SUCCESS:
return Object.assign({}, state, { robots: action.payload, isPending: false })
case REQUEST_ROBOTS_FAILED:
return Object.assign({}, state, { error: action.payload })
default:
return state
}
}
actions.js
import {
CHANGE_SEARCH_FIELD,
REQUEST_ROBOTS_FAILED,
REQUEST_ROBOTS_PENDING,
REQUEST_ROBOTS_SUCCESS
} from './constants'
export const setSearchField = (text) => ({
type: CHANGE_SEARCH_FIELD,
payload: text
});
export const requestRobots = () => (dispatch) => {
dispatch({ type: REQUEST_ROBOTS_PENDING })
fetch('https://jsonplaceholder.typicode.com/users')
.then(data => dispatch({ type: REQUEST_ROBOTS_SUCCESS, payload: data }))
.catch(error => dispatch({ type: REQUEST_ROBOTS_FAILED, payload: error }))
}
index.js:
const logger = createLogger();
const rootReducer = combineReducers({searchRobots, requestRobots});
const store = createStore(rootReducer, applyMiddleware( thunkmiddleware,logger));
ReactDOM.render(<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
registerServiceWorker();
and app.js:
const mapStateToProps = state => {
return {
searchField: state.searchField,
robots: state.requestRobots.robots,
isPending: state.requestRobots.isPending
}
};
const mapDispatchToProps = (dispatch) => {
return {
onSearchChange: (event) => dispatch(setSearchField(event.target.value)),
onRequestRobots: () => dispatch(requestRobots())
}
}
function App(props) {
useEffect(() => {
props.onRequestRobots();
console.log(props);
}, []);
const filteredRobots = robots.filter(robot => {
return robot.name.toLowerCase().includes(searchField.toLowerCase());
});
return !robots.length ?
<h1>Loading</h1> :
(
<div className='tc'>
<h1 className='f1'>RoboFriends</h1>
<SearchBox searchChange={props.onSearchChange} />
<Scroll>
<CardList robots={filteredRobots} />
</Scroll>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
But I get this errror:
ReferenceError: robots is not defined
App
E:/Mijn Documents/UDEMY/REACT/robofriends-master/src/containers/App.js:40
37 |
38 |
39 | //const { robots, searchField, onSearchChange, isPending } = props;
> 40 | const filteredRobots = robots.filter(robot => {
| ^ 41 | return robot.name.toLowerCase().includes(searchField.toLowerCase());
42 | });
43 | return !robots.length ?
my question: what I have to change?
Thank you
You should get robots from props like this:
const filteredRobots = props.robots.filter(robot => {
return robot.name.toLowerCase().includes(searchField.toLowerCase());
});

Store properties showing on mapStateToProps method but store on redux-dev-tools is empty

When I console on line 21 of Bugs.js, its sowing ,me the result of action that i dispathed, but when I am printing the data on in li tag..it's blank. Also the store is not chnaged in redux-dev-tools
That's my App.js code
function App() {
const store = configureStore()
return (
<Provider store={store}>
<Bugs />
</Provider>
);
}
export default App;
That's Bugs.js
const Bugs = (props) => {
useEffect(props.addBug, [])
return (
<ul>
{
props.bugs.map(bug => {
<li>
{bug.description}
</li>
})
}
</ul>
)
}
const mapStateToProps = state => {
console.log('Aftab', state.bugs);
return (
{
bugs: state.bugs
})
}
const mapDispatchToProps = dispatch => {
return (
{
addBug: () => dispatch(addBug({ description: 'Bug 1' }))
}
)
}
export default connect(mapStateToProps, mapDispatchToProps)(Bugs)
And thats reducer
let lastId = 0
const slice = createSlice({
name: 'bugs',
initialState: [],
reducers: {
addBug: (bugs, action) => {
bugs.push({
id: lastId++,
description: action.payload.description,
resolved: false
})
},
resolveBug: (bugs, action) => {
const index = bugs.findIndex(b => b.id === action.payload.id)
bugs[index].resolved = true
},
assignBug: (bugs, action) => {
const index = bugs.findIndex(b => b.id === action.payload.bugId)
bugs[index].userId = action.payload.userId
}
}
})
export const { addBug, resolveBug, assignBug } = slice.actions
export default slice.reducer
export const getBugList = userId => createSelector(
state => state.entities.bugs,
bugs => bugs.filter(b => b.userId === userId)
)
You will need to invoke the function for dispatch to work
In your Bugs.js change
useEffect(props.addBug, [])
To:
useEffect(props.addBug(), [])

setState not resetting state / switching between modals

I asked similar question earlier, but didn't get much back. I have two modals for user auth: join and login. Each modal has a link to the other one. Displayed login errors persist when you click on the "sign up" and switch to the join modal and vise versa. I tried to set the state.errors to empty array, but the errors still persist. I changed handleSwitch to callback. The errors array still has length. I tried using switched as part of the state, resetting it to true in handleSwitch and ternary, no result either. Can anybody suggest an alternative solution.
import React from 'react';
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
errors: [],
switched: false
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleSwitch = this.handleSwitch.bind(this);
this.mapErrors = this.mapErrors.bind(this);
this.handleErrors = this.handleErrors.bind(this);
}
componentDidMount() {
this.setState({ errors: this.props.errors})
}
componentDidUpdate(prev) {
if (prev.errors.length !== this.props.errors.length) {
this.setState( {errors: this.props.errors} )
}
}
handleInput(type) {
return (err) => {
this.setState({ [type]: err.currentTarget.value })
};
}
handleSubmit(event) {
event.preventDefault();
const user = Object.assign({}, this.state);
this.props.processForm(user)
// .then(() => this.props.history.push('/users')); //change to /videos later
}
handleSwitch() {
// debugger
this.setState({ errors: [] }, function () {
this.props.openModal('signup')
});
// debugger
}
mapErrors() {
if (this.state.errors.length) {
return this.state.errors.map((error, i) => {
return <p key={i}>{error}</p>
})
}
}
handleErrors() {
debugger
if (!this.state.switched) {
return <div className="errors">{this.mapErrors}</div>
} else {
return null;
}
};
render() {
console.log(this.state.errors)
return (
<div className="login-form">
<div>
<h2 className="login-header">Log in to Foxeo</h2>
</div>
<form>
<input className="login-email"
type="text"
value={this.state.email}
placeholder="Email address"
onChange={this.handleInput('email')}
/>
<input className="login-password"
type="password"
value={this.state.password}
placeholder="Password"
onChange={this.handleInput('password')}
/>
<div className="errors">{this.mapErrors()}</div>
{/* { this.state.switched ?
<div className="errors">{this.handleErrors()}</div> :
<div className="errors">{this.mapErrors()}</div>
} */}
<button className="login-button" onClick={this.handleSubmit}>Log in with email</button>
<div className="login-footer">Don't have an account?
{/* <button className="login-form-btn" onClick={() => this.props.openModal('signup')}>Join</button> */}
<button className="login-form-btn" onClick={ this.handleSwitch}> Join</button>
</div>
</form>
</div>
);
}
};
export default Login;
I suggest getting the new errors from the props instead of from state:
mapErrors() {
if (this.props.errors.length) {
return this.props.errors.map((error, i) => {
return <p key={i}>{error}</p>
})
Dispatching resetErrors action solved the issue. The handleSwitch method is quite simple:
handleSwitch() {
this.props.resetErrors()
this.props.openModal('signup')
}
session actions:
import * as apiUtil from '../util/session_api_util';
export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER';
export const LOGOUT_CURRENT_USER = 'LOGOUT_CURRENT_USER';
export const RECEIVE_ERRORS = 'RECEIVE_ERRORS';
export const CLEAR_ERRORS = 'CLEAR_ERRORS';
const receiveErrors = (errors) => ({
type: RECEIVE_ERRORS,
errors
})
const clearErrors = () => ({
type: CLEAR_ERRORS,
errors: []
})
const receiveCurrentUser = (user) => ({
type: RECEIVE_CURRENT_USER,
user
});
const logoutCurrentUser = () => ({
type: LOGOUT_CURRENT_USER
});
export const signup = user => dispatch => (
apiUtil.signup(user).then(user => (
dispatch(receiveCurrentUser(user))
), err => (
dispatch(receiveErrors(err.responseJSON))
))
);
export const login = user => dispatch => {
return apiUtil.login(user).then(user => {
dispatch(receiveCurrentUser(user))
}, err => (
dispatch(receiveErrors(err.responseJSON))
))
};
export const logout = () => dispatch => apiUtil.logout()
.then(() => dispatch(logoutCurrentUser()));
export const resetErrors = () => dispatch(clearErrors());
session errors reducer:
import { RECEIVE_ERRORS, RECEIVE_CURRENT_USER, CLEAR_ERRORS } from '../actions/session_actions';
const sessionErrorsReducer = (state = [], action) => {
Object.freeze(state);
switch (action.type) {
case RECEIVE_ERRORS:
return action.errors;
case CLEAR_ERRORS:
return [];
case RECEIVE_CURRENT_USER:
return [];
default:
return state;
}
};
export default sessionErrorsReducer;

React-Redux - State object update, re-render the components but the change isn't pass to the child component

Im working on a calendar app using React and Redux.
The calendar app fetch dates and their events from google api, so users could see their google calendars.
In the debugger i can see that the data is ok all the way from the dispatch to the render function. the only problem is that the component inside (MonthlyCalendar) that gets the events getting an empty object.
when not using redux and instead just react state, the component do get the new props with the updated events object.
I added console.log to each life cycle of the component.
When using redux i get this:
using regular state (not redux):
i don't know what I'm missing with redux flow but when debugging it's shown that render is executed with the updated state events,
still the console.log print empty.
The code from dispatch to render :
Dispatch
useEffect(() => {
window.addEventListener(
'message',
e => {
if (e.data && e.data.data) {
dispatch(connectBtnClicked(e.data.data));
}
},
false,
);
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
}, [])
Action
export const connectBtnClicked = (userName: string) => (dispatch: any) => {
const urlArray = [
'https://calendar-server.codev.co.il/getEvents',
'https://calendar-server.codev.co.il/getCalendarsListIds',
'https://calendar-server.codev.co.il/getSettings',
];
const requestsArray = urlArray.map(url => {
const request = new Request(url, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
method: 'GET',
});
return fetch(request).then(res => res.json());
});
Promise.all(requestsArray).then(allResults => {
console.log('***allResults', allResults);
dispatch({
type: CONNECT_BTN_CLICKED,
payload: {
isConnect: true,
userName,
dates: allResults[0].eventsByDates,
calendarsList: allResults[1].calendarsList,
},
});
});
};
Reducer
import {
CONNECT_BTN_CLICKED,
SET_COMP_ID,
DISCONNECT_BTN_CLICKED,
CONNECT_ERROR,
} from '../actions/actionType';
const initState = {
isConnect: false,
isDisconnect: false,
isSettingsLoaded: false,
userName: '',
isLoader: false,
connectError: false,
statusCode: '',
dates: {},
calendarsList: [],
};
export default (state = initState, action: any) => {
console.log('setReducer', action);
switch (action.type) {
case SET_COMP_ID:
return {
...state,
compId: action.payload,
};
case CONNECT_ERROR:
return {
...state,
connectEtsyError: action.payload.err,
statusCode: action.payload.statusCode,
};
case CONNECT_BTN_CLICKED:
return {
...state,
isConnect: action.payload.isConnect,
userName: action.payload.userName,
dates: action.payload.dates,
calendarsList: action.payload.calendarsList,
isSettingsLoaded: true,
};
case DISCONNECT_BTN_CLICKED:
return {
...state,
isConnect: false,
// isDisconnect: action.payload,
dates: {},
calendarsList: [],
userName: '',
};
default:
return state;
}
};
Root Reducer
import { combineReducers } from 'redux';
//#ts-ignore
import settings from './settingsReducer';
const rootReducer = combineReducers({
settings,
});
export default rootReducer;
Store
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from '../reducers/rootReducer';
import thunk from 'redux-thunk';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Provider Wrapping App.js Component
import { BrowserRouter as Router } from 'react-router-dom';
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
import { I18nextProvider } from 'react-i18next';
import App from './components/App/App';
// import App from './components/App';
import i18n from './i18n';
import { Provider } from 'react-redux';
import store from './components/settings/store';
const locale = window.__LOCALE__;
const baseURL = window.__BASEURL__;
fedopsLogger.appLoaded();
ReactDOM.render(
<Provider store={store}>
<React.Suspense fallback={<div>Please wait...</div>}>
<Router>
{/* <ExperimentsProvider options={{ experiments }}> */}
<App />
{/* </ExperimentsProvider> */}
</Router>
</React.Suspense>
</Provider>,
document.getElementById('root'),
);
App.js render - MonthlyCalendar is the events receiving component
render() {
const { t } = this.props;
const events = this.props.events;
return (
<Switch>
<Route
path="/index"
render={() => (
<MonthlyCalendar
weekStarter={this.state.weekStarter}
events={events}
handleMonthChange={handleMonthChange}
isTimeZoneShown={this.state.isTimeZoneShown}
isTimeShown={this.state.isTimeShown}
locale={this.state.locale}
//timeZone={this.state.timeZone}
isTodayButtonStyleSeconday={this.state.isTodayButtonStyleSeconday}
/>
)}
></Route>
<Route
path="/settings"
render={() => (
<Settings
fetchEvents={this.fetchEvents}
initialState={"this.props.initialState"}
/>
)}
></Route>
<Route
path="/mobile"
render={() => (
<MonthlyCalendar
weekStarter={this.state.weekStarter}
events={events}
handleMonthChange={handleMonthChange}
isTimeZoneShown={this.state.isTimeZoneShown}
isTimeShown={this.state.isTimeShown}
locale={this.state.locale}
isTodayButtonStyleSeconday={this.state.isTodayButtonStyleSeconday}
/>
)}
></Route>
</Switch>
);
}
}
const mapDispatchToProps = (dispatch: any) => ({});
const mapStateToProps = (state: any) => ({
isConnect: state.settings.isConnect,
userName: state.settings.userName,
events: state.settings.dates,
calendarsList: state.settings.calendarsList,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(withTranslation()(withEnhancedStyleLoader(App)));
There is not setState in componentDidMount or any other method in App.js I removed it all, so only props change could re-render
Edit 1
i dropped all the HOCS and still in render i see the updated events, but the child component print empty events.
Render on events received
EDIT 2
the child component only render once. even when events props is updated, and re-render MonthlyCalendar( the child component ) doesn't re-render. I tried:
const events = {...this.props.events};
also:
const events = JSON.parse(JSON.stringfy(this.props.events);
didn't work...
EDIT 3 - MonthlyCalendar Component
constructor(props: any) {
super(props);
console.log('[constructor] props.events: ',props.events)
const timezone = moment.tz.guess();
const dateObject = moment().tz(timezone, true);
this.state = {
dateObject,
timezone,
isTimezonesOpen: false,
};
}
shouldComponentUpdate(nextProps, nextState) {
console.log('[shouldComponentUpdate] props.events: ',this.props.events)
return true
}
....
getCalendar() {
const { events } = this.props;
const { dateObject } = this.state;
const beforeFillers = this.getMonthBeforFillers(dateObject, events);
const days = this.getDays(dateObject, events);
const afterFillers = this.hasAfterFillers(beforeFillers, days) ?
this.getAfterMonthFillers(dateObject, events) : {};
return { days, beforeFillers, afterFillers };
}
async componentDidUpdate(prevProps) {
console.log('[componentDidUpdate] props.events: ',this.props.events)
this.props.locale !== prevProps.locale && await this.updateLocale();
}
updateLocale = async () => {
const { locale, i18n } = this.props;
await i18n.changeLanguage(locale);
moment.locale(locale);
const { timezone, dateObject } = this.state;
const dateObjectToSet = moment(dateObject.format()).tz(timezone, true);
this.setState({ dateObject: dateObjectToSet });
}
async componentDidMount() {
console.log('[componentDidMount] props.events: ',this.props.events)
this.props.locale !== 'en' && await this.updateLocale();
}
render() {
const { t, weekStarter, isTodayButtonStyleSeconday, isTimeZoneShown, isTimeShown } = this.props;
const { dateObject, timezone, isTimezonesOpen } = this.state;
const { days, beforeFillers, afterFillers } = this.getCalendar();
const month = dateObject.format(t('Google_Calendar_Picker_Month'));
const timezoneSelected = moment().tz(timezone).format(t('Google_Calendar_Timezone_Selected'));
const timezoneSelectedTitle = t('Google_Calendar_Timezone_Selected_Title', { timezoneSelected });
console.log('[render] props.events: ',this.props.events)
return (
<TPAComponentsProvider value={{ mobile: false, rtl: false }}>
<div className={classes.MonthlyCalendar}>
<CalendarControllers
isTodayButtonStyleSeconday={isTodayButtonStyleSeconday}
todayClicked={this.todayClickedHander}
onPreviousClicked={() => this.timePickerClickedHandler(false)}
timeToDisplay={month}
onNextClicked={() => this.timePickerClickedHandler(true)}
onTimezoneChange={this.timezoneChangeHandler}
timezone={timezoneSelectedTitle}
isTimezonesOpen={isTimezonesOpen}
openTimezones={this.openTimezones}
closeTimezones={this.closeTimezones}
isTimeZoneShown={isTimeZoneShown}
/>
<MonthTable
weekStarter={weekStarter}
days={days}
beforeFillers={beforeFillers}
dateObject={dateObject}
afterFillers={afterFillers}
renderCell={(
time: any,
events: any,
cellRef: any,
handleEventClick: any,
setExpendedEvent: any,
expendedEvent: any,
isOutsideClicked: any,
) => (
<MonthlyCell
events={events}
handleEventClick={handleEventClick}
time={time}
cellRef={cellRef}
expendedEvent={expendedEvent}
isOutsideClicked={isOutsideClicked}
setExpendedEvent={setExpendedEvent}
isTimeShown={isTimeShown}
/>
)}
/>
</div>
</TPAComponentsProvider>
);
}
}
export default withTranslation()(MonthlyCalendar);
EDIT 4
after searching for solutions i added key to the div of MonthlyCalendar and also i added destructor {...this.props.events}. still no re-render
Following is the updated MonthlyCalendar:
getCalendar() {
const mutableEvents = {...this.props.events};
const { dateObject } = this.state;
const beforeFillers = this.getMonthBeforFillers(dateObject, mutableEvents);
const days = this.getDays(dateObject, mutableEvents);
const afterFillers = this.hasAfterFillers(beforeFillers, days) ?
this.getAfterMonthFillers(dateObject, mutableEvents) : {};
return { days, beforeFillers, afterFillers };
}
async componentDidUpdate(prevProps) {
console.log('[componentDidUpdate] props.events: ',this.props.events)
this.props.locale !== prevProps.locale && await this.updateLocale();
}
updateLocale = async () => {
const { locale, i18n } = this.props;
await i18n.changeLanguage(locale);
moment.locale(locale);
const { timezone, dateObject } = this.state;
const dateObjectToSet = moment(dateObject.format()).tz(timezone, true);
this.setState({ dateObject: dateObjectToSet });
}
async componentDidMount() {
console.log('[componentDidMount] props.events: ',this.props.events)
this.props.locale !== 'en' && await this.updateLocale();
}
render() {
const { t, weekStarter, isTodayButtonStyleSeconday, isTimeZoneShown, isTimeShown, events: propEvents } = this.props;
const eventsKey = Object.keys(propEvents).length;
const { dateObject, timezone, isTimezonesOpen } = this.state;
const { days, beforeFillers, afterFillers } = this.getCalendar();
const month = dateObject.format(t('Google_Calendar_Picker_Month'));
const timezoneSelected = moment().tz(timezone).format(t('Google_Calendar_Timezone_Selected'));
const timezoneSelectedTitle = t('Google_Calendar_Timezone_Selected_Title', { timezoneSelected });
console.log('[render] props.events: ',this.props.events)
return (
<TPAComponentsProvider value={{ mobile: false, rtl: false }}>
<div key={eventsKey} className={classes.MonthlyCalendar}>
<CalendarControllers
isTodayButtonStyleSeconday={isTodayButtonStyleSeconday}
todayClicked={this.todayClickedHander}
onPreviousClicked={() => this.timePickerClickedHandler(false)}
timeToDisplay={month}
onNextClicked={() => this.timePickerClickedHandler(true)}
onTimezoneChange={this.timezoneChangeHandler}
timezone={timezoneSelectedTitle}
isTimezonesOpen={isTimezonesOpen}
openTimezones={this.openTimezones}
closeTimezones={this.closeTimezones}
isTimeZoneShown={isTimeZoneShown}
/>
<MonthTable
weekStarter={weekStarter}
days={days}
beforeFillers={beforeFillers}
dateObject={dateObject}
afterFillers={afterFillers}
renderCell={(
time: any,
events: any,
cellRef: any,
handleEventClick: any,
setExpendedEvent: any,
expendedEvent: any,
isOutsideClicked: any,
) => (
<MonthlyCell
events={events}
handleEventClick={handleEventClick}
time={time}
cellRef={cellRef}
expendedEvent={expendedEvent}
isOutsideClicked={isOutsideClicked}
setExpendedEvent={setExpendedEvent}
isTimeShown={isTimeShown}
/>
)}
/>
</div>
</TPAComponentsProvider>
);
}
}
export default withTranslation()(MonthlyCalendar);

Unable to get state from mapstatetoprops method

I have code as shown below. My question is regarding react and redux usage. I am not able to get a state "categories" via mapstate method. It is showing undefined when I try to console categories via this.props.categories in my Product.js. I have attached product.js file which has reducer and another is my main component Product.js.
Here is my Product.js code:
import React from 'react'
import {connect} from 'react-redux'
import withStyles from 'isomorphic-style-loader/lib/withStyles'
import s from './Product.css'
import { Select, Button, Table, Tooltip, InputNumber, Card} from 'antd'
import ProductForm from './ProductForm'
import {withCurrency} from '../../components'
import debounce from 'lodash/debounce'
import {
changeSelectedProduct,
getProducts,
addProduct,
changeMarkupFactor,
updateProduct,
currencyClicked,
editRetailPrice,
retailPriceUpdated,
handleChange
} from '../../reducers/product'
const children = ["Home","Health","Beauty","Pregnancy","Kits","Animals","Travel","Cooking"]
class Product extends React.Component {
constructor(props) {
super(props)
this.getProducts = debounce(props.getProducts, 800);
}
componentWillMount() {
// TODO get initial list of popular products
this.props.getProducts()
}
handleChange = (values) => {
console.log(values)
let Values = children.filter( (eachone,index) =>{
var includes = values.includes(`${index}`)
if(includes){
return true
}
}
)
this.props.handleChange(Values)
}
render() {
const {
products,
loading,
selectedProduct,
onSubmit,
formRef,
markupFactor,
changeMarkupFactor,
changeSelectedProduct,
currencies,
totalPrice,
currency,
clearForms,
addRetailPrice,
currencyClicked,
editRetailPrice,
retailPriceUpdated,
categories
} = this.props;
console.log('entire state', this.props.state)
const columns = [
{
key: 'name',
dataIndex: 'name',
width: 130,
className: s.nameColumn,
},
{
key: 'price',
dataIndex: 'price',
className: s.priceColumn,
render: (price, row) => {
if (row.id === 'retail-price' && addRetailPrice) {
return (
<InputNumber
value={totalPrice.retail_price[currency.key]}
onChange={(value) => editRetailPrice(value, currency.key)}
defaultValue={price[currency.key]}
onBlur={() => retailPriceUpdated(currency.key)}
/>
);
}
return (
<Tooltip
placement='topRight'
title={
currencies.map(item =>
currency.key !== item.key ? (
<div key={item.key}>
{withCurrency(item, price[item.key])}
</div>
) : null
)
}
>
<div onClick={() => currencyClicked(row)}>
{withCurrency(currency, price[currency.key])}
</div>
</Tooltip>
)
}
}
]
let data = []
if (totalPrice) {
data = [
{id:'receipe', name: 'Recipe', price: totalPrice.recipe.total_price},
{id:'container', name: 'Container', price: totalPrice.container.total_price},
{id:'wholesale-price', name: 'Wholesale Price', price: totalPrice.wholesale_price},
{id:'retail-price', name: 'Retail Price', price: totalPrice.retail_price},
]
}
return (
<Card
className={s.container}
title={'Product'}
bodyStyle={{minHeight: 409}}
extra = {<a onClick={clearForms}>{'Clear'}</a>}
>
{/* <div className={s.container}> */}
{/* <div className={s.headerWrapper}>
<h3 className={s.header}>{'Product'}</h3> */}
{/*use clear here below */}
{/* <a onClick={clearForms}>{'Clear'}</a>
</div> */}
<div className={s.containerSearchWrapper}>
<Select
className = {s.search}
showSearch
allowClear
value={selectedProduct ? `${selectedProduct.id}` : undefined}
className={s.productSearch}
placeholder='Search'
notFoundContent={loading.products ? <Spin size='small'/> : null}
filterOption={false}
onSearch={(search) => this.getProducts({search})}
onChange={(id) => {
const newProduct = products.find(item => item.id === +id)
changeSelectedProduct(newProduct)
}}
>
{products.map(item =>
<Select.Option key={item.id}>{item.name}</Select.Option>
)}
</Select>
</div>
<ProductForm
markupFactor={markupFactor}
changeMarkupFactor={changeMarkupFactor}
ref={formRef}
children={children}
handleChange={this.handleChange}
/>
<Table
className={s.table}
columns={columns}
dataSource={data}
loading={loading.totalPrice}
size='small'
rowKey={(record, i) => i}
pagination={false}
showHeader={false}
locale={{emptyText: 'Total price'}}
/>
<div className={s.actions}>
<Button
onClick={() => onSubmit('update')}
loading={loading.updatingProduct}
disabled={!selectedProduct}
>
{'Save'}
</Button>
<Button
onClick={() => onSubmit('add')}
loading={loading.addingProduct}
>
{'Create new'}
</Button>
</div>
{/* </div> */}
</Card>
)
}
}
const mapState = state => ({
...state.product,
currency: state.global.currency,
currencies: state.global.currencies,
categories: state.global.categories,
})
const mapDispatch = {
getProducts,
addProduct,
updateProduct,
changeMarkupFactor,
changeSelectedProduct,
currencyClicked,
editRetailPrice,
retailPriceUpdated,
handleChange,
}
export default connect(mapState, mapDispatch)(withStyles(s)(Product))
Here is my product.js code
import createReducer, {RESET_STORE} from '../createReducer'
import {getToken} from './user'
import qs from 'query-string'
import _ from 'lodash';
import {message} from 'antd'
import messages from '../messages'
import {getFieldValue} from '../utils'
// ------------------------------------
// Constants
// ------------------------------------
export const GET_PRODUCT_REQUEST = 'Product.GET_PRODUCT_REQUEST'
export const GET_PRODUCT_SUCCESS = 'Product.GET_PRODUCT_SUCCESS'
export const GET_PRODUCT_FAILURE = 'Product.GET_PRODUCT_FAILURE'
export const ADD_PRODUCT_REQUEST = 'Product.ADD_PRODUCT_REQUEST'
export const ADD_PRODUCT_SUCCESS = 'Product.ADD_PRODUCT_SUCCESS'
export const ADD_PRODUCT_FAILURE = 'Product.ADD_PRODUCT_FAILURE'
export const UPDATE_PRODUCT_REQUEST = 'Product.UPDATE_PRODUCT_REQUEST'
export const UPDATE_PRODUCT_SUCCESS = 'Product.UPDATE_PRODUCT_SUCCESS'
export const UPDATE_PRODUCT_FAILURE = 'Product.UPDATE_PRODUCT_FAILURE'
export const GET_TOTAL_PRICE_REQUEST = 'Product.GET_TOTAL_PRICE_REQUEST'
export const GET_TOTAL_PRICE_SUCCESS = 'Product.GET_TOTAL_PRICE_SUCCESS'
export const GET_TOTAL_PRICE_FAILURE = 'Product.GET_TOTAL_PRICE_FAILURE'
export const CHANGE_SELECTED_PRODUCT = 'Product.CHANGE_SELECTED_PRODUCT'
export const CHANGE_MARKUP_FACTOR = 'Product.CHANGE_MARKUP_FACTOR'
export const CHANGE_RETAIL_PRICE = 'Product.CHANGE_RETAIL_PRICE';
export const EDITING_RETAIL_PRICE = 'Product.EDITING_RETAIL_PRICE';
export const RETAIL_PRICE_UPDATED = 'Product.RETAIL_PRICE_UPDATED';
export const CLEAR = 'Product.CLEAR'
export const ADD_CATEGORIES = 'Product.ADD_CATEGORIES'
// ------------------------------------
// Actions
// ------------------------------------
// ------------------------------------
// Actions
// ------------------------------------
export const changeSelectedProduct = (selectedProduct) => (dispatch, getState) => {
dispatch({type: CHANGE_SELECTED_PRODUCT, selectedProduct})
// dispatch(changeSelectedComponents(selectedProduct ? selectedProduct.components : []))
}
export const currencyClicked = (row) => (dispatch) => {
if (!row.id === 'retail-price') return;
dispatch({type: CHANGE_RETAIL_PRICE})
}
export const editRetailPrice = (value, currencyKey) => (dispatch, getState) => {
const { totalPrice } = getState().product;
totalPrice.retail_price[currencyKey] = value;
dispatch({type: EDITING_RETAIL_PRICE, totalPrice});
}
export const retailPriceUpdated = (currencyKey) => (dispatch, getState) => {
const { totalPrice } = getState().product;
const markupFactor = parseFloat(totalPrice.retail_price[currencyKey] / totalPrice.wholesale_price[currencyKey]).toFixed(2);
dispatch({type: RETAIL_PRICE_UPDATED, totalPrice});
dispatch({type: CHANGE_MARKUP_FACTOR, markupFactor});
}
export const getProducts = (params = {}) => (dispatch, getState, {fetch}) => {
dispatch({type: GET_PRODUCT_REQUEST, params})
const {token} = dispatch(getToken())
const {search, ordering} = getState().product
return fetch(`/pands/products/?${qs.stringify({
search,
ordering,
})}`, {
method: 'GET',
token,
success: (res) => dispatch({type: GET_PRODUCT_SUCCESS, res}),
failure: (err) => dispatch({type: GET_PRODUCT_FAILURE}),
})
}
export const addProduct = (values) => (dispatch, getState, {fetch}) => {
dispatch({type: ADD_PRODUCT_REQUEST})
const {token} = dispatch(getToken())
const {currencies} = getState().global
const {selectedRecipe} = getState().recipe
const {selectedContainer} = getState().container
return fetch(`/pands/products/`, {
method: 'POST',
body: {
...values,
recipe: selectedRecipe.id,
container: selectedContainer.id,
currencies: currencies.map(item => item.key),
},
token,
success: (selectedProduct) => {
dispatch({type: ADD_PRODUCT_SUCCESS, selectedProduct})
message.success(messages.addProductSuccess)
},
failure: (err) => {
dispatch({type: ADD_PRODUCT_FAILURE})
message.error(messages.addProductError)
},
})
}
export const updateProduct = (values) => (dispatch, getState, {fetch}) => {
dispatch({type: UPDATE_PRODUCT_REQUEST})
const {token} = dispatch(getToken())
const {currencies} = getState().global
const {selectedProduct} = getState().product
const {selectedRecipe} = getState().recipe
const {selectedContainer} = getState().container
return fetch(`/pands/products/${selectedProduct.id}/`, {
method: 'PATCH',
body: {
...values,
recipe: selectedRecipe.id,
container: selectedContainer.id,
currencies: currencies.map(item => item.key),
},
token,
success: (selectedProduct) => {
dispatch({type: UPDATE_PRODUCT_SUCCESS, selectedProduct})
message.success(messages.updateProductSuccess)
},
failure: (err) => {
dispatch({type: UPDATE_PRODUCT_FAILURE})
message.error(messages.updateProductError)
},
})
}
export const getTotalPrice = () => (dispatch, getState, {fetch}) => {
const {token} = dispatch(getToken())
dispatch({type: GET_TOTAL_PRICE_REQUEST})
const {currencies} = getState().global
const {markupFactor} = getState().product
const {fields: {ingredients}} = getState().rawMaterials
const {fields: {components}} = getState().components
return fetch(`/pands/products/calculate/`, {
method: 'POST',
token,
body: {
recipe: {
ingredients: getFieldValue(ingredients),
},
container: {
components: getFieldValue(components),
},
markup_factor: +markupFactor,
currencies: currencies.map(item => item.key),
},
success: (totalPrice) => dispatch({type: GET_TOTAL_PRICE_SUCCESS, totalPrice}),
failure: (error) => dispatch({type: GET_TOTAL_PRICE_FAILURE, error}),
})
}
export const changeMarkupFactor = (markupFactor) => (dispatch, getState) => {
dispatch({type: CHANGE_MARKUP_FACTOR, markupFactor})
dispatch(getTotalPrice())
}
export const clear = () => ({type: CLEAR})
// -----------------------------------
export const handleChange = (values) => (dispatch) => {
dispatch({type: ADD_CATEGORIES,values});
}
// ------------------------------------
// Reducer
// -----------------------------------
const initialState = {
loading: {
products: false,
addingProduct: false,
updatingProduct: false,
totalPrice: false,
},
products: [],
selectedProduct: null,
error: null,
totalPrice: null,
markupFactor: 1.3,
addRetailPrice: false,
categories:[],
}
export default createReducer(initialState, {
[GET_PRODUCT_REQUEST]: (state, {params}) => ({
search: _.has(params, 'search') ? params.search : state.search,
ordering: params.sorter ? `${params.sorter.order === 'descend' ? '-' : ''}${params.sorter.field}` : state.ordering,
filters: params.filters || state.filters,
loading: {
...state.loading,
products: false,
},
}),
[GET_PRODUCT_SUCCESS]: (state, {res: {results}}) => ({
products: results,
loading: false,
}),
[GET_PRODUCT_FAILURE]: (state, action) => ({
loading: false,
}),
[ADD_PRODUCT_REQUEST]: (state, action) => ({
loading: {
...state.loading,
addingProduct: true,
},
}),
[ADD_PRODUCT_SUCCESS]: (state, {selectedProduct}) => ({
selectedProduct,
loading: {
...state.loading,
addingProduct: false,
},
}),
[ADD_PRODUCT_FAILURE]: (state, action) => ({
loading: {
...state.loading,
addingProduct: false,
},
}),
[UPDATE_PRODUCT_REQUEST]: (state, action) => ({
loading: {
...state.loading,
updatingProduct: true,
},
}),
[UPDATE_PRODUCT_SUCCESS]: (state, {selectedProduct}) => ({
selectedProduct,
loading: {
...state.loading,
updatingProduct: false,
},
}),
[UPDATE_PRODUCT_FAILURE]: (state, action) => ({
loading: {
...state.loading,
updatingProduct: false,
},
}),
[GET_TOTAL_PRICE_REQUEST]: (state, action) => ({
totalPrice: null,
loading: {
...state.loading,
totalPrice: true,
},
}),
[GET_TOTAL_PRICE_SUCCESS]: (state, {totalPrice}) => ({
totalPrice,
loading: {
...state.loading,
totalPrice: false,
}
}),
[EDITING_RETAIL_PRICE]: (state, {totalPrice}) => ({
totalPrice
}),
[RETAIL_PRICE_UPDATED]: (state, {totalPrice}) => ({
totalPrice,
addRetailPrice: false
}),
[GET_TOTAL_PRICE_FAILURE]: (state, {error}) => ({
error,
loading: {
...state.loading,
totalPrice: false,
},
}),
[CHANGE_MARKUP_FACTOR]: (state, {markupFactor}) => ({
markupFactor,
}),
[CHANGE_SELECTED_PRODUCT]: (state, {selectedProduct}) => ({
selectedProduct,
}),
[CHANGE_RETAIL_PRICE]: (state) => ({
addRetailPrice: true,
}),
[ADD_CATEGORIES]: (state,{ categories }) => ({
categories
}),
[CLEAR]: (state, action) => RESET_STORE,
})
In your every reducer you have to return previous state and a new state.
[ACTION]:(state, ...) => ({
...state, <------ previouse state
... < ------- your new data (loading, others)
}),
Right now you don't return previous store state, instead you overwrite the store with only new data
[GET_PRODUCT_REQUEST]: (state, {params}) => ({
...state,
...
}),
[GET_PRODUCT_SUCCESS]: (state, {res: {results}}) => ({
...state,
...
products: results,
loading: false,
}),

Categories