Rendering a component onClick in React - javascript

I'm rank new to React and to Javascript in general. Thus the question.
I've a list of images being displayed in a React component. What I'm trying to achieve is to define a method to handle the onClick method on any of these images and render a new component as an overlay. This is my code:
class Viewer extends React.Component{
constructor(props){
super(props);
this.state = {
images : ImageList
};
}
componentDidMount(){
}
handleClick(mediaId, event){
event.preventDefault();
render(){
<MyNewComponent mediaId=mediaId />
}
}
render(){
return (
<div className="row">
<div className="image-viewer">
<ul className="list-inline">
{this.state.images.map(function (image) {
return (<li key={image.mediaId}><a href="#" onClick={this.handleClick.bind(image.mediaId, event)}><img src={image.src}
className="img-responsive img-rounded img-thumbnail"
alt={image.mediaId}/></a></li>);
})}
</ul>
</div>
</div>
);
}
}
export default Viewer;
This clearly is wrong and throws up a bunch of errors. Can someone point me in the right direction?

Here how to handle the click event and show the overlay
class Viewer extends React.Component{
constructor(props){
super(props);
this.state = {
images : ImageList,
mediaId : 0
};
// Bind `this`
// See "Binding to methods of React class" link below
this.handleClick = this.handleClick.bind(this);
}
componentDidMount(){ }
handleClick(event){
event.preventDefault();
// Get the value of the `data-id` attribute <a data-id="XX">
var mediaId = event.currentTarget.attributes['data-id'].value;
// Set the state to re render
this.setState({ mediaId }); // ES6 shortcut
// this.setState({ mediaId: mediaId }); // Without shortcut
}
render(){
// Set a variable which contains your newComponent or null
// See "Ternary operator" link below
// See "Why use null expression" link below
var overlay = this.state.mediaId ? <MyNewComponent mediaId={this.state.mediaId} /> : null;
return (
<div className="row">
<div className="image-viewer">
{ overlay }
<ul className="list-inline">
{this.state.images.map(image => {
return (<li key={image.mediaId}><a href="#" onClick={this.handleClick} data-id={image.mediaId}><img src={image.src}
className="img-responsive img-rounded img-thumbnail"
alt={image.mediaId}/></a></li>);
}).bind(this)}
</ul>
</div>
</div>
);
}
}
export default Viewer;
Documentation
React and ES6 - Binding to methods of React class
Ternary operator
Why use null expression

Related

unable to find a React.Component by id

