Dynamically Styled Button in React Native using Styled Components - button

A Button component is generally comprised of the Text element wrapped with a TouchableHighlight (or other touchable). I'm trying to create a Button component styled using styled-components, but am having trouble getting my style to respond dynamically to props.
Button Component
Below, I've created a Button component similar to the Adapting based on props example found in the styled-component docs.
import React from 'react';
import { Text, TouchableHighlight } from 'react-native';
import styled from 'styled-components/native';
const colors = {
accent: '#911',
highlight: '#D22',
contrast: '#FFF',
}
const Label = styled.Text`
color: ${props => !props.outline ? colors.contrast : colors.accent};
font-weight: 700;
align-self: center;
padding: 10px;
`
const ButtonContainer = styled.TouchableHighlight`
background-color: ${props => props.outline ? colors.contrast : colors.accent};
width: 80%;
margin-top: 5px;
border-color: ${colors.accent};
border-width: 2px;
`
const Button = (props) => {
return (
<ButtonContainer
onPress={props.onPress}
underlayColor={colors.highlight}
>
<Label>
{props.children}
</Label>
</ButtonContainer>
);
};
export default Button;
Button Usage
After importing it, I'm using the button like this...
<Button
outline
onPress={() => console.log('pressed')}>
Press Me!
</Button>
Expected Result
And so, I would expect my button to look like this...
Actual Result
But instead it looks like this...
What I've done to troubleshoot so far
When I inspect using react-devtools, I can see that the outline prop is being passed down to the Button component.
But the prop is not passed down to any of it's children
The Passed Props part of the docs state, "styled-components pass on all their props", but I guess not all the way down?
My Question
What do I need to change so that I can dynamically style my Button based on it's props?

Here you have:
const Button = (props) => {
return (
<ButtonContainer underlayColor={colors.highlight}>
<Label>
{props.children}
</Label>
</ButtonContainer>
);
};
If ButtonContainer was a normal React component, you wouldn't expect the props passed to Button to be automatically passed to ButtonContainer. You'll have to do <ButtonContainer underlayColor={colors.highlight} {...props} /> to do it.
Actually ButtonContainer is a normal React component, the only difference is you pre-apply some styles using an HOC.
Also if you desugar this to a React.createElement call, you can see there's no way props can be passed automatically, because a Function's arguments don't get passed automatically to the function calls inside it.
const Button = (props) => {
return React.createElement(ButtonContainer, { underlayColor: colors.highlight }, ...);
};
It's nothing specific to styled-components. You just have to pass down the props yourself to ButtonContainer, as well as to Label.
So you'd rewrite your code to:
const Button = (props) => {
return (
<ButtonContainer underlayColor={colors.highlight} onPress={props.onPress} outline={props.outline}>
<Label outline={props.outline}>
{props.children}
</Label>
</ButtonContainer>
);
};
Technically a React component can pass down props to it's children, so ButtonContainer could pass them down to Label using React.Children and React.cloneElement APIs. But ButtonContainer doesn't do that for obvious reasons, e.g. you'd not want underlayColor and onPress to be passed to Label automatically. It would cause a lot of confusing bugs.

Related

How do you style your React.js components differently depending on where you are using them in your application?

Let's say you have a navbar and when you're using this component on your homepage you want it to have a certain background color and display property, but when you use that same navbar component on another page in your application you want to change these CSS properties. Seeing as the component has one CSS file linked how would you change the style of a component depending on where it is being rendered?
My personal favourite method nowadays is styled components. Your component might look something like this:
// NavBar.js
import styled from 'styled-components'
const StyledDiv = styled.div`
width: 100%;
height: 2rem;
background-color: ${props => props.bgColor};
`
const NavBar = (bgColor) => {
return <StyledDiv bgColor={bgColor}>
}
Then to use it in your different contexts you simply pass the color prop:
// homepage.js
<NavBar bgColor="red" />
// otherpage.js
<NavBar bgColor="#123ABC" />
Styled components are becoming a very popular way of doing things, but be aware that there are a huge array of ways you can do this.
https://styled-components.com/
(Code not tested)
Well If you just want to use plain CSS then you can change the className based on route so the styles also changes.
Example:
import { useLocation } from "react-router-dom";
const Navigation = () => {
let location = useLocation();
...
return(
<nav className={location.pathname === "/home" ? "homepage-navbar" : "default-navbar"}>
...
</nav>
)
}
You can write longer condition for multiple pages as well.
Other better thing you can do is pass the location.pathname and value of className as prop.
import { useLocation } from "react-router-dom";
const Home = () => {
let location = useLocation();
...
return (
<>...
<Navigation location={location.pathname} styleClass={"homepage-navbar"}/>
</>
)
}
const Navigation = ({location, styleClass}) => {
...
return(
<nav className={location === "/home" ? styleClass : "default-navbar"}>
...
</nav>
)
}
So now you can pass different values for className from different components and get different styles for the navbar.

