ReactJS component reloading unexpectedly on className change - javascript

TLDR;
Does adding a className to an exciting div reload the parent component in ReactJs? Added images near button to show console.log being called multiple times.
Here is the bug..
I'm building a simple 'order' app, which includes a sidebar.
I recreated the sidebar in a new project for a sanity check. Same issues. Here is the simple version.
class App extends Component {
constructor() {
super();
this.state = {
addList : [],
}
}
render() {
return (
<div className="App">
<Sidebar list = {this.state.addList}/>
</div>
);
}
}
export default App;
and in the sidebar component
class Sidebar extends Component {
constructor(props){
super(props);
this.state = {
active : false
}
}
toggleSidebar () {
if (this.state.active) {
this.setState({
active : false
})
} else {
this.setState({
active: true
})
}
}
render () {
return (
<div className={ 'sidebar ' + ((this.state.active) ? '' : 'hidden')}>
<button className='tab' onClick={(e)=>{this.toggleSidebar()}}>
TAB
</button>
<div className="itemList">
{console.log(this.props.list)}
</div>
</div>
)
}
}
export default Sidebar;
The sideBar class has a position: fixed and I move it out of the screen on a button click and adding a hidden className (.hidden { right: -x })
When an item gets selected in the parent app component, it gets added to its state (addItem).
The Sidebar component has that property passed into so when addItem get a new item, it displays it. It works just as predicted. I was able to add items and display them no problem.
I noticed the bug when I started adding number and doing price totals etc, because it seems the sidebar keep rendering I would find myself getting caught in infinite setState loops
Any solutions or advice?
Images for a those that are visual (clicking the tab and console displaying):

The answer is simple, I'm foolish. In fact I AM changing the state of sidebar causes a reload.
Therefor to get around this is having the parent component hold the values and pass them down as properties for the sidebar to just display. Therefor on reload the values don't change or re-add.

Related

React: Inserting a child into an already-rendered parent

I'm new to React and having some difficulty trying to add a new child component to a component that has already been rendered.
I have an App component which initially contains a Main component (main menu).
I also have Popover components which I want to appear on top of Main when they are children of <App> (and hence siblings of <Main>).
These Popover components vary in number. Each <Popover> can contain buttons which launch another <Popover> over the top again. So the structure would be like
<App>
<Main></Main>
<Popover></Popover>
<Popover></Popover>
...
</App>
However, when the page first loads there are no Popover components open, and the<App> is rendered without any. Here is a stripped-down version of my code:
class App extends React.Component{
constructor(props){ super(props) }
render(){
return (
<div>{this.props.children}</div>
)
}
}
class Main extends React.Component{
constructor(props){ super(props) }
render(){
return (
//main menu stuff here
)
}
}
ReactDOM.render(<App><Main /></App>, root);
How can I add new <Popover>s to my <App> when the user clicks something? Before React I would simply do App.appendChild(Popover) kind of thing, but I'm quite lost here.
I should add that the elements the user will click to trigger an initial <Popover> are not contained within <Main>; they are outside of the <App>, as I am trying to slowly transition my existing page to using React. I think this could be part of my problem.
So basically in React, you have multiple ways of doing this, but to be more reliable you need to have data that represents the dynamic components you will render in your DOM. And to do this you need to create a state and a function that can add new information to your state. Then simply by sharing this function with your other components, you can trigger it from wherever you want, and this will update your state which will increase the amount of dynamic components you will render.
Take a look at this example
import { useState } from "react";
export default function App() {
const [popups, setPopups] = useState([]);
const addNewPopup = () => {
setPopups([...popups, { title: "I am a popup" }]);
};
return (
<div className="App">
<ChildComponent onClick={addNewPopup} />
{popups.map((p) => {
return <Popup title={p.title} />;
})}
</div>
);
}
function ChildComponent({ onClick }) {
return (
<div>
<p>I am a child component</p>
<button onClick={onClick}>Add new element</button>
</div>
);
}
function Popup({ title }) {
return <div>I am a popup with title = {title}</div>;
}

Why is my React app completely reloading after a state change? [duplicate]

