object in redux state disappearing when another action is triggered - javascript

I have an array of data that is loaded into redux state when the Main component loads into the Data field, and I have a default app language of english also stored in redux state, if I click on my button to trigger the setLanguage action it will change the language but it will also empty the data array.
How can I prevent the data array from being emptied when I change the language??
redux
data: []
language: english
Main.js
class Main extends React.Component {
componentDidMount() {
this.props.fetchData()
}
render() {
const {language} = this.props
const e = language === 'english'
const p = language === 'polish'
return(
<Wrap>
<Router>
<ScrollToTop>
<Header />
<Wrap>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/reviews" component={Reviews} />
<button onClick={this.props.fetchData}>click</button>
{/* <Route exact path="/reviews/:catId" component={Reviews} />
<Route exact path="/reviews/:catId/:slug" component={Review} /> */}
{/* <Route exact path="/" component={Home} /> */}
{/* <ScrollToTop path="/reviews/:catId" component={Review} /> */}
{/* <ScrollToTop path="/another-page" component={Reviews} /> */}
</Switch>
</Wrap>
</ScrollToTop>
</Router>
</Wrap>
)
}
}
const mapStateToProps = state => ({
language: state.language
});
export default connect(mapStateToProps, actionCreators)(Main);
MainActions.js
import axios from 'axios'
import {
FETCH_DATA
} from '../../Constants'
export function fetchData() {
return dispatch =>
axios
.get('https://jsonplaceholder.typicode.com/users')
.then((response) => {
dispatch({ type: FETCH_DATA, payload: response.data });
})
.catch((err) => {
console.error(err);
});
}
dataReducer.js
import {
FETCH_DATA
} from '../Constants'
const dataReducer = (state = [], action) => {
return{
...state,
data: action.payload
}
}
export default dataReducer;
Header.js
class Header extends React.Component {
render() {
const {language} = this.props
const e = language === 'english'
const p = language === 'polish'
return (
<Wrapper>
<button onClick={()=>this.props.setLanguage('english')}>english</button>
<button onClick={()=>this.props.setLanguage('polish')}>polish</button>
<div>
{e && <div>language is english</div>}
{p && <div>language is polish</div>}
</Wrapper>
)
}
}
const mapStateToProps = state => ({
language: state.language
});
export default connect(mapStateToProps, actionCreators)(Header);
headerActions.js
import {
SET_LANGUAGE
} from '../../Constants'
export function setLanguage(language) {
return {
type: SET_LANGUAGE,
language
}
}
languageReducer.js
import {
SET_LANGUAGE
} from '../Constants'
const initialState = 'english'
const languageReducer = (state = initialState, action) => {
switch (action.type) {
case SET_LANGUAGE:
action.language
default:
return state;
}
};
export default languageReducer;
combineReducers.js
const rootReducer = combineReducers({
language: languageReducer,
data: dataReducer
});
export default rootReducer;

I have changed the dataReducer , it now stores the data and doesn't disappear when the SET_LANGUAGE action is triggered
const dataReducer = (state = [], action) => {
switch (action.type) {
case 'FETCH_DATA':
return {
...state,
data: action.payload
};
default:
return state;
}
}

Related

React hook component is not updated even though state is changed by useSelector

