How to only select element from current component in React - reactjs

I am completely new to react. I created a component like this:
import React from "react"
function Product(props) {
return (
<div>
<h2>{props.product.name}</h2>
<button onClick={() => document.querySelector(".desc").style.display="block"}>More info</button>
<p className="desc" style={{display: "none"}}>{props.product.price.toLocaleString("en-US", { style: "currency", currency: "USD" })} - {props.product.description}</p>
</div>
)
}
export default Product
Then I render a few of the components in the App. The purpose of the button is that it should show the information about the specific component. However this way, Im always selecting the first element with "desc" class name.
How to select the element from the current component easily?

How to select the element from the current component easily?
You wouldn't do it like that with react. The way to alter what your app shows is to alter the state. Create a component that has state that reflects what should be rendered. Then when an interaction happens (e.g. a button click) alter the state. React will re-render and show your app according to the new state:
function Product(props) {
// create a state that holds the information if details should be displayed
// we initially set it to false
const [showDetails, setShowDetails] = useState(false);
// create a callback that toggles the state
// we will pass that to the onClick handler of our button
const handleToggleInfo = () => setShowDetails(current => !current);
return (
<div>
<h2>{props.product.name}</h2>
<button onClick={handleToggleInfo}>More info</button>
{showDetails && (
/* this will only get rendered if showDetails is true */
/* you don't need any css to "hide" it, it will just not render in the first place */
<p className="desc">
{`${props.product.price.toLocaleString("en-US", {
style: "currency",
currency: "USD"
})} - ${props.product.description}`}
</p>
)}
</div>
);
}
Generally speaking it isn't necessary with react to select DOM elements and alter them. This is all handled by state updates and re-rendering the components. A component describes the DOM given a current state and props. If you want your DOM to change describe that with state and props.
If you are not familiar with state in react you should really search for a good tutorial that covers that. It is a basic core concept of react. Without it you will not be able to create an app that has interactivity.
Tip: If you want to display a string that is constructed from multiple parts use template strings.

This is not reactish, you need to leverage your UI changes to the state
function Product(props) {
const [isDescVisible, setIsDescVisible] = useState(false);
return (
<div>
<h2>{props.product.name}</h2>
<button onClick={() => setIsDescVisible(true)}>More info</button>
{isDescVisible &&
<p className="desc">
{props.product.price.toLocaleString("en-US", { style: "currency", currency: "USD" })} - {props.product.description}
</p>
}
</div>
)
}

Related

How can I render one component conditionally twice and not lose internal states/unmounts?

I have one component which needs to be rendered conditionally. Renders the same component with different styles. So, I did like this
import ComponentToRender from '../../ComponentToRender'
const Main =()=> {
const [expand,setExpand] =useState(false)
return (
<div>
{!expand && <ComponentToRender {...someProps} />}
{expand && <div>
<ComponentToRender {...otherProps} />
</div>
}
<button onClick={()=>setExpand(pre => !pre)}>Expand</button>
</div>
)
}
For the above code, I get what I want in terms of UI. But, all the internal states are lost. I must render two components like that and keep the internal states. Is that possible to do that in React?
You can achieve this by keeping the component rendered unconditionally and hiding it with CSS.
You get to preserve Component‘s state for free along with the DOM state (scroll, focus, and input position). However, this solution has drawbacks, too:
You mount the component on startup, even if the user never accesses it.
You update the component even when it’s invisible.
import ComponentToRender from "../../ComponentToRender";
const Main = () => {
const [expand, setExpand] = useState(false);
return (
<div>
<div style={{ display: expand ? null : "none" }}>
<ComponentToRender {...someProps} />
</div>
<div style={{ display: !expand ? null : "none" }}>
<div>
<ComponentToRender {...otherProps} />
</div>
</div>{" "}
<button onClick={() => setExpand((pre) => !pre)}>Expand</button>
</div>
);
};
The reconciliation algorithm is such that when on next render you move from one component to component of different type (assuming they have same spot in component hierarchy), instance of old component is destroyed.
Since you have <ComponentToRender/> and another one is <div><ComponentToRender/></div>, they are different components (because one is inside a div).
Read about reconciliation.
What you can do is move the state of ComponentToRender to Main and pass it as props. Now even if the component unmounts the state will not be lost.

