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.
Related
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>
);
}
});
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.
So basically what I am doing is iterating through an array of data and making some kind of list. What I want to achieve here is on clicking on a particular list item a css class should get attached.
Iteration to make a list
var sports = allSports.sportList.map((sport) => {
return (
<SportItem icon= {sport.colorIcon} text = {sport.name} onClick={this.handleClick()} key= {sport.id}/>
)
})
A single list item
<div className="display-type icon-pad ">
<div className="icons link">
<img className="sport-icon" src={icon}/>
</div>
<p className="text-center">{text}</p>
</div>
I am not able to figure out what to do with handleClick so that If I click on a particular list it gets highlighted.
If you want to highlight the particular list item it's way better to call the handleClick function on the list item itself, and you can add CSS classes more accurately with this approach,
here is my sample code to implement the single list component
var SingleListItem = React.createClass({
getInitialState: function() {
return {
isClicked: false
};
},
handleClick: function() {
this.setState({
isClicked: true
})
},
render: function() {
var isClicked = this.state.isClicked;
var style = {
'background-color': ''
};
if (isClicked) {
style = {
'background-color': '#D3D3D3'
};
}
return (
<li onClick={this.handleClick} style={style}>{this.props.text}</li>
);
}
});
Keep a separate state variable for every item that can be selected and use classnames library to conditionally manipulate classes as facebook recommends.
Edit: ok, you've mentioned that only 1 element can be selected at a time,it means that we only need to store which one of them was selected (I'm going to use the selected item's id). And also I've noticed a typo in your code, you need to link the function when you declare a component, not call it
<SportItem onClick={this.handleClick} ...
(notice how handleClick no longer contains ()).
And now we're going to pass the element's id along with the event to the handleClick handler using partial application - bind method:
<SportItem onClick={this.handleClick.bind(this,sport.id} ...
And as I said we want to store the selected item's id in the state, so the handleClick could look like:
handleClick(id,event){
this.setState({selectedItemId: id})
...
}
Now we need to pass the selectedItemId to SportItem instances so they're aware of the current selection: <SportItem selectedItemId={selectedItemId} ....Also, don't forget to attach the onClick={this.handleClick} callback to where it needs to be, invoking which is going to trigger the change of the state in the parent:
<div onClick={this.props.onClick} className={classNames('foo', { myClass: this.props.selectedItemId == this.props.key}); // => the div will always have 'foo' class but 'myClass' will be added only if this is the element that's currently selected}>
</div>
How do you change the position of a Component item in React?
Unless I've misunderstood it, React orders list items by key, which is represented in the DOM by data-reactid, but I don't know how to modify the key of components on the page.
i.e. How do you grab the component, change it's key, and then fire a render so that the reordered list renders in the order you've set?
e.g. in the following code example, when the Click me link is clicked, the first list item would be swapped with the last list item.
Ideally, this functionality would allow you to dynamically reorder/relocate any component on the page without changing the order of components in the render method.
Here is a link to the repo where the full project is located: https://github.com/bengrunfeld/gae-react-flux-todos
var TodoBox = React.createClass({
render: function(){
return (
<div className="todo-container">
<h4>GAE React Flux Todos</h4>
<TodoList data={this.state.data} />
</div>
)
}
});
var TodoList = React.createClass({
changePosition: function(e){
// Change position of list item (e.g. to top/certain position/end of list)
},
render:function(){
var todoNodes = this.props.data.map(function(todo) {
return (
<Todo key={todo.id} id={todo.id}>
{todo.todoText}
</Todo>
);
});
return (
<form className="todoList">
{todoNodes}
<a onClick={this.changePosition}>Click me</a>
</form>
)
}
});
var Todo = React.createClass({
render:function(){
return (
<div className="todoItem">
<input type="text" className={this.props.id} onChange={this.checkInput} defaultValue={this.props.children} ref="todoItem"/>
</div>
)
}
});
The key prop is not used to order the element, but to reconciliate it between different render calls. Elements with the same key will not be re-rendered but rather diffed against each other in order to update the DOM optimally. See Reconciliation
If you want to reorder elements, you need to change their position in your JSX or in the element array you pass as children in your render method (todoNodes).
In your case, you could make a copy of this.props.data in the TodoList component state, then update that copy in your changePosition method with something like this.setState({data: reorderedData}). A good place to make that copy would be in getInitialState.
The render method of your TodoList would then be called again, and you would map over your newly reordered this.state.data to create an array of Todo elements ordered to your liking.
However, be aware that props in getInitialState is an anti-pattern. Since your data lives in the state of your TodoBox component, a way to avoid this would be to have your TodoList component call this.props.onReorder(reorderedData) in its changePosition method. Your TodoBox component could then pass an event handler to its TodoList child, and update its state with the new data whenever this handler is called.
var TodoBox = React.createClass({
handleReorder: function(reorderedData) {
this.setState({data: reorderedData});
},
render: function(){
return (
<div className="todo-container">
<h4>GAE React Flux Todos</h4>
<TodoList data={this.state.data} onReorder={this.handleReorder} />
</div>
)
}
});
var TodoList = React.createClass({
changePosition: function(e){
// Change position of list item (e.g. to top/certain position/end of list)
// Create a copy of this.props.data and reorder it, then call
// this.props.onReorder to signal to the parent component that
// the data has been reordered
this.props.onReorder(reorderedData);
},
render:function() {
var todoNodes = this.props.data.map(function(todo) {
return (
<Todo key={todo.id} id={todo.id}>
{todo.todoText}
</Todo>
);
});
return (
<form className="todoList">
{todoNodes}
<a onClick={this.changePosition}>Click me</a>
</form>
)
}
});
Keys are used for something else, not for sorting. React uses keys to optimize its internal Virtual DOM operations. It means you tell React that "no matter the order of these siblings, the individual sibling is still identified by this key". That's how React knows whether it should prepend, insert, delete, or append new siblings by reusing the old, without throwing stuff away unnecessarily.
As for your sorting question: To change the order of the siblings, just sort the JavaScript array this.props.data.
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.