React Communication Between Components - javascript

I am facing an architecture problem when trying this with React. I want to toggle tabs in the most reusable way using React JS.
So, ideally, I am looking for a way, in which on clicking on TabHeading, I deactivate the other TabHeadings in the TabHeader heading and also activate the corresponding TabPane (from this.props.target) in TabContent.
$(".tab-pane").hide();
$(React.findDOMNode(this.props.target)).show();
I want to avoid the solution above. I dont want to use classNames anywhere, or passing the parent component as a prop.Angular has $parent or somekind of variable to call functions on the parent right? Is there a similar way without explicitly passing the parent? Is there a way to communicate with siblings? Please help.
<Tabs>
<TabHeader>
<TabHeading active="active" target={self.refs.pane1}>
Heading 1
</TabHeading>
<TabHeading target={self.refs.pane2}>
Heading 2
</TabHeading>
</TabHeader>
<TabContent>
<TabPane active="active" ref="pane1">
Pane 1
</TabPane>
<TabPane ref="pane2">
Pane 2
</TabPane>
</TabContent>
</Tabs>
JS File
var React = require("react");
var $ = require("jquery");
var TabHeader = React.createClass({
render: function(){
return(
<div className="tab-header">
{this.props.children}
</div>
);
}
});
var TabHeading = React.createClass({
componentDidMount : function(){
},
handleClick : function(e){
//something to set siblings to inactive
//this.props.active="active";
// this.props.target.props.active = "active";
},
render: function(){
return(
<div className="tab-heading" onClick={this.handleClick} >
{this.props.children}
</div>
);
}
});
var TabContent = React.createClass({
render: function(){
return(
<div className="tab-content">
{this.props.children}
</div>
);
}
});
var TabPane = React.createClass({
componentDidMount:function(){
console.log(this.props.children);
},
render: function(){
return(
<div className="tab-pane">
{this.props.children}
</div>
);
}
});
var Tabs = React.createClass({
componentDidMount:function(){
},
render: function(){
return(
<div className="tabs">
{this.props.children}
</div>
);
}
});
Tabs.TabHeader = TabHeader;
Tabs.TabHeading = TabHeading;
Tabs.TabContent = TabContent;
Tabs.TabPane = TabPane;
module.exports = Tabs;
JSfiddle

I found an answer without Flux. Please feel free to suggest ways to improve it.
Tabs.js
var React = require("react");
var $ = require("jquery");
var TabHeading = React.createClass({
render : function(){
var view ;
return (
<div onClick={this.props.handleClick} className={this.props.active? "active tab-heading" : "tab-heading"}>
{this.props.children}
</div>
);
}
});
var TabPane = React.createClass({
render : function(){
return (
<div className={this.props.active? "active tab-pane" : "tab-pane"}>
{this.props.children}
</div>
);
}
});
var Tabs = React.createClass({
handleClick:function(){
this.props.active = arguments[0];
this.forceUpdate();
},
render : function(){
var self = this;
console.log("rendering");
var tabHeadings = this.props.tabHeadings.map(function(item,index){
active = (index==self.props.active);
return <TabHeading key={index} handleClick={self.handleClick.bind(self,index)} index={index} active={active}>{item}</TabHeading>
});
var tabPanes= self.props.tabPanes.map(function(item,index){
active = (index==self.props.active);
return <TabPane key={index} active={active}>{item}</TabPane>
});
return (
<div className="tab">
<div className="tab-header">
{tabHeadings}
</div>
<div className="tab-content">
{tabPanes}
</div>
</div>
);
}
});
module.exports = Tabs;
File.js
render:function(){
var self = this;
var tabHeading1 = (
<p> Heading 1 </p>
);
var tabHeading2 = (
<p> Heading 2 </p>
);
var tabPane1 = (
<p> Pane 1 </p>
);
var tabPane2 = (
<p> Pane 2 </p>
);
var tabHeadings = [tabHeading1,tabHeading2];
var tabPanes = [tabPane1,tabPane2];
return (<div>
<div className="container">
<Tabs tabHeadings={tabHeadings} tabPanes={tabPanes} active="0"/>
</div>
</div>
);
}
JSFIDDLE - link

Are you open to using Flux? An awful lot of tricky things with React alone become quite simple when you combine Flux with React.
In this case, a store would keep track of the active tab. Clicking on an inactive tab would notify the store that a new tab should become active. All of your individual tabs would subscribe to the store so that when the store changed the state of the active tab, all the siblings would be notified.

