Redux: State is only being updated in one place - reactjs

I'm new to redux, I am trying to update state through props in my Survey Component.
In my console.log, the state in my reducer is being updated, but my app state is staying the same.
in my Router.js
const intialState = {
currentQuestionId: 1,
}
function reducer(state = intialState, action) {
console.log('reducer', state, action)
switch (action.type) {
case 'INCREMENT':
return {
currentQuestionId: state.currentQuestionId + 1,
}
case 'DECREMENT':
return {
currentQuestionId: state.currentQuestionId - 1,
}
default:
return state
}
}
const store = createStore(reducer)
const Router = () => (
<BrowserRouter>
<Switch>
<Provider store={store}>
<Route path="/survey/:surveyName" component={CNA} />
<Route component={NotFound} />
</Provider>
</Switch>
</BrowserRouter>
)
in Survey.js
class Survey extends Component {
constructor(props) {
super(props)
this.state = {
currentQuestionId: 1,
}
}
previousQuestion = () => {
this.props.decrement()
}
nextQuestion = () => {
this.props.increment()
}
render() {
const { currentQuestionId } = this.state
const { questions } = this.props
return (
<SurveyContainer>
{console.log('surveyState', currentQuestionId)}
<Question
data={questions.find(q => q.id === currentQuestionId)}
/>
<ButtonContainer>
{currentQuestionId > 1 && (
<Button type="button" onClick={this.previousQuestion}>
Previous
</Button>
)}
<Button type="button" onClick={this.nextQuestion}>
Next
</Button>
</ButtonContainer>
</SurveyContainer>
)
}
}
const mapDispatchToProps = {
increment,
decrement,
}
function mapStateToProps(state) {
return {
currentQuestionId: state.currentQuestionId,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Survey)
My console.log
reducer {currentQuestionId: 5} {type: "INCREMENT"}
SurveyState 1
So it seems that my reducer is, in fact, changing the state, however, my Survey Component does not seem to be aware of these changes.

Related

Firebase/React/Redux Component has weird updating behavior, state should be ok

I am having a chat web app which is connected to firebase.
When I refresh the page the lastMessage is loaded (as the gif shows), however, for some reason, if the component is otherwise mounted the lastMessage sometimes flickers and disappears afterwards like it is overridden. When I hover over it, and hence update the component, the lastMessage is there.
This is a weird behavior and I spent now days trying different things.
I would be very grateful if someone could take a look as I am really stuck here.
The db setup is that on firestore the chat collection has a sub-collection messages.
App.js
// render property doesn't re-mount the MainContainer on navigation
const MainRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => (
<MainContainer>
<Component {...props} />
</MainContainer>
)}
/>
);
render() {
return (
...
<MainRoute
path="/chats/one_to_one"
exact
component={OneToOneChatContainer}
/>
// on refresh the firebase user info is retrieved again
class MainContainer extends Component {
componentDidMount() {
const { user, getUserInfo, firebaseAuthRefresh } = this.props;
const { isAuthenticated } = user;
if (isAuthenticated) {
getUserInfo(user.id);
firebaseAuthRefresh();
} else {
history.push("/sign_in");
}
}
render() {
return (
<div>
<Navigation {...this.props} />
<Main {...this.props} />
</div>
);
}
}
Action
// if I set a timeout around fetchResidentsForChat this delay will make the lastMessage appear...so I must have screwed up the state / updating somewhere.
const firebaseAuthRefresh = () => dispatch => {
firebaseApp.auth().onAuthStateChanged(user => {
if (user) {
localStorage.setItem("firebaseUid", user.uid);
dispatch(setFirebaseAuthUser({uid: user.uid, email: user.email}))
dispatch(fetchAllFirebaseData(user.projectId));
}
});
};
export const fetchAllFirebaseData = projectId => dispatch => {
const userId = localStorage.getItem("firebaseId");
if (userId) {
dispatch(fetchOneToOneChat(userId));
}
if (projectId) {
// setTimeout(() => {
dispatch(fetchResidentsForChat(projectId));
// }, 100);
...
export const fetchOneToOneChat = userId => dispatch => {
dispatch(requestOneToOneChat());
database
.collection("chat")
.where("userId", "==", userId)
.orderBy("updated_at", "desc")
.onSnapshot(querySnapshot => {
let oneToOne = [];
querySnapshot.forEach(doc => {
let messages = [];
doc.ref
.collection("messages")
.orderBy("created_at")
.onSnapshot(snapshot => {
snapshot.forEach(message => {
messages.push({ id: message.id, ...message.data() });
});
});
oneToOne.push(Object.assign({}, doc.data(), { messages: messages }));
});
dispatch(fetchOneToOneSuccess(oneToOne));
});
};
Reducer
const initialState = {
residents: [],
oneToOne: []
};
function firebaseChat(state = initialState, action) {
switch (action.type) {
case FETCH_RESIDENT_SUCCESS:
return {
...state,
residents: action.payload,
isLoading: false
};
case FETCH_ONE_TO_ONE_CHAT_SUCCESS:
return {
...state,
oneToOne: action.payload,
isLoading: false
};
...
Main.js
// ...
render() {
return (...
<div>{React.cloneElement(children, this.props)}</div>
)
}
OneToOne Chat Container
// without firebaseAuthRefresh I don't get any chat displayed. Actually I thought having it inside MainContainer would be sufficient and subscribe here only to the chat data with fetchOneToOneChat.
// Maybe someone has a better idea or point me in another direction.
class OneToOneChatContainer extends Component {
componentDidMount() {
const { firebaseAuthRefresh, firebaseData, fetchOneToOneChat } = this.props;
const { user } = firebaseData;
firebaseAuthRefresh();
fetchOneToOneChat(user.id || localStorage.getItem("firebaseId"));
}
render() {
return (
<OneToOneChat {...this.props} />
);
}
}
export default class OneToOneChat extends Component {
render() {
<MessageNavigation
firebaseChat={firebaseChat}
firebaseData={firebaseData}
residents={firebaseChat.residents}
onClick={this.selectUser}
selectedUserId={selectedUser && selectedUser.residentId}
/>
}
}
export default class MessageNavigation extends Component {
render() {
const {
onClick,
selectedUserId,
firebaseChat,
firebaseData
} = this.props;
<RenderResidentsChatNavigation
searchChat={this.searchChat}
residents={residents}
onClick={onClick}
firebaseData={firebaseData}
firebaseChat={firebaseChat}
selectedUserId={selectedUserId}
/>
}
}
const RenderResidentsChatNavigation = ({
residents,
searchChat,
selectedUserId,
onClick,
firebaseData,
firebaseChat
}) => (
<div>
{firebaseChat.oneToOne.map(chat => {
const user = residents.find(
resident => chat.residentId === resident.residentId
);
const selected = selectedUserId == chat.residentId;
if (!!user) {
return (
<MessageNavigationItem
id={chat.residentId}
key={chat.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
{residents.map(user => {
const selected = selectedUserId == user.residentId;
const chat = firebaseChat.oneToOne.find(
chat => chat.residentId === user.residentId
);
if (_isEmpty(chat)) {
return (
<MessageNavigationItem
id={user.residentId}
key={user.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
</div>
}
}
And lastly the item where the lastMessage is actually displayed
export default class MessageNavigationItem extends Component {
render() {
const { hovered } = this.state;
const { user, selected, chat, isGroupChat, group, id } = this.props;
const { messages } = chat;
const item = isGroupChat ? group : user;
const lastMessage = _last(messages);
return (
<div>
{`${user.firstName} (${user.unit})`}
{lastMessage && lastMessage.content}
</div>
)
}
In the end it was an async setup issue.
In the action 'messages' are a sub-collection of the collection 'chats'.
To retrieve them it is an async operation.
When I returned a Promise for the messages of each chat and awaited for it before I run the success dispatch function, the messages are shown as expected.

App re-renders when redux store is updated

Everytime I dispatch an action and update my store, my entire app re-renders. I assume I'm doing anything wrong w/ my connect/mapDispatchToProps function? Is it right to pass { ...actions } as a 2nd argument to my connect function in App.js?
Here's my code:
class App extends Component {
componentDidMount() {
this.props.fetchPages(api.API_PAGES);
this.props.fetchPosts(api.API_POSTS);
window.addEventListener('resize', () => {
this.props.resizeScreen(window.innerWidth);
});
}
render() {
return (
<div>
{this.props.app.showIntro && <Intro {...this.props} endIntro={this.props.endIntro} />}
{!this.props.pages.isFetching && this.props.pages.data &&
<div>
<Navbar {...this.props} />
<Layout {...this.props}>
<Switch location={this.props.location}>
<Route
path={routes.HOME}
exact
component={() => (
<Home {...this.props} />
)}
/>
<Route
path={routes.ABOUT}
component={() => (
<About {...this.props} />
)}
/>
<Route
path={routes.NEWS}
exact
component={() => (
<News {...this.props} />
)}
/>
<Route
component={NotFound}
/>
</Switch>
</Layout>
</div>
}
</div>
);
}
}
function mapStateToProps(state) {
return {
app: state.app,
pages: state.pages,
posts: state.posts
};
}
export default withRouter(connect(
mapStateToProps,
{ ...actions }
)(App));
actions/index.js
export function resizeScreen(screenWidth) {
return {
type: types.RESIZE_SCREEN,
screenWidth
};
}
export function endIntro() {
return {
type: types.END_INTRO,
showIntro: false
};
}
export function toggleNav(bool) {
return {
type: types.TOGGLE_NAV,
navOpen: bool
};
}
export function toggleVideoPlayer(bool) {
return {
type: types.TOGGLE_VIDEO_PLAYER,
videoIsPlaying: bool
};
}
export function toggleScroll(bool) {
return {
type: types.TOGGLE_SROLL,
disableScroll: bool
};
}
// pages
function requestPages() {
return {
type: types.REQUEST_PAGES
};
}
function receivePages(data) {
return {
type: types.RECEIVE_PAGES,
data
};
}
// posts
function requestPosts() {
return {
type: types.REQUEST_POSTS
};
}
function receivePosts(data) {
return {
type: types.RECEIVE_POSTS,
data
};
}
// creators
export function fetchPages(path) {
return (dispatch, getState) => {
const { pages } = getState();
if (pages.isFetching) return;
dispatch(requestPages());
fetch(`${process.env.API_URL}${path}`)
.then(response => response.json())
.then(json => dispatch(receivePages(json)));
};
}
export function fetchPosts(path) {
return (dispatch, getState) => {
const { posts } = getState();
if (posts.isFetching) return;
dispatch(requestPosts());
fetch(`${process.env.API_URL}${path}`)
.then(response => response.json())
.then(json => dispatch(receivePosts(json)));
};
}
reducers/app.js:
const initialState = {
screenWidth: typeof window === 'object' ? window.innerWidth : null,
showIntro: true,
navOpen: false,
videoIsPlaying: false,
disableScroll: false
};
export default function app(state = initialState, action) {
switch (action.type) {
case RESIZE_SCREEN: {
return {
...state,
screenWidth: action.screenWidth
};
}
case TOGGLE_NAV: {
return {
...state,
navOpen: !state.navOpen
};
}
case END_INTRO: {
return {
...state,
showIntro: false
};
}
case TOGGLE_VIDEO_PLAYER: {
return {
...state,
videoIsPlaying: !state.videoIsPlaying
};
}
case TOGGLE_SCROLL: {
return {
...state,
disableScroll: !state.disableScroll
};
}
default: {
return state;
}
}
}
reducers/posts.js is similar to reducers/pages.js:
const initialState = {
isFetching: false
};
export default function posts(state = initialState, action) {
switch (action.type) {
case REQUEST_POSTS: {
return {
...state,
isFetching: true
};
}
case RECEIVE_POSTS: {
return {
...state,
isFetching: false,
data: action.data
};
}
default: {
return state;
}
}
}
If you have an issue with too much of your app re-rendering with each redux update, it helps to use more connected components and limit the amount of state being passed to each one. I see that you're spreading props down into each page, this is convenient, but a common cause of inefficient re-renders.
<Home {...this.props} />
<About {...this.props} />
<News {...this.props} />
This could result in too much data being passed to each of these components, and each redux action causing the entire page to re-render.
Another potential issue that I see is that you're using an inline anonymous function as the component callback for your routes
<Route
path={routes.ABOUT}
component={() => (
<About {...this.props} />
)}
/>
I'm not exactly sure how React Router is working here, but a potential issue is that each time the router re-renders, those anonymous functions are created brand new again. React will see them as a new component and force a re-render. You can resolve this by making each of these a connected component that pulls in their own props, and then update the router like so
<Route
path={routes.ABOUT}
component={ConnectedAbout}
/>
This is how redux should work: props are changing so the connected componentin re-redered.
You can:
implements your shouldComponentUpdate to limit rerender (note: this will also prevent subcomponents)
use PureComponent instead of Component base class so you'll switch to shallow compare
Limit numbers of connected props, maybe you can connect subcomponents instead.

React/Redux Why does specific component update, when its sibling’s child component updates, though its state doesn’t change

Update
The sidedrawers state is apparently different, but value does not change...
Details:
There is a layout component, which takes in routes from react router as children.
Inside the layout component code, two child components are rendered, Toolbar, and sidedrawer, and a main section that contains this.props.children.
One of the routes renders a component called page. Page renders another component called graphContainer, and passes it a click event, which is applied to the graphContainer’s button that it renders.
How it works is, I grab the first eight graphs and show 4 of them. When the button is clicked, it decides to either show the next 4 or grab the next eight.
This whole thing uses redux. There’s a page state, authentication state, navigation state, and a graph state. The only partial state changing when the button is clicked, is the graphs.
However, both the GraphContainer updates along with the sidedrawer component. As far as I can tell, nothing in the sidedrawer component is changing, so it should not trigger an update.
In the redux page for navigation state, the switch hits the default, which just returns state.
The graph redux portion works just fine, updates accordingly.
My workaround was to implement a dontUpdate prop in the navigation reducer state. And then use shouldComponentUpdate to check that prop, because the shallow check that was done by default, say with pureComponent, was seeing a different state or prop.
tl;dr: Any ideas why the sidedrawer component keeps updating, even though, as far as I can tell, there’s no prop or state change?
Reducers
const graphReducer = (state = initialState, action) => {
...
case SHOW_NEXTFOUR:
console.log('SHOW NEXT FOUR', state);
return {
...state,
ttlShown: action.ttlShown
};
default:
return state;
}
};
const navReducer = (state = initialState, action) => {
...
default:
return {...state, dontUpdate: true};
}
};
Layout Component
class Layout extends Component {
...
handleSideBarOpen = () => {
this.props.onSidebarToggle();
}
render () {
return (
<Aux>
<Toolbar
isAuth={this.props.isAuthenticated}
drawerToggleClicked={this.handleSideBarOpen}
/>
<SideDrawer
open={this.props.sidebarOpen}
closed={this.props.onSidebarToggle}
/>
<main className={classes.Content}>
{this.props.children}
</main>
</Aux>
)
}
}
const mapStateToProps = ({ navigation, auth }) => {
const { sidebarOpen } = navigation;
const { token } = auth;
return {
sidebarOpen,
isAuthenticated: token !== null
};
};
const mapDispatchToProps = {
onSidebarToggle, getNavTree
};
export default connect(
mapStateToProps, mapDispatchToProps
)(Layout);
Sidedrawer Component
class sideDrawer extends Component {
state = {
popupMenuOpen: false
}
shouldComponentUpdate ( nextProps, nextState ) {
if(nextProps.dontUpdate)
return false;
else return true;
}
…
render() {
…
let navitems = [];
if(this.props.navData && !this.props.error) {
navitems = (
<NavigationItems
showClients={this.props.showClientsBtn}
navData={this.props.navData}
curClientid={this.props.curClientid}
curSiteid={this.props.curSiteid}
curDashid={this.props.curDashid}
curPageid={this.props.curPageid}
closeSidebar={this.props.closed}
onPageClick={this.handlePageClick}
onCSDClick={this.handleOpenPopupMenu}
/>
);
} else
navitems = <p>Problem Loading Tree</p>;
return (
<Aux>
<div className={attachedClasses.join(' ')}>
<div className={classes.Logo}>
<div className={classes.CloseWrapper}>
<Chip onClick={this.props.closed} className={classes.CloseChip}>X</Chip>
</div>
<div className={classes.CrumbWrapper}>
<Breadcrumbs
backBtn={this.handleBackClick}
handleCrumbClick={this.handleCrumbClick}
breadcrumbs={this.props.breadcrumbs}
/>
</div>
</div>
<nav>
{navitems}
<Popover
style={{width: "90%"}}
open={this.state.popupMenuOpen}
anchorEl={this.state.anchorEl}
anchorOrigin={{horizontal: 'middle', vertical: 'bottom'}}
targetOrigin={{horizontal: 'middle', vertical: 'top'}}
onRequestClose={this.handleClosePopupMenu}
>
<Menu
style={{width: "87%"}}>
{MIs}
</Menu>
</Popover>
</nav>
</div>
</Aux>
);
}
};
const mapStateToProps = ({ navigation }) => {
const { dontUpdate, clientid, breadcrumbs,currentPage, selectedClient, selectedSite, selectedDash, selectedPage, navigationData, sidebarOpen, navError } = navigation;
...
}
return {
dontUpdate,
clientid,
showClientsBtn,
navData,
curClientid,
curSiteid,
curDashid,
curPageid,
parentPageid,
sidebarOpen,
navError,
breadcrumbs,
currentPage
};
};
const mapDispatchToProps = {
getNavTree,
onPageSelected,
onSwitchCSD,
onPageRoute
};
export default withRouter(connect(
mapStateToProps, mapDispatchToProps
)(sideDrawer));
Page Component
class Page extends Component {
componentWillMount () {
this.props.getCurPage();
}
render () {
let content = null;
if(this.props.location.state && this.props.location.state.currentPage)
content = (<GraphContainer pageid={this.props.location.state.currentPage} />);
return this.props.location.state && this.props.location.state.currentPage ? (
<Aux>
<p>A PAGE!</p>
{content}
</Aux>
) : (<Redirect to="/" />);
}
}
const mapStateToProps = ({pages}) => {
const { clientid, curPage } = pages;
return {
clientid, curPage
};
};
const mapDispatchToProps = {
getSelectedPage, getCurPage
};
export default connect(
mapStateToProps, mapDispatchToProps
)(Page);
Graph Container
class GraphsContainer extends Component {
componentWillReceiveProps(newProps) {
if(this.props.pageid !== newProps.pageid)
this.props.getFirstEight(newProps.pageid);
}
componentDidMount() {
if(this.props.pageid)
this.props.getFirstEight(this.props.pageid);
}
handleNextClick = (event) => {
event.preventDefault();
this.props.getNextEight(this.props.pageid, this.props.lastNum, this.props.ttlShown);
}
render() {
let graphcards = null;
let disableNext = null;
if (this.props.lastNum >= this.props.ttl)
disableNext = true;
if(this.props.graphs && this.props.graphs.length > 0) {
graphcards = ...
}
return (
<div className={classes.Shell}>
{graphcards}
{this.props.lastNum < this.props.ttl ? (
<div className={classes.NavBtns}>
<RaisedButton disabled={disableNext} onClick={this.handleNextClick}>{'V'}</RaisedButton>
</div>
):null}
</div>
);
}
}
const mapStateToProps = ({pageGraphs}) => {
const { graphs, ttl, lastNum, ttlShown } = pageGraphs;
return {
graphs, ttl, lastNum, ttlShown
};
};
const mapDispatchToProps = {
getFirstEight, getNextEight
};
export default connect(
mapStateToProps, mapDispatchToProps
)(GraphsContainer);
Actions
export const getFirstEight = (pageid) => {
let str = ...;
return (dispatch) => {
axios.get( str )
.then( response => {
let data = {};
let graphs;
let ttl;
let newLastNum = 0;
if((typeof response.data !== 'undefined') && (response.data !== null)) {
data = {...response.data};
ttl = data.total;
if(ttl <= 8) {
graphs = [...data.graphs];
newLastNum = ttl;
} else {
graphs = [...data.graphs].slice(0,8);
newLastNum = 8;
}
}
dispatch({type: GET_FIRSTEIGHT, payload: {ttl,graphs, lastNum:newLastNum}});
} )
.catch( error => {
console.log('ERROR FETCHING NAV TREE', error);
dispatch({type: GET_FIRSTEIGHT, payload: {}});
} );
};
};
export const getNextEight = (pageid, lastNum, ttlShown) => {
let str = ...;
let newLastNum = 0;
return (dispatch) => {
if(ttlShown < lastNum) {
dispatch({type: SHOW_NEXTFOUR, ttlShown: ttlShown+4});
} else {
axios.get( str )
.then( response => {
// console.log('[RESPONSE]', response);
let data = {};
let graphs;
let ttl;
if((typeof response.data !== 'undefined') && (response.data !== null)) {
data = {...response.data};
ttl = data.total;
if(ttl <= (lastNum+8)) {
graphs = [...data.graphs].slice(lastNum);
newLastNum = ttl;
} else {
graphs = [...data.graphs].filter((el,index) => {
return (index > (lastNum-1)) && (index < (lastNum+8));
});
newLastNum = lastNum+8;
}
}
dispatch({type: GET_NEXTEIGHT, payload: {ttl,graphs, lastNum:newLastNum, ttlShown: ttlShown+4}});
} )
.catch( error => {
console.log('ERROR FETCHING NAV TREE', error);
dispatch({type: GET_NEXTEIGHT, payload: {}});
} );
}
};
};

Reusable React component with same actions & reducers

I want to reuse a react component and share common actions & reducers. My app dashboard has 3 Lists, where each List is fetched with different query param.
All 3 List components have the same props because all 3 of them are being re-rendered once I receive props from reducer.
Is there an dynamic way to display Lists based on query parameter? What I was thinking is to call different reducer in the action file based on the query param. Is there a better way?
Dashboard.js
const Dashboard = () => {
return(
<div>
<List query={QUERY1} />
<List query={QUERY2} />
<List query={QUERY3} />
</div>
)
}
List.js
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: []
};
}
componentWillMount() {
const { query } = this.props;
this.props.onLoad(query);
}
componentWillReceiveProps() {
const { items } = this.props;
this.setState({ items });
}
render() {
return (
<div>
{
this.state.items.map((item, index) =>
<Item data={item} key={index}/>
)
}
</div>
)
}
}
function mapStateToProps(state) {
const { items } = state.item;
return {
items
}
}
function mapDispatchToProps(dispatch) {
return {
onLoad: bindActionCreators(actions.load, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(List);
action.js
export function load(query) {
return function (dispatch) {
fetch(`//api.example.com/list?type=${query}&limit=10`)
.then((response) => response.json())
.then((data) => {
dispatch(setItems(data));
});
};
}
reducer.js
export default function(state = [], action) {
switch (action.type) {
case actionTypes.ITEMS_SET:
return setItems(state, action);
}
return state;
}
function setItems(state, action) {
const { items } = action;
return { ...state, items };
}
Note I am a contributor on redux-subpace
redux-subspace came around to solve this problem of having the same component displayed on the page, without crossing over the store values.
It has a feature called namespacing that will allow you to isolate your load actions and components from each other.
const Dashboard = () => {
return(
<div>
<SubspaceProvider mapState={state => state.list1}, namespace='list1'>
<List query={QUERY1} />
</SubspaceProvider>
<SubspaceProvider mapState={state => state.list2}, namespace='list'>
<List query={QUERY2} />
</SubspaceProvider>
<SubspaceProvider mapState={state => state.list3}, namespace='list3'>
<List query={QUERY3} />
</SubspaceProvider>
</div>
)
}
You'll also need to namespace your reducers, you can see how to do that here.

how to increment and decrement state value in react?

I am trying to increment and decrement state value in react using react-redux.I add actions , container ,reducer .But I don't know how to subscribe the increment and decrement action here is my code
I want to increment and decrement the value when user click on buttons
here is my code
http://codepen.io/anon/pen/jVjMXv?editors=1010
const abc= (state=0,action) => {
console.log(action.type)
switch(action.type){
case 'INCREMENT':
return state +1
case 'DECREMENT':
return state -1
Default :
return state;
}
}
const {createStore,bindActionCreators} =Redux;
const {Provider,connect} =ReactRedux;
const store = createStore(abc);
class First extends React.Component {
constructor (){
super();
this.state ={
digit :0
}
}
inc (){
console.log('ince')
}
dec (){
console.log('dec')
}
render(){
return (
<div>
<button onClick={this.inc.bind(this)}>INCREMENT</button>
<p>{this.state.digit}</p>
<button onClick={this.dec.bind(this)}>DECREMENT</button>
</div>
)
}
}
const actions = {
increment: () => {
return {
type: 'INCREMENT',
}
},
decrement: () => {
return {
type: 'DECREMENT',
}
}
};
const AppContainer = connect(
function mapStateToProps(state) {
return {
digit: state
};
},
function mapDispatchToProps(dispatch) {
return bindActionCreators(actions, dispatch);
}
)(First);
ReactDOM.render(
<Provider store={store}>
<First/>
</Provider>
,document.getElementById('root'))
You need to make a lot of changes
First: Since you are connecting your First component to state and actions as AppContainer you need to render it in DOM
ReactDOM.render(
<Provider store={store}>
<AppContainer/>
</Provider>
,document.getElementById('root'))
Second: you are dispatching actions INC and DEC and you are handling INCREMENT, DECREMENT in reducer
Third: You should render the state you get from redux and not the component state like
{this.props.digit}
Fourth:
call the action via the props like this.props.increment(), this.props.decrement()
Complete Code
const abc= (state=0,action) => {
console.log('in redux', action.type)
switch(action.type){
case 'INC':
return state +1
case 'DEC':
return state -1
default :
return state;
}
}
const {createStore,bindActionCreators} =Redux;
const {Provider,connect} =ReactRedux;
const store = createStore(abc);
class First extends React.Component {
constructor (props){
super(props);
this.state ={
digit :0
}
}
inc (){
console.log('ince', this.props)
this.props.increment();
}
dec (){
console.log('dec')
this.props.decrement();
}
render(){
return (
<div>
<button onClick={this.inc.bind(this)}>INCREMENT</button>
<p>{this.props.digit}</p>
<button onClick={this.dec.bind(this)}>DECREMENT</button>
</div>
)
}
}
const actions = {
increment: () => {
return {
type: 'INC',
}
},
decrement: () => {
return {
type: 'DEC',
}
}
};
const AppContainer = connect(
function mapStateToProps(state) {
return {
digit: state
};
},
function mapDispatchToProps(dispatch) {
return bindActionCreators(actions, dispatch);
}
)(First);
ReactDOM.render(
<Provider store={store}>
<AppContainer/>
</Provider>
,document.getElementById('root'))
Here is a working codepen
Very simple code INC and DEC: props and state
Complete Code:
class APP extends Component
{
constructor(props)
{
super(props)
this.state ={
digit: 0
}
this.onIncrement = this.onIncrement.bind(this);
this.onDecrement = this.onDecrement.bind(this);
}
onIncrement()
{
this.setState({
digit: this.state.digit + 1
)}
}
onDecrement()
{
this.setState({
digit: this.state.digit - 1
)}
}
render()
{
return(<p>{this.state.digit}</p>
<button type="button" onClick={this.onIncrement}> + </button>
<button type="button" onClick={this.onDecrement}> - </button>)
}
}
export default APP;

Resources