Hello I am trying to get the reducer "removePlayer" to update the state it generates to have a selectedPlayerIndex of -1 but I can't seem to get it to work, I've looked for similar questions but couldn't find anything that is informative enough to answer my question.
Here is the code
import * as PlayerActionTypes from '../actiontypes/player';
const initialState = {
players: [{
name: 'Jim Hoskins',
score: 31,
created: '11/8/2016',
updated: '11/9/2016'
},
{
name: 'Andrew Chalkley',
score: 20,
created: '11/9/2016',
updated: '11/10/2016'
},
{
name: 'Alena Holligan',
score: 50,
created: '11/11/2016',
updated: '11/12/2016'
}
],
selectedPlayerIndex: -1
}
export default function Player(state=initialState, action) {
switch(action.type) {
case PlayerActionTypes.REMOVE_PLAYER:
const removePlayerList = [
...state.players.slice(0, action.index),
...state.players.slice(action.index + 1)
];
return {
...state,
players: removePlayerList,
selectedPlayerIndex: -1
}
case PlayerActionTypes.SELECT_PLAYER:
return {
...state,
selectedPlayerIndex: action.index
}
default:
return state;
}
}
here is the top level component
class Scoreboard extends Component {
static propTypes = {
players: PropTypes.array.isRequired,
selectedPlayerIndex: PropTypes.number.isRequired
};
render() {
const { dispatch, players, selectedPlayerIndex } = this.props;
const addPlayer = bindActionCreators(PlayerActionCreators.addPlayer, dispatch);
const removePlayer = bindActionCreators(PlayerActionCreators.removePlayer, dispatch);
const updatePlayerScore = bindActionCreators(PlayerActionCreators.updatePlayerScore, dispatch);
const selectPlayer = bindActionCreators(PlayerActionCreators.selectPlayer, dispatch)
const playerComponents = players.map((player, index) => (
<Player
index={index}
name={player.name}
score={player.score}
key={player.name}
updatePlayerScore={updatePlayerScore}
removePlayer={removePlayer}
selectPlayer={selectPlayer}
/>
));
return (
<div className="scoreboard">
<Header players={players} />
<div className="players">
{ playerComponents }
</div>
<AddPlayerForm addPlayer={addPlayer} />
<div className="player-detail">
<PlayerDetail players={players} selectedPlayerIndex={selectedPlayerIndex} />
</div>
</div>
);
}
}
const mapStateToProps = state => (
{
players: state.players,
selectedPlayerIndex: state.selectedPlayerIndex
}
);
export default connect(mapStateToProps)(Scoreboard);
Here is the component that child component Where remove_player is dispatched
import React, { PropTypes } from 'react';
import Counter from './Counter';
const Player = props => (
<div className="player">
<div className="player-name" onClick={() => props.selectPlayer(props.index)}>
<a className="remove-player"
onClick={() => props.removePlayer(props.index)}>
✖
</a>
{props.name}
</div>
<div className="player-score">
<Counter
index={props.index}
updatePlayerScore={props.updatePlayerScore}
score={props.score}
/>
</div>
</div>
);
I've tried to keep the code to only what I think is necessary, if you need to see other parts of the app just let me know.
It looks like you've got a nested onClick event. When you click on removePlayer it propagates up to the selectPlayer. This is due to event bubbling. You need to stop the event from propagating up to the selectPlayer component.
event.stopPropagation();
You'll need to change the removePlayer onClick function to something like:
function(event) {
props.removePlayer(props.index);
event.stopPropagation();
}
More information on event bubbling and capturing can be found here
Related
Order is an array of Objects and there is a key 'count' inside each variable. When ADD_ITEM_IN_ORDER case is executed than the count of particular object should be increment by 1. But, in this case, when the particular item is already present in the array than the value of count of that item incrementing by 2, which should not happen(it should increment by 1).
reducer.js
export const initialState = {
Order: [],
};
const reducer = (state, action) => {
console.log(action);
switch (action.type) {
case "ADD_ITEM_IN_ORDER":
const tempOrder1 = [...state.Order];
const index1 = state.Order.findIndex((item) => item.id === action.item.id);
if (index1 >= 0) {
console.log("before",tempOrder1[index1].Count);
tempOrder1[index1].Count += 1;
return { ...state, Order: tempOrder1 };
}
else {
console.log("New item added");
return {
...state,
Order: [...state.Order, action.item]
};
}
default:
return state;
}
};
export default reducer;
action file
import React from 'react';
import VegIcon from '../Images/VegIcon.png';
import NonVegIcon from '../Images/NonVegIcon.png';
import { useStateValue } from '../StateProvider';
import { db } from '../firebase';
const CartMenu = (props) => {
const [{ Order }, dispatch] = useStateValue();
const add = () => {
dispatch({
type: "ADD_ITEM_IN_ORDER",
item: {
id: props.id,
menuCollectionName:props.menuCollectionName,
VegNonV: props.VegNonV,
Menu: props.Menu,
Price: props.Price,
Count: 1,
// RestuarantId: props.restuarantId
}
});
console.log(Order);
};
const remove = () => {
dispatch({
type: "REMOVE_ITEM_FROM_ORDER",
item: {
id: props.id, // id of a dish
restuarantId: props.restuarantId
}
});
};
return (
<div className='Menu_Display'>
<div className='Menu_Display_subsection1'>
{props.VegNonV === "Veg" ?
<img className="Menu_Veg_NonVeg" src={VegIcon} />
:
<img className="Menu_Veg_NonVeg" src={NonVegIcon} />
}
<div className='Menu_Name'>{props.Menu}</div>
</div>
<div className="Menu_Add_Button" >
<div className="Menu_minus" onClick={remove}>−</div>
<span>{props.Count}</span>
<div className="Menu_plus" onClick={add}>+</div>
</div>
<div className='Menu_Price'>
<span></span>
<span>₹{Math.round(props.Price * props.Count * 100) / 100}</span>
</div>
</div>
);
};
export default CartMenu;
StateProvider.js
//setup data layer
// we need this to track the basket data
import React,{createContext,useContext,useReducer} from 'react';
export const StateContext = createContext();
//Build Provider
export const StateProvider = ({reducer,initialState,children}) =>
(
<StateContext.Provider value = {useReducer(reducer,initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
The context API broadcasts updates when it notices a change in the value. Since you are invoking useReducer within the value props, that returns an array (state value and dispatch function), it is this, that is likely causing double dispatch. I recommend you re-write your Provider logic.
import React,{createContext,useContext,useReducer} from 'react';
export const StateContext = createContext({ //Make sure to export this
Order:[], //Will be consuimg the state value from here
addItem:(arg)=>{} //This function will be hooked to a dispatch function below
});
//Build Provider
export const StateProvider = ({reducer,initialState,children}) =>
{
const [state,dispatchFn] = useReducer(reducer,initialState)
const addItemHandler = (item) => {
dispatchFn(item)
}
return(<StateContext.Provider value = {{Order:state.Order,addItem:addItemHandler}}>
{children}
</StateContext.Provider>)
};
You can then wrap your root component with the context provider component StateProvider so that all the components can access the state values.
In your index.js wrap the component like this:
import {StateProvider} from './path/to/provider'
ReactDOM.render(<StateProvider><App/></StateProvider>,doucment.getElementById("root"))
You can then use the context state and dispatch function from useContext hook by, passing the context variable.
In your action file:
import {useContext},React from 'react'; //Import use context
import {StateContext} from './path/to/state/context'
import VegIcon from '../Images/VegIcon.png';
import NonVegIcon from '../Images/NonVegIcon.png';
import { db } from '../firebase';
const CartMenu = (props) => {
const order_ctx = useContext(StateContext);
const add = () => {
order_ctx.addItem({
type: "ADD_ITEM_IN_ORDER",
item: {
id: props.id,
menuCollectionName:props.menuCollectionName,
VegNonV: props.VegNonV,
Menu: props.Menu,
Price: props.Price,
Count: 1,
// RestuarantId: props.restuarantId
}
});
console.log(Order);
};
const remove = () => {
order_ctx.rmItem({ //Note: rmItem method is not added to createContext, but this is just to demonstrate how state update methods can be invoked by using the context instance.
type: "REMOVE_ITEM_FROM_ORDER",
item: {
id: props.id, // id of a dish
restuarantId: props.restuarantId
}
});
};
return (
<div className='Menu_Display'>
<div className='Menu_Display_subsection1'>
{props.VegNonV === "Veg" ?
<img className="Menu_Veg_NonVeg" src={VegIcon} />
:
<img className="Menu_Veg_NonVeg" src={NonVegIcon} />
}
<div className='Menu_Name'>{props.Menu}</div>
</div>
<div className="Menu_Add_Button" >
<div className="Menu_minus" onClick={remove}>−</div>
<span>{props.Count}</span>
<div className="Menu_plus" onClick={add}>+</div>
</div>
<div className='Menu_Price'>
<span></span>
<span>₹{Math.round(props.Price * props.Count * 100) / 100}</span>
</div>
</div>
);
};
export default CartMenu;
And also since, your state contains only an array of orders, you can just return the updated array without having to override the previous state.
In your reducer:
case "ADD_ITEM_IN_ORDER":
const tempOrder1 = [...state.Order];
const index1 = state.Order.findIndex((item) => item.id === action.item.id);
if (index1 >= 0) {
console.log("before",tempOrder1[index1].Count);
tempOrder1[index1].Count += 1;
return { Order: [...tempOrder1] }; //Return updated array only
}
else {
console.log("New item added");
return {
Order: [...state.Order, action.item] //Return updated array only
};
}
default:
return state;
}
I'm new to react and I'm trying component functional style.
I have simple todo list. I would like to strike out todo item from list using style property. From Chrome debug mode I do not see immediate reaction on checkbox changes, also Item is not striked out... It seams to me, that it is problem with how I manage state of components. I would appreciate some guidance.
App.js
import React, {useState} from 'react';
import Todos from "./components/Todos";
import './App.css'
const App = () => {
const [todos, setTodos] = useState(
[
{id: 1, title: 'Take out the trash', completed: false},
{id: 2, title: 'Dinner with wife', completed: false},
{id: 3, title: 'Meeting boss', completed: false}
]
);
const markComplete = id => {
console.log((new Date()).toString());
todos.map(todo => {
if (todo.id === id) {
todo.completed = ! todo.completed;
}
return todo;
});
setTodos(todos);
};
return (
<div className="App">
<Todos todos={todos} markComplete={markComplete}/>
</div>
);
};
export default App;
Todos.js
import React from "react";
import TodoItem from "./TodoItem";
const Todos = ({todos, markComplete}) => {
return (
todos.map(todo => (
<TodoItem key={todo.id} todoItem={todo} markComplete={markComplete} />
))
);
};
export default Todos;
TodoItem.js
import React from "react";
const TodoItem = ({todoItem, markComplete}) => {
const getStyle = () => {
console.log("style: " + todoItem.completed);
return {
background: '#f4f4f4',
padding: '10px',
borderBottom: '1px #ccc dotted',
textDecoration: todoItem.completed ? 'line-through' : 'none'
}
};
return (
<div style={getStyle()}>
<p>
<input type="checkbox" onChange={markComplete.bind(this, todoItem.id)}/>{' '}
{todoItem.title}
</p>
</div>
);
};
export default TodoItem;
I expect that this getStyle() will follow state... somehow...
Don't mutate state. In markComplete function, you are mutating the todos array directly. Change your function like this to avoid mutation
const markComplete = id => {
console.log((new Date()).toString());
let newTodos = todos.map(todo => {
let newTodo = { ...todo };
if (newTodo.id === id) {
newTodo.completed = !newTodo.completed;
}
return newTodo;
});
setTodos(newTodos);
};
Array.prototype.map() returns a new Array, which you are throwing away. You need to use the new array, e.g.:
const markComplete = id => {
...
setTodos(totos.map(...))
I am using react-redux.
I have the following JSX (only relevant snippets included):
getQuestionElement(question) {
if (question) {
return <MultiChoice questionContent={this.props.question.question} buttonClicked={this.choiceClicked} />
}
else {
return (
<div className="center-loader">
<Preloader size='big' />
</div>
)
}
}
render() {
return (
<div>
<Header />
{
this.getQuestionElement(this.props.question)
}
</div>
)
}
function mapStateToProps({ question }) {
return { question };
}
export default connect(mapStateToProps, questionAction)(App);
When the action fires, and the reducer updates the question prop
this.props.question
I expect
{this.getQuestionElement(this.props.question)}
to be reloaded and the new question rendered.
However this is not happening. Am I not able to put a function in this way to get it live reloaded?
My MultiChoice component:
import React, { Component } from 'react';
import ReactHtmlParser from 'react-html-parser';
import './questions.css';
class MultiChoice extends Component {
constructor(props) {
super(props);
this.state = {
question: this.props.questionContent.question,
answerArray : this.props.questionContent.answers,
information: null
}
this.buttonClick = this.buttonClick.bind(this);
}
createButtons(answerArray) {
var buttons = answerArray.map((element) =>
<span key={element._id} onClick={() => { this.buttonClick(element._id) }}
className={"span-button-wrapper-25 " + (element.active ? "active" : "")}>
<label>
<span>{element.answer}</span>
</label>
</span>
);
return buttons;
}
buttonClick(id) {
var informationElement;
this.props.buttonClicked(id);
var buttonArray = this.state.answerArray.map((element) => {
if (element._id === id ){
element.active = true;
informationElement = element.information;
return element;
}
else{
element.active = false;
return element;
}
});
this.setState({
answerArray: buttonArray,
information: informationElement
})
}
render() {
return (
<div className="question-container">
<div className="question-view">
<div className="icon-row">
<i className="fa fa-code" />
</div>
<div className="title-row">
{this.state.question}
</div>
<div className="button-row">
{this.createButtons(this.state.answerArray)}
</div>
<div className="information-row">
{ReactHtmlParser(this.state.information)}
</div>
</div>
</div>
);
}
}
export default MultiChoice;
QuestionAction.js
import axios from "axios";
import { FETCH_QUESTION } from "./types";
export const fetchQuestion = (questionId, answerId) => async dispatch => {
let question = null;
if (questionId){
question = await axios.get("/api/question/next?questionId=" + questionId + "&answerId=" + answerId);
}
else{
question = await axios.get("/api/question/next");
}
console.log("question", question);
dispatch({ type: FETCH_QUESTION, payload: question });
};
questionReducer.js
import {FETCH_QUESTION } from "../actions/types";
export default function(state = null, action) {
switch (action.type) {
case FETCH_QUESTION:
console.log("payload", action.payload.data);
return { question: action.payload.data, selected: false };
default:
return state;
}
}
index.js (Combined Reducer)
import { combineReducers } from 'redux';
import questionReducer from './questionReducer';
export default combineReducers({
question: questionReducer
});
and my entry point:
index.js
const store = createStore(reducers, {}, applyMiddleware(reduxThunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
requested console.log response:
render() {
console.log("Stackoverflow:", this.props.question)
.....
and after clicking the button (and the reducer updating, the console.log is updated, but the
this.getQuestionElement(this.props.question)
does not get re-rendered
MultiChoice Component shouldn't store his props in his state in the constructor, you have 2 options here :
Handle props changes in componentWillReceiveProps to update the state :
class MultiChoice extends Component {
constructor(props) {
super(props);
this.state = {
question: this.props.questionContent.question,
answerArray : this.props.questionContent.answers,
information: null
}
this.buttonClick = this.buttonClick.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({
question: nextProps.questionContent.question,
answerArray : nextProps.questionContent.answers,
information: null
});
}
We have to keep using the constructor to set an initial state as from docs :
React doesn’t call componentWillReceiveProps() with initial props
during mounting.
2nd Option : Make it as a "dumb component" by having no state and only using his props to render something (some more deep changes in your component to do, especially to handle the "active" element, it will have to be handled by the parent component).
I am using react and redux.
I have a Container component defined as so:
import { connect } from 'react-redux';
import {addTag} from 'actions';
import ExpenseTagsControl from './expense_tags_control'
const mapStateToProps = (state, own_props={selected_tags:[]}) => {
return {
tags_list: state.tags.tags_list
};
};
const mapDispatchToProps = (dispatch) => {
return {
addTag: (tag_name) => {
dispatch(addTag(tag_name))
}
};
};
const AddExpenseTagsContainer = connect(
mapStateToProps,
mapDispatchToProps
)(ExpenseTagsControl);
export default AddExpenseTagsContainer;
The container wraps a presentational component which is defined as so:
// expense_tags_control.js
import React, {Component, PropTypes} from 'react';
import ChipInput from 'material-ui-chip-input';
import Chip from 'material-ui/Chip';
import Avatar from 'material-ui/Avatar';
import Tag from 'common/svg_icons/tag';
import AutoComplete from 'material-ui/AutoComplete'
import _ from 'underscore';
class ExpenseTagsControl extends React.Component {
constructor(props) {
super(props);
this.state = {
chips: []
};
};
handleAdd(chip) {
// If the chip does not already exist, add it. the id here will be a dummy value that is not there in the tags_list
if (!(_.contains( _.map(this.props.tags_list, (tag) => tag.id), chip.id))) {
this.props.addTag(chip.name);
}
// This is wrong.
this.setState({
chips: [...this.state.chips, chip]
});
};
handleDelete(chip) {
this.setState({
chips: this.state.chips.filter((c) => c !== deletedChip)
});
};
chipRenderer({ text, value, isFocused, isDisabled, handleClick, handleRequestDelete }, key) {
const style = {
margin: '8px 8px 0 0',
float: 'left',
pointerEvents: isDisabled ? 'none' : undefined
};
return (
<Chip key={key} style={style} onTouchTap={handleClick} onRequestDelete={handleRequestDelete}>
<Avatar size={24} icon={<Tag />} />
{text}
</Chip>
);
};
render() {
return (
<ChipInput
hintText="Tags"
value={this.state.chips}
onRequestAdd={(chip) => this.handleAdd(chip)}
onRequestDelete={(deletedChip) => this.handleDelete(deletedChip)}
fullWidth={true}
dataSourceConfig={{ text: 'name', value: 'id' }}
dataSource={this.props.tags_list}
chipRenderer={this.chipRenderer}
openOnFocus={false}
filter={AutoComplete.fuzzyFilter}
onRequestDelete={console.log("Deleted")}
/>);
};
};
ExpenseTagsControl.PropTypes = {
tags_list: PropTypes.array.isRequired,
addTag: PropTypes.func.isRequired,
value: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired
};
export default ExpenseTagsControl;
The presentational component above, maintains a state, which indicates the chips that have been selected.
The ChipInput component allows you to select chips which are objects with an id, and a name, defined from a pre-existing data source. The component also allows you to add a new chip by typing in the name. If the typed in name does not exist in the data source, it is added to the data source.
My Problem
The id of the newly added chip is assigned once the addTag() action is dispatched. How do I get the value of the result of the action that was just dispatched?
I thought about working around this by maintaining the state of the ChipInput in the global state, and manipulate the global state upon dispatching the addTag() action. But that feels like too much overhead.
If what I understand is correct, you might want something like this:
class ExpenseTagsControl extends React.Component {
// ...
/*
* assuming your reducers are working fine and 'addTag'
* has updated global 'state.tags.tags_list'
*/
componentWillReceiveProps(nextProps) {
this.setState({ chips: this.nextProps.tags_list });
}
// ...
}
NB: You might need to optimize calling setState inside componentWillReceiveProps based on some conditions to avoid unnecessary re-render.
From what I understand, the OP's problem is how to dispatch an action to modify the redux store and at the same time update the component's local state.
Edit: added a working example
const initialState = {
tags: ['hello', 'hi', 'howdy']
}
function reducer(state = {}, action) {
switch (action.type) {
case 'ADD_TAG':
return {
...state,
tags: [
...state.tags,
action.payload.tag
]
}
default:
return state;
}
}
const store = Redux.createStore(reducer, initialState);
const addTag = (tag) => ({
type: 'ADD_TAG',
payload: {
tag
}
})
class Chips extends React.Component {
constructor(props) {
super(props);
this.chipToAdd = false;
this.state = {
chips: []
}
this.handleAdd = this.handleAdd.bind(this);
}
componentWillReceiveProps(nextProps) {
console.log(this.chipToAdd);
if (this.chipToAdd) {
this.setState({
chips: [...this.state.chips, this.chipToAdd]
}, (this.chipToAdd = false));
}
}
handleAdd(chip) {
if (this.props.tags.filter(tag => tag === chip).length === 0) {
this.chipToAdd = chip;
this.props.addTag(chip);
} else {
if (this.state.chips.filter(existingChip => existingChip === chip).length === 0) {
this.setState({
chips: [...this.state.chips, chip]
});
}
}
}
render() {
return <div >
< h3 > Tags added in component 's chip state</h3>
<ul>
{this.state.chips.map((chip, index) => <li key={index}>{chip}</li>)}
</ul>
<hr />
<h3>Tags in Redux Store</h3>
{this.props.tags.map(
(tag, index) => <li key={index}>
{tag} <button onClick={() => this.handleAdd(tag)}>Add</button>
</li>
)}
<button onClick={() => this.handleAdd('
new tag - ' + Math.floor((Math.random() * 100) + 1))}>Add a chip with new tag</button>
</div>
}
}
const mapStateToProps = ({ tags = [] }) => ({ tags });
const ConnectedChips = ReactRedux.connect(mapStateToProps, { addTag })(Chips);
class App extends React.Component {
render() {
return <div>
<h1>React/Redux Demo</h1>
<ConnectedChips />
</div>
}
}
const Provider = ReactRedux.Provider;
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.getElementById('
root ')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://unpkg.com/redux#3.6.0/dist/redux.min.js"></script>
<script src="https://unpkg.com/react-redux#4.4.6/dist/react-redux.min.js"></script>
<div id="root"></div>
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".