Related

How to get my handleClick to render different components

I am a beginner so please excuse my ignorance. I am posting one component from a larger app that I am building. This component has a handleClick function that changes the state when an image is clicked. When the image is clicked, a new component is rendered. Currently, the same 'new component' is rendered no matter which image is clicked. I'd like the component to be based on which image was clicked.
var AllocationDiv = React.createClass({
getInitialState: function(){
return {clicked: false};
},
handleClick: function() {
this.setState (
{clicked: true}
);
},
render: function () {
var handleFunc = this.handleClick; //because it wasn't brining this.handleClick into the render function
var chartMap = pieCharts.map(function(prop){
return <img onClick={handleFunc} id={prop} src={prop} />;
});
return (
<div id='bottomSection'>
<h2>Select Desired Asset Allocation</h2>
<div id='pieCharts'>
<table>
<tr>{pieHeadMap}</tr>
</table>
<div>
{chartMap}
<div id='test'>
{this.state.clicked ? <TestComponent /> : null}
</div>
</div>
</div>
</div>
);
}
});
var chartMap renders three images. Assuming I create three unique test components, how would I get them to be rendered depending on which image was clicked? Here is the entire app. I know the whole thing is a mess atm, but I'm using this as a sandbox to learn through problem-solving. Thanks!
http://codepen.io/sdpercussion/pen/NRQNLv?editors=0010
So, here is what I would do for this. Instead of having a boolean value for your clicked state, you should have a string. The string should be the name of the image being clicked. (you need to assign names or ID's or anything to differentiate them)
so.. initial state is:
getInitialState: function(){
return {clicked:''};
},
next your handleClick would have to change and you'd need to pass the image name/Id in to it.
handleClick: function(image) {
this.setState ({
clicked: image
});
},
then, inside your render..
(make sure to .bind(this) in your map so you can use the component scope if you want to call your methods. var self = this; type workarounds show a misunderstanding of scope)
render: function () {
var chartMap = pieCharts.map(function(prop){
// pass in your image name to your callback using bind, null value here skips over the scope portion and is what you need
return <img onClick={this.handleClick.bind(null, prop)} id={prop} src={prop} />;
}.bind(this));
// get the component you want for each specific image and save to a variable for display
var imgVar = null;
switch (this.state.image) {
case 'image1':
imgVar = <NewComponent />;
break;
case 'image2':
imgVar = <DifferentComponent />;
break;
}
return (
<div id='bottomSection'>
<h2>Select Desired Asset Allocation</h2>
<div id='pieCharts'>
<table>
<tr>{pieHeadMap}</tr>
</table>
<div>
{chartMap}
<div id='test'>
{imgVar}
</div>
</div>
</div>
</div>
);
}
You can add a dynamic "id" to your <img> tag as below. So that based on clicked image you can render a component.
handleClick: function() {
//alert(event.target.id);
if(event.target.id === "2"){
this.setState (
{clicked: true}
); }
},
render: function () {
var handleFunc = this.handleClick; //because it wasn't brining this.handleClick into the render function
var count =0;
var chartMap = pieCharts.map(function(prop){
count++;
return <img onClick={handleFunc} id={count+1} src={prop} />;
});
return (
<div id='bottomSection'>
<h2>Select Desired Asset Allocation</h2>
<div id='pieCharts'>
<table>
<tr>{pieHeadMap}</tr>
</table>
<div>
{chartMap}
<div id='test'>
{this.state.clicked ? <TestComponent /> : null}
</div>
</div>
</div>
</div>
);
}
});

How to query the px width of a div 100% width element in react.js?