This question already has answers here:
Basic React form submit refreshes entire page
(4 answers)
Closed 3 years ago.
I am pretty new to React so apologies if this is a dumb question, which I suspect it is.
I have a simple React app with a dropdown, button and list. When the button is clicked, the selected item in the dropdown is added to the list. Each item added to the list also has a delete button associated with it.
I need the SelectComponent (dropdown and button) and ListComponent (list and buttons) to know what the items in the list are so they can add/remove items from it, so I am storing the state in the parent App component and passing it down to the children as props, along with a callback function that can update it (using setState()). Here is what I have:
Select Component
class SelectComponent extends Component<SelectProps, {}> {
constructor(props: any) {
super(props);
this.changeHandler = this.changeHandler.bind(this);
this.clickHandler = this.clickHandler.bind(this);
}
changeHandler(event: ChangeEvent<HTMLSelectElement>) {
currentSelection = event.target.value;
}
clickHandler(event: MouseEvent<HTMLButtonElement>) {
this.props.selectedItems.push(currentSelection);
this.props.updateList(this.props.selectedItems);
}
render() {
let optionItems = this.props.options.map((optionItem, index) =>
<option>{optionItem}</option>
);
return (
<form>
<div>
<select onChange={this.changeHandler}>
<option selected disabled hidden></option>
{optionItems}
</select>
<br />
<button type="submit" onClick={this.clickHandler}>Add to list</button>
</div>
</form>
);
}
}
List Component
class ListComponent extends Component<ListProps, {}> {
constructor(props: any) {
super(props);
this.removeListItem = this.removeListItem.bind(this);
}
removeListItem(i: number) {
this.props.selectedItems.filter((selection, j) => i !== j);
this.props.updateList(this.props.selectedItems);
}
render() {
let listItems;
if (this.props.selectedItems) {
listItems = this.props.selectedItems.map((listItem, index) =>
<li>{listItem}<button onClick={() => this.removeListItem(index)}>Delete</button></li>
);
}
return (
<div>
<ul>
{listItems}
</ul>
</div>
);
}
}
main App
class App extends Component<{}, State> {
constructor(props: any) {
super(props);
this.state = {
selectedItems: []
}
this.updateList = this.updateList.bind(this);
}
updateList(selectedItems: string[]) {
this.setState({selectedItems});
}
render() {
return (
<div>
<SelectComponent options={["Cyan", "Magenta", "Yellow", "Black"]} selectedItems={this.state.selectedItems} updateList={this.updateList} />
<ListComponent selectedItems={this.state.selectedItems} updateList={this.updateList} />
</div>
);
}
}
I also have a couple interfaces defining the props and state as well as a variable to hold the currently selected item in the dropdown.
What I want to happen is: the "Add to list" button is pressed, adding the current dropdown selection to the props, then passing the props to the updateList() function in the parent class, updating the state. The parent class should then re-render itself and the child components according to the new state. From what I can tell by looking at the console, this does happen.
However for some reason after it gets done rendering the ListComponent, the app completely reloads, clearing the state and the list and returning the dropdown to it's default value. I can tell because I see Navigated to http://localhost:3000/? in the console right after the ListComponent render function is called.
So what have I done wrong? Again I am pretty new to React so I have a feeling this is something simple I am missing. Any help would be greatly appreciated!
Edit: Forgot to mention (although it is probably obvious) that I am coding this in TypeScript, although I don't think that is related the issue.
If you are working with form and submit handler then you have to set the event.preventDefault() in your submit method.
You have to set preventDefault() in clickHandler method.
clickHandler(event) {
event.preventDefault(); // set it...
this.props.selectedItems.push(currentSelection);
this.props.updateList(this.props.selectedItems);
}
because you're using form and button type submit. Type submit is reason your app reload.
To prevent app reload by default, in the clickHandler function of Select Component, add preventDefault in the top of function
event.preventDefault();

How to open a single popup in React on click and pass props to it

I'm fairly inexperienced in React, so I'm building a Jeopardy game in it. Right now, my idea is to open a popup by clicking on the boxes on the Jeopardy board and displaying the clue of the clicked box component in the popup. I don't want to have multiple popups for each clue, since I just want the popup to update its contents with the specific clue selected.
I'm stuck on how to implement this, I initially attempted this by including the BoardPopup component in the BoardClue component, but that didn't seem to be an ideal way of doing this as it obviously just created multiple popups.
My idea is to click the BoardClue component and pass the clue props to a single BoardPopup component.
function BoardPopup(props) {
return (
<div className="popup">
{props.clue}
</div>
);
}
class BoardClue extends React.Component {
constructor(props) {
super(props);
this.state = {
showPopup: false
}
}
handleClick = () => {
this.setState({
showPopup: !this.state.showPopup
})
}
render() {
return (
<div className="board-clue" onClick={this.handleClick}>
<div>${this.props.value}</div>
<div style={{display:'none'}}>{this.props.clue}</div>
<div style={{display:'none'}}>{this.props.answer}</div>
</div>
);
}
}
The render function of the Board component. Questions are fetched from a JSON file and mapped into BoardClue components.
render() {
const error = this.state.error;
const isLoaded = this.state.isLoaded;
const questions = this.state.data.questions;
if (error) {
return <div>Error {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>
} else {
const clues = questions.map((question, index) => {
return (
<BoardClue key={index} value={question.value} clue={question.clue} answer={question.answer} />
);
});
return (
<div className="board">
{clues}
</div>
);
}
}
}
There are many ways and libraries that can help you build a modal, but, you can do it just with React and CSS. Here is my take on your case:
CodeSandbox - Board Popup
Using React.useState We generate the local state to manage the Popup visibility. We toggle it by clicking on the question boxes. The overlay div is to bring the Panel in to focus and to make it impossible to click another box. To close it you can click on the overlay or the clue.
Please check React's Hooks documentation page to learn how to use them. They are not the only way to handle state, but it's one of the easiest.
I hope it helps.

