Rendering a dashboard with reusable components - reactjs

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

Related

React Re-Render Component on props Change

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;

How to render one Component on button click in another Component

I am new to React and am having trouble wrapping my head around props/states.
So I have component SortingVisualizer, that generates a visual representation of an unsorted array as follows:
class SortingVisualizer extends React.Component {
constructor(props){
super(props);
this.state = {
array: [],
};
}
componentDidMount(){
this.resetArray();
}
resetArray(){
const array = [];
for(let i = 0; i<100; i++){
array.push(this.getRandomInt(1,500))
}
this.setState({array});
}
//Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
render(){
const {array} = this.state;
return(
<div className="array-container">
{array.map((value, idx) => (
<div className = "array-elem" key = {idx} style = {{height: `${value}px`}}>
</div>
))}
</div>
);
}
}
export default SortingVisualizer;
Now, I have a Navbar Component with a button "Generate new Array" :
class Navbar extends React.Component {
render(){
return(
<nav className = "navbar">
<button className = "new-array-btn" onClick ={this.props.GNAclick}>Generate New Array</button>
</nav>
)
}
}
export default Navbar;
What I want to achieve is that on button click, resetArray will be called on SortingVisualizer so a new array will be generated.
Here is my App.js:
class App extends React.Component {
GNAclickHandler = () => {
console.log("clicked!");
/*
this.setState((prevState) => {
return {GNA: !prevState.GNA}
});
*/
}
render() {
return (
<>
<header>
<Navbar GNAclick = {this.GNAclickHandler}/>
</header>
<div className="App">
<SortingVisualizer />
</div>
</>
);
}
}
export default App;
I am not sure how to progress from here, any help will be appreciated.
Here is the website: https://roy-05.github.io/sort-visualizer/
I would recommend you to read this article https://reactjs.org/docs/thinking-in-react.html, so you can have a better understanding of state and props.
Regarding to your app, you just need to know which component has state and which will react to props change.
App.js
// Navbar manages the state
// and provides it to their children
// so they can rerender whenever the app state changes
class App extends React.Component {
state = {
data: Date.now()
}
createNewArray = () => {
this.setState({
data: Date.now()
})
}
render() {
return (
<React.Fragment>
<Navbar onGenerate={this.createNewArray}/>
<SortingVisualizer data={this.state.data}/>
</React.Fragment>
)
}
}
Navbar.js
// Navbar just notifies that
// and action has to be executed
// (the parent handles that action by creating a new set of data)
function Navbar(props) {
return (
<nav>
<button onClick={props.onGenerate}>Generate array</button>
</nav>
)
}
SortingVisualizer.js
// SortingVisualizer renders what App tells it to render
function SortingVisualizer(props) {
return (
<main>
{props.data}
</main>
);
}

Re-render a component on repeat click on the same tab in another component reactjs

I know the title might be confusing, as well as might sound as a repeat, please read the whole description before marking it as repeat, I am new to react and need some help.
I am building a dashboard. I have a navigation bar div which has multiple tabs and a content div which has the corresponding content. Once a tab is clicked i render its corresponding content. Within any tab the user can do various things/changes. Lets say i have a tab ABC which when clicked produces an initial view, when i click this tab again when it is already clicked i need to re-render the ABC tabs content.
Essentially what i want to do is when after clicking test and test-demo once, user clicks test again the text 'test-demo' should disappear.
import React, { Component } from 'react';
const Button = (props) => {
return (
<button onClick={() => props.onClick(props.buttonName.trim())}>{props.buttonName}</button>
);
};
class Test extends Component {
static initialState = () => ({
appContent:null,
});
state = Test.initialState();
switchTab = (buttonKey) => {
this.setState(prevState => ({
appContent:<a>{buttonKey}</a>
}));
}
render() {
let appContent = null;
switch(this.props.navigationTab) {
case "test":
appContent = <Button onClick={this.switchTab} buttonName='test-demo' />
break;
default:
appContent = null
break;
};
return (
<div>
{appContent}
{this.state.appContent}
</div>
);
}
}
class AppContent extends Component {
render() {
return (
<div>
<Test navigationTab={this.props.navigationTab}/>
</div>
);
}
}
class App extends Component {
static initialState = () => ({
navigationTab:null,
});
state = App.initialState();
switchTab = (buttonKey) => {
this.setState(prevState => ({
navigationTab:buttonKey,
}));
}
render() {
return (
<div>
<div>
<Button onClick={this.switchTab} buttonName='test'/>
</div>
<AppContent navigationTab={this.state.navigationTab} />
</div>
);
}
}
export default App;
https://stackblitz.com/edit/react-fs8u7o?embed=1&file=index.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
const Button = (props) => {
return (
<button onClick={() => props.onClick(props.buttonName.trim())}>{props.buttonName}</button>
);
};
class Test extends Component {
constructor(props) {
super(props);
this.state = {
appContent: null,
hideTestDemo:false,
};
}
componentWillReceiveProps(nextProps){
this.setState(prevState => ({
hideTestDemo:nextProps.hideTestDemo,
}));
}
switchTab = (buttonKey) => {
this.setState(prevState => ({
appContent: <a>{buttonKey}</a>,
hideTestDemo:false,
}));
}
render() {
let appContent = null;
switch (this.props.navigationTab) {
case "test":
appContent = <Button onClick={this.switchTab} buttonName='test-demo' />
break;
default:
appContent = null
break;
};
return (
<div>
{appContent}
{(!this.state.hideTestDemo ) ? this.state.appContent:null}
</div>
);
}
}
class AppContent extends Component {
render() {
return (
<div>
<Test {...this.props} />
</div>
);
}
}
class App extends Component {
constructor() {
super();
this.state = {
navigationTab: null,
};
}
hideTestDemo = false;
switchTab = (buttonKey) => {
if (this.hideTestDemo)
this.setState(prevState => ({
navigationTab: buttonKey,
hideTestDemo: true,
}));
else
this.setState(prevState => ({
navigationTab: buttonKey,
hideTestDemo:false,
}));
this.hideTestDemo=!this.hideTestDemo;
}
render() {
return (
<div>
<div>
<Button onClick={this.switchTab} buttonName='test' />
</div>
<AppContent {...this.state} />
</div>
);
}
}
render(<App />, document.getElementById('root'));

How can I prevent all of my accodion components being toggled when clicked in React?

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;

Hiding and showing text in React

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

Resources