I have a React.Component with render() declared this way:
render(){
return <div>
<button id="butt" onClick={()=> $("#noti").change("test") }>click me</button>
<Notification id="noti" onMounted={() => console.log("test")}/>
</div>
}
And this is my Notification class:
class Notification extends React.Component {
constructor(props) {
super(props)
this.state = {
message: "place holder",
visible: false
}
}
show(message, duration){
console.log("show")
this.setState({visible: true, message})
setTimeout(() => {
this.setState({visible: false})
}, duration)
}
change(message){
this.setState({message})
}
render() {
const {visible, message} = this.state
return <div>
{visible ? message : ""}
</div>
}
}
As the class name suggests, I am trying to create a simple notification with message. And I want to simply display the notification by calling noti.show(message, duration).
However, when I try to find noti by doing window.noti, $("#noti") and document.findElementById("noti"), they all give me undefined, while noti is displayed properly. And I can find the butt using the code to find noti.
How should I find the noti? I am new to front end so please be a little bit more specific on explaining.
It's not a good idea using JQuery library with Reactjs. instead you can find a appropriate react library for notification or anything else.
Also In React we use ref to to access DOM nodes.
Something like this:
constructor(props) {
super(props);
this.noti = React.createRef();
}
...
<Notification ref={this.noti} onMounted={() => console.log("test")}/>
more info: https://reactjs.org/docs/refs-and-the-dom.html
I have hardcoded the id to 'noti' in the render method. You can also use the prop id in the Notification component.I have remodelled the component so that you can achieve the intended functionality through React way.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
messageContent: 'placeholder'
}
}
setMessage = (data) => {
this.setState({messageContent : data});
}
render() {
return (
<div className="App">
<button id='butt' onClick= {() => this.setMessage('test')} />
<Notification message = {this.state.messageContent} />
</div>
);
}
}
class Notification extends React.Component {
render () {
const {message} = this.props;
return (
<div id='noti'>
{message}
</div>
)
}
}
Before beginning: Using id/class to reach DOM nodes is not suggested in React.js, you need to use Ref's. Read more at here.
In your first render method, you give id property to Notification component.
In react.js,
if you pass a property to some component, it becomes a props of that
component. (read more here)
After you give the id to Notification, you need to take and use that specific props in your Notification component.
You see that you inserted a code line super(props) in constructor of Notification? That means, take all the props from super (upper) class and inherit them in this class.
Since id is HTML tag, you can use it like:
class Notification extends React.Component {
constructor(props) {
// inherit all props from upper class
super(props);
this.state = {
message: "place holder",
visible: false,
// you can reach all props with using this.props
// we took id props and assign it to some property in component state
id: this.props.id
}
}
show(message, duration){
// code..
}
change(message){
// code..
}
render() {
const {visible, message, id} = this.state
// give that id to div tag
return <div id={id}>
{message}
</div>
}
}
You can't pass id/class to a React Component as you would declare them in your normal HTML. any property when passed to a React Component becomes a props of that component which you have to use in the component class/function.
render() {
const {visible, message} = this.state
// give your id props to div tag as id attr
return <div id={this.props.id}>
{message}
</div>
}
This answer does not provide the exact answer about selecting a component as you want. I'm providing this answer so you can see other alternatives (more React way maybe) and improve it according to your needs.
class App extends React.Component {
state = {
isNotiVisible: false
};
handleClick = () => this.setState({ isNotiVisible: true });
render() {
return (
<div>
<button onClick={this.handleClick}>Show Noti</button>
{this.state.isNotiVisible && (
<Noti duration={2000} message="This is a simple notification." />
)}
</div>
);
}
}
class Noti extends React.Component {
state = {
visible: true
};
componentDidMount() {
setTimeout(() => this.setState({ visible: false }), this.props.duration);
}
render() {
return this.state.visible && <div>{this.props.message}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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>
<div id="root" />

How to Target DOM Elements in ReactJS?

Within my React app, I have a sidebar which needs to have a CSS class added to it when the sidebar close button is clicked. I'm using React.createRef() to create a reference to the element, however, I'm receiving the following error:
Here's my code:
import React from 'react';
import './css/Dashboard.css';
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.sidebar = React.createRef();
}
sidebarClose() {
console.log('test');
this.sidebar.className += "hidden";
}
render() {
return (
<div id="dashboard">
<div ref={this.sidebar} id="sidebar">
<img width="191px" height="41px" src="logo.png"/>
<div onClick={this.sidebarClose} className="sidebar-close">X</div>
</div>
</div>
);
}
}
export default Dashboard;
The console.log('test') is just so that I can confirm the function is being executed (which it is).
Thank you.
Instead of manually trying to add a class to a DOM node, you can keep a variable in your state indicating if the sidebar is open and change the value of that when the button is clicked.
You can then use this state variable to decide if the sidebar should be given the hidden class or not.
Example
class Dashboard extends React.Component {
state = { isSidebarOpen: true };
sidebarClose = () => {
this.setState({ isSidebarOpen: false });
};
render() {
const { isSidebarOpen } = this.state;
return (
<div id="dashboard">
<div
ref={this.sidebar}
id="sidebar"
className={isSidebarOpen ? "" : "hidden"}
>
<img
width="191px"
height="41px"
src="logo.png"
alt="craftingly-logo"
/>
<div onClick={this.sidebarClose} className="sidebar-close">
X
</div>
</div>
</div>
);
}
}
I think you forget to bind sidebarClose method to your class in constructor.
constructor(props) {
super(props);
this.sidebar = React.createRef();
this.sidebarClose = this.sidebarClose.bind(this); // here
}

React - Event bubbling

