Render multiple components without changing route path - javascript

My goal is to render a Component without changing its url pathname. This Component is responsible to render three other child components when user clicks on the link. My code is working, but I don't think it is the right approach. My question is: What would be the best way of implementing this. Here's my code below:
const ComponentOne = () => {
return (
<div><h1>Component 1</h1></div>
)
}
const ComponentTwo = () => {
return (
<div><h1>Component 2</h1></div>
)
}
const ComponentThree = () => {
return (
<div><h1>Component 3</h1></div>
)
}
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
linkName: 'three'
};
this.renderList = <ComponentThree/>;
}
handleClick = (linkName) => {
if (linkName === 'one') {
this.renderList = <ComponentOne/>;
this.setState({ linkName });
} else if (linkName === 'two') {
this.renderList = <ComponentTwo />;
this.setState({ linkName });
} else {
this.renderList = <ComponentThree />;
this.setState({ linkName });
}
return this.renderList;
};
render() {
return (
<div>
<ul>
<li><a
className={this.state.linkName === 'one' ? 'active' : ''}
onClick={() => this.handleClick('one')}>
Component 1
</a></li>
<li><a
className={this.state.linkName === 'two' ? 'active' : ''}
onClick={() => this.handleClick('two')}>
Component 2
</a></li>
<li><a
className={this.state.linkName === 'three' ? 'active' : ''}
onClick={() => this.handleClick('three')}>
Component 3
</a></li>
</ul>
{this.renderList}
</div>
)
}
}
ReactDOM.render(<Header/>, document.querySelector('#root'))
to better demonstrate here is a link to codepen:
codepen

Your code can be simplified by keeping the index in the state and then rendering based on the current index. Here's my slightly simplified solution:
https://codepen.io/olavih/pen/zYGobaV
return (
<div>
<h1>Component 1</h1>
</div>
);
};
const ComponentTwo = () => {
return (
<div>
<h1>Component 2</h1>
</div>
);
};
const ComponentThree = () => {
return (
<div>
<h1>Component 3</h1>
</div>
);
};
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
linkIndex: 3
};
}
handleClick = linkIndex => {
this.setState({ linkIndex });
};
render() {
return (
<div>
<ul>
<li>
<a
className={this.state.linkIndex === 1 ? "active" : ""}
onClick={() => this.handleClick(1)}
>
Component 1
</a>
</li>
<li>
<a
className={this.state.linkIndex === 2 ? "active" : ""}
onClick={() => this.handleClick(2)}
>
Component 2
</a>
</li>
<li>
<a
className={this.state.linkIndex === 3 ? "active" : ""}
onClick={() => this.handleClick(3)}
>
Component 3
</a>
</li>
</ul>
{this.state.linkIndex === 1 && <ComponentOne />}
{this.state.linkIndex === 2 && <ComponentTwo />}
{this.state.linkIndex === 3 && <ComponentThree />}
</div>
);
}
}
ReactDOM.render(<Header />, document.querySelector("#root"));

<a> tag by default will reload the page.
You can pass the event object from onClick action (onClick={(e) => this.handleClick(e,'three')}) to your handleClick method and in that method block reloading of the page with e.preventDefault().
You can also use React Router to switch from using default HTML <a> to <Link>. Link component is rendering <a> but it does not reload the entire page after clicking.

Related

React Setstate callback called but render delayed

