I wanted to make my components as reusable as it possible but when I started adding events the problems occured. I am using one button component in a lot of places in my app and I just change its name. It worked fine when I passed one onClick event to it (to change menu button name) but when I wanted to do the same with another button (to change cycle name) and when I passed second onClick event to the same button component the menu button stopped working. I tried to find solution but found only different topics. I know I could make a wrapper around the button and make onClick on the wrapper, but I think I am doing something wrong and there must be more elegant way to handle this.
Button component
export const Button = ({text, changeButtonName, changeCycle}) => {
return (
<AppButton onClick={changeButtonName, changeCycle}>
{text}
</AppButton>
);
};
Navbar component where cycle and menuu buttons are placed
export const Navbar = () => {
const menuButton = 'Menu';
const closeButton = 'Zamknij';
const [menuButtonName, setMenuButtonName] = useState(menuButton);
const changeButtonName = () => {
menuButtonName === menuButton ? setMenuButtonName(closeButton) : setMenuButtonName(menuButton);
}
const interiorButton = 'Interior →';
const structuralCollageButton = 'Structural Collage →';
const [cycleButtonName, setCycleButtonName] = useState(interiorButton);
const changeCycle = () => {
cycleButtonName === interiorButton ? setCycleButtonName(structuralCollageButton) : setCycleButtonName(interiorButton);
}
return (
<Nav>
<AuthorWrapper>
<AuthorName>
Michał Król
</AuthorName>
<AuthorPseudonym>
Structuralist
</AuthorPseudonym>
</AuthorWrapper>
<CycleButtonWrapper >
<Button text={cycleButtonName} changeCycle={changeCycle} />
</CycleButtonWrapper>
<MenuButtonWrapper>
<Button text={menuButtonName} changeButtonName={changeButtonName} />
</MenuButtonWrapper>
</Nav>
)
}
this is not a really reusable approach for a Button. For every new method name you would have to include in the props params and you could face something like:
export const Button = ({text, changeButtonName, changeCycle, changeTheme, changeDisplay})
the proper way to make it reusable would be by passing only one handler to your button:
export const Button = ({text, clickHandler}) => {
return (
<AppButton onClick={clickHandler}>
{text}
</AppButton>
);
};
fwiw, the reason you have problem is because at this code onClick={changeButtonName, changeCycle} you are passing multiple expressions with comma operator where the last operand is returned.
You cannot pass two functions to onClick. Either do a conditional check that call that function which is passed or make a wrapper function.
export const Button = ({text, changeButtonName, changeCycle}) => {
return (
<AppButton onClick={changeButtonName || changeCycle}>
{text}
</AppButton>
);
};
or
export const Button = ({text, changeButtonName, changeCycle}) => {
return (
<AppButton
onClick={() => {
changeButtonName && changeButtonName();
changeCycle && changeCycle();
}
}>
{text}
</AppButton>
);
};
update your code like
<AppButton onClick={()=> {
changeButtonName && changeButtonName();
changeCycle && changeCycle();
}}>
{text}
</AppButton>
Related
I developed a Simple React Application that read an external API and now I'm trying to develop a Like Button from each item. I read a lot about localStorage and persistence, but I don't know where I'm doing wrong. Could someone help me?
1-First, the component where I put item as props. This item bring me the name of each character
<LikeButtonTest items={item.name} />
2-Then, inside component:
import React, { useState, useEffect } from 'react';
import './style.css';
const LikeButtonTest = ({items}) => {
const [isLike, setIsLike] = useState(
JSON.parse(localStorage.getItem('data', items))
);
useEffect(() => {
localStorage.setItem('data', JSON.stringify(items));
}, [isLike]);
const toggleLike = () => {
setIsLike(!isLike);
}
return(
<div>
<button
onClick={toggleLike}
className={"bt-like like-button " + (isLike ? "liked" : "")
}>
</button>
</div>
);
};
export default LikeButtonTest;
My thoughts are:
First, I receive 'items' as props
Then, I create a localStorage called 'data' and set in a variable 'isLike'
So, I make a button where I add a class that checks if is liked or not and I created a toggle that changes the state
The problem is: I need to store the names in an array after click. For now, my app is generating this:
App item view
localStorage with name of character
You're approach is almost there. The ideal case here is to define your like function in the parent component of the like button and pass the function to the button. See the example below.
const ITEMS = ['item1', 'item2']
const WrapperComponent = () => {
const likes = JSON.parse(localStorage.getItem('likes'))
const handleLike = item => {
// you have the item name here, do whatever you want with it.
const existingLikes = likes
localStorage.setItem('likes', JSON.stringify(existingLikes.push(item)))
}
return (<>
{ITEMS.map(item => <ItemComponent item={item} onLike={handleLike} liked={likes.includes(item)} />)}
</>)
}
const ItemComponent = ({ item, onLike, liked }) => {
return (
<button
onClick={() => onLike(item)}
className={liked ? 'liked' : 'not-liked'}
}>
{item}
</button>
)
}
Hope that helps!
note: not tested, but pretty standard stuff
I am new to React and have had some hard time to understand the concept of states.
Down below I export a stepper from MUI. I use state
export default function CustomizedSteppers() {
const steps = ['Zoninfo', 'Betalsätt', 'Börja ladda'];
const [activeStep, setActiveStep] = React.useState(0);
const handleNext = () => {
setActiveStep((activeStep+1));
};
return (
//...
//Stepper stuff...
//...
<Button variant="contained" onClick={handleNext}>Hello
World</Button>
</Stack>
);
}
I now want to split this code so that I can setActiveStep from another component.
Meaning, I want to put the button outside of this component and put it in another class, but still allow that button to change the value of activeStep - by accessing the method handleNext on click on a button outside this class. How do I manage to do this?
You need to make a parent component, define activeStep and handleNext there, and pass them to your CustomizedSteppers
Parent.js
import CustomizedSteppers from "./CustomizedSteppers"
export default function Parent() {
const steps = ['Zoninfo', 'Betalsätt', 'Börja ladda'];
const [activeStep, setActiveStep] = React.useState(0);
const handleNext = () => {
setActiveStep((activeStep+1));
};
return (
<>
<CustomizedSteppers activeStep={activeStep} handleNext={handleNext}>
<Button variant="contained" onClick={handleNext}>Hello
World</Button>
</>
)
}
CustomizedSteppers.js
export default function CustomizedSteppers({activeStep, handleNext}) {
return (
//...
//Stepper stuff...
//...
</Stack>
);
}
You have a couple options.
The first is to lift your state, like #Peter said. That is, you'll need to move activeStep to the component that houses both this component and the other component you want to have activeStep's value, and then pass that value to both (a practice known as prop drilling). This is probably the simplest way, if this is the only variable you want to do that for.
The second is to use state management tools, like #ahmetkilinc said. The Context API is native to React, so it won't require any extra installations or tools. There are also third-party tools like Redux that try to solve this problem (though I believe Redux is still just using Contexts behind the scenes, anyway). This option is better if this is a need you anticipate having multiple times throughout your application.
You can just pass the state updating function as a prop to your component.
Your Button Component:
function CustomButtonComponent (props) {
return (
<button onClick={() => props.setCount(count => count + 1)}>
Click me
</button>
)
}
export default CustomButtonComponent
And In Your Main Component
<CustomButtonComponent setCount={setCount} />
Also Since state updates are scheduled in React, you shouldnt use
onClick={() => setCount(count + 1)}
Instead use
onClick={() => setCount(prevCount => prevCount + 1)}
Try understanding following code .
const Child = (props)=>{
return (
<Button variant="contained" onClick={props.handleNext}>Hello
World</Button>
)
}
const Parent = ()=>{
const steps = ['Zoninfo', 'Betalsätt', 'Börja ladda'];
const [activeStep, setActiveStep] = React.useState(0);
const handleNext = () => {
setActiveStep((activeStep+1));
};
return (
<Child handleNext={(e)=>handleNext(e)} />
)
}
Take this code as a sample example and try implementing it with your code .
What is happening in this Parent Function ?
We are passing handleNext as a prop to to Child component which triggers handleNext function inside Parent component when a button is clicked inside Child component.
I am new to React and have had some hard time to understand the concept of states.
Down below I export a stepper from MUI. I use state
export default function CustomizedSteppers() {
const steps = ['Zoninfo', 'Betalsätt', 'Börja ladda'];
const [activeStep, setActiveStep] = React.useState(0);
const handleNext = () => {
setActiveStep((activeStep+1));
};
return (
//...
//Stepper stuff...
//...
<Button variant="contained" onClick={handleNext}>Hello
World</Button>
</Stack>
);
}
I now want to split this code so that I can setActiveStep from another component.
Meaning, I want to put the button outside of this component and put it in another class, but still allow that button to change the value of activeStep - by accessing the method handleNext on click on a button outside this class. How do I manage to do this?
You need to make a parent component, define activeStep and handleNext there, and pass them to your CustomizedSteppers
Parent.js
import CustomizedSteppers from "./CustomizedSteppers"
export default function Parent() {
const steps = ['Zoninfo', 'Betalsätt', 'Börja ladda'];
const [activeStep, setActiveStep] = React.useState(0);
const handleNext = () => {
setActiveStep((activeStep+1));
};
return (
<>
<CustomizedSteppers activeStep={activeStep} handleNext={handleNext}>
<Button variant="contained" onClick={handleNext}>Hello
World</Button>
</>
)
}
CustomizedSteppers.js
export default function CustomizedSteppers({activeStep, handleNext}) {
return (
//...
//Stepper stuff...
//...
</Stack>
);
}
You have a couple options.
The first is to lift your state, like #Peter said. That is, you'll need to move activeStep to the component that houses both this component and the other component you want to have activeStep's value, and then pass that value to both (a practice known as prop drilling). This is probably the simplest way, if this is the only variable you want to do that for.
The second is to use state management tools, like #ahmetkilinc said. The Context API is native to React, so it won't require any extra installations or tools. There are also third-party tools like Redux that try to solve this problem (though I believe Redux is still just using Contexts behind the scenes, anyway). This option is better if this is a need you anticipate having multiple times throughout your application.
You can just pass the state updating function as a prop to your component.
Your Button Component:
function CustomButtonComponent (props) {
return (
<button onClick={() => props.setCount(count => count + 1)}>
Click me
</button>
)
}
export default CustomButtonComponent
And In Your Main Component
<CustomButtonComponent setCount={setCount} />
Also Since state updates are scheduled in React, you shouldnt use
onClick={() => setCount(count + 1)}
Instead use
onClick={() => setCount(prevCount => prevCount + 1)}
Try understanding following code .
const Child = (props)=>{
return (
<Button variant="contained" onClick={props.handleNext}>Hello
World</Button>
)
}
const Parent = ()=>{
const steps = ['Zoninfo', 'Betalsätt', 'Börja ladda'];
const [activeStep, setActiveStep] = React.useState(0);
const handleNext = () => {
setActiveStep((activeStep+1));
};
return (
<Child handleNext={(e)=>handleNext(e)} />
)
}
Take this code as a sample example and try implementing it with your code .
What is happening in this Parent Function ?
We are passing handleNext as a prop to to Child component which triggers handleNext function inside Parent component when a button is clicked inside Child component.
React beginner here.
I have a react button component that I would like to reuse across my app.
This is my reusable button component:
const Button = (props) => {
const [buttonPress, setbuttonPress] = useState('0');
const click = () => {
buttonPress !== '1' ? setbuttonPress('1') : setbuttonPress('0');
}
return (
<Button className={buttonPress == '1' ? 'active' : ''} onClick={click}>
{props.children}
</Button>
)
};
On click, it add an 'active' class to the component.
What I can't figure out is how to remove the class of the the previous 'active' button once I click on a different button.
<Button>Dog</Button>
<Button>Cat</Button>
<Button>Horse</Button>
I misunderstood your question, but I think I was able to figure out.
You can check the example here.
In general, you will always need to keep the data inside the parent component and do a validation inside the child component, the data has to be accessible to every button.
You need to store index of pressed button in parent container and update it in function click. You can try this
const Button = (props) => {
return (
<Button className={props.isPress == '1' ? 'active' : ''} onClick={props.click}>
{props.children}
</Button>
)
};
// Parent container
const [buttonPress, setbuttonPress] = useState(null);
const click = index => {
setbuttonPress(index)
}
const buttonList = ['Dog', 'Cat', 'Horse'].map((item, index) => <Button click={click(index)} isPress={buttonPress == index ? '1' : '0'}>{item}</Button>)
I've managed to solve it myself and it works as expected.
I used: useEffect, useCallback, useRef.
https://codesandbox.io/s/buttons-fc6xq?file=/src/index.js
I want to handle the click of my custom button component in two separate conditions.
This is the custom-button component:
function CustomButton({status, onStop, onClick}) {
return (
// ...
<button onClick={status ? onStop : onClick}/>
// ...
)
}
This code is just a simple example to explain my question. and I can't separate my component according to the value of status.
My question is, Is this an anti-pattern or bad practice in a component? If yes, what's the best practice to do?
It's not an anti-pattern and more like a bad practice, such code isn't maintainable for when more conditions and callbacks will be added.
Components should be simple, readable and reusable while using all provided props as possible (i.e don't add unused props to component's logic):
const NOOP = () => {}
function CustomButton({ onClick = NOOP }) {
return <button onClick={onClick}>Cool Button</button>;
}
Better practice is to handle condition in parent's logic:
function Parent() {
const onContinue = // ...
const onStop = // ...
return <CustomButton onClick={status ? onStop : onContinue} />;
}
I think that is better to pass a single callback onClick and handle the business logic inside it.
Create a function handleClick inside CustomButton component before return and use if-else blocks to achieve this functionality. It is the right way, according to me.
Create your custom button and use it into any place with any props
import React from 'react';
const CustomButton = ({ children, type = 'button', onClick }) => (
<button
type={type}
onClick={onClick}
>
{children}
</button>
);
const Parent = ({ status }) => {
const stop = useCallback(() => {}, []);
const onClick = useCallback(() => {}, []);
return (
status
? <CustomButton onClick={useCallback}>stop</CustomButton>
: <CustomButton onClick={onClick}>click</CustomButton>
);
}