How to render one Component on button click in another Component - reactjs

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

Related

React: Onclick Event new component is not returned

I am new to React. I am trying to display new component in the content on click of menu from Navbar. But the return of component on click function doesnot work. So return doesnot work. Here are my files. Click function "getItemsData" works but the component is not returned. I am trying to create a application with restaurant menu and it relevant content
App.jsx
import React from 'react';
import "./style.css";
import Slider from "./carousel.js"
class App extends React.Component{
render(){
return(
<div>
<Header/>
</div>
);
}
}
class Header extends React.Component{
constructor(props){
super(props);
this.state = {
sections: [],
menu: []
};
}
componentDidMount(){
fetch('http://localhost:3001/api/sections')
.then(res => res.json())
.then(sections => this.setState({ 'sections': sections }))
fetch('http://localhost:3001/api/menu')
.then(res => res.json())
.then(menu => this.setState({ 'menu': menu }))
}
render(){
return(
<div id="header-bar">
<div id="header-logo">
<img src={require('./images/1200px-Burger_King_Logo.png')} alt="Logo" id="logo-class"></img>
</div>
<Slider sections={this.state.sections} menu={this.state.menu} />
</div>
);
}
}
export default App;
carousel.js
import React , { Component } from 'react';
import contentCards from "./contentcards.js";
const Slider = (data) => {
console.log(data)
var Menuoptions = data.menu.options
var Sectionsref = data.sections
const getItemsData = (sectionData) => {
sessionStorage.setItem("sectionData" , JSON.stringify(sectionData));
return <contentCards />;
}
return(
<div className="sectionNavBar">
{(()=> {
if (Menuoptions !== undefined && Sectionsref !== undefined) {
if (Menuoptions.length > 0 && Sectionsref.length > 0){
let rowmenu = []
for(let i=0; i< Menuoptions.length ; i++){
for(let j=0; j<Sectionsref.length; j++){
if(Menuoptions[i]._ref === Sectionsref[j]._id){
// console.log(Menuoptions[i]._ref+ "==" +Sectionsref[j]._id)
let imageId = Sectionsref[j].carouselImage.asset._ref;
imageId = imageId.slice(6)
imageId = imageId.replace("-png", ".png")
//console.log(Sectionsref[j])
rowmenu.push(<div key={j} className="listNavBar" onClick = {() => getItemsData(Sectionsref[j])}> <img src={window.location.origin + '/images/'+imageId} className= "navBar-image" /> <br/>{Sectionsref[j].name.en} </div>)
}
}
}
return rowmenu
}
}
})()}
</div>
)
}
}export default Slider
contentCards.js
import React , { Component } from 'react';
class contentCards extends React.Component{
render(){
return (
<div className = "cardColumn"> Menu1 </div>
)
}
}
export default contentCards
Custom components names must be uppercased or React will treat them as DOM elements.
React treats components starting with lowercase letters as DOM tags.
// not <contentCards />
<ContentCards />

Child Component does not refresh when List Array is updated

