I have a parent ButtonGroupcomponent that takes in children like this.props.children. The children I'm passing to it is the btnItem component that renders out single buttons. We can add as many of these buttons as we want.
//ButtonGroup Component
render() {
return (
<div>
{this.props.children}
</div>
)
}
//buttonItem component:
render() {
return (
<button disabled={this.props.disabled}>{this.props.caption}</button>
)
}
//final render
<ButtonGroupComponent>
<buttonItem caption="Nothing"/>
<buttonItem caption="Something" disabled={true}/>
<buttonItem caption="Refresh"/>
</ButtonGroupComponent>
^ This is what I get out of the above code.
What I want to achieve is a way for me style the border radius of the first and last item so that they have a curved border. This would have to be dynamic as this styling will be dependent on how many children buttonItem we render.
I should also mention that I'm using styled-components for the css of each button.
you can use first and last child css pseudo selector here. in your Component write following code.
const ButtonGroupComponent= styled.div`
button:first-child {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
button:last-child {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
`;
and render function like this
<ButtonGroupComponent>
<buttonItem caption="Nothing"/>
<buttonItem caption="Something" disabled={true}/>
<buttonItem caption="Refresh"/>
</ButtonGroupComponent>
check out this demo
I wrap a quick example here, I didn't test it yet but you could try:
const StyledButtonGroupComponent = styled(
({ willBeStyled, children, ...rest }) =>(
<ButtonGroupComponent {...rest}>{children}</ButtonGroupComponent>
))`
${props => React.Children.toArray(props.children).length <= props.willBeStyled && `
...first child, last child styles go here...
`}
`
Related
I am creating a list of divs, which was created with map.
function renderButtons(){
const options = [...Array(10).keys()] // returns [0,1,2...9]
return _.map(options, (option)=> renderOption(option))
}
function renderOption(option:number){
return (
<div className="option-container" onClick={() => setLowerContainerVisible(true)}>
<img alt="" src={"./images/feedback-icons/icon-"+option.toString()+".svg"}/>
{option+1}
</div>
)
}
this renders a list of divs, and I was able to change each div background, when hover, like this:
.option-container{
width: 76px;
height: 100px;
background-color: #7777ff;
display: flex;
}
.option-container:hover{
background-color: #adadf3;
}
I wish to be able to click on a div, and change its background color to white. everything I try will change the background of all the 10 divs to white. How can I make it so only the clicked one is changed?
I suggest that you use renderOption and renderButtons as two components rather than plain functions. In the RenderButtons component, you can use some state to maintain which item is clicked, and within RenderOption you can control whether the background color is white or not based on wehther or not the current rendered button is the clicked option. In your .map() method, you can use component rather than a function call <RenderOption option={option} ... />.
See example below:
const {useState} = React;
function RenderButtons() {
const [clickedItem, setClickedItem] = useState(-1);
return Array.from(
Array(10).keys(),
option => <RenderOption isClicked={clickedItem === option} option={option} setClicked={setClickedItem}/>
);
}
function RenderOption({isClicked, option, setClicked}) {
const handleClick = () => {
// setLowerContainerVisible(true) / other code to run when you click
setClicked(option); // set to current option
}
return (
<div className={"option-container " + (isClicked ? "clicked" : "")} onClick={handleClick}>
{option+1}
</div>
)
}
ReactDOM.render(<RenderButtons />, document.body);
.option-container {
width: 76px;
height: 100px;
background-color: #7777ff;
display: flex;
}
.option-container.clicked, .option-container:hover {
background-color: #adadf3;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
The className is a little messy as it involves a ternary, to clean this up it might be worth looking into using a node package such as classnames which allows you to easily build a list of classes based on conditions.
Do it in the event listener function:
<div className="option-container" onClick={highlightAndsetLowerContainerVisible}>
function highlightAndsetLowerContainerVisible(event){
event.preventDefault();
setLowerContainerVisible(true)
event.currentTarget.style.backgroundColor = "red";
}
You then might also want to reset the background color of the other divs
I want to use a useState hook to change the color of my react icons to blue in my sidebar upon clicking one of them. I tried this
const [changeColor, setChangeColor] = useState('blue');
and then in the return
<IconOuter onClick={() => setChangeColor(changeColor)}>
{item.icon}
I would like to know what I am doing wrong? Any help would be greatly appreciated. Thank you.
Upon further inspection, I styled using component styles, so this is my css for the icon. It looks like theres a span surounding the icons which may be easier to style.
const IconOuter = styled.span`
background-color: white;
border-radius: 5px;
padding: 10px;
width: 44px;
height: 44px;
left: 8px;
top: 8px;
`;
When using the useState hook you create a variable and a method, the variable is used to store the state and the method to change the value of the variable. The variables initial value is gained from the value inside the useState hook and you can change that value latter by using the method you defined from the useState hook
This is the basic form of the useState hook:
const [state, setState] = UseState(<initial state>)
So your code should be :
const [myColor, setmyColor] = useState('white'); //the color is currently white
<IconOuter onClick={() => setColor('blue')} />
const IconOuter = styled.span`
background-color: ${ myColor };
border-radius: 5px;
padding: 10px;
width: 44px;
height: 44px;
left: 8px;
top: 8px;
`;
I can see that the default value for this state is blue and in your code, you just call setChangeColor and pass 'blue' again so if you click on it again and aging, still state is blue since you just pass 'blue' to your changeColor state method(setChangeColor()).
So I can just see that the state gets the same value as the default value all the time.
You haven't put the rest of the code but in this small piece of code that you have shared here, I can see that you are not using this state value anywhere.
You can try inline CSS as
<IconOuter onClick={()=>setChangeColor('blue')} style={{color:changeColor}}/>
If you want component style try using color instead of background-color in CSS.
Here is an example that can be helpful. In this case, I want to update the colour of my heart IonIcon when a user clicks on it once, so I first wrapped it in touchableOpacity to get that blink effect. If you don't want to wrap it, you simply put the onPress function inside the icon.
function MyComponent= () => {
const [defaultcolor, setNewColor] = useState('white')
return (
<View>
<TouchableOpacity style={styles.MyRoundButtonStyle} onPress={() =>
setColor('green')} >
<Ionicons name="heart" size={50} color={defaultcolor} />
</TouchableOpacity>
</View>
)};
export default MyComponent;
Note that in the IonIcons I did not make color='green' etc, I rather used the name of the initial state of the state hook (defaultcolor).
Note that it is just a name that you can set to anything like 'mycolor' etc. You may be familiar with the naming convention that is something like [state, setState], it is just a choice.
Do let me know if you need more clarity with this.
The docs for styled-components show its default styled export accepts a single argument:
styled
This is the default export. This is a low-level factory we use to create the styled.tagname helper methods.
Arguments
component / tagname
Description
Either a valid react component or a tagname like 'div'.
I've emphasized "valid react component" here because they explicitly aren't saying this has to be a React component created by styled, although traditionally that is how this is used (as well as documented under their Extending Styled section). An example of this is shown below:
const RedBox = styled.div`
border: 1px solid black;
color: red;
`;
// Traditionally, the argument you pass to `styled` is
// a react element *created by a previous `styled` call*
const BlueBox = styled(RedBox)`
color: blue;
`;
function Example() {
return (
<div>
<RedBox>I am a red box</RedBox>
<BlueBox>I am a blue box</BlueBox>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://unpkg.com/react#17.0.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/react-is#17.0.1/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/styled-components#5.2.1/dist/styled-components.js"></script>
<div id="root"></div>
Nothing unsurprising with the above.
However, my question is what if you pass a non styled component as the argument to the styled call? Shouldn't that returned element also get the styles applied?
Consider the following simple example:
// Create two simple components, one functional, one class-based
const BoxFunctional = (props) => <div>{props.children}</div>;
class BoxClass extends React.Component {
render() {
return <div>{this.props.children}</div>
}
}
// Here, I pass a functional React Component to `styled`
const RedBoxFunctional = styled(BoxFunctional)`
color: red;
border: 1px solid black;
`;
// Again, passing another regular React component, this time a class component
const RedBoxClass = styled(BoxClass)`
color: red;
border: 1px solid black;
`;
function Example() {
return (
<div>
<p>The below two Boxes are regular react elements:</p>
<BoxFunctional>I am a functional box</BoxFunctional>
<BoxClass>I am a class box</BoxClass>
<hr />
<p>The below two boxes <em>should</em> be styled:</p>
<RedBoxFunctional>I am a functional red box</RedBoxFunctional>
<RedBoxClass>I am a class red box</RedBoxClass>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://unpkg.com/react#17.0.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/react-is#17.0.1/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/styled-components#5.2.1/dist/styled-components.js"></script>
<div id="root"></div>
Running the above snippet you can see that the styled components are not styled, despite them extending a "valid" react component.
Is there something I'm missing? Are the docs just incorrect? Can styled only apply styles to an existing react component that is made by a previous styled call?
I don't really know where the confusion lies, you can pass any React component to the styled Higher Order Component.
The issue you have is that you aren't trying to style the BoxFunctional or BoxClass components, but rather you are trying to style the JSX they render. You just need to proxy the className prop through to what each renders.
Styling any Component
The styled method works perfectly on all of your own or any
third-party component, as long as they attach the passed className
prop to a DOM element.
const BoxFunctional = (props) => (
<div className={props.className}>{props.children}</div>
);
class BoxClass extends Component {
render() {
return <div className={this.props.className}>{this.props.children}</div>;
}
}
Demo
i am trying to create a popupdialog to be shown when user clicks on a button. for that i am using portal.
i want it to look like in the picture below,
So basically, when user clicks on the add button i want the popup dialog to display like in the picture above.
in the popup component i want to render overlay with children. and when user clicks on overlay div the popup should close.
I have something that kind of works without using Portal and is like below,
below is my code that is without using Portal,
function Parent({isDialogOpen, setDialogOpen, setSomething}: Props) {
const [isClicked, setIsClicked] = React.useState(false);
const handleButtonClick = () => {
if (setIsDialogOpen) setIsDialogOpen(!isDialogOpen);
if (setSomething) setSomething(isDialogOpen);
setIsClicked(!isClicked);
};
return (
<button onClick={handleButtonClick}>click</button>
{isDialogOpen && isClicked &&
<Overlay>
<Dialog>
//some divs
</Dialog>
</Overlay>
}
);
}
const Overlay = styled.div`
position: fixed;
padding-top:60px;
bottom: 40px;
top: 0;
left: 0;
right: 0;
backdrop-filter: blur(8px);
z-index: 100;
display: flex;
justify-content: center;
align-items: center;
`;
const Dialog = styled.div`
padding: 16px;
width: 384px;
max-height: calc(100% - 200px);
display: flex;
flex-direction: column;
`;
Now i am rewriting above using portal like below,
function Parent({isDialogOpen, setDialogOpen, setSomething}: Props) {
const [isClicked, setIsClicked] = React.useState(false);
const handleButtonClick = () => {
if (setIsDialogOpen) setIsDialogOpen(!isDialogOpen);
if (setSomething) setSomething(isDialogOpen);
setIsClicked(!isClicked);
};
return (
<button onClick={handleButtonClick}>click</button>
{isDialogOpen && isClicked &&
<Popup setSomething={setSomething} setIsDialogOpen={setIsDialogOpen} setIsClicked=
{setIsClicked}>
<Dialog>
//some divs
</Dialog>
</Overlay>
}
);
}
function Popup({setIsClicked, setSomething, setIsDialogOpen, children}: Props) {
return ReactDom.createPortal(
<>
<Overlay
onClick={() => {
if (setIsDialogOpen) setIsDialogOpen(false);
if (setSomething) setSomething(true);
setIsClicked(false);
}}
>
{children}
</Overlay>
</>,
//dont know what to pass here
);
}
Basically as seen in picture above, i want to render the overlay with dialog.
now in popup component i want to create div with classname 'popup' and find the div element with class navbar and attach this div popup to the navbar div
and pass this div element with class popup in the reactDOM.createPortal.
i am new to react and not sure how to do this. could someone help me with this.
thanks.
As I mentioned to you in the comment, react doesn't create the dom node for you. You must do it yourself. How you do it depends on your needs. the most basic example i can think of is below:
When component mounts first time, we need to create the portal and insert it into document.body
once we are sure portal exists, we can render into the portal using our dom ref.
function Popup({setIsClicked, setSomething, setIsDialogOpen, children}: Props) {
const [portal,setPortal] = React.useState<HTMLDivElement|null>( (document.getElementById('my-portal') as HTMLDivElement)||null);
const createPortalIfNotExists = React.useCallback(()=>{
if(portal===null){
const el = document.createElement('div');
el.id='my-portal';
document.body.appendChild(el);
// switch this line for the one above if you want it to be first in tree
// document.body.insertBefore(el, document.body.firstChild);
setPortal(document.getElementById('my-portal') as HTMLDivElement);
}
},[portal]);
createPortalIfNotExists();
if(portal===null){
return null;
}
return ReactDom.createPortal(
<>
<Overlay
onClick={() => {
if (setIsDialogOpen) setIsDialogOpen(false);
if (setSomething) setSomething(true);
setIsClicked(false);
}}
>
{children}
</Overlay>
</>,
portal
);
}
This is just one possible way of doing it. There are other more advanced use cases where you would have the portal be rendered by some other element in your component tree. But this should be enough to get you started. Also, i haven't tested this as i don't have tsc/tslint on this machine so YMMV
As an example, let's say I've a component that can take in props like this:
const testComponent = (props: {isBold: boolean}) => {
if(props.isBold)
return <strong><div>hello</div></strong>
return <div>hello</div>
}
In this case, my example component that can take in props and the result depends on the props given to it.
Now, if I extend this component in styled-components, how can I pass my props into the base component? The idea is something like this:
const styledTestComponent = styled(testComponent({isBold: true}))`
width: 100%;
opacity: 0.5
/* etc etc... */
`
Well, obviously not going to work. This part will fail: styled(testComponent({isBold: true}))
But the idea is that what I want to do is to use CSS to style a particular instance of a component. So in that case, I will need to pass a pre-defined props to the base component, testComponent.
How can I achieve this?
Update:
I've come up with a quick example to illustrate the issue. The code below attempts to style a react component MyCustomImage as a styled-component StyledMyCustomImage. When this is run, you can see that StyledMyCustomImage does render itself as MyCustomImage. However, the CSS styles are not applied.
const MyCustomImage = props => (
<img
src={`https://dummyimage.com/${props.width}x${props.height}/619639/000000`}
/>
);
const StyledMyCustomImage = styled(MyCustomImage)`
border: 2px dotted red;
`;
function App() {
return (
<div className="App">
<h3>Test passing props from styled component to base component</h3>
<StyledMyCustomImage width="600" height="400" />
</div>
);
}
I've created a sandbox for this demo: https://codesandbox.io/s/k21462vjr5
Update 2:
Oh! Thanks to #SteveHolgado's answer, I've gotten it to work! I didn't know styled component will pass the CSS as a prop to its base component! Here's the code after adding in the class name for future reference:
const MyCustomImage = props => (
<img
src={`https://dummyimage.com/${props.width}x${props.height}/619639/000000`}
className={props.className}
/>
);
const StyledMyCustomImage = styled(MyCustomImage)`
border: 2px dotted red;
`;
The sadnbox of the working demo: https://codesandbox.io/s/j4mk0n8xkw
Try this, it should work
const StyledTestComponent = styled(testComponent)`
width: 100%;
opacity: 0.5
/* etc etc... */
`
and pass the prop to instance in this way.
<StyledTestComponent isBold />
Feedbacks are welcome. I have not checked it working, but feels it will work
Note: I checked and it's working. Should work for you.
When you use the styled function like that, your wrapped component will get passed a prop called className, which you need to apply to the element that you want the styles to affect:
const testComponent = (props) => {
return <div className={props.className}>hello</div>
}
You will have access to all props in your styles, which you can use like this:
const styledTestComponent = styled(testComponent)`
width: 100%;
opacity: 0.5;
font-weight: ${props => props.isBold ? "bold" : "normal"};
/* etc etc... */
`