onClick event on stateless components controlling a prop on that Component - javascript

I have a stateless component called EmailItem: I want to be able to give it a new prop via some function when I click on it.
<EmailItem key={i} onClick={// onClick function} emailData={email} read={false} />
I want the value of read prop to change to true when the EmailItem is clicked.
I understand this can be done by making EmailItem a stateful component; however, it is an iterable component and from my understanding adding state where you don't NEED it, is bad. Correct me if I'm wrong.
I am confused about the content of the function I would use since e.target and refs will not work.
This read prop will change the class of an item in the stateless component.
const EmailItem = (props) => {
let readClass = props.emailData.read ? '--read' : ''
return (
<div onClick={props.onClick} className='email'>
<div className={'email__read' + readClass} />
<div className='email__leftside'>
<div className='email__from'>{props.emailData.from}</div>
<div className='email__subject'>{props.emailData.subject}</div>
<div className={'email__body'}>{props.emailData.body}</div>
</div>
<div className='email__rightside'>
<div className='email__date'>{props.emailData.date}</div>
<div className='email__time'>{props.emailData.time}</div>
</div>
</div>
)
}
The email__read className is an indicator of whether the email has been read or not

From what I understood, You can pass function(onClick) from parent to child component and bind emaildata to that function. ES6 arrow function syntax takes care of parameter binding and you can get emailData(which was clicked) in parent.
Try out following example(sorry for no css)
class Inbox extends React.Component {
constructor(props) {
super(props)
this.state = {
emails: [
{ read: false, from: "aaa", to: "aaato", subject: "aaasubject", body: "aaabody", date: "aaadate", time: "aaatime" },
{ read: true, from: "bbb", to: "bbbto", subject: "bbbsubject", body: "bbbbody", date: "bbbdate", time: "bbbtime" },
{ read: false, from: "aaa", to: "cccto", subject: "cccsubject", body: "cccbody", date: "cccdate", time: "ccctime" },
{ read: false, from: "ddd", to: "dddto", subject: "dddsubject", body: "dddbody", date: "ddddate", time: "dddtime" },
]
}
}
handleClick(index, ele) {
// ele is emaildata, do anything you want
var newEmails = this.state.emails
newEmails[index].read = true
this.setState({
emails: newEmails
})
}
render() {
return (
<div>
<p>Emails</p>
{
this.state.emails.map((e, i) => {
return <EmailItem emailData={e} key={i} onClick={() => { this.handleClick(i, e) }} />
})
}
</div>
)
}
}
const EmailItem = (props) => {
let readClass = props.emailData.read ? '--read' : '--unread'
return (
<div onClick={props.onClick} className='email'>
<div className={'email__read' + readClass} />
<div className='email__leftside'>
<p>{readClass}</p>
<div className='email__from'>From {props.emailData.from}</div>
<div className='email__subject'>To {props.emailData.subject}</div>
<div className={'email__body'}>Body {props.emailData.body}</div>
</div>
<div className='email__rightside'>
<div className='email__date'>Date {props.emailData.date}</div>
<div className='email__time'>Time {props.emailData.time}</div>
</div>
<p>---------------------</p>
</div>
)
}

What you need to do to change your props is to change the state. It doesn't matter that the state doesn't live in this component. You can pass callback from your parent component and than call this callback inside your stateless component and change state in your parent component.
Note: State doesn't to be in the parent. It can be higher up.
This should help you: https://facebook.github.io/react/docs/lifting-state-up.html
I hope this is helpful for you.

May be this link can help you i jut gave its answer there
How to pass function as props in React?

Related

React component function call only updates one component instance

I have a component called RightTab like this
const RightTab = ({ data }) => {
return (
<div className="RightTab flex__container " onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};
export default RightTab;
The tab has an active state in its CSS like this
.RightTab.active {
background-color: var(--primaryGreen);
}
as you have seen it changes the color when an active class is added. I have an array in the parent component that I pass down to the child component as props. Here is the array
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleDashBoardClick,
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleInventoryClick,
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleReportsClick,
},
];
Here is how I pass the props down.
<RightTab data={dataArray[0]} />
<RightTab data={dataArray[1]} />
<RightTab data={dataArray[2]} />
The data prop passed into the component is an object containing a function call as one of its properties like this. I have an onclick attribute on the child components' main container that is supposed to call the respective function.
The function is what adds the active class to make the background change color. However each time I click on the component it only changes the background of the first occurrence. And as you may have noticed I call the component thrice. No matter which component I click only the first ones background changes.
Here is an example of the function that is on the prop object.
const handleDashBoardClick = () => {
const element = document.querySelector(".RightTab");
element.classList.toggle("active");
};
I don't get what I'm doing wrong. What other approach can I use?
Although you use the component 3 times, it doesn't mean that a change you make in one of the components will be reflected in the other 2, unless you specifically use a state parameter that is passed to all 3 of them.
Also, the way you add the active class is not recommended since you mix react with pure js to handle the CSS class names.
I would recommend having a single click handler that toggles the active class for all n RightTab components:
const MainComponent = () => {
const [classNames, setClassNames] = useState([]);
const handleClick = (name) =>
{
const toggledActiveClass = classNames.indexOf('active') === -1
? classNames.concat(['active'])
: classNames.filter((className) => className !== 'active');
setClassNames(toggledActiveClass);
switch (name) {
case 'Dashboard';
// do something
break;
case 'Inventory':
// ....
break;
}
}
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleClick.bind(null, 'Dashboard'),
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleClick.bind(null, 'Inventory'),
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleClick.bind(null, 'Reports'),
},
];
return (
<>
{dataArray.map((data) =>
<RightTab key={data.name}
data={data}
classNames={classNames} />)}
</>
);
};
const RightTab = ({ data, classNames }) => {
return (
<div className={classNames.concat(['RightTab flex__container']).join(' ')}
onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};

React check the elements of an array for a conditional display

I would like to check if the value of my element appears in
an array if it is the case I give it the class
another class.
this is to make a map from another object array
How to check in the whole table if
Is there the value I want?
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
class Dbz extends React.Component {
constructor(props) {
super(props)
This.state = {
sayien = ['goku','vegeta','broly']
warrioz = [
{ name:goku
power: 1500
},
{ name: yamcha
power: 150
},
{ name: cell
power: 2500
},
]
}
}
render(){
return(
<div>
{ this.state.warrioz.map((data) => {
return (
<div className={this.state.sayien === data.name ? "sayien" :"nosayien"}>
<p>{data.name} </p>
</div>
})}
</div>
)
}
}
export default Dbz
You can see if data.name is included in your state. But the question remains, where is data coming from?
<div className={this.state.sayien.includes(data.name) ? "sayien" :"nosayien"}>
<p>goku </p>
</div>
If you are curious here is a link to the mdn doc for includes.

Rendering the navigation list from an array based on different label on toggle mode

I have a header component where I need to render three buttons, so every three buttons have three props. One is the class name, click handler and text.
So out of three buttons, two buttons act as a toggle button, so based on the click the text should change.
See the below code:
class App extends Component(){
state = {
navigationList: [{
text: 'Signout',
onClickHandler: this.signoutHandler,
customClassName: 'buttonStyle'
}, {
text: this.state.isStudents ? 'Students' : 'Teachers',
onClickHandler: this.viewMode,
customClassName: 'buttonStyle'
}, {
text: this.state.activeWay ? 'Active On' : 'Active Hidden',
onClickHandler: this.activeWay,
customClassName: 'buttonStyle'
}]
}
signoutHandler = () => {
// some functionality
}
viewMode = () => {
this.setState({
isStudents: !this.state.isStudents
})
}
activeWay = () => {
this.setState({
activeWay: !this.state.activeWay
})
}
render(){
return (
<Header navigationList={this.state.navigationList}/>
)
}
}
const Header = ({navigationList}) => {
return (
<>
{navigationList && navigationList.map(({text, onClickHandler, customClassName}) => {
return(
<button
onClick={onClickHandler}
className={customClassName}
>
{text}
</button>
)
})}
</>
)
}
The other way is I can pass all the props one by one and instead of an array I can write three button elements render it, but I am thinking to have an array and render using a map.
So which method is better, the problem that I am facing is if use the array. map render
the approach I need to set the initial value as a variable outside and how can I set the state.
And I am getting the onClick method is undefined, is it because the function is not attached to the state navigation list array.
Update
I declared the functions above the state so it was able to call the function.
So in JS, before the state is declared in the memory the functions should be hoisted isn't.
class App extends React.Component {
constructor(props){
super();
this.state = {
isStudents:false,
activeWay:false,
}
}
createList(){
return [{
text: 'Signout',
onClickHandler: this.signoutHandler.bind(this),
customClassName: 'buttonStyle'
}, {
text: this.state.isStudents ? 'Students' : 'Teachers',
onClickHandler: this.viewMode.bind(this),
customClassName: 'buttonStyle'
}, {
text: this.state.activeWay ? 'Active On' : 'Active Hidden',
onClickHandler: this.activeWay.bind(this),
customClassName: 'buttonStyle'
}];
}
signoutHandler(){
}
viewMode(){
this.setState({
isStudents: !this.state.isStudents
})
}
activeWay(){
this.setState({
activeWay: !this.state.activeWay
})
}
render(){
return (
<div>
<div>ddd</div>
<Header navigationList={this.createList()} />
</div>
)
}
}
const Header = ({navigationList}) => {
console.log(navigationList);
return (
<div>
{navigationList && navigationList.map(({text, onClickHandler, customClassName}) => {
return(
<button
onClick={onClickHandler}
className={customClassName}
>
{text}
</button>
)
})}
</div>
)
}
ReactDOM.render(<App />, document.querySelector("#app"))
https://jsfiddle.net/luk17/en9h1bpr/
Ok I will try to explain, If you see you are using function expressions in your class and as far as hoisting is concerned in JavaScript, functions expressions are not hoisted in JS only function declarations are hoisted, function expressions are treated as variables in JS.
Now for your case you don't have to shift your functions above the state, you can simply use constructor for initializing state as
constructor(props) {
super(props);
this.state = {
isStudents: false,
activeWay: false,
navigationList: [
{
text: "Signout",
onClickHandler: this.signoutHandler,
customClassName: "buttonStyle"
},
{
text: "Teachers",
onClickHandler: this.viewMode,
customClassName: "buttonStyle"
},
{
text: "Active Hidden",
onClickHandler: this.activeWay,
customClassName: "buttonStyle"
}
]
};
}
Now you will have your handlers available as it is
Sandbox with some modification just to show
EDIT:
You can have default text for buttons and change it when clicking,
Sandbox updated
Hope it helps

In React, how to bind an input's value when rendering a list of inputs?

I'm rendering a list of inputs and I want to bind each input's value to a link's href. My current attempt renders https://twitter.com/intent/tweet?text=undefined:
class App extends React.Component {
tweets = [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
];
render() {
return (
<div>
{this.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={e => tweet.text = e.target.value} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.text}`}>Tweet</a>
</div>
)}
</div>
);
}
}
This probably needs to involve setState but I have no idea how to achieve that when rendering a list. I've tried to do some research on this but didn't found anything helpful.
JSFiddle: https://jsfiddle.net/nunoarruda/u5c21wj9/3/
Any ideas?
You can move the tweets variable to the state to maintain consistency in that array.
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
tweets: [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
]
};
};
setTweets = index => e => {
const { tweets } = this.state
tweets[index].text = e.target.value
this.setState({ tweets })
}
render() {
const { tweets } = this.state
return (
<div>
{tweets.map((tweet, index) =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={this.setTweets(index)} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.text}`}>Tweet</a>
</div>
)}
</div>
);
}
}
Updated Jsfiddle: https://jsfiddle.net/u5c21wj9/6/
You can reach the desired result using state.
return (
<div>
{tweets.map(({ id, link }) =>
<div key={id}>
<input type="text" placeholder="text" onChange={({ target }) => this.setState({ [id]: target.value })} />
<a href={`https://twitter.com/intent/tweet?text=${this.state[id] || link}`}>Tweet</a>
</div>
)}
</div>
);
Note: I would move tweets outside the component and implement few ES6 features.
Updated Jsfiddle: https://jsfiddle.net/u5c21wj9/7/
You really should use a state here and make your tweets variable be part of it. To do that, add a constructor:
constructor() {
super();
this.state = {
tweets: [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
]
};
}
Then you need to mutate each linkwhenever you type in one of the inputs. There are a few pitfalls here, so let me go through them one-by-one:
changeTweet = (id, e) => {
let arr = this.state.tweets.slice();
let index = arr.findIndex(i => i.id === id);
let obj = Object.assign({}, arr[index]);
obj.link = e.target.value;
arr[index] = obj;
this.setState({tweets: arr});
}
First, you need to create a copy of your state variable. This gives you something to work with, without mutating the state directly which is anti-pattern. This can be done with slice().
Since you are sending in the id of the object to modify, we need to find it in our array (in case the items are unordered). This is done with findIndex(). You might want to handle the scenario in which such index is not found (I have not done that).
Now we know where in the array the object with the given id key is. Now, create a copy of that item (which is an object). This is also to prevent mutating the state directly. Do this with Object.assign().
Now change the link to the input value we typed in. Replace the old item object with the new one (obj) and replace the old tweets array with the new one (arr).
Here's the full example:
class App extends React.Component {
constructor() {
super();
this.state = {
tweets: [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
]
};
}
changeTweet = (id, e) => {
let arr = this.state.tweets.slice();
let index = arr.findIndex(i => i.id === id);
let obj = Object.assign({}, arr[index]);
obj.link = e.target.value;
arr[index] = obj;
this.setState({tweets: arr});
}
render() {
return (
<div>
{this.state.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={(e) => this.changeTweet(tweet.id, e)} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.link}`}>Tweet</a>
</div>
)}
</div>
);
}
}
ReactDOM.render(<App />, 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>
<div id="root"></div>
You need to save the text from the input in the state (using setState), not in the tweets array. Then you can render it getting the text from the state.
class App extends React.Component {
tweets = [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
];
state = {
tweetsText :{}
}
handleTextChange = (event, tweetId) => {
const tweetsTextCopy = Object.assign({}, this.state.tweetsText)
tweetsTextCopy[tweetId] = event.target.value
this.setState({tweetsText: tweetsTextCopy})
}
render() {
return (
<div>
{this.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={e => this.handleTextChange(e, tweet.id)} />
<a href={`https://twitter.com/intent/tweet?text=${this.state.tweetsText[tweet.id]}`}>Tweet</a>
</div>
)}
</div>
);
}
}
Links info is in the link property of your tweets array. The property text is not defined.
So, your render function should look like this
render() {
return (
<div>
{this.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={e => tweet.text= e.target.value} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.link}`}>Tweet</a>
</div>
)}
</div>
);
}

Move method from one component to another in React and still be able to use in original

I am working to create a small contact app using React with ES6. I had some data displaying in the render function of a component - see the link to the question below for the original structure...
How to specify a key for React children when mapping over an array
However, because I was also putting a form on the same page and I needed to update my data in state, I had to move the data to a higher level component.
Now I'm having trouble traversing the components so that my original list of contacts shows up on the left. I had to remove most of what was in my render function on the contact-list component because it was completely breaking the build.
First, here is the address-book component with the form - this is working, both pulling in my initial 3 contacts from state, then concating new contacts from the form to the array. (Still need cleanup code here to make UI work right...)
import React from 'react';
import ContactList from './contact-list.jsx';
import ContactForm from './contact-form.jsx';
import ShortContact from './short-contact.jsx';
class AddressBook extends React.Component {
constructor() {
"use strict";
super();
this.state = {
showContacts: true,
contacts: [
{
id: 1,
fName: "aaa",
lName: "aaaaa",
imgUrl: "http://brainstorminonline.com/wp-content/uploads/2011/12/blah.jpg",
email: "aaa#aaaa.com",
phone: "999999999999"
},
{
id: 2,
fName: "bbbbb",
lName: "bbbbbbb",
imgUrl: "https://media.licdn.com/mpr/mpr/shrinknp_200_200/bbb.jpg",
email: "bbb#bbb-bbb.com",
phone: "888888888888"
},
{
id: 3,
fName: "Number",
lName: "Three",
imgUrl: "http://3.bp.blogspot.com/-iYgp2G1mD4o/TssPyGjJ4bI/AAAAAAAAGl0/UoweTTF1-3U/s1600/Number+3+Coloring+Pages+14.gif",
email: "three#ccccc.com",
phone: "333-333-3333"
}
];
};
}
render() {
"use strict";
return (
<div className="row address-book">
<div className="col-md-6">
<ContactList />
</div>
<div className="col-md-6">
<button className='btn' id="contact-submit-button" type="submit" value="Create Contact">Create New Contact </button>
<div>
<ContactForm addContact={this._addContact.bind(this)}/>
</div>
</div>
</div>
);
}
_addContact (fName, lName, company, email, phone, imgURL) {
"use strict";
const contact = {
id: this.state.contacts.length + 1,
fName,
lName,
company,
email,
phone,
imgURL
};
this.setState({ contacts: this.state.contacts.concat([contact]) });
}
_getContacts() {
"use strict";
return contactList.map((contact) => {
"use strict";
return (
<ShortContact contact={contact} key={contact.id}/>)
});
}
_getContactsTitle (contactCount) {
"use strict";
if(contactCount === 0) {
return 'No Contacts';
} else if (contactCount === 1) {
return '1 contact';
} else {
return `${contactCount} contacts`;
}
}
}
export default AddressBook;
However, the bottom 2 methods _getContacts and _getContactsTitle are the ones that are needed in my ContactForm component - which is this one:
import React from 'react';
import ShortContact from './short-contact.jsx';
class ContactList extends React.Component {
render() {
const contacts = this._getContacts();
return (
<div>
<h3>List of Contacts</h3>
<h4 className="contact-count">{this._getContactsTitle((contacts.length))}</h4>
<ul className="contact-list">
{contacts}
</ul>
</div>
);
}
}
export default ContactList;
The const that defines contacts as well as the <h4> through the <ul> is what breaks the app because as you can see it references the _getContactTitle method from the other component as well as {contacts} which is in the _getContacts method.
I'm guessing I need to do something like wrap them into functions and pass them - but I've gotten turned around and can't quite see how that would work here with React. Any help would be welcome. Thanks!
You need to pass the contacts down via props instead of trying to build the list of contacts in the parent to pass down. what I mean is this.
ADDRESS BOOK
render() {
"use strict";
let contacts = this._getContacts();
-----------^---------------^----------- get the list in the parent
return (
<div className="row address-book">
<div className="col-md-6">
<ContactList contacts={contacts} />
-------------------------------^-----------^---- important part here
</div>
<div className="col-md-6">
<button className='btn' id="contact-submit-button" type="submit" value="Create Contact">Create New Contact </button>
<div>
<ContactForm addContact={this._addContact.bind(this)}/>
</div>
</div>
</div>
);
}
then in your CONTACT LIST component just use the list as props.
render() {
let {contacts} = this.props;
------------^--------------^---- its in the props now
let title = contacts.length > 1 ? `${contacts.length} contacts` : contacts.length === 1 ? '1 contact' : 'No Contacts';
return (
<div>
<h3>List of Contacts</h3>
<h4 className="contact-count">{title}</h4>
<ul className="contact-list">
{contacts}
</ul>
</div>
);
}
}
from here you can remove the contactTitle function. let the child render that since it knows the length of the array (because its in props).
As a side note, in your _addContact function. instead of creating a new array and concatenating it with the state contact array just push the new one onto the current state. its better storage (i.e. dont need to make a new array to combine a new item to the array).
this.setState({ contacts: this.state.contacts.push(contact) });

Categories