Having trouble incrementing the age when the button is clicked - javascript

New to react and I am having a hard time trying to increment the age of the person when the button is clicked.
EventComp.js code:
import React, { Component } from 'react';
class EventComp extends Component{
constructor(props) {
super(props);
this.state = {
clicks: 0,
show: true,
};
}
IncrementItem = () => {
this.setState({clicks: this.state.clicks + 1});
}
render(){
return(
<div>
<div class = "person">
<h1 class="person_h1">
{this.props.LastName},
{this.props.FirstName}</h1>
<p>Age: {this.props.Age}</p>
</div>
<button onClick = {this.IncrementItem}>Celebrate</button>
</div>
)
}
}
export default EventComp;
App.js code:
import React from 'react';
import EventComp from './components/EventComp';
import './App.css';
function App() {
return (
<div class ="main">
<div>
<EventComp FirstName="Jane "
LastName=" Doe " Age = {45}/>
</div>
<div>
<EventComp FirstName = "Joe " LastName = " Doe " Age={88}/>
</div>
<div>
</div>
</div>
);
}
export default App;
I'm just confused on how it's supposed to work. There are no errors, but when the button is clicked nothing happens.

…having a hard time trying to increment the age…
A common way to do this sort of thing is to have a parent component and a child component (App and EventComp) with responsibilities separated such that the parent manages the state (keeps track of the age) and the child (EventComp) is a "dumb" component that just renders whatever information it's given.
You're already doing most of this by passing the "person" information as props:
<EventComp FirstName="Joe" LastName="Doe" Age={88} /> // 👍
If you keep track of the age as state in App, then changing "age" will re-render, passing the updated age to EventComp:
function App () {
// create a state variable and a setter function for updating its value
const [age, setAge] = React.useState(88);
// pass age as a prop:
return (
<EventComp FirstName="Joe" LastName="Doe" Age={age} />
);
}
How does age get updated? By calling setAge. Here's the same example from above with the addition of a button that increments age:
function App () {
const [age, setAge] = React.useState(88);
return (
<div>
<button onClick={() => setAge(age + 1)}>Increment Age</button>
<EventComp FirstName="Joe" LastName="Doe" Age={age} />
</div>
);
}
Each click will trigger a re-render showing the new age.
But you want the button in EventComp not in App. How do we connect the button in EventComp to the state in App? By passing the update function as a prop:
function App () {
const [age, setAge] = React.useState(88);
return (
<EventComp
FirstName="Joe"
LastName="Doe"
Age={age}
onIncrementAge={() => setAge(age + 1)} // <= pass the age incrementing function as a prop
/>
);
}
function EventComp (props) {
return (
<div>
<div class = "person">
<h1 class="person_h1">
{props.LastName},
{props.FirstName}</h1>
<p>Age: {props.Age}</p>
</div>
<button onClick={props.onIncrementAge}> // <= onClick calls the function provided via props
Celebrate
</button>
</div>
)
}
Using this pattern, there's a single source of truth about the value of age, and EventComp doesn't need to know or care where it comes from.
Extending this to handle more than one person:
The examples above demonstrate the fundamental pattern but don't address the need to handle more than one person. Suppose you have a list of people:
const people = [
{
firstName: 'Jane',
lastName: 'Doe',
age: 45
},
{
firstName: 'Joe',
lastName: 'Doe',
age: 88
},
]
You can iterate over this list, rendering a component for each person:
function App () {
// initial state with 2 people
const [people, setPeople] = React.useState([
{ firstName: 'Jane', lastName: 'Doe', age: 45 },
{ firstName: 'Joe', lastName: 'Doe', age: 88 },
]);
return (
people.map((person) => ( // render an EventComp for each person
<EventComp
FirstName={person.firstName}
LastName={person.lastName}
age={person.age}
key={person.firstName} // key is a react thing. ignore for now.
/>
));
)
}
Updating a person's age becomes slightly more complicated, but it's still fairly straightforward. We need a function that does the following:
Find the person by index (their position in the list)
Update their age
Update state with the new info
const incrementAge = index => {
const person = people[index];
person.age += 1;
setPeople([...people]);
}
Pass this new function to EventComp and you're done.
function App () {
// initial state with 2 people
const [people, setPeople] = React.useState([
{ firstName: 'Jane', lastName: 'Doe', age: 45 },
{ firstName: 'Joe', lastName: 'Doe', age: 88 },
]);
const incrementAge = index => {
const person = people[index];
person.age += 1;
setPeople([...people]); // spread to a new array so react recognizes it as new
}
return (
people.map((person, index) => ( // render an EventComp for each person
<EventComp
FirstName={person.firstName}
LastName={person.lastName}
age={person.age}
onIncrementAge={() => incrementAge(index)} // <= indicate which person via index
key={person.firstName} // key is a react thing. ignore for now.
/>
));
)
}
Notice that we've added the index argument to the map call:
people.map((person, index) => {
And we're using it when we invoke the click handler:
onIncrementAge={() => incrementAge(index)}
Voila.
You could also pass the person as a single prop:
<EventComp
person={person}
onIncrementAge={() => incrementAge(index)}
/>
// use props.person.FirstName, etc. in EventComp
Hope this helps.

You're not updating age in your IncrementItem function. Instead, you're updating clicks. The reason you're not seeing anything happening is because clicks is not rendered anywhere. If you add a log statement and log clicked, you will see it being updated.
But if you are interested in incrementing age, you first need to add state to age:
this.state = {
clicks: 0,
show: true,
age: this.props.Age
};
As a note, in the future, props are usually lowercase and I would suggest having that prop be passed in as "age" instead.
The second thing you should do in your increment function is to update the age rather than clicks. If you add a console statement, you will see that the function is actually being invoked. You will also notice that age is being incremented rather than clicks because we are interested in that piece of state. The function should also be defined where the state is defined to retain context.
Note: functions in react are lowercased, components are capitalized. Functions also tend to use a "handle" naming convention, in this case, the function should be named something like "handleIncrementAge"
IncrementItem = () => {
this.setState({clicks: this.state.clicks + 1,
age: this.state.age + 1
});
}
Lastly, in your render, because it seems like you are interested in updating state, you want to display that piece of state.
<p>Increment Age: {this.state.age}</p>
*class attributes should be changed to className because class is a reserved keyword in React. Not doing so will result in your styles not being rendered.
I have attached a CodeSandbox with your code and the recommended changes: Code Sandbox
Updating State with React Class Components

Related

React : Best practice to handle complex objects passed as props

Lets assume I have a React component (functional component) and i am passing multiple props. One of these props is a nested object like...
Example nested object
const user = {
id: 101,
email: 'jack#dev.com',
personalInfo: {
name: 'Jack',
address: {
line1: 'westwish st',
line2: 'washmasher',
city: 'wallas',
state: 'WX'
}
}
}
For simplicity I want to pass the whole object, but instead of accessing it via user.personalInfo.adress.line1 I would like to save this to variable.
I am passing it to the following component...
Example React Component
const ExampleComponent = (props) => {
// Example 1
const [state1, setState1] = useState();
useEffect(() => {
setState1(user.personalInfo.adress.line1);
})
// Example 2
const state1Var = user.personalInfo.adress.line1
}
I know that saving props to states is a bad practice. (= Example1) So whats the smartest way to do that?
Saving it to a class variable? (= Example2)
You do not need a state for this. You can simply destructure the object on render of components.
Note: using useEffect without dependency variable, will retrigger the callback function on each render. It will be a memory issue useEffect(() => {},[]).
const user = {
id: 101,
email: "jack#dev.com",
personalInfo: {
name: "Jack",
address: {
line1: "westwish st",
line2: "washmasher",
city: "wallas",
state: "WX",
},
},
};
const ExampleComponent = ({ user = {} }) => {
const { personalInfo = {} } = user;
const { name = "somedefault name", address = {} } = personalInfo;
return (
<div>
{name}
<br />
{address.line1}
<br />
{address.line2}
</div>
);
};
There's nothing wrong with passing complex objects as props. Why do you need to save it to state? Just destructure the object in the child component (assuming it is not possibly null or undefined). React functional components are just functions, you don't need to save something to state in order to use it. Since you're passing it as a prop from a parent component, presumably the parent component keeps track of its state; when the parent state updates, the prop will change and your child component will rerender (or, in other words, your child component's function will be called with the new prop value(s)).
const ExampleComponent = (props) => {
const {personalInfo} = props.user;
/* ... */
}

Why we are making copy of reference types in react before we mutate them?

I am new in react world. I have this example code here where with the deletePersonHandler method i am deleting some objects from the array.
class App extends Component {
state = {
persons: [
{ name: "peter", age: 24 },
{ name: "john", age: 25 },
{ name: "jake", age: 30 }
],
showPersons: true
}
deletePersonHandler = index => {
const persons = this.state.persons;
persons.splice(index,1);
this.setState({persons:persons})
}
togglePersonsHandler = () => {
this.setState({ showPersons: !this.state.showPersons })
}
render() {
let persons = null;
if (this.state.showPersons) {
persons = (
<div>
{this.state.persons.map((person, index) => {
return <Person
click={() => this.deletePersonHandler(index)}
name={person.name}
age={person.age}
key={index}
/>
})}
</div>
);
}
return (
<div className="App">
{persons}
<button onClick={this.togglePersonsHandler}>Click to hide/show</button>
</div>
)
}
}
export default App;
and it is working fine.
My question is:
when this works - without making copy in this case on persons
deletePersonHandler = index => {
const persons = this.state.persons;
persons.splice(index,1);
this.setState({persons:persons})
}
why the recommended way is to make the COPY FIRST AND THEN MODIFY THE REFERENCE TYPE ?
deletePersonHandler = index => {
const persons = [...];
persons.splice(index,1);
this.setState({persons:persons})
}
I am explaining this based on my experience with object in JavaScript. I had one publisher and subscriber code where there was an array of object which used to keep track of some message number and their handler like this
let observer = {"8000":[handler1, handler2]};
So when something happens i publish 8000 message and all handlers get executed like this
for(var item in observer[8000]){
//execute handler/
}
. Till here it was working pretty cool. Then I started removing handler when it has been processed. So after removing handler length of array observer[8000] reduced by 1. So in next sequence it could not find next handler which didn't execute(Objects are pass by reference in JavaScript). So to resolve this I had to make a copy of array object before directly modifying this. In short if object has many dependencies then before processing make copy of it then process or if it is used only single place then use in place processing. It depends on situation, there aren't any strict rules to follow like copy then process.

How to remove object from Array in ReactJS after click event

I'm building little app in React and I have a problem to remove and object from Array after I click on icon.
I have form with inputs and when user fill this inputs and click on a button then inputs will into an array. I have array in a state and the default is empty array.
const [attendees, setAttendees] = useState([]);
I have a function for delete a object from array after I click on icon
const deleteAttendee = (attendee) => {
setAttendees({
attendees: attendees.filter(
(element) => element.firstName !== attendee.firstName
),
});
};
but there is a problem when I clicked on icon I get an error. That map is not a function.
I have in another component map function for rendering each user from the inputs
<div className="attendee-container">
{attendees.map((_attendee, index) => {
return (
<div key={index}>
<div onClick={() => deleteAttendee(_attendee)}>{trashAltIcon}</div>
<h3>
{index + 1}. {_attendee.firstName} {_attendee.lastName} (
{_attendee.age})
</h3>
</div>
);
})}
</div>
Thank you in advance for your help.
When using setState you pass in the full state object like you'r doing. However when using useState hooks the setState functions takes in just that value.
Check out these docs for understanding the difference between setState and updating state with useState hook
setAttendees takes in an array so your handler should be
const deleteAttendee = (attendee) => {
//Pass in filtered array as new state for attendees
setAttendees( attendees.filter((element) => element.firstName !== attendee.firstName);
};
setAttendees comes with a parameter that allows you to have the previous state.
Here's what I did to your deleteAttendee function
const deleteAttendee = (event) => {
const attendeeId = event.target.dataset.id;
setAttendees((previousState) => previousState.filter((attendee) => attendee.id !== attendeeId))
};
I am using the previous state and updating that instead and instead of removing the first name I am instead removing matching ids
Here's what the jsx looks like
return (
<div className="App">
{attendees.map((_attendee, index) => {
return (
<div key={_attendee.id}>
<div data-id={_attendee.id} onClick={(event) => deleteAttendee(event)}>trash</div>
<h3>
{index + 1}. {_attendee.firstName} {_attendee.lastName} (
{_attendee.age})
</h3>
</div>
);
})}
</div>
);
I am using a data-id attribute so whenever the user clicks on the trash icon, I can pass that event into the deleteAttendee, get the value from the attribute and filter the array out if it matches. I made this change because I saw that you was filtering based on names, what if two attendees had the same name?
Initial state of attendees
const [attendees, setAttendees] = useState([
{
id: 'tskdh3-3dfdgg-23ds-23dfsdd-2332',
firstName: 'bob',
lastName: 'jeo',
age: 73
},
{
id: 't3-3dfdgg-23ds-23dfsdd-2332',
firstName: 'bob',
lastName: 'woah',
age: 43
},
{
id: 'bsd233-33dfg-3ds-2f3fsdd-2dfdf',
firstName: 'bob',
lastName: 'yum',
age: 23
}
]);
For random ids to be generated, you an use an npm package called uuid, it generates unique ids.

TypeError: this.state.persons.map is not a function Thanks

I'm new to react and only understand the basics. I got this project from someone to look at, but I'm scratching my head since morning with this problem:
Uncaught TypeError: this.state.persons.map is not a function.
Please, if you can try to try to go over it in easy but in under the hood way. Thank You!
import React, { useState, Component } from 'react';
import './App.css';
import Person from './Person/Person';
import person from './Person/Person';
import { render } from '#testing-library/react';
class App extends Component {
state =
{
persons:[
{id: '123', name:'Max', age: 28 },
{id: '124',name:'Mari', age: 26 },
{id: '125',name: 'laly', age: 20 }
],
showPersons: false
}
nameChangeHandler=( event,id ) =>
{ const personIndex = this.state.persons.findIndex(p=>{
return p.id === id;
});
const person = {...this.state.persons[personIndex]
};
person.name=event.target.value;
const persons=[ ...this.state.persons];
persons[personIndex]=person;
this.setState(
{
persons:person
}
)
}
togglePersonHandler = ()=>
{
const doesShow = this.state.showPersons;
this.setState ({showPersons: !doesShow});
}
deletePersonHandler= (personIndex)=> {
//const persons = this.state.persons;
const persons = [...this.state.persons]
persons.splice(personIndex,1);
this.setState({persons:persons});
}
render()
{
const style ={
backgroundColor:'yellow',
font:'inherit',
border:'1px solid blue',
padding:'8px',
cursor:'pointer'
};
let persons=null;
if (this.state.showPersons){
persons= (
<div>
{this.state.persons.map((person,index)=> {return <Person click={() => this.deletePersonHandler(index)}
name = {person.name}
age = {person.age}
key={person.id}
change ={(event) => this.nameChangeHandler(event,person.id)}
/>
})};
</div>)
};
return (
<div className="App">
<h1>Hi This is react App</h1>
<button style={style} onClick={this.togglePersonHandler}> Toggle Persons</button>
{persons}
</div>
);
}
}
export default App;
Error lies here :
nameChangeHandler=( event,id ) =>
{ const personIndex = this.state.persons.findIndex(p=>{
return p.id === id;
});
const person = {...this.state.persons[personIndex]
};
person.name=event.target.value;
const persons=[ ...this.state.persons];
persons[personIndex]=person;
this.setState(
{
persons:person ------------ > You are assigning a single object to a list in your state, so your map is giving an error, it must be {persons: persons}
}
)
}
Whenever you see ___ is not a function, try looking at whatever the function is referring to.
In this case you're using map, which is an Array function. So you need to verify if the array (the thing at the left of the dot) is actually an array.
The function call:
this.state.persons.map
The array you need to pay attention to is persons. So try to look for the place in the code where persons is not getting recognized an array.
You have a typo here, you are assigning and object to state instead of an array. in the funcion nameChangeHandler.
this.setState(
{
persons:person
}
)
With a simple console inside the render or just watching it inside the Components Menu in the browser you can notice it bro.
It probably happens here, where you replace the array of persons with a single person:
this.setState(
{
persons: person
}
)
You probably want to do this instead:
this.setState({persons});

Set input values on receiving changed prop from redux

I am trying to set the change the input value to its previous redux state value. For that, I am storing each redux state as history node. When a user presses ctrl+z in the form, my action creator should fetch the previous history node and find the input value(which is being done) and set the input value. I have cross-checked whether the input component is being rerendered or not with props being changed.
*My history node is double linked list if that helps
Although the props are being changed, the input value is not getting updated.
So, I have thought of updating the input field with componentWillRecieveProps life cycle where I set the ref to input component and by accessing the ref in life cycle hook, the input value gets updated.
For basic inputs like a single field in the form, it is easy to access the ref. I have a field which is an array of objects consisting of 3 inputs fields. Now, if I want to access these refs in componentWillRecieveProps life cycle hook, I should set the ref for each input box which is quite complicated since the array is dynamic.
Consider the array structure as
persons:[
{
firstName: 'a',
middleName: 'b',
lastName: 'c'
},
{
firstName: 'd',
middleName: 'e',
lastName: 'f'
}
.
.
.
]
**MainComponent**
class MainComponent extends Component{
componentWillReceiveProps = (nextProps) => {
const { title, subtitle, sponsor, email, subject } = this.props;
if (nextProps.title !== title) {
this.uncontrolledInputRefs.title.current.value = nextProps.title;
}
}
uncontrolledInputRefs = {
title: React.creatRef()
}
onKeyDown = e => {
if(e.key == 'z' && e.ctrlKey){
e.preventDefault();
this.props.undoHistory()
}
}
render(){
return(
<Form onKeyDown={onKeyDown}>
<Title
value={title}
handleTitleChange={(e) => {
const value = e.target.value;
this.props.handleTitleChange(value);
}}
refer={this.uncontrolledInputRefs.title}
/>
<div>
{persons.map((person,id)=>{
<input value={person.firstName} handlePersonChange={e=>{
this.props.handlePersonChange('firstName',e.target.value,id)
}}/>
<input value={person.middleName} handlePersonChange={e=>{
this.props.handlePersonChange('middleName',e.target.value,id)
}}/>
<input value={person.lastName} handlePersonChange={e=>{
this.props.handlePersonChange('lastName',e.target.value,id)
}}/>
})}
<button onClick={this.addPerson}>AddPerson</button>
</div>
</Form>
);
}
}
mapStateToProps = state => {
title: state.reducer.title,
persons: state.reducer.persons
}
mapDispatchToProps = dispatch => {
handleTitleChange: value => dispatch(actions.handleTitleChange(value)),
handlePersonChange: (field,value) =>
dispatch(actions.handlePersonChange(field,value)),
undoHistory: _ => dispatch(reducer.undoHistory())
}
initialState
initialState = {
title: null,
persons: [{firstName: null, middleName:null, lastName:null}]
}
reducer
const reducer = (state=initialState,action) => {
switch(action.type){
case actionTypes.CHANGE_TITLE:
return{
...state,
title: action.payload.title
}
case actionTypes.CHANGE_PERSON:
return{
...state,
[action.payload.persons[action.id].[fieldToModify]:
action.payload.value
}
}
}
If the problem gets resolved at the root which is without using any life cycle hook and updating the input value, the life cycle update can be skipped.

Categories

Resources