I just started to learn to react 2 days ago and I'm having a hard time with react's setstate method, all I know is use revstate parameter if want to change state based on previous state, and callback parameter to be executed right after the state change (please correct me if this wrong), so I just change the array content (which I render it using javascript's array.map) and I wish it renders right after the state is changed, it is changing but delayed, it only render after I do another click but the render method is called
for any senpai out there thanks for the help.
Handle click for changing to render content based on index passed on my button "onClick"
class App extends React.Component {
constructor(props){
super(props)
this.state = {
clickeditem : -1
}
this.torender = [
{
display : "first",
content : []
},
{
display : "second",
content : []
}
]
}
handleclick = (i) =>{
this.setState(prevstate=>{
if (prevstate.clickeditem === -1) {
return {clickeditem : i}
} else {
return prevstate.clickeditem === i ? {clickeditem : -1} : {clickeditem : i}
}
},() => {
return this.state.clickeditem === -1 ? (this.torender[0].content = [], this.torender[1].content = [])
: (this.state.clickeditem === 0) ? (this.torender[0].content = ["torender-0 content","torender-0 content"],this.torender[1].content = [])
: (this.state.clickeditem === 1) ? (this.torender[1].content = ["torender-1 content","torender-1 content"],this.torender[0].content = [])
: null
})
}
render(){
return(
<div>
<ul>
{
this.torender.map((item,index) => {
return(
<li key = {index}>
{item.display}
<ul>
{item.content.map((content,contentindex) => {
return(<li key = {contentindex}>{content}</li>)
})}
</ul>
</li>
)
})
}
</ul>
<button onClick={()=>this.handleclick(0)}>first-button</button>
<button onClick={()=>this.handleclick(1)}>second-button</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Refactor your code and Approach the simpler way
Actually, you shouldn't use the second param callback.
Whenever the state is changed, the life cycle of React Js will re-render it properly (See the image below to clarify in detail ^^!)
There are some things to note:
Move the content of each item in torender accordingly --> This is clearer about the initial data as well as it should not be mutated.
Default clickeditem is one of the items in torender, for example, the first item.
After that, you just control the content to be rendered in this way
___________ The condition to call renderContent() method ______________
{index === this.state.clickeditem && this.renderContent(item)}
_____________renderContent() looks like below_____________
renderContent = (item) => {
return (
<ul>
{item.content.map((content, contentindex) => {
return <li key={contentindex}>{content}</li>;
})}
</ul>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
clickeditem: 0
};
this.torender = [
{
display: "first",
content: ["torender-0 content", "torender-0 content"]
},
{
display: "second",
content: ["torender-1 content", "torender-1 content"]
}
];
}
handleclick = (index) => {
this.setState({clickeditem: index});
};
renderContent = (item) => {
return (
<ul>
{item.content.map((content, contentindex) => {
return <li key={contentindex}>{content}</li>;
})}
</ul>
);
};
render() {
return (
<div>
<ul>
{this.torender.map((item, index) => {
return (
<li key={index}>
{item.display}
{index === this.state.clickeditem && this.renderContent(item)}
</li>
);
})}
</ul>
<button onClick={() => this.handleclick(0)}>first-button</button>
<button onClick={() => this.handleclick(1)}>second-button</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"> </div>
Issue
this.torender isn't part of react state, the component would need to rerender once more to see the mutation you did in the previous render cycle.
Solution
It is best to just compute it's value when you are rendering your UI then it should work as I suspect you meant it to.
handleclick = (i) =>{
this.setState(prevstate=>{
if (prevstate.clickeditem === -1) {
return { clickeditem: i }
} else {
return prevstate.clickeditem === i ? { clickeditem: -1 } : { clickeditem: i }
}
})
}
render(){
const { clickeditem } = this.state;
let torender = [
{
display : "first",
content : []
},
{
display : "second",
content : []
}
];
if (clickeditem === -1) {
torender[0].content = [];
torender[1].content = [];
} else if (clickeditem === 0) {
torender[0].content = ["torender-0 content","torender-0 content"];
torender[1].content = [];
} else if (clickeditem === 1) {
torender[1].content = ["torender-1 content","torender-1 content"];
torender[0].content = [];
} else {
torender = []; // <-- render nothing
}
return(
<div>
<ul>
{torender.map((item,index) => {
return(
<li key = {index}>
{item.display}
<ul>
{item.content.map((content, contentindex) => (
<li key={contentindex}>{content}</li>;
))}
</ul>
</li>
)
})
}
</ul>
<button onClick={()=>this.handleclick(0)}>first-button</button>
<button onClick={()=>this.handleclick(1)}>second-button</button>
</div>
)
}
}

conditional passing functions as props to a component

i have this breadcrump component that map over props and renders a list of chip components like this:
class BreadCrumb extends React.Component {
render () {
const {
steps,
activeIndex
} = this.props;
const chips = steps
.map((step,index) => {
return <Chip
key={index}
title={step.category}
onClick = {()=> this.props.selectChip(index)} // this should be passed only if
// active == true
active={activeIndex >= index} />
})
return (
<div className="chip-container">
{chips}
</div>
)
}
}
i need to click on chips only if his active prop is true,
this is the chip component
class Chip extends React.Component {
render(){
const {
active,
title
} = this.props;
const activeClassName = active ? 'chip active' : 'chip';
return (
<div
className = {activeClassName}
onClick = {() => this.props.onClick()} >
<span>{title}</span>
</div>
)
}
}
how can i make chip clickable only if the active prop is true?
For further information selectChip() function sets the state of a component App, parent of Breadcrump component, so it is binded to App component.
You could e.g. make that onClick function as a class method and use a simple condition inside:
class Chip extends React.Component {
handleClick = () => {
if (this.props.active) {
this.props.onClick(); // call only if active props is true
}
}
render() {
const { active, title } = this.props;
const activeClassName = active ? 'chip active' : 'chip';
return (
<div
className = {activeClassName}
onClick = {this.handleClick}
>
<span>{title}</span>
</div>
)
}
}
Either execute the handler or an empty function
onClick = {isActive ? this.props.onClick : () =>{} } >
You can do it like this:-
// If chip component expects a function all the time
<Chip
key={index}
title={step.category}
onClick = {step.active ? ()=> this.props.selectChip(index) : () => {}}
active={activeIndex >= index} />
// If onClick is an optional prop to chip component
<Chip
key={index}
title={step.category}
onClick = {step.active ? ()=> this.props.selectChip(index) : undefined}
active={activeIndex >= index} />
// of onClick handler is optional, possibly an alternative solution
type ChipProps = {
title: string;
active: boolean;
onClick?: ()=>void;
}
<Chip
key={index}
title={step.category}
active={activeIndex >= index}
{...(step.active ? {onClick:()=> this.props.selectChip(index)} : {})}
/>

React. How to add class in element on click?

I'm new to React. Currently I'm creating one app in learning purposes.
I'm stuck trying to change color of button on click. When I click on a button, the color of all the buttons are changed. But I need to have just one button active, while others are not active. When I click on different button, the previous active button loses its class and become inactive.
Could you help me out?
state = {
clicked: false
};
timeChangeHandler = () => {
this.setState({ clicked: true });
};
render() {
return (
<div>
<button
type="button"
className={
this.state.clicked
? "list-group-item list-group-item-action active"
: "list-group-item list-group-item-action"
}
onClick={this.timeChangeHandler}
>
8:00
</button>
<button
type="button"
className={
this.state.clicked
? "list-group-item list-group-item-action active"
: "list-group-item list-group-item-action"
}
onClick={this.timeChangeHandler}
>
9:00
</button>
</div>
);
}
Here is a simple example of how you can do it. I map the buttons and use index as the clicked value. But, if you are using your buttons statically as in your code you can use #Kyle's solution.
const buttons = [
{
name: "foo"
},
{
name: "bar"
},
{
name: "baz"
}
];
class App extends React.Component {
state = {
clicked: null
};
handleClick = id => this.setState({ clicked: id });
render() {
return (
<div>
{buttons.map((button, index) => (
<button
className={this.state.clicked === index && "clicked"}
onClick={() => this.handleClick(index)}
>
{button.name}
</button>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.clicked {
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
Your solution would look something like this.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activeButton: 0
};
}
render() {
const { activeButton } = this.state;
return (
<div>
<button
className={ activeButton === 0 && "active" }
onClick={() => this.setState({activeButton: 0})}
/>
<button
className={ activeButton === 1 && "active" }
onClick={() => this.setState({activeButton: 1})}
/>
<button
className={ activeButton === 2 && "active" }
onClick={() => this.setState({activeButton: 2})}
/>
</div>
);
}
}

Toggle Two Things Separately React Constructor

I'm trying to toggle two different dropdown menus and can't seem to get it working. New to react and have probably been looking at it too long and it's something simple. The problem is when I toggle one the other gets toggled as well, so they both show.. Here is what I have:
import React from "react";
import { Link } from "./component/link";
import styles from "./header.module.css";
class Header extends React.Component {
constructor(props) {
super ( props )
this.state = {
show : false
}
this.toggleBusiness = this.toggleBusiness.bind(this);
this.state = {
show : false
}
this.togglePersonal = this.togglePersonal.bind(this);
}
toggleBusiness = () => {
const { show } = this.state;
this.setState( { show : !show } )
}
togglePersonal = () => {
const { show } = this.state;
this.setState( { show : !show } )
}
render() {
return (
<div className={ styles.topNav} >
<div className="grid">
<div className="grid-cell">
<div className={ styles.logoText }>
Logo
</div>
</div>
<nav>
<div className="grid-cell">
<ul className="list-unstyled">
<li><Link to={'/design'}>About</Link></li>
<li><a onClick={this.toggleBusiness}>Business</a></li>
<li><a onClick={this.toggleBusiness}>Personal</a></li>
<li><Link to={'/posts'}>Blog</Link></li>
<li><Link to={'/contact'}>Contact</Link></li>
<li className={styles.menuButton}><a className="button button-secondary" href="tel:2252931086">File a Claim</a></li>
<li className={styles.menuButton}><a className="button" href="/">Get Insurance</a></li>
</ul>
</div>
</nav>
</div>
{ this.state.show && <BusinessDropdown /> }
{ this.state.show && <PersonalDropdown /> }
</div>
)}
}
class BusinessDropdown extends React.Component {
render () {
return (
<div className="dropdown">BusinessTest</div>
)
}
}
class PersonalDropdown extends React.Component {
render () {
return (
<div className="dropdown">PersonalTest</div>
)
}
}
export default Header;
So basically I want it to toggle the Business Dropdown one when I click Business and the Personal Dropdown when I press Personal. Also, if you have something that would work better than this approach let me know!
Change your toggleBusiness and togglePersonal to this:
toggleBusiness = () => {
const { show } = this.state;
this.setState({ show: show === "business" ? null : "business" });
};
togglePersonal = () => {
const { show } = this.state;
this.setState({ show: show === "personal" ? null : "personal" });
};
then in the conditional rendering, do this:
{this.state.show === "business" && <BusinessDropdown />}
{this.state.show === "personal" && <PersonalDropdown />}
...also in your links, you have this:
<li><a onClick={this.toggleBusiness}>Business</a></li>
<li><a onClick={this.toggleBusiness}>Personal</a></li>
Where you should have this:
<li><a onClick={this.toggleBusiness}>Business</a></li>
<li><a onClick={this.togglePersonal}>Personal</a></li>
Edit: I realise this is not quite what you asked for - this toggles business off when personal is switched on. Personally I think this approach would actually suit better given the fact you're opening a new dropdown menu, you will probably want the other one to close.
You are using this.state.show for both Business and Personal.
Adding a second state variable like showBusiness and showPersonal
should solve your problem.
Also you can/should declare your state only once with
this.state = {
showBusiness: false,
showPersonal: false
};
In your constructor you set this.state.show to false 2 times separate this into two separate variables, perhaps this.state.showBuisness and this.state.showPersonal?
My approach would be something like this.
1. set initial state
constructor(props){
super(props);
this.state={
business: false,
personal: false
}
}
2. Create ONE function to update both status.
handleClick = (e) => {
this.setState(prevState => {
[e.target.id]: !prevState[e.target.id]
}
}
3. Add function to the onclick AND an id
<button id="personal" onClick={this.handleClick}>SHOW PERSONAL</button>

How to add a class to only the specifically clicked element in React?

Im looping over an object and displaying li tags and attaching a click handler.
Below that is a game component that displays further information.
Can someone help me with my logic and how to tell React to only add the class to the specific component related to the click.
at
Currently when I click one element, all of the following game components open at the same time. I just want the one below to open.
Library Component
class Library extends React.Component {
state = {
active: false
};
toggleClass = index => {
let active = this.state.active;
active = !active;
this.setState({ active });
};
render() {
return (
<section>
<h2>Game Library</h2>
<ul>
{this.props.games.map((game, index) => (
<Fragment key={`${index}-${game.name}`}>
<li
key={`${index}-${game.gameId}`}
onClick={() => this.toggleClass(index)}
>
{game.name}
</li>
<Game
key={index}
index={index}
game={this.props.games[index]}
active={this.state.active}
/>
</Fragment>
))}
</ul>
</section>
);
}
}
Game Component
class Game extends React.Component {
render() {
return (
<div
key={this.props.index}
className={this.props.active ? "show" : "hide"}
>
<p>{this.props.game.name}</p>
<p>{this.props.game.gameId}</p>
<a>Close</a>
</div>
);
}
}
Instead of having a boolean active that you use for each game, you could use an object with a key for each index that indicates if that particular game is active.
Example
class Library extends React.Component {
state = {
active: {}
};
toggleClass = index => {
this.setState(previousState => {
const active = { ...previousState.active };
active[index] = !active[index];
return { active };
});
};
render() {
return (
<section>
<h2>Game Library</h2>
<ul>
{this.props.games.map((game, index) => (
<Fragment key={`${index}-${game.name}`}>
<li
key={`${index}-${game.gameId}`}
onClick={() => this.toggleClass(index)}
>
{game.name}
</li>
<Game
key={index}
index={index}
game={game}
active={this.state.active[index]}
/>
</Fragment>
))}
</ul>
</section>
);
}
}

Categories