I'm new to react and I'm sure I'm not the only one with this question. I have a div in my component that is width:100%; I need to calculate it's pixel width. How would I do this?
To put this in perspective of my actual use-case, here's what I'm doing:
I'm building a slider in react. My slider has a div with the class name "slide-holder" inside of this div are the individual div's for the slides. The slider is a very basic slider and the slide-holder will move horizontally the width of one slide to change to the next/previous slide.
Here is my full code - be aware I'm still working on this actively so it may break at times but you should be able to get the idea of how it works from it, I'm just doing the basics in react nothing fancy yet.
http://codepen.io/thewebtech/pen/JRXybb?editors=0110
/*var React = require("react");
var ReactDOM = require('react-dom');*/
var Slide = React.createClass({
render: function() {
return (<div className="slide" style={{backgroundImage:'url(' + this.props.imgsrc + ')'}}>
<div className="caption">{this.props.children}</div>
</div>);
}
});
var SliderControlButton = React.createClass({
render: function() {
var btnClasses="slider-control-button "+this.props.direction;
return (
<button className={btnClasses}>
{this.props.direction}
</button>
);
}
});
var SliderControls = React.createClass({
render: function() {
return (
<div className="slider-controls">
<SliderControlButton direction="left"/> <SliderControlButton direction="right"/>
</div>
);
}
});
var SliderHolder = React.createClass({
getInitialState:function(){
var setWidth= React.Children.count(this.props.children)* 200+"px";
var setSlideWidth= setWidth / React.Children.count(this.props.children);
return{width: setWidth,
slideWidth: setSlideWidth
}
},
render:function(){
return (
<div className="slide-holder" style={{width: this.state.width}}>
{this.props.children}
</div>
)
}
});
function renderChildren(props) {
return React.Children.map(props.children, child => {
if (child.type === Slide)
return React.cloneElement(child, {
name: props.name
})
else
return child
})
}
var Slider = React.createClass({
render: function() {
return (
<div className="slider">
<SliderControls/>
<SliderHolder>
<Slide imgsrc="http://jonmclarendesigns.com/wedding/wp-content/uploads/2016/09/DSC_4050.jpg">hello</Slide>
<Slide imgsrc="http://jonmclarendesigns.com/wedding/wp-content/uploads/2016/09/DSC_3819.jpg"/>
</SliderHolder>
</div>
);
}
});
ReactDOM.render(<Slider/>, document.getElementById("app"));
I realize I'm not actually answering your question directly, but I think for the actual usecase you've described, you'd be better off using some prebuilt tools.
react-css-transition-replace solves this exact problem. All you need to do is apply CSS transition classes and render the desired slide component.

Render two components adjacent to each other in React

