React List item logic handling - javascript

I'm curious to what's a more performant way of handling list logic. For example...
The Item component
export default function Item({data}) => {
return <div>
<h1>{data.name}
<button> Do Something </button>
</div>
}
The List component
export default function List({list}) => {
return <div>
{list.map(item) => <Item data={item} />}
</div>
}
The Main component
export default function Main() {
return <div>
<List list={someList} />
</div>
}
If i want to do something with the button in the Item component is it better to place the logic inside the Item component like this:
export default function Item({data}) => {
const handleDoSomething = () => {
logic goes here
}
return <div>
<h1>{data.name}
<button onClick={() => handleDoSomething()}> Do Something </button>
</div>
}
Or should i propagate back the event to the List component and handle it there like this:
export default function Item({data, handleButtonClick}) => {
return <div>
<h1>{data.name}
<button onClick={() => handleButtonClick()}> Do Something </button>
</div>
}
export default function Main() {
const handleDoSomething = () => {
logic goes here
}
return <div>
<List list={someList} handleButtonClick={() => handleDoSomething()} />
</div>
}
What's the better to do this ? I want a better performance.

I guess that propagating back the event to the List component is better because the handler function will only be declared once, instead of once for every button that you render.
And btw, if you won't pass any custom argument to your handler function, you don't need to create an arrow function, you can just do:
<button onClick={handleButtonClick}> Do Something </button>

Related

Cannot render list created in one component in another