I am using react. I am not quite how should I write a event handling so when I click on a, I can alert data-href. Right now if I click on div or img, it will trigger an event, but since the target is actually not a it will alert null, which is not what I want.
test(e){alert(e.target.getAttribute('data-href'))}
<div>
<ui>
<li >
<a data-href = 'http://cnn.com' onClick = {e = > this.test(e)>
<div>
<img src = 'someimage.jpg'}>
hi there
</div>
</a>
<li>
</ui>
</div>
edit:
Something like this
conversationList() & selectConversation() is what I am talking about here~
import React from 'react';
import { connect, Provider } from 'react-redux';
import fetch from 'isomorphic-fetch';
import * as actions from './actions/index';
import io from 'socket.io-client';
class Chat extends React.Component{
constructor(props){
super(props);
this.state = {recipientId: '', messageBuffer:'asdfadsfasdf'};
this.userList = this.userList.bind(this);
this.changeRecipient = this.changeRecipient.bind(this);
this.insertText = this.insertText.bind(this);
}
componentWillMount(){
this.props.loadConversationsSocket();
this.props.loadConversations();
this.props.loadRecipients();
}
participantsNames(participants){
console.log('in par ');
console.log(participants);
return participants.map(participant => (<div key= {participant._id}>{participant.name}</div>));
}
selectConversation(e){
e.preventDefault();
console.log(this.tagName);
let data = this.getAttribute('data-href');
alert(data);
}
conversationList(){
if(!(this.props.conversations.length === 0)){
console.log('in conversation if');
return this.props.conversations.map(conversation =>(<li key = {conversation.conversation._id} >
<a data-href = {'http://localhost:8000/messaages/'+conversation.conversation._id} onClick = {(e)=>this.selectConversation(e)}>
<div>
participants: {this.participantsNames(conversation.conversation.participants)}
</div>
<div>
{conversation.message[0].auther}
{conversation.message[0].body}
</div>
</a>
</li>))
}
}
render(){
return (
<div>
<ul>
{this.conversationList()}
</ul>
</div>)
}
}
function mapStateToProps(state) {
return { recipients: state.recipients, conversations: state.conversations};
}
export default connect(mapStateToProps, actions)(Chat);
Your onClick handler is attached to the img, not to a. This is the first problem.
Then you should update your code to query event.currentTarget.
event.currentTarget - identifies the current target for the event, as the event traverses
the DOM. It always refers to the element to which the event handler
has been attached, as opposed to event.target which identifies the
element on which the event occurred.
You should write your event handler like this:
class Hello extends React.Component {
test(e){
alert(e.currentTarget.getAttribute('data-href'))
}
render() {
return (
<div>
<a onClick={this.test} data-href="foo">
<img src="https://vignette.wikia.nocookie.net/mrmen/images/5/52/Small.gif/revision/latest?cb=20100731114437" />
<div>clickMe</div>
</a>
</div>
);
}
}
I would suggest also reading this article about handling events.

React components only setting state for one key in constructor function. ES6

I have this component, but it's not setting show in the the state constructor. I can console.log the props and they show the correct params, but for some reason, show is not getting set.
class SubstitutionPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
suggestions: this.props.synonyms_with_levels,
show: this.props.show
}
}
handleToggleShow() {
this.setState({
show: false
})
}
render() {
console.log("sub panel")
console.log(this.state)
console.log(this.props)
if (this.props.synonyms_with_levels.length > 0 && this.state.show) {
return(
<div className="substitution-panel">
<div onClick={() => this.handleToggleShow()} className="glyphicon glyphicon-remove hover-hand"></div>
{this.props.synonyms_with_levels}
</div>
);
} else {
return (
<span>
</span>
);
}
}
}
The parent that renders this child component looks like this:
<SubstitutionPanel synonyms_with_levels= {this.props.synonyms_with_levels} show={this.state.showSubPane} />
I'm really just trying to make a "tooltip" where the parent can open the tooltip.
Is everything ok when you console.log(this.props)?
It may be just a typo here, but in the parent component you have
show={this.state.showSubPane}
and maybe it should be 'showSubPanel' with an L at the end?

Toggle Class in React