I am trying to call a component from another component to display some html and it works if I call the component alone, but if I add more markup inside the same return function, it throws the following error:
Adjacent JSX elements must be wrapped in an enclosing tag (9:12) while parsing file
Product.jsx
var React = require('react');
var Product = React.createClass({
render: function() {
return (<p>Product</p>);
}
});
module.exports = Product;
ProductSlider.jsx (Works)
var React = require('react');
var Product = require('./Product.jsx');
var ProductSlider = React.createClass({
render: function() {
return (
<div><Product /></div>
);
}
});
module.exports = ProductSlider;
ProductSlider.jsx (Doesn't Work)
var React = require('react');
var Product = require('./Product.jsx');
var ProductSlider = React.createClass({
render: function() {
return (
<div><Product /></div>
<div><p>Something else</p></div>
);
}
});
module.exports = ProductSlider;
Does anybody knows what is wrong with this code?
You have to wrap a rendered component in a top level component, that's your problem. If you did
return (
<div>
<div><Product /></div>
<div><p>Something else</p></div>
</div>
);
It would work.
You don't need a top level <div> wrapper
If you are using react 16.2+. Simply use Fragments:
return(
<Fragment>
<div><Product /></div>
<div><p>Something else</p></div>
</Fragment>
)
You can also try this way:
return(
[
<div key="unique1"><Product /></div>
<div key="unique2"><p>Something else</p></div>
]
)
Note: key should be unique.

Build ReactJS component where siblings components need to commuicate

So I am trying to figure out the best way to structure a particular type of ReactJS Element.
So lets say I have this element called ContentArea. A ContentArea can be composed on a number of other custom elements, ContentAreaHeader, ContentAreaContent, and ContentAreaAction. ContentArea, ContentAreaHeader, and ContentAreaContent are basically wrapper elements that wrap its child in the correct HTML element with the proper classes. Implementation of ContentAreaAction is not important to this question, just wanted to mention it to show there are a number of different elements. The ContentArea should only have 1 header element but should be able to support multiple other items (ContentAreaContent and/or ContentAreaAction).
One feature is being able to click on the header and toggle the display the other elements beside the header. Coming from the AngularJS world, my initial though was to create a directive that I could just reuse so I tried that in ReactJS and my code looked this this:
var MyPage = React.createClass({
render: function() {
return (
<ContentArea>
<ContentAreaHeader>My Header</ContentAreaHeader>
<ContentAreaContent className={cssClasses.join(' ')}>My Content</ContentAreaContent>
</ContentArea>
);
}
});
Now I could add the event and collapsed state stuff this the MyPage component but then I can only have 1 ContentArea per page element or have multiple copied of that for each ContentArea, neither of which are good. In AngularJS, each component can have its own scope and inherit from its parent which would prevent this issue.
My current solution is that I have created the following mixin:
var ContentAreaCollapsableMixin = {
getInitialState: function() {
return {
collapsed: false
};
},
toggleCollapse: function() {
this.setState({
collapsed: !this.state.collapsed
});
}
}
Now to be able to have multiple ContentAreas per page elements, I create a custom ContentArea element for the needs of the page:
var MyContentArea = React.createClass({
mixins: [
contentArea.mixins.collapsable
],
render: function() {
var cssClasses = [];
console.log(this.state.collapsed);
if(this.state.collapsed) {
cssClasses.push('u-hide');
}
return (
<ContentArea>
<span onClick={this.toggleCollapse}><ContentAreaHeader>My Header</ContentAreaHeader></span>
<ContentAreaContent className={cssClasses.join(' ')}>My Content</ContentAreaContent>
</ContentArea>
);
}
});
var MyContentArea2 = React.createClass({
mixins: [
contentArea.mixins.collapsable
],
render: function() {
var cssClasses = [];
if(this.state.collapsed) {
cssClasses.push('u-hide');
}
return (
<ContentArea>
<span onClick={this.toggleCollapse}><ContentAreaHeader>My Header</ContentAreaHeader></span>
<ContentAreaContent className={cssClasses.join(' ')}>My Content</ContentAreaContent>
<ContentAreaContent className={cssClasses.join(' ')}>My Content2</ContentAreaContent>
</ContentArea>
);
}
});
var ContentAreaComponents = React.createClass({
render: function() {
return (
<div>
<h1 id="test" className="test">Content Area</h1>
<MyContentArea />
<MyContentArea2 />
</div>
);
}
});
Note I am using the span to attach my event since as far as I know I can't attach event to custom/child elements and the header should not always have this event so I don't want to pollute the header directive with that content (and maybe I might want to add that event to an icon in the header instead of the whole header).
Is this the correct way to build this type of functionality when dealing with element that are wrappers and have an hierarchy like this?
The cleanest way to do this is by passing components as props. For example:
<ContentArea
header={"My Header"}
content={[
<div>My Content</div>,
<div>My Other Content</div>
]}
/>
This looks a bit odd in JSX, so you can do it without if you prefer.
React.createElement(ContentArea, {
header: "My Header",
content: [
<div>My Content</div>,
<div>My Other Content</div>
]
})
In ContentArea you can simply render these props as you'd render props.children, but with more control.
var ContentArea = React.createClass({
getInitialState: function(){ return {open: true} },
toggleOpen: function(){ this.setState({open: !this.state.open}) },
render: function(){
var className = this.state.open ? "" : "hidden";
return (
<div>
<ContentAreaHeader onClick={this.toggleOpen}>
{this.props.header}
</ContentAreaHeader>
{this.props.content.map(function(element, index){
return (
<ContentAreaContent className={className} key={index}>
{element}
</ContentAreaContent>
);
})}
</div>
);
}
});
The resulting structure in this example would be:
<ContentArea>
<div>
<ContentAreaHeader>My Header</ContentAreaHeader>
<ContentAreaContent className="..." key="0">
<div>My Content</div>
</ContentAreaContent>
<ContentAreaContent className="..." key="1">
<div>My Other Content</div>
</ContentAreaContent>
</div>
</ContentArea>
This is the way that doesn't break any rules. The way to do it with the API you mentioned is with React.Children.map and determining if it's a header or content based on the index (e.g. 0 is the header, and 1..infinity are content), and you wrap it in a div to apply the click handler and className respectivley.

Is there any required div wrapper inside a React component

I create a menu using React JS:
var Dropdown = React.createClass({
render: function() {
return (
<Title />
<OptionsDropdown />
);
}
});
where Title and OptionsDropdown are other React classes.
The problem is this code has error until I wrap them around a div like :
var Dropdown = React.createClass({
render: function() {
return (
<div class="something">
<Title />
<OptionsDropdown />
</div>
);
}
});
Is there anyway better to handle this situation when I want no div is wrapped outside Title and OptionsDropdown.
Finally. I found out there is maximum one root node in render function in React JS. Better to wrap it with a div.

Categories

Resources