Long story short I've got a react + redux application with basic logic: <App /> is the main component in which useEffect with no deps is firing requests for the required data (user info, etc) by dispatching actions. Until data is returned simple "Loading" message is shown. The problem is that after those actions are completed and the redux state is changed, doesn't get re-rendered and "Loading" stays.
import {
Outlet,
Route,
Routes
} from 'react-router-dom';
// Screens
import Login from './public/Login';
import './App.module.scss';
import Layout from "../../common/partials/Layout";
import Requests from "./private/Requests";
import Profile from "./private/Profile";
import AuthValidator from "../../common/components/AuthProvider";
import Register from "./public/Register";
import { useDispatch, useSelector } from "react-redux";
import { getUserData } from '../actions/auth';
import Home from "./Home";
import { State } from "../store";
import VerifyEmail from "./public/VerifyEmail";
import { onMobileLayoutSet } from '../../common/actions/layout';
import ProfileWizard from "./private/profile_wizard/ProfileWizardContainer";
import { getMetaData } from "../../common/actions/metadata";
const App = () => {
const dispatch = useDispatch();
const {
auth: { isLogged },
layout: { isMobile },
metadata: { countries },
profile: { profileExists }
} = useSelector((state: State) => state);
useEffect( () => {
dispatch(getUserData());
dispatch(getMetaData());
const { innerWidth: width } = window;
if (width < 1400 && !isMobile) {
dispatch(onMobileLayoutSet());
}
}, []);
if (isLogged == null || (isLogged && countries.length == 0) || profileExists == null) {
return (
<div>Loading</div>
);
}
return (
<Routes>
<Route path={ "/" } element={ <Home/> }/>
<Route
element={ <AuthValidator loginAddress={ '/login' } defaultAddress={ '/requests' } isLogged={ isLogged }><Outlet/></AuthValidator> }>
<Route element={ <AuthValidator.Block mustBeLogged={ false }><Outlet/></AuthValidator.Block> }>
<Route path={ "/login" } element={ <Login/> }/>
<Route path={ "/register" } element={ <Register/> }/>
<Route path={ "/verify-email" } element={ <VerifyEmail/> }/>
</Route>
<Route element={ <AuthValidator.Block mustBeLogged={ true }><Outlet/></AuthValidator.Block> }>
<Route element={ <Layout isMobile={ isMobile }><Outlet/></Layout> }>
<Route path={ "/requests" } element={ <Requests/> }/>
<Route path={ "/profile" } element={ <Profile/> }/>
</Route>
<Route path={ "/profile-wizard" } element={ <ProfileWizard/> }/>
</Route>
</Route>
</Routes>
);
};
export default App;
But when I check both react and redux states they appear to be changed (given one can trust react/redux tools).
React state:
Redux State:
Update:
But if I do the same thing without useEffect, everything works as expected (but looks uglier)
// useEffect( () => {
// dispatch(getUserData());
// dispatch(getMetaData());
//
// const { innerWidth: width } = window;
// if (width < 1400 && !isMobile) {
// dispatch(onMobileLayoutSet());
// }
// }, []);
if (isLogged == null && !userDataIsBeingRequested) {
dispatch(getUserData());
}
if(countries.length === 0 && !metadataRequested) {
dispatch(getMetaData());
}
const { innerWidth: width } = window;
if (width < 1400 && !isMobile) {
dispatch(onMobileLayoutSet());
}
Update 2:
Sample action creator (I'm using self-implemented api middleware):
export const login = ({ email }) => requestAction({
actionType: 'LOGIN',
url: '/api/worker/login',
onRequest: () => {
return { type: AuthActions.LoginAttempt, email }
},
onError: (responseData) => {
if (responseData.data.error.code === 'UserNotFound') {
return onLoginError(responseData.data.error.message);
}
},
onSuccess: () => onLoginSuccess(),
method: RequestMethod.Post,
data: { email },
});
Sample reducer:
export enum Actions {
LoginAttempt = "LoginAttempt",
LoginError = "LoginError",
LoginSuccess = "LoginSuccess",
GetUserDataSuccess = "GetUserDataSuccess",
GetUserDataFail = "GetUserDataFail",
GetUserDataAttempt = "GetUserDataAttempt",
RegisterAttempt = "RegisterAttempt",
RegisterError = "RegisterError",
RegisterSuccess = "RegisterSuccess",
BackToSignUp = "BackToSignUp",
EmailChanged = "EmailChanged",
LogoutAttempt = "LogoutAttempt",
LogoutSuccess = "LogoutSuccess",
LogoutFail = "LogoutFail",
}
export type AuthState = {
passwordSent: boolean,
isLogged: boolean | null,
isLoading: boolean,
userId: string | null,
firstName: string | null,
lastName: string | null,
number: number | null,
email: string | null,
userDataIsBeingRequested: boolean,
registrationComplete: boolean
loginComplete: boolean
emailErrorMessage: string | null
}
const initialState: AuthState = {
passwordSent: false,
isLogged: null,
isLoading: false,
registrationComplete: false,
userId: null,
firstName: null,
lastName: null,
number: null,
email: null,
userDataIsBeingRequested: false,
emailErrorMessage: null,
loginComplete: false
};
const authReducer = (state = initialState, action) => {
switch (action.type) {
case Actions.GetUserDataSuccess: {
return { ...state, ...action.userInfo, isLogged: action.isLogged, userDataIsBeingRequested: false };
}
case Actions.GetUserDataFail: {
return { ...state, isLogged: false, userDataIsBeingRequested: false };
}
case Actions.GetUserDataAttempt: {
return { ...state, userDataIsBeingRequested: true };
}
case Actions.RegisterAttempt: {
return {...state, isLoading: true, email: action.email};
}
case Actions.RegisterSuccess: {
return {...state, isLoading: false, registrationComplete: true};
}
case Actions.BackToSignUp: {
return {...state, email: null};
}
case Actions.RegisterError: {
return {...state, emailErrorMessage: action.emailErrorMessage, isLoading: false}
}
case Actions.EmailChanged: {
return {...state, emailErrorMessage: null}
}
case Actions.LoginAttempt: {
return {...state, isLoading: true}
}
case Actions.LoginError: {
return {...state, emailErrorMessage: action.emailErrorMessage, isLoading: false}
}
case Actions.LoginSuccess: {
return {...state, loginComplete: true, isLoading: false}
}
default:
return state;
}
};
export default authReducer;
Am I missing something?

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);

