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;
Related
//child component
import React, { Component } from 'react'
const NavButton = ({ active, title, href, onSetActive }) => {
return (
<button
className={active ? "btn btn-light regular-btn active" : "btn btn-light regular-btn"}
href={href}
onClick={onSetActive} >
{title}
</button>
)
}
class Child extends Component {
constructor(props) {
super(props);
this.state = {
activeIndex: 1,
buttons: [
{
title: "1",
key: 0,
value: 1
},
{
title: "3",
key: 1,
value: 3
}
]
}
}
//It was changing active index
handleChangeActive(newActiveIndex) {
// console.log("Working");
this.setState({ activeIndex: newActiveIndex });
}
render() {
const { activeIndex } = this.state;
return (
<div>
<nav id="navbarMain">
<div className="navbar-nav flex-row">
{this.state.buttons.map((button, buttonIndex) =>
/* determine which nav button is active depending on the activeIndex state */
<NavButton onClick={() => this.investmentHandler.bind(this)} value={button.value} onSetActive={() => this.handleChangeActive(buttonIndex)} active={buttonIndex === activeIndex} title={button.title} key={button.key} />)}
</div>
</nav>
</div>
)
}
}
export default Child
// parent component
import React, { Component } from 'react'
import Child from './Child';
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
firstValue: 1,
secondValue: 3,
result: ''
}
// this.add = this.add.bind(this);
}
//calculating the input values
Add = () => {
var { firstValue, secondValue, result } = this.state;
result = firstValue + secondValue;
console.log(result);
document.getElementById("result").innerHTML = result;
}
render() {
return (
<>
//Child component is used inside the parent component using props
<Child investmentHandler={this.add} />
<p id="result">Result</p>
</>
)
}
}
export default Parent
I need the event handler(Add) has to work inside the child component.
How to use event handler using props in class components.
How to call the parent Method in child class component in react.
I was tried using props but it was not working.
Based on the child component input button it has to get the result.
On Child class your component NavButton has an onClick attribute that calls onSetActive, so you can call investmentHandler on the onSetActive function:
//child component
import React, { Component } from "react";
const NavButton = ({ active, title, href, onSetActive }) => {
return (
<button
className={
active
? "btn btn-light regular-btn active"
: "btn btn-light regular-btn"
}
href={href}
onClick={onSetActive}
>
{title}
</button>
);
};
class Child extends Component {
constructor(props) {
super(props);
this.state = {
activeIndex: 1,
buttons: [
{
title: "1",
key: 0,
value: 1,
},
{
title: "3",
key: 1,
value: 3,
},
],
};
}
//It was changing active index
handleChangeActive(newActiveIndex) {
// console.log("Working");
this.setState({ activeIndex: newActiveIndex });
this.props.investmentHandler();
}
render() {
const { activeIndex } = this.state;
return (
<div>
<nav id="navbarMain">
<div className="navbar-nav flex-row">
{this.state.buttons.map((button, buttonIndex) => (
/* determine which nav button is active depending on the activeIndex state */
<NavButton
value={button.value}
onSetActive={() => this.handleChangeActive(buttonIndex)}
active={buttonIndex === activeIndex}
title={button.title}
key={button.key}
/>
))}
</div>
</nav>
</div>
);
}
}
export default Child;
On the Parent class when getting the props from the Child class you was calling "this.add" when you should be calling "this.Add":
// parent component
import React, { Component } from 'react'
import Child from './Child';
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
firstValue: 1,
secondValue: 3,
result: ''
}
// this.add = this.add.bind(this);
}
//calculating the input values
Add = () => {
console.log('Im here')
var { firstValue, secondValue, result } = this.state;
result = firstValue + secondValue;
console.log(result);
document.getElementById("result").innerHTML = result;
}
render() {
return (
<>
<Child investmentHandler={this.Add} />
<p id="result">Result</p>
</>
)
}
}
export default Parent
I made this few changes and the code worked for me.
Check this might help
//child component
import React, { Component } from 'react'
const NavButton = ({ active, title, href, onSetActive }) => {
return (
<button
className={active ? "btn btn-light regular-btn active" : "btn btn-light regular-btn"}
href={href}
onClick={onSetActive} >
{title}
</button>
)
}
class Child extends Component {
constructor(props) {
super(props);
this.state = {
activeIndex: 1,
buttons: [
{
title: "1",
key: 0,
value: 1
},
{
title: "3",
key: 1,
value: 3
}
]
}
}
//It was changing active index
handleChangeActive(newActiveIndex) {
// console.log("Working");
this.setState({ activeIndex: newActiveIndex });
}
render() {
const { activeIndex } = this.state;
return (
<div>
<nav id="navbarMain">
<div className="navbar-nav flex-row">
{this.state.buttons.map((button, buttonIndex) =>
/* determine which nav button is active depending on the activeIndex state */
<NavButton onClick={() => this.props.investmentHandler()} value={button.value} onSetActive={() => this.handleChangeActive(buttonIndex)} active={buttonIndex === activeIndex} title={button.title} key={button.key} />)}
</div>
</nav>
</div>
)
}
}
export default Child
// parent component
import React, { Component } from 'react'
import Child from './Child';
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
firstValue: 1,
secondValue: 3,
result: ''
}
// this.add = this.add.bind(this);
}
//calculating the input values
Add = () => {
var { firstValue, secondValue, result } = this.state;
result = firstValue + secondValue;
console.log(result);
document.getElementById("result").innerHTML = result;
}
render() {
return (
<>
//Child component is used inside the parent component using props
<Child investmentHandler={this.Add} />
<p id="result">Result</p>
</>
)
}
}
export default Parent
I'm also using semantic-ui-react. When I pass the child component down from the parent the css styling gets all messed up, I lose my images and the click doesn't work.
I can call the cardClickHandler method in the parent component and am console logging the correct child, i just can't get it to render (am not hitting the console.log in the child component).
I also tried to run the cardClickHandler method in the images container to pass it down but that didn't work.
please help and explain what i'm doing wrong. thanks!
images container:
import React from 'react';
import SearchBar from '../components/SearchBar';
import Images from '../components/Images';
import ImageCard from '../components/ImageCard';
class ImagesContainer extends React.Component {
state = {
images: [],
image: {},
sortValue: '',
inputValue: '',
};
componentDidMount() {
fetch('http://localhost:3000/images').then((resp) => resp.json()).then((resp) => {
this.setState({
images: resp
});
});
}
imageFilterOnChange = (event) => {
this.setState({
inputValue: event.target.value
});
};
sortImages = (images) => {
if (this.state.sortValue === 'location') {
return [ ...images ].sort((a, b) => {
if (a.location > b.location) {
return 1;
} else if (a.location < b.location) {
return -1;
} else {
return 0;
}
});
} else {
return images;
}
};
render() {
const filteredImages = this.state.images.filter((image) => {
return image.location.toLowerCase().includes(this.state.inputValue.toLowerCase());
});
return (
<div>
<Images
images={this.sortImages(filteredImages)}
onClick={this.cardClickHandler}
/>
<SearchBar
images={this.sortImages(filteredImages)}
imageFilterOnChange={this.imageFilterOnChange}
inputValue={this.state.inputValue}
onChange={this.handleSortImages}
/>
</div>
</div>
);
}
}
export default ImagesContainer;
parent component:
import React from 'react';
import ImageCard from './ImageCard';
import { Card, Image } from 'semantic-ui-react';
class Images extends React.Component {
state = {
image: []
};
cardClickHandler = (e) => {
let cardId = e.target.dataset.id;
this.props.images.find((image) => {
return image.id === cardId;
});
console.log('hi, cardId', cardId);
fetch(`http://localhost:3000/images/${cardId}`)
.then((resp) => resp.json())
.then((resp) => {
this.setState({
image: resp
})
console.log(this.state.image);
})
}
render() {
const allImages = this.props.images;
return allImages.map((image) => {
return (
<Card
key={image.id}
className="photo"
data-id={image.id}
data-name={image.name}
onClick={this.cardClickHandler}
>
<img
src={image.image}
alt=""
data-id={image.id}
data-name={image.name}
className="photo-image"
height={265}
/>
</Card>
);
});
}
}
export default Images;
child component:
i'm not hitting the console.log here, so no more code!
import React from 'react';
import { Card, Image } from 'semantic-ui-react';
class ImageCard extends React.Component {
render() {
console.log('image card');
return (
<Card>
</Card>
);
}
}
export default ImageCard;
I left a comment with a few improvements to the code you could make. Specifically:
You have an extra </div> in your ImagesContainer.
Also, you'll want to remove onClick={this.cardClickHandler} from ImagesContainer as cardClickHandler is defined not on ImagesContainer but instead on your Images component.
But the problem is that you are not rendering your ImageCard component at all. You are just rendering <Card> instead of <ImageCard>
Specifically, your parent component's render should change from this:
render() {
const allImages = this.props.images;
return allImages.map((image) => {
return (
<Card
key={image.id}
className="photo"
data-id={image.id}
data-name={image.name}
onClick={this.cardClickHandler}
>
<img
src={image.image}
alt=""
data-id={image.id}
data-name={image.name}
className="photo-image"
height={265}
/>
</Card>
);
});
}
to this:
render() {
const allImages = this.props.images;
return allImages.map((image) => {
return (
<ImageCard
key={image.id}
className="photo"
data-id={image.id}
data-name={image.name}
onClick={this.cardClickHandler}
>
<img
src={image.image}
alt=""
data-id={image.id}
data-name={image.name}
className="photo-image"
height={265}
/>
</ImageCard>
);
});
}
This works, but I need to separate out the cellRenderer component.
// Grid.js
import React, { Component } from "react";
class Grid extends Component {
render() {
const index = 3;
return (
<div style={{ height: "5em", width: "6em", border: "1px solid black" }}>
{this.props.text}
{this.props.children({ index, cellText: "no." })}
</div>
);
}
}
export default Grid;
And App.js. If I click on "no.3", it correctly logs "x: 6"
import React, { Component } from "react";
import Grid from "./Grid";
class App extends Component {
constructor(props) {
super(props);
this.state = {
x: 5
};
}
handleIncrement = () => {
this.setState(
state => ({ x: state.x + 1 }),
() => console.log(`x: ${this.state.x}`)
);
};
cellRenderer = ({ index, cellText }) => {
return <div onClick={() => this.handleIncrement()}>{cellText + index}</div>;
};
render() {
return (
<div className="App">
<Grid text={"Hello "}>{this.cellRenderer}</Grid>
</div>
);
}
}
export default App;
Now, if I have to separate out cellRenderer component as below, how can I pass the handleIncrement function to it ?
import React, { Component } from "react";
import Grid from "./Grid";
const cellRenderer = ({ index, cellText }) => {
return <div>{cellText + index}</div>;
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
x: 5
};
}
handleIncrement = () => {
this.setState(
state => ({ x: state.x + 1 }),
() => console.log(`x: ${this.state.x}`)
);
};
render() {
return (
<div className="App">
<Grid text={"Hello "}>{cellRenderer}</Grid>
</div>
);
}
}
Edit:
This works:
// pass handleIncrement to Grid
<Grid text={"Hello "} handleIncrement={this.handleIncrement} >{cellRenderer}</Grid>
// And within Grid, pass it to cellRenderer
{this.props.children({ index, cellText: "no.", handleIncrement: this.props.handleIncrement })}
// Update cellRenderer to this
const cellRenderer = ({ index, cellText, handleIncrement }) => {
return <div onClick={handleIncrement}>{cellText + index}</div>;
};
But the problem is that Grid is a component from the library react-window, and I cannot override the library code. Any other way possible ?
The fact that you have separated our cellRenderer as a functional component, you could render it as a component and pass on the props
const CellRenderer = ({ index, cellText, handleIncrement }) => {
return <div onClick={handleIncrement}>{cellText + index}</div>;
};
...
return (
<div className="App">
<Grid text={"Hello "}>{({index, cellText}) => <CellRenderer handleIncrement={this.handleIncrement} index={index} cellText={cellText}/>}</Grid>
</div>
);
This will work:
import React, { Component } from 'react'
import Grid from './components/Grid'
const CellRenderer = ({ index, cellText, handleIncrement }) => {
return <div onClick={handleIncrement}>{cellText + index}</div>
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
x: 5
}
}
handleIncrement = () => {
this.setState(
state => ({ x: state.x + 1 }),
() => console.log(`x: ${this.state.x}`)
)
}
render() {
return (
<div className="App">
<Grid text={'Hello '}>
{props => (
<CellRenderer {...props} handleIncrement={this.handleIncrement} />
)}
</Grid>
</div>
)
}
}
export default App
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'));
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}/>;
}
}