Is there a way to style react select while keeping most of the original style?

I am using https://react-select.com/home#getting-started and I am trying to create something as close as possible to this:
The original style of select that I coded (without customStyles function from the select library) gave me something like this:
But as soon as I start adding the styles (customStyles function from the select library) as showed here below:
import { useEffect, useState } from 'react';
// styled components
import styled from 'styled-components';
// icons
import { IoIosArrowDown } from 'react-icons/io';
// select input
import Select from 'react-select';
const ContextItem = ({ text, options }) => {
const [treeNodeNames, setTreeNodeNames] = useState();
// THIS IS FROM THE LIBRARY
const customStyles = {
option: (provided, state) => ({
...provided,
// borderBottom: '1px dotted pink',
color: state.isSelected ? 'white' : 'black',
}),
control: () => ({
// none of react-select's styles are passed to <Control />
width: 200,
}),
singleValue: (provided, state) => {
const opacity = state.isDisabled ? 0.5 : 1;
const transition = 'opacity 300ms';
return { ...provided, opacity, transition };
},
};
useEffect(() => {
if (options) {
const treeNodes = options.data.findTreeNodesByTree.map((element) => {
return {value: element.name, label: element.name };
});
setTreeNodeNames(treeNodes);
}
}, [options]);
return (
<ContextContainer>
<ContextTitle>
<p> {text}</p>
</ContextTitle>
{treeNodeNames && (
<SearchInput>
<Select options={treeNodeNames} styles={customStyles} />
</SearchInput>
)}
</ContextContainer>
);
};
// STYLES
const ContextContainer = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`;
const ContextTitle = styled.div`
display: flex;
width: 30%;
align-items: center;
justify-content: space-between;
`;
const SearchInput = styled.div`
width: 60%;
padding: 5px;
outline: black;
`;
export default ContextItem;
I get something like this:
So the question is: how do I achieve the desired results showed in the first photo using the customStyles from select without destroying everything? is there a way to have the original react-select's style passed to and modify them accordingly. At the end I just need the:
No border around the select option
The select option moved to the right
same color
But different background on when selected or hovered
and no changed border color when clicked on the selected input field
I hope makes sense, let me know if the question is not clear:) I appreciate any sort of help and clues.
This react-select component is composed of multiple smaller parts.
You're on the right track to apply custom styles. Here is a list of various parts of react-select that you can customize.
The syntax:
const customStyles = {
option: (provided, state) => ({
...provided,
color: state.isSelected ? 'red' : 'blue',
// rest of styling
})
}
For example, if you want to change the text for no options available, you'll style the noOptionsMessage.
Other things to note:
To access the pseudo-classes like :hover for the option component, CSS-in-JS uses this syntax: "&:hover": {...}.
More details on how to inspect and style a component's pseudostates here.
Here is a codesandbox which has the react-select styled close to your first picture.
You can use styled-components then use the base component as the Select you want then change specific things about it as you wish
Here's another SO thread on the matter
Styled-components docs on the matter https://styled-components.com/docs/advanced#issues-with-specificity

In React, how can I apply a CSS transition on state change, re-mount, or re-render?

Say I have a React functional component with some simple state:
import React, { useState } from 'react'
import { makeStyles } from "#material-ui/core"
export default function Basket() {
const [itemCount, setItemCount] = useState<number>(0)
return (
<div>
<Count count={itemCount} />
<button onClick={() => setItemCount(itemCount + 1)}>
Add One
</button>
</div>
)
}
function Count({count}: {count: number}) {
const classes = useStyles()
return (
<div className={classes.count}>
{count}
</div>
)
}
const useStyles = makeStyles({
count: {
backgroundColor: "yellow",
transition: "backgroundColor 2s ease" // ???
}
}
I want the Count component to apply a property whenever the count changes and then remove it again; say, turn on backgroundColor: yellow for 2 seconds and then gradually fade it over 1 second. What's the simplest way to achieve this?
Note that presumably this could be either triggered by a state change on the parent or by a re-rendering of the child. Alternatively, I could add the key property to <Count/> to force a re-mount of the child:
<Count
key={itemCount}
count={itemCount}
/>
Any of those would be acceptable; I'm looking for the simplest, cleanest solution, hopefully one that doesn't require additional state and is compatible with Material-UI styling APIs.
Just an idea.
const Component = () => {
useEffect(() => {
// will trigger on component mount
return () => {
// will trigger on component umount
}
}, [])
}
...
document.getElementById('transition').classList.add('example')
You can use useEffect along with useRef containing a reference to the element or directly getting it with document.getElementById and then update the transition class that way in component mount/unmount. Not sure if it'll work, I haven't tested it myself.

How to dynamically adjust textarea height with React?

I want to adjust my textarea height dynamically with Refs and pass it to the state but it don't work correctly.
I created a codesandbox to help you to understand what exactly I want.
https://codesandbox.io/s/ol5277rr25
You can solve this by using useRef and useLayoutEffect built-in hooks of react. This approach updates the height of the textarea before any rendering in the browser and therefor avoids any "visual update"/flickering/jumping of the textarea.
import React from "react";
const MIN_TEXTAREA_HEIGHT = 32;
export default function App() {
const textareaRef = React.useRef(null);
const [value, setValue] = React.useState("");
const onChange = (event) => setValue(event.target.value);
React.useLayoutEffect(() => {
// Reset height - important to shrink on delete
textareaRef.current.style.height = "inherit";
// Set height
textareaRef.current.style.height = `${Math.max(
textareaRef.current.scrollHeight,
MIN_TEXTAREA_HEIGHT
)}px`;
}, [value]);
return (
<textarea
onChange={onChange}
ref={textareaRef}
style={{
minHeight: MIN_TEXTAREA_HEIGHT,
resize: "none"
}}
value={value}
/>
);
}
https://codesandbox.io/s/react-textarea-auto-height-s96b2
Here's a simple solution that doesn't involve refs. The textarea is dynamically adusted using some CSS and the rows attribute. I used this myself, recently (example: https://codesandbox.io/embed/q8174ky809).
In your component, grab the textarea, calculate the current number of rows, and add 1:
const textArea = document.querySelector('textarea')
const textRowCount = textArea ? textArea.value.split("\n").length : 0
const rows = textRowCount + 1
return (
<div>
<textarea
rows={rows}
placeholder="Enter text here."
onKeyPress={/* do something that results in rendering */}
... />
</div>
)
And in your CSS:
textarea {
min-height: 26vh; // adjust this as you see fit
height: unset; // so the height of the textarea isn't overruled by something else
}
You can check the repo. Or you can add the package to your project.
https://github.com/andreypopp/react-textarea-autosize
Also if you really willing to learn how the logic working exactly;
https://github.com/andreypopp/react-textarea-autosize/blob/master/src/calculateNodeHeight.js
There is a source code with all calculations together.

Get font color of ReactJS element

I have a React component, a button, and I need to set the background-color of a child element to the color of the button. I know that you're not supposed to call this.refs.myElement.getDOMNode() in the render() function, so I'm not sure how I'm supposed to lay this out.
At the moment, my code looks like this:
import React from 'react';
import { Button, Glyphicon } from 'react-bootstrap';
import classnames from 'classnames';
export default class GlyphButton extends Button {
constructor(props) {
super(props);
}
render() {
let {
glyph,
className,
children,
...props
} = this.props;
return (
<Button ref='btn' {...props} className={classnames([className, 'glyph-button'])}>
<Glyphicon glyph={glyph} />
{children}
</Button>
);
}
}
I need to do something like this:
let color = this.refs.btn.style.color;
return (
<Button ref='btn' ...>
<Glyphicon glyph={glyph} style={{backgroundColor: color}} />
{children}
</Button>
);
Unfortunately, this.refs hasn't been populated yet.
In case you're curious, the reason I'm doing this is because I'm using Glyphicon's free version PNGs for some icons, which are all black on a transparent background, and I'm using:
glyphicon.glyphicon-playing-dice:before {
content: "";
width: 20px;
height: 20px;
-webkit-mask-size: 100%;
-webkit-mask-image: url(/img/glyphicons/glyphicons-playing-dice.png);
display: block;
}
to make it act like a font icon. This class will make the element's background-color the color of the displayed icon.
You can set color as a state and change it in componentDidMount stage.
getInitialState: function(){
return {bgColor: ''}
},
componentDidMount: function(){
var color = React.findDOMNode(this.refs.btn).style.color;
this.setState({bgColor : color});
}
Because React recommend us that :
your first inclination is usually going to be to try to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy.

Resources