onChange() fires twice making changes to checkboxes impossible (react app) - reactjs

I did a tutorial on Scrimba (it's a website with an in-browser IDE) that had this code as one of the exercises. And it worked fine in that environment. But when I imported it into VS code and ran it using "npm start" I noticed my checkbox "onChange" events would fire twice. This caused them to not work as they essentially toggle back to the value they start at.
After this you'll see the code from the two files from the project. App.js is where the function handleChange() is defined. It is then sent as a prop to the TodoItem.js functional component. That component runs this function whenever someone clicks a checkbox. But for some reason, it runs twice in a row. I tried using onClick too and the same happens.
App.js
import React from "react"
import TodoItem from "./TodoItem"
import todosData from "./todosData"
import "./style.css"
class App extends React.Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState((prevState) => {
console.log("this gets printed 2 times")
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
return {
todos: updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map(item => <TodoItem key={item.id} item={item} handleChange={this.handleChange}/>)
return (
<div className="todo-list">
{todoItems}
</div>
)
}
}
export default App
TodoItem.js:
import React from "react"
function TodoItem(props) {
const completedStyle={
textDecoration: "line-through",
fontStyle: "italic",
color: "#cdcdcd"
}
return (
<div className="todo-item">
<input
type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<p style={props.item.completed ? completedStyle : null}>{props.item.text}</p>
</div>
)
}
export default TodoItem

Do you have your App component wrapped in React.StrictMode by any chance? Also, you could try putting event.preventDefault() inside your onChange().

Related

I'm trying to do state lifting in React. What am I missing?

I'm new to react - literally a couple of days old.
All I'm trying to do is state lifting. The state for CreateTweet is to be lifted to App.js which is the root so that the lifted states can be passed onto other components rendered by the root.
Things seem to be in place, but I'm getting the following error:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
App.js
class App extends Component {
render() {
const [textInput, setTextInput] = useState("");
const [tweets, setTweets] = useState([]);
const name = "SomeNameJohnDoe";
return (
<div>
<h1>Hello React learners!</h1>
<CreateTweet
textInput={textInput}
setTextInput={setTextInput}
tweets={tweets}
setTweets={setTweets}
/>
<TweetList name={name} tweets={tweets}/>
</div>
);
}
}
CreateTweet.js
function CreateTweet({textInput, setTextInput, tweets, setTweets}){
const userInputHandler = (e) => {
setTextInput(e.target.value);
}
const submitTweetHandler = (e) => {
e.preventDefault();
setTweets([...tweets, textInput]);
setTextInput("");
}
return(
<form onSubmit={submitTweetHandler}>
<textarea value={textInput} onChange={userInputHandler} cols="50" rows="5"></textarea>
<button>Submit</button>
</form>
);
}
Thank you for helping me out. Cheers!
You cannot use classes with react hooks. Simple as that.
More on class and functional components:
https://levelup.gitconnected.com/react-state-in-class-and-function-components-2269614579c4
class App extends Component {
state = {
textInput: "",
tweets: ""
}
textInputHandler = (val) => {
this.setState({
textInput: val
})
}
tweetsHandler = (val) => {
this.setState({
tweets: val
})
}
name = "SomeNameJohnDoe";
render() {
return (
<div>
<h1>Hello React learners!</h1>
<CreateTweet
textInput={this.state.textInput}
setTextInput={this.textInputHandler }
tweets={this.state.tweets}
setTweets={this.tweetsHandler }
/>
<TweetList name={this.name} tweets={this.tweets}/>
</div>
);
}
}
In React you should understand the difference between a functional component and class component. Hooks have been introduced in react 16.8 to compensate the lack of state maganement (useState), lifeCycle (ComponentWillMount, ComponentDidMount,...etc) in Functional component.
So every time you are trying to use a hook make sure you have declare a functional Component instead of a class.
P.S: a functionnal component is a pure Javascript function.
So you had 2 ways to implement your Component App
1)- As a functional Component
import React, {useState} from 'react';
const App =(props)=> { //if you are planning to use any props
const [textInput, setTextInput] = useState("");
const [tweets, setTweets] = useState([]);
const name = "SomeNameJohnDoe";
return (
<div>
<h1>Hello React learners!</h1>
<CreateTweet
textInput={textInput}
setTextInput={setTextInput}
tweets={tweets}
setTweets={setTweets}
/>
<TweetList name={name} tweets={tweets}/>
</div>
);
}
2)- As a class Component.
import React from 'react';
const name = "SomeNameJohnDoe";
class App extends Component {
state = {
textInput: "",
tweets: ""
}
setTextInput = (input) =>{
this.setState({textInput:input});
}
setTweets = (tweet) =>{
this.setState({tweets:tweet});
}
render() {
return(
<div>
<h1>Hello React learners!</h1>
<CreateTweet
textInput={this.state.textInput}
setTextInput={this.setTextInput}
tweets={this.state.tweets}
setTweets={this.setTweets}
/>
<TweetList name={name} tweets={this.state.tweets}/>
</div>
);
}
}