I am new to React and I've ran into a problem where I am updating an array list in my app component from a function that is called from user input. This is working fine but there is a child component that is get's the content of that list and maps it to buttons that it displays. My problem is then the list is updated in in the app component it the child component dose not update.
This is the code for my app component:
import React from 'react';
import './App.scss';
import DiceSelector from "./components/DiceSelector";
import ListOfDiceTypes from "./shared/list-available-dice";
const DiceToRollList = [];
const RolledTotal = 45;
DiceToRollList.push(ListOfDiceTypes[4]);
DiceToRollList.push(ListOfDiceTypes[4]);
DiceToRollList.push(ListOfDiceTypes[3]);
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="App">
<header className="App-header">
<div className="Dice-Box">
<h2>Select Dice to Roll</h2>
{ListOfDiceTypes.map((dice, index) => (
<DiceSelector
key={"typeList" + index}
dicetype={dice.dicetype}
imagename={dice.imageName}
maxvalue={dice.maxvalue}
onClick={AddDieClick}
/>
))}
</div>
<div className="Dice-Box">
<h2>Selected Dice</h2>
{DiceToRollList.map((dice, index) => (
<DiceSelector
key={"SelectedList" + index}
dicetype={dice.dicetype}
imagename={dice.imageName}
maxvalue={dice.maxvalue}
onClick={RemoveDieClick}
/>
))}
<h3>Total Rolled = {RolledTotal}</h3>
</div>
</header>
</div>
);
}
}
And here is my child component:
import React from "react";
export default class DiceSelector extends React.Component {
constructor(props) {
super(props);
}
render() {
return <button
className="number"
onClick={() => this.props.onClick(this.props.dicetype)}
>
<img src={require(`../images/${this.props.imagename}`)} alt={this.props.imagename} />
</button>
}
}
You can store key in state of App component and use different key each time when you update props that you are going to pass child component at
<DiceSelector
key= { this.state.key+ offset + index} // insted of {"typeList" + index}
dicetype={dice.dicetype}
imagename={dice.imageName}
maxvalue={dice.maxvalue}
onClick={AddDieClick} // or onClick={RemoveDieClick}
/>
// offset is used to escape duplicate elements next time when child component will render
// offset is the length of the array on that you are mapping
You can add logic of update of key in clickHandler methods
This will rerender the child component.
Or you can use componentWillReceiveProps method to update the state of child component when props changes.
I had a couple of things wrong here. I realized that I did not need the child to refresh when I updated the list I needed the app component to refresh. Also I had functions outside of the app class declared as function functionName(){} rather than in the class as functionName = () = {}. This was keeping me from properly accessing this.state. Finally I was binding to the DiceToRollList rather than this.sate.DiceToRollList. Here is how I updated the app component.
import React, { Component } from 'react';
import './App.scss';
import DiceSelector from "./components/DiceSelector";
import ListOfDiceTypes from "./shared/list-available-dice";
const DiceToRollList = [];
const RolledTotal = 45;
DiceToRollList.push(ListOfDiceTypes[4]);
DiceToRollList.push(ListOfDiceTypes[4]);
DiceToRollList.push(ListOfDiceTypes[3]);
const offset1 = 100;
const offset2 = 500;
export default class App extends Component {
constructor(props) {
super(props);
this.state = { DiceToRollList }
}
render() {
return (
<div className="App">
<header className="App-header">
<div className="Dice-Box">
<h2>Select Dice to Roll</h2>
{ListOfDiceTypes.map((dice, index) => (
<DiceSelector
key={ListOfDiceTypes.length + index} // insted of {"typeList" + index}
dicetype={dice.dicetype}
imagename={dice.imageName}
maxvalue={dice.maxvalue}
onClick={this.AddDieClick}
/>
))}
</div>
<div className="Dice-Box">
<h2>Selected Dice</h2>
{this.state.DiceToRollList.map((dice, index) => (
<DiceSelector
key={this.state.DiceToRollList.length + offset2 + index} // insted of {"dieToRollList" + index}
dicetype={dice.dicetype}
imagename={dice.imageName}
maxvalue={dice.maxvalue}
onClick={this.RemoveDieClick}
/>
))}
<h3>Total Rolled = {RolledTotal}</h3>
</div>
</header>
</div>
);
}
// Functions
AddDieClick = (diceType) => {
let RowToAdd = ListOfDiceTypes.findIndex(obj => {
return obj.dicetype === diceType
});
DiceToRollList.push(ListOfDiceTypes[RowToAdd]);
this.reSetState();
}
reSetState = () => {
this.setState({ DiceToRollList });
};
RemoveDieClick = (diceType) => {
let DieToAdd = DiceToRollList.findIndex(obj => {
return obj.dicetype === diceType
});
DiceToRollList.splice(DiceToRollList[DieToAdd], 1);
this.reSetState();
}
}

Rendering a dashboard with reusable components

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

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