Use child components as real components, not stateless child objects - reactjs

Some component contains a Tabs with Tabs:
class App {
render() {
<div>
<p>Tabs here:</p>
<Tabs>
<Tab name="Page 1"> .. content here .. </Tab>
<Tab name="Page 2"> .. content here .. </Tab>
<Tab name="Page 3"> .. content here .. </Tab>
</Tabs>
</div>
}
}
Tabs is responsible for most of the markup:
class Tabs {
click() {
// I want the Tab component, because I want to tab.setState()
// `this` is not a `Tab` object
}
render() {
return (
( print the tab labels: )
<ul>
{ this.props.children.map(tab =>
// >>> `tab` is not a `Tab` object <<<
<li><a onClick={ this.click.bind(tab) } href="#">{ tab.props.name }</a></li>
) }
</ul>
( print the tab content: )
{ this.props.children }
);
}
}
A Tab itself is very little, maybe nothing:
const Tab = ({children}) => <div>{ children }</div>;
How does Tabs.click know which Tab was clicked. If I click.bind(tab) it's the child object, not the Tab object. If I click.bind(this) it's the Tabs object.
This might be a very longwinded way of asking why props.children don't contain the Component objects, but a kind of proxy (?) child object. It does have the props, but not the methods, state etc.
edit 1:
In this example, I'd like to let Tab decide how to render itself: once as tab link and once as tab content. Tabs would be responsible for calling the render methods, but Tab would know how. As you can see in Tabs.render() there are 2 renders. It'd be nice if it could do this:
<ul>
{ this.props.children.map(tab => tab.renderLink()) }
</ul>
{ this.props.children.map(tab => tab.renderContent()) }
or just
{ this.props.children }
because content is the normal render
But Tabs can't do that, because it doesn't have Tab objects for children... Why?