React: Setting and updating based on props

Currently I am facing the problem that I want to change a state of a child component in React as soon as a prop is initialized or changed with a certain value. If I solve this with a simple if-query, then of course I get an infinite loop, since the components are then rendered over and over again.
Component (parent):
function App() {
const [activeSlide, setActiveSlide] = useState(0);
function changeSlide(index) {
setActiveSlide(index);
}
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}
Component (child):
function Button(props) {
const Icon = Icons[props.icon];
const [activeClass, setActiveClass] = useState("");
// This attempts an endless loop
if(props.active == props.index) {
setActiveClass("active");
}
function toggleView(e) {
e.preventDefault();
props.handler(props.index);
}
return(
<button className={activeClass} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
}
Is there a sensible and simple approach here? My idea would be to write the if-query into the return() and thus generate two different outputs, even though I would actually like to avoid this
The React docs have a nice checklist here used to determine if something does or does not belong in state. Here is the list:
Is it passed in from a parent via props? If so, it probably isn’t state.
Does it remain unchanged over time? If so, it probably isn’t state.
Can you compute it based on any other state or props in your component? If so, it isn’t state.
The active class does not meet that criteria and should instead be computed when needed instead of put in state.
return(
<button className={props.active == props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
This is a great use of useEffect.
instead of the if statement you can replace that with;
const {active, index} = props
useEffect(_ => {
if(active == index) {
setActiveClass("active");
}
}, [active])
The last item in the function is a dependency, so useEffect will only run if the active prop has changed.
React automatically re-renders a component when there is a change in the state or props. If you're just using activeClass to manage the className, you can move the condition in the className as like this and get rid of the state.
<button className={props.active === props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
however, if you still want to use state in the child component, you can use the useEffect hook to to update the state in the child component.
Try to use the hook useEffect to prevent the infinite loop. (https://fr.reactjs.org/docs/hooks-effect.html)
Or useCallback hook. (https://fr.reactjs.org/docs/hooks-reference.html#usecallback)
Try this and tell me if it's right for you :
function App() {
const [activeSlide, setActiveSlide] = useState(0);
const changeSlide = useCallback(() => {
setActiveSlide(index);
}, [index]);
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}

how to render a same component one or multiple time using event handler

I want to build form in Which there are some text fields as shown in picture
in this form there is a button and after clicking the Add Units button a new form will appear
how can i render this sub form by using button onClick Event-Handler and i also want to render it as many times as i click the button , if i click button one time then it shows the the sub form only one time if i click the button two times then it will show two times
-The main issue is I want the sub form to appear as many time i click the button
(optional if i want to remove the rendered sub form using a button click then please mention the code )
use of react hooks is preferable
you can ask me anything related to question
Here is an example of how to solve your problem. You don't need useEffect, and you don't need three separate state variables. You just need one array of objects.
import React, { useState } from "react";
import ContainerSlot from "./ContainerSlot";
function ReceiptContainer() {
const [containers, setContainers] = useState([]);
const handleAddContainer = () => {
// adding an empty object; you will likely need to
// initialize this object with whatever values are stored
// in the container form
setContainers((current) => [...current, {}]);
};
const handleRemove = (index) => {
const current = [...containers];
current.splice(index, 1);
setContainers(current);
};
return (
<div className="receiptContainer container">
{/* Heading */}
<div className="receiptContainer__heading mt-3">
<h4>Container Invoice</h4>
<hr />
</div>
<button onClick={handleAddContainer}>Add container</button>
<div className="receiptContainer__container">
{/* You will likely need to pass whatever values are in
the container to your ContainerSlot component */}
{containers.map((container, index) => {
const handleRemoveClick = () => {
handleRemove(index);
};
return (
<div
key={`container-${index}`}
style={{ display: "flex", alignItems: "center" }}
>
<div>
<ContainerSlot {...container} />
</div>
<div>
<button onClick={handleRemoveClick}>Remove</button>
</div>
</div>
);
})}
</div>
</div>
);
}
export default ReceiptContainer;
EDIT: updated to show how to remove an item

React Button Click Hiding and Showing Components

I have a toggle button that show and hides text. When the button is clicked I want it to hide another component and if clicked again it shows it.
I have created a repl here:
https://repl.it/repls/DapperExtrasmallOpposites
I want to keep the original show / hide text but I also want to hide an additional component when the button is clicked.
How to I pass that state or how do I create an if statement / ternary operator to test if it is in show or hide state.
All makes sense in the repl above!
To accomplish this you should take the state a bit higher. It would be possible to propagate the state changes from the toggle component to the parent and then use it in any way, but this would not be the preferred way to go.
If you put the state in the parent component you can use pass it via props to the needed components.
import React from "react";
export default function App() {
// Keep the state at this level and pass it down as needed.
const [isVisible, setIsVisible] = React.useState(false);
const toggleVisibility = () => setIsVisible(!isVisible);
return (
<div className="App">
<Toggle isVisible={isVisible} toggleVisibility={toggleVisibility} />
{isVisible && <NewComponent />}
</div>
);
}
class Toggle extends React.Component {
render() {
return (
<div>
<button onClick={this.props.toggleVisibility}>
{this.props.isVisible ? "Hide details" : "Show details"}
</button>
{this.props.isVisible && (
<div>
<p>
When the button is click I do want this component or text to be
shown - so my question is how do I hide the component
</p>
</div>
)}
</div>
);
}
}
class NewComponent extends React.Component {
render() {
return (
<div>
<p>When the button below (which is in another component) is clicked, I want this component to be hidden - but how do I pass the state to say - this is clicked so hide</p>
</div>
)
}
}
I just looked at your REPL.
You need to have the visibility state in your App component, and then pass down a function to update it to the Toggle component.
Then it would be easy to conditionally render the NewComponent component, like this:
render() {
return (
<div className="App">
{this.state.visibility && <NewComponent />}
<Toggle setVisibility={this.setVisibility.bind(this)} />
</div>
);
}
where the setVisibility function is a function that updates the visibility state.

React not rendering all elements within a list of components

I have a system in which when the user presses a button, a new component is pushed to a list of components and the state is updated. I render the list of components using {}, but only the first element is rendered.
I've used console.log to ensure that my list is actually updating. All the questions I've seen so far for this problem involve using a class that extends React.Component. Since I'm using a function to render, I don't see how I can use those solutions
export default function ExampleManager() {
const [examples, setExamples] = React.useState([<Example key={0}/>);
function handleClick() {
examples.push(<Example key={examples.length}/>);
setExamples(examples);
}
return (
<>
{examples}
<Button variant="outlined" color="primary" onClick={handleClick}>
Add Example
</Button>
</>
);
}
If the button was to be clicked multiple times, I would expect there to be multiple Example components, however at the moment only the first element works
As far as I know, examples is immutable and isn't updated by using examples.push().
Change your handleClick to the following code, to remove the reference of your example variable:
function handleClick() {
// create a new array to prevent referencing the old on
setExamples([
...examples,
<Example key={examples.length}/>
]);
}
Nonetheless you shouldn't add components per se into your array. Try to split values and its representation like the following:
function ExampleManager() {
const [examples, setExamples] = React.useState([0]);
const handleClick = () => setExamples([...examples, examples.length])
return (
<>
{examples.map((item, key) => <Example key={key} data={item} />)}
<Button variant="outlined" color="primary" onClick={handleClick}>
Add Example
</Button>
</>
)
}

Resources