I'm having troubles wrapping my head around this. I'm trying to show/hide text inside one of my components, but I'm not able to do it. I get I was clicked! message so I know the function is being passed down. What am I missing?
Do I also need to declare a visibility CSS declaration, maybe that's what I'm missing?
SnippetList.jsx
import React, { Component, PropTypes } from 'react'
import { createContainer } from 'meteor/react-meteor-data';
import Snippet from './snippet'
import { Snippets } from '../../../api/collections/snippets.js'
class SnippetList extends React.Component {
constructor(props) {
super(props);
this.state = { visible: false }
this.toggleVisible = this.toggleVisible.bind(this);
}
toggleVisible() {
this.setState( { visible: !this.state.visible } )
console.log('I was clicked');
}
renderSnippets() {
return this.props.snippets.map( (snippet) => (
<Snippet
key={snippet._id}
title={snippet.title}
content={snippet.content}
onClick={this.toggleVisible}
/>
));
}
render() {
const snippets = Snippets.find({}).fetch({});
return (
snippets.length > 0
?
<ul>{this.renderSnippets()}</ul>
:
<p>No Snippets at this time</p>
)
}
}
SnippetList.propTypes = {
snippets: PropTypes.array.isRequired,
}
export default createContainer(() => {
Meteor.subscribe('snippets');
return {
snippets: Snippets.find({}).fetch()
};
}, SnippetList);
Snippet.jsx
import React, { Component, PropTypes } from 'react'
export default class Snippet extends React.Component {
render() {
const visible = this.props.toggleVisible
return (
<article>
<header>
<h1 className='Snippet-title'>{this.props.title}</h1>
</header>
<div className={visible ? 'show' : 'hidden'} onClick={this.props.onClick}>
<p className='Snippet-content'>{this.props.content}</p>
</div>
</article>
)
}
}
Snippet.propTypes = {
title: PropTypes.string.isRequired,
content: PropTypes.string.isRequired
// toggleVisible: PropTypes.func.isRequired
}
the issue is you aren't passing the hide part as a prop.
in Snippet you do const visible = this.props.toggleVisible but... toggleVisible isn't passed to your Snippet component thus its always undefined
return this.props.snippets.map( (snippet) => (
<Snippet
key={snippet._id}
title={snippet.title}
content={snippet.content}
onClick={this.toggleVisible}
/>
));
add toggleVisible... aka change to this.
return this.props.snippets.map( (snippet) => (
<Snippet
key={snippet._id}
title={snippet.title}
content={snippet.content}
toggleVisible={this.state.visible}
onClick={this.toggleVisible}
/>
));
you should probably also bind your renderSnippets this to the class as well... meaning add this to your constructor this.renderSnippets = this.renderSnippets.bind(this);
Now to talk about your code, why are you rendering a <ul> as the parent of a <article> ? the child of a ul should be a <li> I would refactor your components to be more like this.
class SnippetList extends React.Component {
constructor(props) {
super(props);
this.state = { visible: false };
this.toggleVisible = this.toggleVisible.bind(this);
this.renderSnippets = this.renderSnippets.bind(this);
}
toggleVisible() {
this.setState( { visible: !this.state.visible } )
console.log('I was clicked');
}
renderSnippets() {
return this.props.snippets.map( (snippet) => (
<Snippet
key={snippet._id}
title={snippet.title}
content={snippet.content}
toggleVisible={this.state.visible}
onClick={this.toggleVisible}
/>
));
}
render() {
const snippets = Snippets.find({}).fetch({});
return (
snippets.length > 0
? <ul>{this.renderSnippets()}</ul>
: <p>No Snippets at this time</p>
)
}
}
export default class Snippet extends React.Component {
render() {
const {toggleVisible: visible} = this.props;
return (
<li>
<article>
<header>
<h1 className="Snippet-title">{this.props.title}</h1>
</header>
<div onClick={this.props.onClick}>
<p className={visible ? 'show Snippet-content' : 'hidden Snippet-content'}>{this.props.content}</p>
</div>
</article>
</li>
)
}
}
Related
I have a Tabbar in my Tabbar Component, Which I Change the index props in it :
class Tabbar extends Component {
state = {
index: this.props.index,
name: this.props.name,
image: this.props.image
};
changeTabs = () => {
this.setState({index: this.props.index});
}
render() {
return (
<React.Fragment>
<div id={this.state.index} className="col">
<button onClick={this.changeTabs}></button>
</div>
</React.Fragment>
);
}
}
export default Tabbar;
And Then In my Other Component, I Wanna Re-Render a fragment after props change. Here's my Code :
import Tabbar from './Tabbar';
class Tabview extends Component {
constructor(props) {
super(props);
this.state = {
tabs: [
{index: 0, name: "tab0", image:require('../Assets/profile.svg'),childView: {ProfilePage} },
{index: 1, name: "tab1", image:require('../Assets/home.svg'),childView: {HomePage}},
{index: 2, name: "tab2", image:require('../Assets/blog.svg'),childView: {BlogPage}},
],
}
}
handleRender = () => {
this.state.tabs.map(item => {
if (item.index === this.props.index) {
return <item.childView/>;
}
})
return <BlogPage/>;
}
render() {
return (
<div>
<Header/>
{this.handleRender()}
{this.state.tabs.map(item =>
<Tabbar key={item.index} index={item.index} name={item.name} image={item.image}/>
)}
</div>
);
}
}
export default Tabview;
The Method "handleRender" should handle the rendering.
I tried to use "componentDidMount" or "componentDidUpdate", But I didn't work.
How Can I Make it Work?
Thank you in advance!
You dont need to have a state in the child component for this reason
You can simply have a callback in parent and call it in child component like below.
import React, { Component } from "react";
class Tabbar extends Component {
render() {
return (
<React.Fragment>
<div id={this.props.index} className="col">
<button
onClick={() => this.props.changeTabs(this.props.index)}
></button>
</div>
</React.Fragment>
);
}
}
export default Tabbar;
And in parent you maintain the active index state
import Tabbar from "./Tabbar";
import React, { Component } from "react";
class Tabview extends Component {
constructor(props) {
super(props);
this.state = {
tabs: [
//your tabs
],
activeIndex: 0
};
}
handleRender = () => {
this.state.tabs.map((item) => {
if (item.index === this.state.activeIndex) {
return <item.childView />;
}
});
return <div />;
};
render() {
return (
<div>
{this.handleRender()}
{this.state.tabs.map((item) => (
<Tabbar
key={item.index}
index={item.index}
name={item.name}
image={item.image}
changeTabs={(index) => this.setState({ activeIndex: index })}
/>
))}
</div>
);
}
}
export default Tabview;
I'm trying to do this example of react popout but it doesn't seem to be working.
https://github.com/JakeGinnivan/react-popout#readme
example is at the bottom.
import React from "react"
import Popout from "react-popout"
class PopupLogin extends React.Component {
constructor(props) {
super(props);
this.popout = this.popout.bind(this);
this.popoutClosed = this.popoutClosed.bind(this);
this.state = { isPoppedOut: false };
}
popout() {
this.setState({isPoppedOut: true});
}
popoutClosed() {
this.setState({isPoppedOut: false});
}
render() {
if (this.state.isPoppedOut) {
return (
<Popout title='Window title' onClosing={this.popoutClosed}>
<div>Popped out content!</div>
</Popout>
);
} else {
var popout = <span onClick={this.popout} className="buttonGlyphicon glyphicon glyphicon-export"></span>
return (
<div>
<strong>Section {popout}</strong>
<div>Inline content</div>
</div>
);
}
}
}
export default PopupLogin
This is supposed to look like http://jake.ginnivan.net/react-popout/ this.
But in my output looks like this.
You forgot to add a text to the span ,according to their docs, so as a result there was no link, hence no onClick was fired. You could style the link as per your needs
Sandbox: https://codesandbox.io/s/react-example-vxtu9
import React from "react";
import Popout from "react-popout";
import ReactDOM from "react-dom";
class PopupLogin extends React.Component {
constructor(props) {
super(props);
this.popout = this.popout.bind(this);
this.popoutClosed = this.popoutClosed.bind(this);
this.state = { isPoppedOut: false };
}
popout() {
this.setState({ isPoppedOut: true });
}
popoutClosed() {
this.setState({ isPoppedOut: false });
}
render() {
if (this.state.isPoppedOut) {
return (
<Popout
url="popout.html"
title="Window title"
onClosing={this.popoutClosed}
>
<div>Popped out content!</div>
</Popout>
);
} else {
var popout = (
<span
onClick={this.popout}
className="buttonGlyphicon glyphicon glyphicon-export"
>
Open
</span>
);
return (
<div>
<strong>Section {popout}</strong>
<div>Inline content</div>
</div>
);
}
}
}
ReactDOM.render(<PopupLogin />, document.getElementById("root"));
It looks like the code in documentation missing the text. Add (pop window out) in the popout.
import React from "react";
import Popout from "react-popout";
class PopupLogin extends React.Component {
constructor(props) {
super(props);
this.popout = this.popout.bind(this);
this.popoutClosed = this.popoutClosed.bind(this);
this.state = { isPoppedOut: false };
}
popout() {
this.setState({ isPoppedOut: true });
}
popoutClosed() {
this.setState({ isPoppedOut: false });
}
render() {
if (this.state.isPoppedOut) {
return (
<Popout title="Window title" onClosing={this.popoutClosed}>
<div>Popped out content!</div>
</Popout>
);
} else {
var popout = (
<span
onClick={this.popout}
className="buttonGlyphicon glyphicon glyphicon-export"
>
<a
style={{
textDecoration: "underline",
color: "blue",
cursor: "pointer"
}}
onClick={this.popout}
>
(pop window out)
</a>
</span>
);
return (
<div>
<strong>Section {popout}</strong>
<div>Inline content</div>
</div>
);
}
}
}
export default PopupLogin;
I googled and found some relevant answers but they don't seem to be complete. eg. react.js don't render until ajax request finish
One of the answer suggest to put if else in template, and I have the following Loader component doing this:
var LoaderWrapper = function (props) {
return (
<div>
{props.loaded ? props.children :
<div className="margin-fixer">
<div className="sk-spinner sk-spinner-wave">
<div className="sk-rect1"></div>
<div className="sk-rect2"></div>
<div className="sk-rect3"></div>
<div className="sk-rect4"></div>
<div className="sk-rect5"></div>
</div>
</div>}
</div>
)
};
Now if I use this wrapper:
<LoaderWrapper loaded={variable!=null}>
<MyComponent variable={variable}/>
</LoaderWrapper>
In MyComponent:
render () {
const {variable} = this.props;
return (<div>{variable.abc}</div>)
}
Problem is that still complains about variable is null.
Also tried the following, complains about the same thing...
<LoaderWrapper loaded={false}>
<MyComponent variable={variable}/>
</LoaderWrapper>
You must be doing something wrong, the following code still works and is based on your above idea
import React, { Component } from 'react';
import { render } from 'react-dom';
var LoaderWrapper = function (props) {
return (
<div>
{props.loaded ? props.children :
<h2> Loading ... </h2>}
</div>
)
};
class MyComponent extends Component {
render() {
const { variable } = this.props;
return (<div>{variable.abc}</div>)
}
}
class MyApp extends Component {
constructor() {
this.state = { loaded: false };
this.changeLoading();
}
changeLoading() {
setTimeout(() => {
this.setState({
loaded: true
})
}, 2000)
}
render() {
return (
<LoaderWrapper loaded={this.state.loaded}>
<MyComponent variable={{ abc: 'This is news' }} />
</LoaderWrapper>
)
}
}
render(<MyApp />, document.getElementById('root'));
Please see here for working example https://stackblitz.com/edit/react-q6wynn?file=index.js
I have to render several tabs on my dashboard, where I can navigate between them. Each tab must be reusable. In my sample code, I have 2 tabs and if I click on a tab, the matching tab renders. I just don't know if I am reasoning in the right way. How can I do this? My current Code is this:
import React, {Component} from 'react';
function SelectTab(props) {
var tabs = ['Overview', 'Favorites'];
return (
<ul>
{tabs.map(function(tab){
return (
<li key={tab} onClick={props.onSelect.bind(null, tab)}>
{tab}
</li>
)
}, this)}
</ul>
)
}
function Content(props) {
return (
<div>Content {props.tab}</div>
)
}
export default class MainContent extends Component {
constructor(props){
super(props);
this.state = {
selectedTab: 'Overview'
}
this.updateTab = this.updateTab.bind(this);
}
componentDidMount() {
this.updateTab(this.state.selectedTab);
}
updateTab(tab){
this.setState(function(){
return {
selectedTab: tab
}
});
}
render(){
return (
<div>
<SelectTab selectedTab={this.state.selectedTab} onSelect={this.updateTab}/>
<Content tab={this.state.selectedTab}/>
</div>
)
}
}
You did almost good. But I would rather render Content in SelectTab component as its already getting selectedTab prop, this way you can render specific content based on that prop. Also:
componentDidMount() {
this.updateTab(this.state.selectedTab);
}
is not necessary as state is already set.
Refactored example:
import React, { Component } from 'react';
function SelectTab(props) {
var tabs = ['Overview', 'Favorites'];
return (
<div>
<ul>
{tabs.map(function (tab) {
return (
<li key={tab} onClick={props.onSelect.bind(null, tab)}>
{tab}
</li>
)
}, this)}
</ul>
<Content tab={props.selectedTab}/>
</div>
)
}
function Content(props) {
return <div>Content {props.tab}</div>
}
export default class MainContent extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 'Overview'
};
this.updateTab = this.updateTab.bind(this);
}
updateTab(tab) {
this.setState(function () {
return {
selectedTab: tab
}
});
}
render() {
return <SelectTab selectedTab={this.state.selectedTab} onSelect={this.updateTab}/>;
}
}
Also keep in mind that with proper babel-transpiling your code could be much simplified like this:
import React, { Component } from 'react';
const TABS = ['Overview', 'Favorites'];
const SelectTab = ({ selectedTab, onSelect }) => (
<div>
<ul>
{
TABS.map(
tab => <li key={tab} onClick={() => onSelect(tab)}> {tab} </li>
)
}
</ul>
<Content tab={selectedTab}/>
</div>
);
const Content = ({ tab }) => <div>Content {tab}</div>;
export default class MainContent extends Component {
state = {
selectedTab: 'Overview'
};
updateTab = tab => this.setState(() => ({ selectedTab: tab }));
render() {
return <SelectTab selectedTab={this.state.selectedTab} onSelect={this.updateTab}/>;
}
}
I created a custom Accordion component which again consist of two child components called AccordionTitle and AccordionContent:
The AccordionTitle component has a button. When clicked, the AccordionContent part toggles its style from display:none to block and back when clicked again.
AccordionTitle.js
class AccordionTitle extends Component {
constructor() {
super();
this.show = false;
}
toggle() {
this.show = !this.show;
if (this.props.onToggled) this.props.onToggled(this.show);
}
render() {
return (
<div style={this.props.style}>
<Button onClick={e => this.toggle(e)} />
{this.props.children}
</div>
);
}
}
export default AccordionTitle;
AccordionContent.js
class AccordionContent extends Component {
render() {
let style = this.props.style ? this.props.style : {};
style = JSON.parse(JSON.stringify(style));
style.display = this.props.show ? 'block' : 'none';
return (
<div style={style}>
{this.props.children}
</div>
);
}
}
export default AccordionContent;
Also, I use the following parent component:
Accordion.js
class Accordion extends Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
Accordion.Title = AccordionTitle;
Accordion.Content = AccordionContent;
export default Accordion;
Now, when I use the Accordion component, it's possible that I might need multiple accordions in a row which would look like this:
ProductAccordion.js
import React, { Component } from 'react';
import Accordion from '../Accordion/Accordion';
class ProductAccordion extends Component {
constructor() {
super();
this.state = {
show: false,
};
}
toggled() {
this.setState({
show: !this.state.show,
});
}
render() {
this.productsJsx = [];
const products = this.props.products;
for (let i = 0; i < products.length; i += 1) {
this.productsJsx.push(
<Accordion.Title onToggled={e => this.toggled(e, this)}>
{products[i].name}
<img src="{products[i].imgsrc}" />
</Accordion.Title>,
<Accordion.Content show={this.state.show}>
{products[i].name}<br />
{products[i].grossprice} {products[i].currency}<br />
<hr />
</Accordion.Content>,
);
}
return (
<Accordion style={styles.container}>
{this.productsJsx}
</Accordion>
);
}
}
export default ProductAccordion;
As you can see, I am grabbing the toggled Event from Accordion.Title and I bind it to the prop show of Accordion.Content via the toggled() method.
Now, this works perfectly fine as long as there is just one product, but if there are more of them, clicking on the button will toggle all AccordionContent instances.
How can I change this so that only the content-part which belongs to the title that contains the clicked button will be toggled?
I also have the feeling that the component Accordion should take care of this (rather than ProductAccordion) by allowing Accordion.Title to delegate the toggled event directly to its sibling Accordion.Content. How can I achieve this?
I would suggest storing the index of the open item in state, instead of a boolean. Then in your render, show={this.state.show} would be something like show={this.state.show === i}.
Full example:
import React, { Component } from 'react';
import Accordion from '../Accordion/Accordion';
class ProductAccordion extends Component {
constructor() {
super();
this.state = {
show: null,
};
}
toggled(event, ind) {
const index = this.state.index;
this.setState({ show:ind === index ? null : ind });
}
render() {
this.productsJsx = [];
const products = this.props.products;
for (let i = 0; i < products.length; i += 1) {
this.productsJsx.push(
<Accordion.Title onToggled={e => this.toggled(e, i)}>
{products[i].name}
<img src="{products[i].imgsrc}" />
</Accordion.Title>,
<Accordion.Content show={this.state.show === i}>
{products[i].name}<br />
{products[i].grossprice} {products[i].currency}<br />
<hr />
</Accordion.Content>,
);
}
return (
<Accordion style={styles.container}>
{this.productsJsx}
</Accordion>
);
}
}
export default ProductAccordion;
and this
class AccordionTitle extends Component {
constructor() {
super();
}
render() {
return (
<div style={this.props.style}>
<Button onClick={this.props.onToggled} />
{this.props.children}
</div>
);
}
}
export default AccordionTitle;