Well, there are a few methods to achieve this. To start, you're right - since you're using ES6 arrow functions in the map, this isn't rebound - it's "inherited" from the enclosing scope (I think, technically, it's less inherited and more just left alone - unchanged).
First, instead of binding in the render method, bind click in the constructor.
constructor(props) {
super(props);
this.click = this.click.bind(this);
}
or use ES6 Arrow Functions
click = (event) => {...}
Although this doesn't directly solve your issue, it cleans up the context mess that arises from using bound callbacks.
From there, you can get the clicked tab by using event.target.
You can also use partials - the bind method accepts additional parameters which are prepended to the parameters of the bound function. That would look something like this:
render (
<div>
{ this.props.children.map((tab) => {
<a <li><a onClick={ this.click.bind(null, tab) } href="#">{ tab.props.name }</a></li>
}) }
);
you would have to adjust your click method to accept tab as well: click(tab, event)
I also don't think it's wise, or even recommended to mess with a component's state from another component. Why aren't you using the Tab component? Pass in Tabs onClick as a prop and handle click events like this...
class Tab extends React.Component {
onClick = (event) => {
this.setState({...}); // Tab onClick
if (this.props.onClick) this.props.onClick(); // Tabs onClick
}
}
edit: Working example
Tabs class
class Tabs extends React.Component {
onTabClick = (tab) => {
tab.setState({ test: true });
}
render() {
return (
<div>
{ this.props.children.map((tab) => {
return React.cloneElement(tab, {
onClick: this.onTabClick
});
}) }
</div>
);
}
}
Tab class
class Tab extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
onClick = (event) => {
console.log(event);
event.preventDefault();
if (this.props.onClick) {
this.props.onClick(this);
}
}
render() {
return <div>{ !this.state.test && <a href={ this.props.url } onClick={ this.onClick }>{ this.props.label }</a> }</div>;
}
}
example tabs
<Tabs>
<Tab url="http://www.google.com" label="Google" />
<Tab url="http://www.google.com" label="Google" />
</Tabs>
although it begs the question again - why not just set the state inside of the Tab class? Why do you need to set the state of a child from within a parent? Can that state not be maintained by the parent (using this.setState(...) and passed into the child as a prop?

Related

To change color on onclick event using react

I need to change the colour of a div tag which I have designed when it is clicked. I need to change the colour of the div and I need to add a class to the div when it is clicked, which I need answer in react
Like others mentioned, a little more code details would be nice from your side.
One of the ways to achieve what you want: https://jsfiddle.net/hawk939393/Ly0912nf/
class TodoApp extends React.Component {
state = {
divClicked: false
};
getClassname = () => {
return !this.state.divClicked ?
"toDoApp" : "toDoApp-isClicked"
};
render() {
return (
<div
className={this.getClassname()}
onClick={() => this.setState(prevState => ({divClicked: !prevState.divClicked}))}>
Hello world!
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
^ Have a state that tracks whether a div's className has been changed.

How to hide / show the content of a component that is reused by different views in ReactJS

I have two views that call a particular component and this makes it difficult for me to hide / show when the component is needed. In this way, I have the following:
I tried to make a display: none to overide the styles but it is a problem. This is because the views when using the same component, the classes of the CSS are called the same and when I make the display: none, I get it not to show in "News" but in "Home" it also affects and it is not shown to me.
I tried it:
Component Section:
<div className="section_news">
{orden.map(this.renderSection)}
<ViewAllNews mostrarCategorias={false} categoriaId={newsId} />
If I remove the title like this, I'll leave the view "Home" and "News", and that's not what I intend to do.
Next, I leave the original piece of code:
View named "Home":
<div className="contentbar">
<Section title="Ult. News" />
<Section
notices_id={19}
orden={[[0, 3], [3, 1], [4, 2]]}
/>
</div>
**View named "News": **
<Section
notices_id={data.post.category.id}
orden={[[0, 2], [2, 3]]}
/>
Component Section:
<div className="section_news">
<Titles title={title || (category && category.title)} />
{orden.map(this.renderSection)}
<ViewAllNews categoriaId={newsId} />
Component ViewAllNews:
return (
<Link to={`/news/c/${categoryId}/`}>
<div className="parentLineViewAll">
<div className="LineViewAll" />
<div className="line" />
<div className="Views">View All</div>
<div className="arrowMore">
<FontAwesomeIcon icon="chevron-circle-right" />
</div>
</div>
</Link>
);
};
As you can see, the view of "Home" and "Views" make use of the same component.
What I really need is to hide the component named for the "News" view and also hide the "ViewAllNews" component only for the "News" view.
Thanks for your help!
The easiest way to hide / show react components is to use conditional rendering
Here is a basic example:
class MyComponent Extends React.Component {
constructor(props){
super(props)
this.state = {
isVisible: false, // <-- add a value to state so we can track the components visibility
}
}
show = () => {
this.setState({ isVisible: false })
}
hide = () => {
this.setState({ isVisible: true })
}
render(){
if(this.state.isVisible){
return (
/* your component code */
)
}
}
}
If you want to toggle the component from inside a parent component then you can control this with a ref:
class Parent Extends React.Component {
constructor(props){
this.myRef = React.createRef()
}
render(){
return <MyComponent ref={this.myRef}>
}
}
Once the component mounts, you can check if the ref has been set and call the method anywhere in your code:
if(this.myRef.current){
this.myRef.current.show() // or .hide()
}
You can also control the components visibility via props:
function MyComponent({ isVisible }){
if(isVisible){
return (
/* your component code */
)
}
}
Hope that helps, let me know if you have any questions!
a way to implement this componentWillUnmount() and componentWillMount() ,
like to this example , hope help you this.

How to show a block of collapsible text on click of button

I am trying to implement a collapsible component. I have designed it such as, on click of a button, a block of dynamic text will appear. I made a functional component and using the tags in a class. The name of the component is, CustomAccordion.jsx and using this component in Container.jsx
I have tried to create a button and a function for onClick event.
Part of the CustonAccordion.jsx
const handleToggle = () : string =>{
let content = this.nextElementSibling;
if (content.style.maxHeight){
content.style.maxHeight = null;
}else{
content.style.maxHeight = content.scrollHeight +'px';
}
}
export default function CustomAccordion(props: PropType): React.Component<*> {
const { title, children } = props
return(
<div>
<AccordionButton onClick={() => this.handleToggle()}>{title}</AccordionButton>
<AccordionContent>
<p>{children}
</p>
</AccordionContent>
</div>
)
}
Part of calling Container.jsx
<CustomAccordion title = {this.props.name}>
<p>This is the text passed to component.</p>
</CustomAccordion>
<br />
This does not show the expanded text and it seems that the click event does not work properly. I am very new in react, guessing the syntax might be incorrect.
In react you should generally try to avoid touching DOM directly unless you really have to.
Also you are accessing the handleToggle function wrongly. It should be onClick={() => handleToggle()} because this in your case is window/null and so it has no handleToggle method.
Instead you can use a stateful class component to achieve the same thing.
export default class CustomAccordion extends React.Component {
state = {show: false};
toggle = () => this.setState({show: !this.state.show});
render() {
const {title, children} = this.props;
const {show} = this.state;
return (
<div>
<AccordionButton onClick={this.toggle}>{title}</AccordionButton>
{show && (
<AccordionContent>
<p>{children}</p>
</AccordionContent>
)}
</div>
)
}
}
If you want to have some kind of animation, you can set different className based on the show state instead of adding/removing the elements.

Can't get button component value onClick

I'm sure this is something trivial but I can't seem to figure out how to access the value of my button when the user clicks the button. When the page loads my list of buttons renders correctly with the unique values. When I click one of the buttons the function fires, however, the value returns undefined. Can someone show me what I'm doing wrong here?
Path: TestPage.jsx
import MyList from '../../components/MyList';
export default class TestPage extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleButtonClick(event) {
event.preventDefault();
console.log("button click", event.target.value);
}
render() {
return (
<div>
{this.props.lists.map((list) => (
<div key={list._id}>
<MyList
listCollection={list}
handleButtonClick={this.handleButtonClick}
/>
</div>
))}
</div>
);
}
}
Path: MyListComponent
const MyList = (props) => (
<div>
<Button onClick={props.handleButtonClick} value={props.listCollection._id}>{props.listCollection.title}</Button>
</div>
);
event.target.value is for getting values of HTML elements (like the content of an input box), not getting a React component's props. If would be easier if you just passed that value straight in:
handleButtonClick(value) {
console.log(value);
}
<Button onClick={() => props.handleButtonClick(props.listCollection._id)}>
{props.listCollection.title}
</Button>
It seems that you are not using the default button but instead some sort of customized component from another libray named Button.. if its a customezied component it wont work the same as the internatls might contain a button to render but when you are referencing the event you are doing it throug the Button component

React/Redux - Events between parent and childs for menu and items

I'm trying to build a Menu, with MenuItem(s), from scratch. I'm using React 0.14 and Redux, with CoffeeScript (cjsx). (I'm just getting started with all of those)
I'm stuck because I don't know how to give the MenuItem(s) the reference to the callback function onChildrenClick. Because they are generated through {this.props.children} I don't know how I'm supposed to add a props there.
Menu.cjsx
React = require 'react'
class Menu extends React.Component
#propTypes =
children: React.PropTypes.array.isRequired
render: ->
<div className="ui pointing menu">
{this.props.children} # I don't see how I can bind the "onChildrenClick" function to the child, because they're generated outside of this scope. Maybe by looping on "this.props.children"? I tried but it wasn't successful.
</div>
onChildrenClick: (event) ->
console.log event
module.exports = Menu
MenuItem.cjsx
React = require 'react'
classNames = require('classnames')
class MenuItem extends React.Component
#propTypes =
label: React.PropTypes.string.isRequired
active: React.PropTypes.bool
constructor: (props) ->
super props
#state =
classes: classNames(
'ui'
'item'
'active': this.props.active
)
render: ->
<a className={#state.classes} onClick={#onClick}>
{ this.props.label }
</a>
onClick: =>
console.log Object.assign #state.classes, {active: !#state.classes.active}
#setState({classes: Object.assign #state.classes, {active: true}})
console.log #state.classes # I need to notify the parent so it can notify the current active children to get disabled.
module.exports = MenuItem
Here is how I build my menu and its items:
<Menu>
<MenuItem active={true} label="Menu 1"></MenuItem>
<MenuItem label="Menu 2"></MenuItem>
<MenuItem label="Menu 3"></MenuItem>
</Menu>
In the example, I manually generate "Menu 1 ... 3" but they would actually be generated dynamically.
I don't know if I got the right approach here. Maybe I should use Redux for this? Eventually, I should use Routes, because each MenuItem should have its own url (Angular-like, with #menu-1, for instance)
I also wonder what would be the best approach to give a MenuItem a content. Thanks for insight.
This could be a candidate for React.Children, whereby you're able to specify props to be passed down to any abritrary child element - in this case onClick:
Menu.jsx
<div className="ui pointing menu">
{
React.Children.map(this.props.children,
function(child) {
return React.cloneElement(child, {
onClick: this.onClick
});
}.bind(this)
)
}
</div>
But I might suggest an alternative approach, whereby you pass the list items to the Menu component itself, which can then render each list item individually, rather than just as this.props.children:
Menu.jsx (Alt)
render() {
return (
<div className="ui pointing menu">
{
this.props.menuItems.map((item, index) => (
<MenuItem item={item} onClick={(e) => this.onClick(e, item)} }/>
)
}
</div>
)
}
I'd recommend passing the onClick function as a prop of MenuItem from wherever you're rendering the Menu.
E.g.
menu_wrapper.js
// This is a functional component because it is 'dumb' - does not have state
React = require('react')
Menu = require('menu')
MenuItem = require('menu_item')
menuWrapper = (props)=>
onMenuItemClick = (e)=>
console.log(e)
menuItems = ()=>
props.list.map(function(item) {
isActive = (item.label == props.activeItem)
<MenuItem onClick={onMenuItemClick} active={isActive} label={item.label}/>
}
<div id="menu-wrapper">
<Menu>
{menuItems()}
</Menu>
</div>
menu.js
// Also a 'dumb' component - component without state
React = require('react')
Menu = (props)=>
<div className="ui pointing menu">
{props.children}
</div>
module.exports = Menu
menu_item.js
React = require 'react'
classNames = require('classnames')
class MenuItem extends React.Component
#propTypes =
label: React.PropTypes.string.isRequired
active: React.PropTypes.bool
onClick: React.PropTypes.func.isRequired
constructor: (props) ->
super props
#state =
classes: classNames(
'ui'
'item'
'active': this.props.active
)
render: ->
<a className={#state.classes} onClick={#onClick}>
{ this.props.label }
</a>
onClick: (e)=>
#props.onClick(e)
console.log Object.assign #state.classes, {active: !#state.classes.active}
#setState({classes: Object.assign #state.classes, {active: true}})
console.log #state.classes # I need to notify the parent so it can notify the current active children to get disabled.
module.exports = MenuItem
Lastly, consider moving from CoffeeScript to ES6. Most everything you get in CoffeeScript is now available in ES6, and some. That said, I do miss implicit returns.

Resources