I can't figure out why setState function not working for setting a value

Hope you are all well and healthy.
I am learning react and following a tutorial : https://www.youtube.com/watch?v=DLX62G4lc44&t=9947s
Time is also mentioned in YouTube video where I got stuck. This is my Code:
This is the ToDoComponent
import React from 'react';
function ToDoItem(props){
return(
<div className="tasks">
<label>{props.item.taskName}</label><input type="checkbox"
checked={props.item.completed} onChange={() => props.handleChange(props.item.Id)}/>
</div>
);
}
export default ToDoItem;
And This is the App.js
import React, { Component } from 'react';
import './App.css';
import ToDoItem from './components/ToDoItem';
import toDoData from './datas/tododata';
class App extends Component{
constructor(){
super();
this.state = {todos : toDoData}
this.handleChange = this.handleChange.bind(this);
}
handleChange(Id){
this.setState(prevState => {
const updatedToDos = prevState.todos.map(todo => {
if(todo.Id == Id){
console.log(todo.completed)
todo.completed = !todo.completed
console.log(todo.completed)
}
return todo
})
return{
todos : updatedToDos
}
})
}
render(){
var toDoComponent = this.state.todos.map(
item =>
<ToDoItem key={item.Id} item={item} handleChange={this.handleChange}/>
);
return(
<div>
{toDoComponent}
</div>
);
}
}
export default App;
Now look at this picture. When I click the CheckBox, the setState is called twice and the CheckBox value is not changing because of that.
I can't find where the problem is.
It sounds like you are using React's strict mode, which causes setState updater functions, and other lifecycle functions, to be called twice in order to test whether these functions are actually pure, as they should be.
It seems the problem here is that you are mutating your state, on this line:
todo.completed = !todo.completed
Here, you are modifying an object that is part of the previous state. So you are modifying the previous state when you should only be creating a new state and leaving the previous one alone.
Strict mode is intended to identify these kinds of problems.
To solve this, make sure that your setState updater does not mutate the state:
const updatedToDos = prevState.todos.map(todo => {
if(todo.Id == Id){
console.log(todo.completed)
const newTodo = { ...todo, completed: !todo.completed }
console.log(newTodo.completed)
return newTodo;
}
return todo
})

Stripe - how do I save card element in react?