React Link To not updating the component

I am using React with Redux to list number of items and inside the item I have a list of similar items
In Home Page (there is a list of items when you click on any of them , it goes to the item path ) which is working well , but inside the item page , when you click on any items from similar items list (the view not updating )
the codeSandobx is here
App.js
const store = createStore(ItemsReducer, applyMiddleware(...middlewares));
class App extends React.Component {
render() {
return (
<Provider store={store}>
<Main />
</Provider>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
main.js
const Main = () => {
return (
<Router>
<div>
<Header />
<div className="container-fluid">
<Switch>
<Route exact path="/" component={Home} />
<Route path="/item/:id" component={Item} />
</Switch>
</div>
</div>
</Router>
);
};
export default Main;
Home.js
class Home extends React.Component {
render() {
const itemsList = this.props.items.map(item => {
return <ItemList item={item} key={item.id} />;
});
return <div className="items-list"> {itemsList}</div>;
}
}
const mapStateToProps = state => ({
items: state.items,
user: state.user
});
export default connect(mapStateToProps, null, null, {
pure: false
})(Home);
Item.js
class Item extends React.Component {
constructor(props) {
super();
this.state = {
item_id: props.match.params.id,
};
}
render() {
const itemsList = this.props.items.map(item => {
return <ItemList item={item} key={item.id} />;
});
return (
<div id="item-container">
<div className="item-list fav-items"> {itemsList} </div>;
</div>
);
}
}
const mapStateToProps = state => ({
items: state.items,
user: state.user
});
export default connect(mapStateToProps, null, null, {
pure: false
})(Item);
and finally the ItemList.js
class ItemList extends React.Component {
render() {
const item = this.props.item;
const item_link = "/item/" + item.id;
return (
<Link to={item_link}>
<div className="item-li">
{item.title}
</div>
</Link>
);
}
}
export default ItemList;
I've tired to use this solution from react-redux docs , but it didn't work
What do you expect to update on link click?
Any path /item/:id (with any id: 2423, 2435, 5465) will show the same result, because you don't use params.id inside the Item component
UPDATED
When id changes the component doesn't remount, only updates component (It's correct behavior)
If you want to fetchData on each changes of id, the next solution has to work for you
on hooks:
const Item = () => {
const params = useParams();
useEffect(() => {
axios.get(`/item/${params.id}`).then(...)
}, [params.id]);
return (
...
)
}
useEffect will call fetch each time when id is changing
and in class component you have to use componentDidUpdate:
class Item extends Component {
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (prevProps.match.params.id !== this.props.match.params.id) {
this.fetchData();
}
}
fetchData = () => {
...
}
...
}

Redux returns only initial state

I want to create a login page. When I enter value to textInput(Email or password) on emulator, it returns INITIAL_STATE(empty string) constantly. And render() in Login.js does not re-render when I enter any input. If I change for instance email, EMAIL_CHANGED case in LoginReducer.js activated but cannot change the state. No error on debugger console. How could I solve this problem?
Main.js
render() {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider store={store}>
<Login />
</Provider>
);}}
Login.js
render(){
const {containerStyle, subContainerStyle, inputStyle} = styles;
return(
<View style={containerStyle}>
<View style={subContainerStyle}>
<TextInput
placeholder="E-mail"
style={inputStyle}
value={this.props.email}
onChangeText={email => this.props.emailChanged(email)}
/>
</View>
<View style={subContainerStyle}>
<TextInput
secureTextEntry
placeholder="Password"
style={inputStyle}
value={this.props.password}
onChangeText={password => this.props.passwordChanged(password)}
/>
</View>
<View style={subContainerStyle}>
{this.renderLoginButton()}
</View>
</View>
);}}
const mapStateToProps = state => {
return {
email: state.auth.email,
password: state.auth.password,
loading: state.auth.loading
};
};
export default connect(mapStateToProps, { emailChanged, passwordChanged,loginWithEmail })(Login);
LoginReducer.js
import { EMAIL_CHANGED, PASSWORD_CHANGED, LOGIN_USER, LOGIN_USER_SUCCESS, LOGIN_USER_FAIL } from '../actions/types';
const INITIAL_STATE = {
email: '',
password: '',
loading: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case EMAIL_CHANGED:
console.log(state.email);
return { ...state, email: action.payload };
case PASSWORD_CHANGED:
return { ...state, password: action.payload };
case LOGIN_USER:
return { ...state, loading: true };
case LOGIN_USER_SUCCESS:
return { ...state, loading: false };
case LOGIN_USER_FAIL:
return { ...state, loading: false };
default:
return { ...state };
}
};
reducers/index.js
import { combineReducers } from 'redux';
import LoginReducer from './LoginReducer';
export default combineReducers({
auth: LoginReducer
});
types.js
export const EMAIL_CHANGED = 'email_changed';
export const PASSWORD_CHANGED = 'password_changed';
export const LOGIN_USER = 'login_user';
export const LOGIN_USER_SUCCESS = 'login_user_succes';
export const LOGIN_USER_FAIL = 'login_user_fail';
loginActions.js
import { EMAIL_CHANGED, PASSWORD_CHANGED, LOGIN_USER, LOGIN_USER_SUCCESS, LOGIN_USER_FAIL } from './types';
export const emailChanged = (email) => {
return (dispatch) => {
dispatch({
type: EMAIL_CHANGED,
payload: email
});
};
};
export const passwordChanged = (password) => {
return (dispatch) => {
dispatch({
type: PASSWORD_CHANGED,
payload: password
});
};
};
Your issue is that you are not passing the email and password to thunks.
const mapDispatchToProps = (dispatch) => {
return {
emailChanged: (email) => {
dispatch(emailChanged(email))
},
passwordChanged: (password) => {
dispatch(passwordChanged(password))
},
loginWithEmail: () => {
// TODO: complete this yourself, as you did not provide what it is in your code.
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);

How do I use redux when a prop function is changing the state?

I'm trying to wrap my head around how one uses redux in the case where a prop that is being passed into a component is supposed to be used to change the state of the application.
I have a working example here.
let Input = ({handleChange}) => (
<input type="text" onChange={handleChange('mySpecialInput')} />
)
let Text = ({message, color}) => (
<span style={{color}}>{message}</span>
)
let App = ({message, color, handleChange}) => (
<div>
<Text message={message} color={color} /> <br />
<Input handleChange={handleChange} />
</div>
)
class ConnectedApp extends React.Component {
constructor(props) {
super(props)
this.state = {
color: 'orange',
message: 'Hello World'
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
return (event) => {
console.log(id)
if (id === 'mySpecialInput') {
this.setState({'color': event.target.value})
}
}
}
render() {
return (
<App
message={this.state.message}
color={this.state.color}
handleChange={this.handleChange} />
)
}
}
ReactDOM.render(
<ConnectedApp/>,
document.getElementById('react_example')
);
How would something simple like this be worked into using redux?
Here's the code above built using redux!
Working Example
let {createStore} = Redux
let {connect, Provider} = ReactRedux
// React component
let Input = ({handleChange}) => (
<input type="text" onChange={handleChange('mySpecialInput')} />
)
let Text = ({message, color}) => (
<span style={{color}}>{message}</span>
)
let App = ({message, color, handleChange}) => (
<div>
<Text message={message} color={color} /> <br />
<Input handleChange={handleChange} />
</div>
)
// Action
const CHANGE_COLOR = 'CHANGE_COLOR'
function changeColorAction(color) {
return {
type: CHANGE_COLOR,
color
}
}
// Reducer
function reducer(state = {color: "#ffa500"}, action) {
let count = state.count
switch (action.type) {
case CHANGE_COLOR:
return { color: action.color }
default:
return state
}
}
// Store
let store = createStore(reducer)
// Map Redux state to component props
function mapStateToProps(state) {
return {
color: state.color,
message: "Hello World"
}
}
function changeColorDispatcher (dispatch) {
return (id) => {
return (event) => {
if (id === 'mySpecialInput') {
return dispatch(changeColorAction(event.target.value))
}
}
}
}
// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
return {
handleChange: changeColorDispatcher(dispatch)
}
}
// Connected Component
let ConnectedApp = connect(
mapStateToProps,
mapDispatchToProps
)(App)
ReactDOM.render(
<Provider store={store}>
<ConnectedApp/>
</Provider>,
document.getElementById('react_example')
);
You can simply connect each of your components individually instead of using connect() on your top-level component and passing down the props in the hierarchy tree.
This will come way more handy.
See a working example!

Categories