I'm using react for a project where I have a menu button.
<a ref="btn" href="#" className="btn-menu show-on-small"><i></i></a>
And a Sidenav component like:
<Sidenav ref="menu" />
And I wrote the following code to toggle the menu:
class Header extends React.Component {
constructor(props){
super(props);
this.toggleSidenav = this.toggleSidenav.bind(this);
}
render() {
return (
<div className="header">
<i className="border hide-on-small-and-down"></i>
<div className="container">
<a ref="btn" href="#" className="btn-menu show-on-small"><i></i></a>
<Menu className="menu hide-on-small-and-down"/>
<Sidenav />
</div>
</div>
)
}
toggleSidenav() {
this.refs.btn.classList.toggle('btn-menu-open');
}
componentDidMount() {
this.refs.btn.addEventListener('click', this.toggleSidenav);
}
componentWillUnmount() {
this.refs.btn.removeEventListener('click', this.toggleSidenav);
}
}
The thing is that this.refs.sidenav is not a DOM element and I cant add a class on him.
Can someone explain me how to toggle class on the Sidenav component like I do on my button?
You have to use the component's State to update component parameters such as Class Name if you want React to render your DOM correctly and efficiently.
UPDATE: I updated the example to toggle the Sidemenu on a button click. This is not necessary, but you can see how it would work. You might need to use "this.state" vs. "this.props" as I have shown. I'm used to working with Redux components.
constructor(props){
super(props);
}
getInitialState(){
return {"showHideSidenav":"hidden"};
}
render() {
return (
<div className="header">
<i className="border hide-on-small-and-down"></i>
<div className="container">
<a ref="btn" onClick={this.toggleSidenav.bind(this)} href="#" className="btn-menu show-on-small"><i></i></a>
<Menu className="menu hide-on-small-and-down"/>
<Sidenav className={this.props.showHideSidenav}/>
</div>
</div>
)
}
toggleSidenav() {
var css = (this.props.showHideSidenav === "hidden") ? "show" : "hidden";
this.setState({"showHideSidenav":css});
}
Now, when you toggle the state, the component will update and change the class name of the sidenav component. You can use CSS to show/hide the sidenav using the class names.
.hidden {
display:none;
}
.show{
display:block;
}
refs is not a DOM element. In order to find a DOM element, you need to use findDOMNode menthod first.
Do, this
var node = ReactDOM.findDOMNode(this.refs.btn);
node.classList.toggle('btn-menu-open');
alternatively, you can use like this (almost actual code)
this.state.styleCondition = false;
<a ref="btn" href="#" className={styleCondition ? "btn-menu show-on-small" : ""}><i></i></a>
you can then change styleCondition based on your state change conditions.
Toggle function in react
At first you should create constructor
like this
constructor(props) {
super(props);
this.state = {
close: true,
};
}
Then create a function like this
yourFunction = () => {
this.setState({
close: !this.state.close,
});
};
then use this like
render() {
const {close} = this.state;
return (
<Fragment>
<div onClick={() => this.yourFunction()}></div>
<div className={close ? "isYourDefaultClass" : "isYourOnChangeClass"}></div>
</Fragment>
)
}
}
Please give better solutions
Ori Drori's comment is correct, you aren't doing this the "React Way". In React, you should ideally not be changing classes and event handlers using the DOM. Do it in the render() method of your React components; in this case that would be the sideNav and your Header. A rough example of how this would be done in your code is as follows.
HEADER
class Header extends React.Component {
constructor(props){
super(props);
}
render() {
return (
<div className="header">
<i className="border hide-on-small-and-down"></i>
<div className="container">
<a ref="btn" href="#" className="btn-menu show-on-small"
onClick=this.showNav><i></i></a>
<Menu className="menu hide-on-small-and-down"/>
<Sidenav ref="sideNav"/>
</div>
</div>
)
}
showNav() {
this.refs.sideNav.show();
}
}
SIDENAV
class SideNav extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
}
}
render() {
if (this.state.open) {
return (
<div className = "sideNav">
This is a sidenav
</div>
)
} else {
return null;
}
}
show() {
this.setState({
open: true
})
}
}
You can see here that we are not toggling classes but using the state of the components to render the SideNav. This way, or similar is the whole premise of using react. If you are using bootstrap, there is a library which integrates bootstrap elements with the react way of doing things, allowing you to use the same elements but set state on them instead of directly manipulating the DOM. It can be found here - https://react-bootstrap.github.io/
Hope this helps, and enjoy using React!
For anybody reading this in 2019, after React 16.8 was released, take a look at the React Hooks. It really simplifies handling states in components. The docs are very well written with an example of exactly what you need.

Categories

Resources