I'm trying to save card details for use later.
I have generated the SetupIntent client secret
I'm trying to use confirm card setup.
I'm following the docs here for react.
The following line:
const cardElement = this.props.elements.getElement('card')
is throwing me this error:
TypeError: Cannot read property 'getElement' of undefined
Where am I going wrong? My code is below:
This is the relevant portion of the main component:
import React from "react";
import { Elements, StripeProvider } from "react-stripe-elements";
import SaveCardForm from "./SaveCardForm";
<StripeProvider
apiKey={process.env.REACT_APP_API_STRIPE_PUBLISH}
>
<Elements>
<SaveCardForm/>
</Elements>
</StripeProvider>
And this is the SaveCardForm component
import React, { Component } from "react";
import { Stripe, CardElement, injectStripe } from "react-stripe-elements";
import axios from "axios";
class SaveCardForm extends Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
submit = e => {
e.preventDefault()
const cardElement = this.props.elements.getElement('card');
axios.get(`${process.env.REACT_APP_API}/saveCardDetails`).then(res => {
console.log('res.data', res.data)
this.props.stripe.confirmCardSetup(res.data.client_secret, {
payment_method: {
card: cardElement,
},
}).then( confirmCardSetupRes => {
console.log('confirmCardSetupRes', confirmCardSetupRes)
})
})
}
render() {
return (
<div>
<CardElement />
<button onClick={this.submit}>
Bid For Tickets
</button>
</div>
);
}
}
export default injectStripe(SaveCardForm);
Given your components, there is no prop named elements passed into SaveCardForm. If it's access to CardElement you are after, use a ref which will give you a direct reference to that component e.g.
constructor(props) {
...
this.cardEl = React.createRef();
}
submit = e => {
...
const card = this.cardEl.current.<accessDomHere>;
this.props.stripe.confirmCardSetup(res.data.client_secret, {
payment_method: {
card
},
}).then(...)
}
render() {
...
<div>
<CardElement ref={this.cardEl} />
...
</div>
}
Switch out <accessDomHere> for whatever DOM query you need to perform to get the information you need. There may even be a React property or function you can access (I'm not familiar with the component).
I resolved this by updating to the latest version of react-stripe-elements.
There is an error in the versions before 5.1.0

Reactjs refresh parent after react modal popup changes data

I am using react modal to set up a pop up in on one of my components. I have a component that renders a div wrapped with react modal. My parent component renders the modal component on load with the isOpen set to false. Clicking a link on the parent sets isOpen to true and causes the modal popup to open.
I make changes to my data within the open popup and then save the changes and close the model when the close button is clicked.
I am using a redux set up with actions and reducers handling the data changes and state changes.
Conceptually, how would I update the parent to show the changes made from the popup? Shouldn't changes to my data using an action cause the store to regenerate and hence update the data in my components or do I have to explicitly "refresh" my store after saving? My issue is that right now when the popup closes it is not tirggering any kind of "refresh" on the DataView component.
Components below:
DataView component
import React from 'react';
import DataView from './MonthView.js';
import DataViewPopup from './MonthViewPopup.js';
import { connect } from 'react-redux';
import { getAction } from '../actions/actions.js';
import { getActionAll } from '../actions/actions.js';
import { getPopupData } from '../actions/actions.js';
class DataViewContainer extends React.Component {
constructor() {
super();
this.popupCategory = undefined;
this.popupMonth = undefined;
this.state = {
detailPopup : false,
refreshView: false
}
this.handleAddYear = this.handleAddYear.bind(this);
this.handleSubtractYear = this.handleSubtractYear.bind(this);
this.handleGetDetail = this.handleGetDetail.bind(this);
}
componentDidMount() {
this.props.getAction(2016);
this.props.getActionAll(2016);
}
render() {
return (
<div className="row">
<div className="col-sm-8">
<MonthView transactions={this.props.storeData.storeData} selectedYear={this.props.storeData.selectedYear} onAddYear={this.handleAddYear} onSubtractYear={this.handleSubtractYear} onHandleGetDetail={this.handleGetDetail} />
</div>
<div className="col-sm-4">
<MonthViewPopup modalActive={this.state.detailPopup} transactions={this.props.storePopupData.getPopupData} selectedYear={this.props.storeTransactions.selectedYear} category={this.popupCategory} month={this.popupMonth}/>
</div>
</div>
)
}
handleGetDetail(category,month) {
console.log("props inside handleGetDetail: ", this.props);
this.popupCategory = category;
this.popupMonth = month;
let popupYear = this.props.storeTransactions.selectedYear
this.props.getPopupData(popupYear, month, category);
this.setState({ detailPopup: true}, function () {});
}
}
const mapStateToProps = (state) => ({
storeData: state.storeData,
storePopupData: state.storePopupData,
storeDataAll: state.storeDataAll
});
export default connect(mapStateToProps, {getAction,getActionAll,getPopupData})(DataViewContainer);
DataViewPopup component
import React from 'react';
import Modal from 'react-modal';
import { connect } from 'react-redux';
import { saveAction } from '../actions/actions.js';
import { getActionAll } from '../actions/actions.js';
class DataViewPopup extends React.Component {
constructor (props) {
super(props)
this.editedData = new Map();
this.state = {
modalIsOpen: false
};
this.openModal = this.openModal.bind(this);
this.afterOpenModal = this.afterOpenModal.bind(this);
this.closeModal = this.closeModal.bind(this);
this.renderFilteredTransactions = this.renderFilteredTransactions.bind(this);
}
openModal() {
this.setState({modalIsOpen: true});
}
afterOpenModal () {
console.log("inside afterOpenModal");
}
closeModal () {
this.props.saveTransactions(this.editedData);
this.editedData = new Map();
this.setState({ modalIsOpen: false }, function () {});
}
componentWillUnmount() {
this.props.getDataAll(); // i tried this but it does not work because the component (modal popup) is still mounted, but just not visible
//this.props.refreshParent();
}
componentWillReceiveProps(nextProps){
if (nextProps.modalActive === true) {
this.openModal();
return;
}
}
render () {
return <div>
<Modal
isOpen={this.state.modalIsOpen}
//isOpen={this.modalActive}//not needed as is currently setup
onAfterOpen={this.afterOpenModal}
onRequestClose={this.closeModal}
//style={customStyles}
contentLabel="Example Modal"
>
<button onClick={this.closeModal}>close</button>
{this.renderFilteredTransactions(this.props.data,this.props.category,this.props.month)};
</Modal>
</div>
}
renderFilteredTransactions(trans,category,month){
return <div>
<table>
<tbody className="mo-tblBody">
{trans && trans.map((data,index) =>
<tr key={data.transid}>
<td>{data.transid}</td>
<td>
{this.renderCategory(data.category,index,data.transid)}
</td>
</tr>
)}
</tbody>
</table>
</div>
}
handleCategoryChange(value,transIndex,transId){
let trans = JSON.parse(JSON.stringify(this.props.transactions));
//add or updated transaction to this.editedTransactions based on whether or not the transid already exists
trans.filter(item => item.transid === transId)
.map(item => this.editedData.set(transId,
Object.assign({}, item, { category: value })));
}
}
const mapStateToProps = (state) => {
return {storeDataAll: state.storeDataAll,
storeSaveData: state.storeSaveData
}
};
export default connect(mapStateToProps, {getDataAll,saveData})(DataViewPopup);
I covered this kind of question in my recent post Practical Redux, Part 10: Managing Modals and Context Menus.
The basic approaches are:
Pass a callback function as a prop to the modal component (which will work, but if you're driving the modal display from Redux state, putting functions into the Redux state is not encouraged)
Create a "pre-built" action, pass that as a prop to the modal, and have the modal dispatch that action as a "return value" when it closes
Use a middleware like redux-promising-modals to track actions for opening and closing modals, and use the promises it returns to handle "return values" when modals are closed.

forceUpdate is not re-rendering children

I'm using the react, redux react-router stack for my webapp. In the top level component's(the component that renders on the root path) componentDidMount I'm subscribing to the store as shown below
import NotificationsList from './components/notifier';
import React from 'react';
let Spinner = ({
isVisible,
showSpinner,
solidBackdrop
}) => (
<div style={{opacity: solidBackdrop ? 1 : 0.5}} className={"spinner " + (isVisible ? '' : 'hide')}></div>
);
export default class AppPage extends React.Component {
static contextTypes = {
store: React.PropTypes.object,
router: React.PropTypes.object
};
handleDismissNotification(notification) {
this.context.store.dispatch({
type: 'REMOVE_NOTIFICATION',
data: notification
});
}
componentDidMount() {
this.context.store.subscribe(() => this.forceUpdate());
}
render() {
let state = this.context.store.getState();
let props = {
notifications: state.notifications,
handleDismiss: this.handleDismissNotification.bind(this)
};
return (
<div className="'apppage-container">
{this.props.children}
<NotificationsList {...props} />
<Spinner isVisible={state.initialFetchInProgress || state.requestInProgress}
showSpinner={!state.initialFetchInProgress} solidBackdrop={state.initialFetchInProgress}/>
</div>
);
}
}
this.props.children here renders the component shown below
import Header from './components/header';
import React from 'react';
class ContentPage extends React.Component {
static contextTypes = {
store: React.PropTypes.object
};
render() {
let user = this.context.store.getState().user;
return <div className="content-container">
<Header user/>
</div>
}
}
export default ContentPage;
The problem is that when the first time a render happens, everything goes fine. Then when the render happens through forceUpdate, the child component is not getting re-rendered.
I think I got it. Every container component should be subscribed to the store separately. So accordingly, ContentPage should also have
componentDidMount() {
this.context.store.subscribe(() => this.forceUpdate());
}
As you replied to yourself, indeed the container component should subscribe to the store , but in addition to the subscription, it's good practice for the the container to also unsubscribe when unmounted :
componentDidMount() {
this.unsubscribe = this.context.store.subscribe(() => this.forceUpdate());
}
componentWillUnmount() {
this.unsubscribe();
}

Resources