Conditional rendering a button value

I'm creating a game in which the initial state of the cards is face down, upon clicking a single card, the card's icon is revealed. When two cards match, both cards are removed from the board.
The challenge I'm having is in my conditional rendering. When I click on one card, all cards reveal their icon, not just one per click. Here is the codesandbox
I'm trying to keep state as local as possible the the relevant components and also trying the do it the "React" way by not toggling classnames and directly manipulating the DOM. How can I fix this and is the btnMask class the right way to go? If I can get this, then matching id's on cards may be a quick implementation. Here is the Card class
class Card extends Component {
constructor(props) {
super(props);
this.state = {
hidden: true
};
this.revealIcon = this.revealIcon.bind(this);
}
revealIcon(e) {
console.log("clicked", e.target.id);
this.setState({ hidden: false });
}
render() {
const card = this.props.cardsEasy["cards"].map((card, index) => {
if (this.state.hidden) {
return (
<button
key={card + index}
style={btn}
onClick={this.revealIcon}
id={card}
>
<div style={btnMask}>{card}</div>
</button>
);
} else {
return (
<button
key={card + index}
style={btn}
onClick={this.revealIcon}
id={card}
>
<div>{card}</div>
</button>
);
}
});
return <div style={divContainer}>{card}</div>;
}
}
export default Card;
You problem is that you have a common state for all cards. This means you have Card Component in which you render all the cards. The component has the state of hidden and renders a bunch of cards. Those cards access all the same variable in the state to check if they should hide. Therefore if you change that variable, all of then show up. What I would suggest you to do is to create a component "CardCollection" which renders the card.
class CardCollection extends React.Component {
render() {
this.props.cardsEasy["cards"].map((item) => {
<Card {...item} />
})
}
}
Like this each CardComponent will have their own state. You will need to adjust your CardComponent a little bit.
Instead of using a class component / stateful component you could also use a statless component for the CardCollection, as the collection doesn't need any state and is only there to render the childrens.
const CardCollection = (props) => {
return this.props.cardsEasy["cards"].map((item) => {
<Card {...item} />
});
}

React - toggle display of one component from the onClick of another component

I'm trying to build my first React project, and am currently putting together a burger nav button, and a menu which appears when clicking the nav.
I've broken this into two components; Hamburger and MenuOverlay. The code for both is below.
Currently I have an onClick on Hamburger toggling a class on it, but how would I also toggle the menu from that click? It's hidden with display: none; by default. Probably a very basic question so apologies - still trying to get my head around React.
MenuOverlay
import React from 'react';
import { Link } from 'react-router';
const MenuOverlay = () => {
return (
<div className="menuOverlay">
<div className="innerMenu">
<p><Link to="/">Home</Link></p>
<p><Link to="/">About</Link></p>
<p><Link to="/">Contact</Link></p>
</div>
</div>
);
};
export default MenuOverlay;
Hamburger
import React, { Component } from 'react';
class Hamburger extends Component {
constructor(props) {
super(props);
this.state = { active: '' };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
var toggle = this.state.active === 'is-active' ? '' : 'is-active';
this.setState({active: toggle});
}
render() {
return (
<button className={`hamburger hamburger--emphatic fadein one ${this.state.active}`} onClick={this.handleClick} type="button">
<span className="homeMenuTextButton">Menu</span>
<span className="hamburger-box">
<span className="hamburger-inner"></span>
</span>
</button>
);
}
}
export default Hamburger;
In the most simplistic form you would have a container component that wraps around both of them and manages the state of the components.
<MenuContainer>
<Hamburger />
<MenuOverlay />
</MenuContainer>
And in <MenuContainer> you would have a state of active some quick pseudocode.
class MenuContainer extends React.Component {
constructor() {
super();
this.state = { active: false}
}
toggleMenu = () => {
// function that will toggle active/false
this.setState((prevState) => {
active: !prevState.active
});
}
render() {
return (
<div>
<Hamburger active={this.state.active} onClick={this.toggleMenu} />
<MenuOverlay active={this.state.active} />
</div>
)
}
}
so in hamburger you would just use the this.props.onClick to change the state of active and then in those corresponding components use the prop of this.props.active to determine what classes should be applied, etc.
Given that one element is not the parent of another element, you will have to pull up the variable keeping the toggle information up the chain of elements until it resides in one common place.
That is, keep the "active" state variable in an ancestor of the two elements and provide to the Hamburger a callback in the props that, when called, modifies the state of that ancestor component. At the same time, also pass the active state variable down to the MenuOverlay as a prop, and everything should work together.
See here for more information:
https://facebook.github.io/react/tutorial/tutorial.html#lifting-state-up
Specifically,
When you want to aggregate data from multiple children or to have two child components communicate with each other, move the state upwards so that it lives in the parent component. The parent can then pass the state back down to the children via props, so that the child components are always in sync with each other and with the parent.

Categories