how to increment and decrement state value in react? - reactjs

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;

Related

How do I update state through a React context?

Here's a trivial example using a React context:
type MyState = { counter: number };
const MyContext = createContext<[MyState, () => void]|undefined>(undefined);
class MyComponent extends Component<any, MyState> {
constructor(props: any) {
super(props);
this.state = { counter: 42 };
}
render() {
return (
<MyContext.Provider value={[this.state, () => this.setState({ counter: this.state.counter + 1 })]}>
{this.props.children}
</MyContext.Provider>
)
}
}
function App() {
return (
<MyComponent>
<MyContext.Consumer>
{
pair => {
const [state, increment] = pair || [];
return (<button onClick={increment}>Click to increment: {state?.counter}</button>);
}
}
</MyContext.Consumer>
</MyComponent>
);
}
This works, and clicking the button increments the counter as expected. But when I try to pull up the incrementer into a method on the parent component:
type MyState = { counter: number };
const MyContext = createContext<MyComponent|undefined>(undefined);
class MyComponent extends Component<any, MyState> {
constructor(props: any) {
super(props);
this.state = { counter: 42 };
}
render() {
return (
<MyContext.Provider value={this}>
{this.props.children}
</MyContext.Provider>
)
}
increment() {
this.setState({ counter: this.state.counter + 1 });
}
}
function App() {
return (
<MyComponent>
<MyContext.Consumer>
{
me => (<button onClick={() => me?.increment()}>Click to increment: {me?.state.counter}</button>)
}
</MyContext.Consumer>
</MyComponent>
);
}
Now the button no longer repaints when clicked.
The counter is incrementing, but the state change doesn't propagate to the consumer. Why is this?
I suppose it's possible to use a reducer instead of an object:
type MyState = { counter: number };
type MyAction = { type: "increment" };
const MyContext = createContext<[MyState, React.Dispatch<MyAction>]|undefined>(undefined);
function myReducer(state: MyState, action: MyAction) {
switch (action.type) {
case "increment":
return { counter: state.counter + 1 };
default:
throw new Error();
}
}
function MyComponent({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(myReducer, { counter: 42 });
return (
<MyContext.Provider value={[state, dispatch]}>
{children}
</MyContext.Provider>
)
}
function App() {
return (
<MyComponent>
<MyContext.Consumer>
{
pair => {
const [state, dispatch] = pair || [];
return (<button onClick={() => dispatch && dispatch({ type: "increment" })}>Click to increment: {state?.counter}</button>);
}
}
</MyContext.Consumer>
</MyComponent>
);
}
But this means cutting up existing code that's nicely-encapsulated in a class into a reducer function with a big switch statement. Is there a way to avoid this?
I think you should not put this as the value of the context on your provider.
When you're passing () => this.setState({ counter: this.state.counter + 1 })]} you're passing an anonymous function (() => {}) that runs your this.setState(). As it is an arrow function, it guarantees that this.setState and this.state are refereeing to the correct this.
If you'd like to use the increment method, you can keep using the same logic:
<MyContext.Provider value={[this.state, () => this.increment()]}>
Of course, you don't need to use an array if you want, you can use an object:
<MyContext.Provider value={{ state: this.state, increment: () => this.increment() }}>
This is common in React, and it's better to store in the context only the values you need to pass to the consumers, this way you can better understand which part of your code is changing what, and only have access to what it needs.

Redux: State is only being updated in one place

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.

React if statement not showing result

In React i have a button that when clicked will decrement state object value by one. When state value is decremented to 0, it should activate alert method but for some reason it only activates after state value one has reached to -1 not 0.
Any help?
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props){
super(props);
this.state = {
numbers:{
one:1
},
};
}
decrement = () => {
this.setState(prevState => {
return {
numbers:{
...prevState.numbers,
one: prevState.numbers.one - 1
}
}
}, () => console.log(
this.state.numbers.one,
));
if(this.state.numbers.one===0){
alert('test');
}
}
render() {
return (
<div className="App">
<div>{this.state.numbers.one}</div>
<br/>
<button onClick={this.decrement}>ok</button>
</div>
);
}
}
export default App;
Like i was saying in the comments. setState is async, you need to wait for the state change or use a lifecycle method.
So in your decrement function you can alert in the callback you are already using, which has the updated state value there.
decrement = () => {
this.setState(prevState => {
return {
numbers:{
...prevState.numbers,
one: prevState.numbers.one - 1
}
}
}, () => {
console.log(this.state.numbers.one)
if(this.state.numbers.one===0){
alert('test');
}
});
}
Alternatively, you can use the componentDidUpdate lifecycle method to check this value
componentDidUpdate(prevProps, prevState) {
if (prevState.numbers.one > 0 && this.state.numbers.one === 0) {
alert('test');
}
}
Because setState is async, you need to add the alert in the call back of setState.
class App extends Component {
constructor(props){
super(props);
this.state = {
numbers:{
one:1
},
};
}
decrement = () => {
this.setState(prevState => {
return {
numbers:{
...prevState.numbers,
one: prevState.numbers.one - 1
}
}
},
() => {
console.log(this.state.numbers.one)
if(this.state.numbers.one===0){
alert('test');
}
});
}
render() {
return (
<div className="App">
<div>{this.state.numbers.one}</div>
<br/>
<button onClick={this.decrement}>ok</button>
</div>
);
}
}
export default App;
you could implement the decrement function as below
decrement = () => {
this.setState({numbers: { ...this.state.numbers, one: this.state.numbers.one -1} },
() => {
this.state.numbers.one===0 && alert("test")
}
)
}