Ok. I have the app.js (which will render all components on my screen) and inside this file i embeded two other js files (components). The first one is basically a button that adds one more word to an array. It goes something like this:
import { useState } from "react";
function DescriptionSector() {
const [title, setTitle] = useState([]);
return (
<button onClick={() => setTitle([...title, "New title defined"])}>add word</button>
)
This first component is working just fine as I used console.log to test it.
THe problem is with the second part.
The second part consists basically of a list that renders the array create on the first part and here's where i having trouble.
function FinancialResume({ title }) {
return (
<ul>
{title.map(e => {
return (
<li>{e}</li>
)
})}
</ul>
)
}
I tried using the props object to send the updated array like this:
import { useState } from "react";
function DescriptionSector() {
const [title, setTitle] = useState([]);
return (
<button
onClick={() => {
setTitle([...title, "New title defined"]);
FinancialResume(title);
}}
>
add word
</button>
)
BUT IT DIDNT WORKED
EDIT: here's my app.js
import DescriptionSector from "./Components/descriptionSector/description";
import FinancialResume from "./Components/financialresume/financialresume";
function App() {
return (
<div className="App">
<div className="user-body__leftSector">
<DescriptionSector />
</div>
<div className="user-body__rightSector">
<FinancialResume />
</div>
</div>
)}
export default App;
Assuming you want the changes made in DescriptionSector to be rendered by FinancialResume, one way you can do that with React is by passing props from a shared parent.
Let App control the title state. It can pass the setter down to DescriptionSector and the value down to FinancialResume.
React states are reactive to changes. App and FinancialResume will re-render when title changes without you having to call any functions.
function App() {
const [title, setTitle] = useState([]);
return (
<div className="App">
<div className="user-body__leftSector">
<DescriptionSector setTitle={setTitle} />
</div>
<div className="user-body__rightSector">
<FinancialResume title={title} />
</div>
</div>
);
}
function DescriptionSector({ setTitle }) {
return (
<button
onClick={() => {
setTitle((title) => [...title, "New title defined"]);
}}
>
add word
</button>
);
}
function FinancialResume({ title }) {
return (
<ul>
{title.map((e, i) => {
return <li key={i}>{e}</li>;
})}
</ul>
);
}
There are of course other ways to manage shared state such as Context and state stores like Redux Toolkit but those are more advanced topics.

Can we pass setState directly to the button element?

Generally, i would pass setVisFalse as a prop to Modal and then define a button inside Modal component that calls it, but i want to make Modal dynamic such that, instead of a button it could be anything (another component) defining the onClick event listener.
The following code works fine, but i want to know is it correct approach?
const Parent = () => {
const [vis, setVis] = useState(false);
return (
<>
{vis && (
<Modal> // generally, i pass here a setVisFalse as a prop.
<h1>Hello Modal</h1>
<button onCLick={setVisFalse}>Close Modal</button> // directly defining onCLick here only.
</Modal>
)}
</>
);
};
export default class Modal extends React.Component {
render() { // instead of definig a button here,
return <div className="modal">{this.props.children}</div> it should be already inside children
}
}
You obviously want to pass the boolean to the the vis state so the way you are doing it won't achieve that
You need to do this instead
const Parent = () => {
const [vis, setVis] = useState(false);
const setVisState = () => {
setVis(value => !value);
}
return (
<>
{vis && (
<Modal> // generally, i pass here a setVisFalse as a prop.
<h1>Hello Modal</h1>
<button onCLick={setVisState }>Close Modal</button> // directly defining onCLick here only.
</Modal>
)}
</>
);
};
use useContext API .
pros - can be used in any nested level of children.

useState passed to useContext not updating state

I am trying to use useContext to create a generic Tooltip component that passes a close() function to the content inside the Tooltip. I have written my Tooltip like this
export function Tooltip(props) {
const [active, setActive] = useState(false);
const close = () => {
setActive(false);
}
return (
<div className="tooltip-wrapper"
onClick={() => setActive(true)}
>
{props.children}
<TooltipContext.Provider value={{close}}>
{active && (
<div className='tooltip-tip bottom' ref={node}>
{props.content}
</div>
)}
</TooltipContext.Provider>
</div>
)
}
I create the Tooltip in a different class component as follows
function Category(props) {
return (
<Tooltip content={<AddCategoryInnerTooltip name={props.name}/>}>
<p className="tooltip-name-opener">{props.name}</p>
</Tooltip>
);
}
function AddCategoryInnerTooltip(props) {
const {close} = useContext(TooltipContext);
return(
<div className="inner-tooltip-wrapper">
<input
className="tooltip-custom-input"
type="text"
defaultValue={props.name}
/>
<div className="button-end">
<button onClick={close}>Cancel</button>
<button>Ok</button>
</div>
</div>
)
}
When I attempt to call close within the AddCategoryInnerTooltip, the state passed from the Tooltip component doesn't update. When I console.log the state, it always comes as true without changing. What am I doing wrong?
should be a callback function
<button onClick={()=>close}>Cancel</button>

Efficiently adding click listener to an arbitrary number of elements

I want to listen for click events on an arbitrary number of elements and inside the click event handler, I want to retrieve some info about the clicked element (info which was easily accessible during element creation).
A common solution to this problem is this:
render() {
return (
<div>
{this.props.users.map(user => (
<button onClick={() => this.buttonClicked(user.email)}>
{user.name}
</button>
))}
</div>
);
}
The flaw I see in this approach is that we're creating a new function for every element. Is that a problem worth solving? If it is, how do you feel about this solution:
render() {
return (
<div>
{this.props.users.map((user, index) => (
<button data-index={index} onClick={this.buttonClicked}>
{user.name}
</button>
))}
</div>
);
}
buttonClicked(event) {
const { index } = event.currentTarget.dataset;
const { email } = this.props.users[index];
// ...
}
Create another component and dispatch the event from it.
class Button extends React.PureComponent{
handleOnClick = ()=>{
const {onClick, ...rest} = this.props
if(onClick typeof ==='function'){
onClick(rest)
}
}
render(){
const {name} = this.props
return (
<button onClick={this.handleOnClick}>
{name}
</button>)
}
}
...
render() {
return (
<div>
{this.props.users.map(user => (
<Button {...user} key={user.email} onClick={this.buttonClicked} />
))}
</div>
);
}
I believe it is better to use a defined function instead of anonymous/inline functions, otherwise the onClick handler only gets created during the render stage.
In your second example, you can actually bind the argument to the handler:
render() {
return (
<div>
{this.props.users.map((user, index) => (
<button data-index={index} onClick={this.buttonClicked.bind(this, user.email)}>
{user.name}
</button>
))}
</div>
);
}
Secondly, wanted to point out that there is no performance issue with having many handlers. React uses one event handler at the root of your document.

Toggle component in react on button click

I have 4 components. I only want to render one at a time. I have buttons in my nav, when i click one it should render that component and then hide the other 3 (i.e. set them to null)
This is easy with 2 components. I just have a toggle function like so:
toggle() {
this.setState(prevState => ({
showTable: !prevState.showTable
}));
}
I have tried to adapt this for now where I have this:
showComponent(component) {
this.setState(prevState => ({
[component]: !prevState.component
}));
}
This currently shows the component when i click the corresponding button. However, it wont hide the component once the same button is clicked again.
I have all my buttons calling this method like so:
<button onClick={() => this.showComponent('AddPlayer')}>Add</button>
<button onClick={() => this.showComponent('ShowPlayers')}>Players</button>
<button onClick={() => this.showComponent()}>Table</button>
<button onClick={() => this.showComponent()}>Matches</button>
any ideas?
EDIT:
{this.state.AddPlayer ?
<div className="add-container">
<AddPlayer />
</div>
:
null
}
{this.state.ShowPlayers ?
<div className="players-container">
<Players />
</div>
:
null
}
You can do this in multiple ways,
One way is, create a const with all state values and components like
const components = {
"AddPlayer": <AddPlayer />,
"ShowPlayers": <Players />,
"Something1": <Something1 />,
"Something2": <Something2 />
}
set value to state like
showComponent(componentName) {
this.setState({displayedTable: componentName});
}
and inside render simply
render(){
return(
<div>
{components[this.state.displayedTable]}
</div>
)
}
Using Switch case
renderComponent(){
switch(this.state.displayedTable) {
case "AddPlayer":
return <AddPlayer />
case "ShowPlayers":
return <Players />
}
}
render () {
return (
<div>
{ this.renderComponent() }
</div>
)
}

Categories

Resources