How to get my handleClick to render different components - javascript

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>
);
}
});

Related

react-dnd DragSource that has child DragSource

I have a tree structure with a root "Tree" component that has a list of root "TreeNodes", then TreeNodes can have an arbitrary number of children.
So inside of the TreeNode render method I have
childrenHTML = this.state.children.map((child) => {
return (<TreeNode nodeClick ={this.props.nodeClick} parentNode={this}
key={child.childId} node={child} level={this.state.level+1} />);
});
and
const { isDragging, connectDragSource, connectDragPreview} = this.props;
Then the final return for the render method looks like
return connectDragSource(
<div>
<div style={nodeStyle}>
{connectDragPreview(
<div className = {"nodeContainer" + ' ' + this.state.nodeHover} onMouseLeave={this.nodeUnHover} onMouseOver={this.nodeHover} onClick={()=>this.props.nodeClick(this)}>
<img alt = {this.state.titleIcon} className = "titleIcon" src = {Connections.getImageURLByName(this.state.titleIcon)} />
<p className="nodeLabel"> {this.state.nodeName}</p>
{nodeLabelsHTML}
<DescriptiveIcons descriptiveIcons={this.state.icons} />
</div>
)}
</div>
{childrenHTML}
</div>
);
I am exporting:
export default DragSource(DragTypes.STRUCTURE, treeNodeSource, collect)(TreeNode);
Then in the parent Tree file I am exporting
export default DragDropContext(HTML5Backend)(Tree)
and rendering the rootnodes like
rootNodesHTML = rootNodes.map((node) => {
return <TreeNode nodeClick={this.props.nodeClick} key={node.childId} node={node} level={0}/>
});
...
return (
<div className="treeContainer">
<div className="wrapContainer">
{rootNodesHTML}
</div>
</div>
);
This works great but only for the rootnodes, when I try to render the children (the childrenHTML variable is only populated after the parent is clicked on) I get the following error:TypeError: connectDragPreview is not a function
Leading me to believe that those react-dnd props that come from the "collect" function is not being passed to the rootnodes but not the children. It seems like it should to me because the same code should be executed for the parents as for the children as its the same class... really stuck here.
I am relatively new to react, and new to ideas like HOCs so all tips or suggestions are appreciated. Thank you!
I was able to get this working. Check out the example posted at the end of the thread in
https://github.com/react-dnd/react-dnd/issues/332.
Ultimately the solution was to wrap the TreeNode in a "DragContainer" with a very simple render method
render(){
const {...props} = this.props;
return <TreeNode {...props}/>
}
Then in the TreeNode render method, when rendering the child nodes render a DragContainer instead, passing in all the usual props.
childrenHTML = this.state.children.map((child) => {
return <DragNodeContainer modalFunctions = {this.props.modalFunctions} nodeClick ={this.props.nodeClick} parentNode={this} key={child.childId} node={child} level={this.state.level+1} />;
});
I am still unsure as to the technical reason for this, however, the fix seems to work for other people and it works for me!

React CSSTransitionGroup does not work even with key being set

I have been trying since yesterday to make an animation to my image carousel. As far as I understand, you wrap the content to be animated with the CSSTransitionGroup and make sure it stays in the dom and also specify a unique key to each child of the transition group. I believe I have followed all this yet I see no transition.
One thing worth to mention, While I was trying to get this working I suspected if something could be wrong with the key, so I tried setting the key with a random string. The key would change every-time the state changes, and for some unknown reason I could see the animation. Can someone explain this to me.
I am not sure where I am going wrong, whether the version of transition group or in setting the key to children, No clue !
Below is the code replicating my problem.
var CSSTransitionGroup = React.addons.CSSTransitionGroup
class Images extends React.Component {
constructor(props){
super(props);
this.state = {
showComponent: false,
}
}
componentWillReceiveProps(nextProps){
if (this.props.name === nextProps.showComponentName){
this.setState({
showComponent: true,
})
} else {
this.setState({
showComponent: false,
})
}
}
render() {
if (this.state.showComponent){
return (
<img src={this.props.url} />
)
} else {
return null;
}
}
}
class TransitionExample extends React.Component {
constructor (props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state= {
showComponentName: null,
}
}
onClick(button) {
this.setState({
showComponentName: button.currentTarget.textContent,
})
}
render() {
var imageData = [
"http://lorempixel.com/output/technics-q-c-640-480-9.jpg",
"http://lorempixel.com/output/food-q-c-640-480-8.jpg",
"http://lorempixel.com/output/city-q-c-640-480-9.jpg",
"http://lorempixel.com/output/animals-q-c-640-480-3.jpg"
];
var images = [];
for (var i in imageData) {
i = parseInt(i, 10);
images.push(
<Images url={imageData[i]} showComponentName={this.state.showComponentName} name={imageData[i]} key={imageData[i]} />
);
}
return (
<div>
<div>
<button onClick={this.onClick}>{imageData[0]}</button>
<button onClick={this.onClick}>{imageData[1]}</button>
<button onClick={this.onClick}>{imageData[2]}</button>
<button onClick={this.onClick}>{imageData[3]}</button>
</div>
<div className="transitions">
<CSSTransitionGroup
transitionName="viewphoto"
transitionEnterTimeout={2000}
transitionLeaveTimeout={2000}
transitionAppearTimeout={2000}
transitionAppear={true}
transitionEnter={true}
transitionLeave={true}>
{images}
</CSSTransitionGroup>
</div>
</div>
);
}
}
ReactDOM.render(
<TransitionExample />,
document.getElementById('container')
);
I am also providing the link to the example on jsfiddle
The problem with your code is that images is always an array of elements that don't mount/unmount. The correct approach for this is to change the child. For example, if you substitute the return of the render method of your fiddle with this:
return (
<div>
<div>
<button onClick={this.onClick}>{imageData[0]}</button>
<button onClick={this.onClick}>{imageData[1]}</button>
<button onClick={this.onClick}>{imageData[2]}</button>
<button onClick={this.onClick}>{imageData[3]}</button>
</div>
<div className="transitions">
<CSSTransitionGroup
transitionName="viewphoto"
transitionEnterTimeout={2000}
transitionLeaveTimeout={2000}
transitionAppearTimeout={2000}
transitionAppear={true}
transitionEnter={true}
transitionLeave={true}>
<img src={this.state.showComponentName} key={this.state.showComponentName}/>
</CSSTransitionGroup>
</div>
</div>
);
The animation works! Using a simple img instead of your Images component and giving it the image url (this only works when you have clicked a button, showComponentName should be initialized to show the first image). You could also use a custom component of course, but the point here is that the children elements of CSSTransitionGroup must be changed if you want the animation to trigger because otherwise you are always rendering the same four Images components no matter whether they return the img or not. You might want to check out react-css-transition-replace since it usually works better when it comes to replacing.

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