Updating Item Listing using React, Redux. and Redux Form

How do I update the horse listing after the add horse action is fully done?
I think that the reloadHorseList in CreateHorse is running before createHorse actions is completely done so sometimes I see new horse in list and sometimes not. A full reload shows an update always.
Horses Component
...
import { getHorses } from '../../actions';
import ListHorses from './ListHorses';
import CreateHorse from './forms/createHorseForm';
class Horses extends React.Component {
constructor(props) {
super(props);
this.state = {
...
};
this.reloadHorseList = this.reloadHorseList.bind(this);
}
componentDidMount() {
this.reloadHorseList();
}
reloadHorseList() {
this.props.getHorses(this.props.current_user.selected_stable);
}
render() {
return (
<div className="content page-content-wrapper1">
<CreateHorse
current_user={this.props.current_user}
reloadHorseList={this.reloadHorseList}
/>
<ListHorses
current_user={this.props.current_user}
horses={this.props.horses}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
horses: state.horses
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
getHorses: getHorses
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Horses);
Create Horse Form
...
import { Field, reduxForm, getFormValues } from 'redux-form';
import {
createHorse,
getHorseSelect,
updateHorseCount
} from '../../../actions';
import { connect } from 'react-redux';
const renderField = (...
);
class CreateHorse extends Component {
constructor(props) {
super(props);
this.state = {
...
};
this.setMessage = this.setMessage.bind(this);
}
onSubmit(props) {
//let p = this.props.reloadHorseList;
try {
this.props.createHorse(props, this.setMessage);
//running before I have finished creating my horse
this.props.reloadHorseList();
} catch (err) {
...
}
}
render() {
const { handleSubmit } = this.props;
return (
<div>
...
{this.state.displayHorseCreateForm && (
<div>
<h4 className="header-content">Add Horse</h4>
<p> * required field</p>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
// fields here
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
</div>
)}
</div>
);
}
}
function validate(values) {
...
}
function mapStateToProps(state) {
---
}
export default connect(mapStateToProps, {
createHorse,
getHorseSelect,
updateHorseCount
})(
reduxForm({
form: 'HorseCreatetForm',
initialValues: {
...
},
validate
})(CreateHorse)
);
//create horse action
export const createHorse = (props, setMessage) => async dispatch => {
try {
const request = await axios.post(`/api/horse/create`, props);
return {
type: CREATED_HORSE,
payload: request.data
};
} catch (err) {
...
}
};
ListHorses
...
import { deleteHorse } from '../../actions';
class HorsesList extends React.Component {
render() {
let horses = this.props.horses;
let horseCount = this.props.horse_count;
return (
<div className="content">
horse count: {horseCount}
<ul className="list-inline box-body">
{horseCount > 0 &&
horses.map((horse, key) => (
<li key={key}>
...//listing here
</li>
))}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
return {
horse_count: state.horse_count
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
...
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps)(HorsesList);
The solution that worked for me is to send a callback to the CreateHorse component to send to the createHorse action which runs Horse components action to getHorses.
class Horses extends React.Component {
constructor(props) {
super(props);
this.state = {
horses: this.props.horses,
};
this.reloadHorses = this.reloadHorses.bind(this);
}
componentDidMount(prevProps) {
this.props.getHorses(this.props.current_user.selected_stable);
}
reloadHorses = () => {
this.props.getHorses(this.props.current_user.selected_stable);
};
...
<CreateHorse
current_user={this.props.current_user}
reloadHorses={this.reloadHorses}
/>
<ListHorses
horses={this.props.horses}
/>
...
function mapStateToProps(state) {
return {
horses: state.horses
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
getHorses: getHorses
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Horses);
then in CreateHorse component
onSubmit(props) {
this.props.createHorse(props, this.setMessage, this.props.reloadHorses);
}
}
Then in the createHorse action
export const createHorse = (
props,
setMessage,
reloadHorses
) => async dispatch => {
try {
const request = await axios.post(`/api/horse/create`, props);
reloadHorses();
return {
type: CREATED_HORSE,
payload: request.data
};
} catch (err) {
...
}
};
You should be posting real code at this point. To trigger a component re render you need to be changing it's state. I would recommend setting your props from redux into local state and render your list from that. You also will need to be using componentWillRecieveProps();
componentDidMount() {
this.reloadHorseList();
this.setState=({list: this.props.horseList});
}
componentWillRecieveProps(nextProps){
this.setState=({list: nextProps.horseList})
}
You are correct in your assumption that the component finishes loading first. So you need to utilize the componentWillRecieveProps lifecycle hook .
Alternatively, if you're using mapStateToProps() with redux your component should be rerendering when anything within mapStateToProps() changes.

React re-renders whole app after rendering a component

I use react and redux in my web app. It's the simple app which has 4 components, one reducer and 3 actions. After I add a new entry to list, react renders component of list (the listItem), then re-renders the whole app. What is the cause of re-rendering whole app after rendering one component?
Updated:
App container:
class App extends Component {
static propTypes = {
groups: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
render() {
return (<div>
<Header addGroup={this.props.actions.addGroup} />
<List groups={this.props.groups} />
</div>
);
}
}
function mapStateToProps(state) {
return { groups: state.groups };
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(AppActions, dispatch) };
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Reduser:
export default function groupDiseases(state = initialState, action){
switch (action.type) {
case ADD_GROUP:
return [
{
id: '',
name: action.name
},
...state
];
case DELETE_GROUP:
return state.filter(group =>
group.id !== action.id
);
case EDIT_GROUP:
return state.map(group => (group.id === action.id ? { id: action.id, name: action.name } : group));
default:
return state;
}
}
Components:
export default class Add extends Component {
static propTypes = {
addGroup: PropTypes.func.isRequired
}
componentDidMount() {
this.textInput.focus();
}
handleAdd = () => {
const name = this.textInput.value.trim();
if (name.length !== 0) {
this.props.addGroup(name);
this.textInput.value = '';
}
}
render() {
return (
<form className="add_form">
<input
type="text"
className="add__name"
defaultValue=""
ref={(input) => this.textInput = input}
placeholder="Name" />
<button
className="add__btn"
ref="add_button"
onClick={this.handleAdd}>
Add
</button>
</form>
);
}
}
export default class ListGroups extends Component {
static propTypes = {
groups: PropTypes.array.isRequired
};
render() {
let data = this.props.groups;
let groupTemplate = <div> Группы отсутствуют. </div>;
if (data.length) {
groupTemplate = data.map((item, index) => {
return (
<div key={index}>
<Item item={item} />
</div>
);
});
}
return (
<div className="groups">
{groupTemplate}
<strong
className={'group__count ' + (data.length > 0 ? '' : 'none')}>
Всего групп: {data.length}
</strong>
</div>
);
}
}
It's likely due to the fact that you are letting the <form> continue its default behavior, which is to submit to a targeted action. Take a look at the w3c spec for buttons:
http://w3c.github.io/html-reference/button.html
Specifically, a button with no type attribute will default to submit.
So your button is telling the form to submit, with the target being the current page since none is provided. In your handleAdd method, you can do something like:
handleAdd = (event) => {
event.preventDefault(); // prevent default form submission behavior
const name = this.textInput.value.trim();
if (name.length !== 0) {
this.props.addGroup(name);
this.textInput.value = '';
}
}
Or you can modify your button to have type="button".

Resources