Style parent component on input focus of child component in styled-components - javascript

I've seen some burdensome solutions to this problem, using refs or event handlers in React. I'm wondering if there's a solution at all in styled-components.
The code below is clearly incorrect. I'm trying to figure out the easiest way to style my parent component, when my input child component has focus. Is this possible using styled-components?
What's the best way to go about this, specifically with styled-components in mind, even if it means relying on one of the React methods mentioned above?
const Parent = () => (
<ParentDiv>
<Input/>
</ParentDiv>
);
const ParentDiv = styled.div`
background: '#FFFFFF';
${Input}:focus & {
background: '#444444';
}
`;
const Input = styled.input`
color: #2760BC;
&:focus{
color: '#000000';
}
`;

Check out :focus-within! I think it's exactly what you're looking for.
https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within

// update 2022:
You can use :focus-within (thanks for figuring out #hoodsy)
div:focus-within {
background: #444;
}
<div>
<input type="text" />
</div>
// original answer (with IE and Edge support):
Sadly there is no way of selecting the parent, based only on the child's state with pure CSS/styled-components. Although it is a working draft in CSS4, currently no browsers support it. More about this here.
I usually add an onFocus and onBlur attribute to the input field which then triggers a state change. In your case you have a stateless component. Therefore you could use innerRef to add or remove a class from the parent. But I guess you have found this solution already. Nevertheless I'll post it as well:
const styled = styled.default;
const changeParent = ( hasFocus, ref ) => {
// IE11 does not support second argument of toggle, so...
const method = hasFocus ? 'add' : 'remove';
ref.parentElement.classList[ method ]( 'has-focus' );
}
const Parent = () => {
let inputRef = null;
return (
<ParentDiv>
<Input
innerRef={ dom => inputRef = dom }
onFocus={ () => changeParent( true, inputRef ) }
onBlur={ () => changeParent( false, inputRef ) }
/>
</ParentDiv>
);
};
const ParentDiv = styled.div`
background: #fff;
&.has-focus {
background: #444;
}
`;
const Input = styled.input`
color: #2760BC;
&:focus{
color: #000;
}
`;
ReactDOM.render( <Parent />, document.getElementById( 'app' ) );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>
<div id="app"></div>
A more rigid method is to convert Parent to a class, so you'd be able to use states:
const styled = styled.default;
class Parent extends React.Component {
state = {
hasFocus: false,
}
setFocus = ( hasFocus ) => {
this.setState( { hasFocus } );
}
render() {
return (
<ParentDiv hasFocus={ this.state.hasFocus }>
<Input
onFocus={ () => this.setFocus( true ) }
onBlur={ () => this.setFocus( false ) }
/>
</ParentDiv>
);
}
};
const ParentDiv = styled.div`
background: ${ props => props.hasFocus ? '#444' : '#fff' };
`;
const Input = styled.input`
color: #2760BC;
&:focus{
color: #000;
}
`;
ReactDOM.render( <Parent />, document.getElementById( 'app' ) );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>
<div id="app"></div>
This way you are in better control of your component and can decide more easily to pass the focus/blur state to other elements.

#hoodsy thanks,it worked like a charm I used like below on the parent div to change color of a label when an input focused .
&:focus-within label{
color: blue;
}

Related

React Toggle Body Class with button

I'm still learning React but I'm having an issue toggling a body class with a button in the menu.
const toggleSideMenu = event => {
// toggle class on click
//Below is not correct
event.getElementsByTagName('body').classList.toggle('sb-sidenav-toggled');
};`
<button onClick={toggleSideMenu} id="sidebarToggle" href="#!"><i className="fas fa-bars"></i></button>
I'm used to doing this easily in jQuery but it's not recommended to use jQuery in React because of the dom. I would appreciate any suggestions.
Thanks so much!
In this example, we are using the useState hook to keep track of the toggle state. The initial state is set to false. We are using the isToggled state in the JSX to determine what to render on the screen, and to update the text of the button.
We have an onClick event on the button, which calls the setIsToggled function and pass the negation of the current state (!isToggled), this is the way to toggle the state, every time the button is clicked.
import React, { useState } from 'react';
const MyComponent = () => {
// useState hook to keep track of the toggle state
const [isToggled, setIsToggled] = useState(false);
return (
<div>
{/* render some content or change className based on the toggle state */}
<p className={isToggled? "class1" : "classB">Toggled on</p>
<button onClick={() => setIsToggled(!isToggled)}>
{isToggled ? 'Turn off' : 'Turn on'}
</button>
</div>
);
}
export default MyComponent;
But if you need to do something more advanced, maybe you can learn more about React Context.
import React, { useState } from 'react';
// Create a context to share the toggle state
const ToggleContext = React.createContext();
const MyApp = () => {
// useState hook to keep track of the toggle state
const [isToggled, setIsToggled] = useState(false);
return (
<ToggleContext.Provider value={{ isToggled, setIsToggled }}>
<MyComponent1 />
<MyComponent2 />
{/* any other components that need access to the toggle state */}
</ToggleContext.Provider>
);
}
const MyComponent1 = () => {
// use the toggle state and toggle function from the context
const { isToggled, setIsToggled } = useContext(ToggleContext);
return (
<div>
<p className={isToggled? "class1" : "classB">Toggled on</p>
<button onClick={() => setIsToggled(!isToggled)}>
{isToggled ? 'Turn off' : 'Turn on'}
</button>
</div>
);
}
const MyComponent2 = () => {
// use the toggle state from the context
const { isToggled } = useContext(ToggleContext);
return (
<div>
{isToggled ? <p>Toggled on</p> : <p>Toggled off</p>}
</div>
);
}
export default MyApp;
A very basic example to show you how to use state to maintain whether a menu should be open or not.
It has one button that when clicked calls a function that updates the state.
It has one Menu component that accepts that state, and uses CSS to determine whether it should be "open" (ie on/off screen).
Like I said, as simple as I could make it.
const { useState } = React;
function Example() {
// The state set to either true or false
// Initially it's false / menu closed
const [ menuOpen, setMenuOpen ] = useState(false);
// When the button is clicked we take the
// previous state and toggle it - either from true
// to false, or vice versa
function handleClick() {
setMenuOpen(prev => !prev);
}
// One Menu component that accepts that state
// and one button that updates the state
return (
<div>
<Menu open={menuOpen} />
<button onClick={handleClick}>
Toggle Sidebar Menu
</button>
</div>
);
}
// Small menu (an aside element) which uses CSS
// to work out its position on the screen
// It does this by creating a classList using the default
// "menu" which it ties together with "open" but it only
// adds that if the state is true
// And then just use that joined array as the className on
// the element
// You can see in the CSS what both those classes do
function Menu({ open }) {
const menuStyle = [
'menu',
open && 'open'
].join(' ');
return (
<aside className={menuStyle}>
I am a sidebar
</aside>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.menu { width: 100px; top: 0px; left: -120px; background-color: salmon; position: fixed; height: 100vh; padding: 10px; transition-property: left; transition-duration: 0.25s;}
.open { left: 0px; }
button { position: fixed; left: 150px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
getElementsByTagName() is method of Document or Element, not react event.
What you need to do, is to look for body inside document.
Also getElementsByTagName(), returns HTMLCollection (many elements), so you need to grab first one (usually there is only one body element on page)
document.getElementsByTagName('body')[0].classList.toggle('sb-sidenav-toggled');
There is also shortcut for body element document.body, so it can be also written as:
document.body.classList.toggle('sb-sidenav-toggled');

How to extend styling of component without using wrapper element?

I'm working with styled-components in atomic system.
I have my <Atom title={title} onClick={onClick}/>
And I have molecule that extends that atom, by just adding some functionality to it without wrapping element:
const Molecule = ({}) => {
const [title, setTitle] = useState('Base title')
const onClick = () => {
// some actions
}
useEffect(()=>{
setTitle('New title')
},[conditions changed])
return(
<Atom title={title} onClick={onClick}/>
)
}
I have base styling in <Atom/> but I would like to add some more to it in <Molecule/>. Is it possible to do it without creating extra wrapper?
It is possible, but the question is if it's worthy the effort - the most anticipated way would be to do it as the documentation says - to wrap the styled component and extend the styles (but this is what you want to avoid). So either:
you could assign a className to Atom, so you can adjust/overwrite the styles with CSS
pass the extraStyles props to Atom and then pass to the styled component and just use inside after the previous, default styles to overwrite them
or either pass some extraStyles as CSSProperties object and just use them as inline styling.
https://codesandbox.io/s/withered-leftpad-znip6b?file=/src/App.js:64-545
/* styles.css */
.extraClass {
color: green;
}
const AtomStyled = styled.div`
font-size: 17px;
color: blue;
font-weight: 600;
`;
const Atom = ({ children, className, extraStyles }) => {
return (
<AtomStyled style={extraStyles} className={className}>
{children}
</AtomStyled>
);
};
const Molecule = () => {
return (
<Atom className={'extraClass'} extraStyles={{ fontSize: 27 }}>
Atom
</Atom>
);
};

Change the background-color of the whole app on button click React.js

I am trying to change the color of the background of my whole React App on button click. However, I only change the color of the button itself. I am importing the App.css into my app and I want to dynamically change the CSS of the App from a separate function called ChangeColor. This function is then placed in my Header.js that is then placed in the App.js
Is there a way that I can do this? This is my code:
import React, {useState} from "react";
import Button from "react-bootstrap/esm/Button";
import '../../../App.css'
function ChangeColor() {
const [isActive, setIsActive] = useState(false);
const handleClick = () => {
setIsActive(current => !current);
};
return(
<Button
style={{
backgroundColor: isActive ? 'red' : '',
color: isActive ? 'white' : '',
}}
onClick={handleClick}
> Test </Button>
)
}
export default ChangeColor
.App {
text-align: center;
background-color: white;
}
There are a couple of solutions that come to mind.
Store the background color in state, and toggle between the colours depending on the current state.
(Small note - you shouldn't call your component ChangeColor as it's not really representative of what the component is - changeColor might be a good name for a function. You should call your component ButtonChangeColor, for example.)
const { useState } = React;
function Example() {
const [bgColor, setBgColor] = useState('white');
function toggleBackground() {
if (bgColor === 'white') setBgColor('black');
if (bgColor === 'black') setBgColor('white');
}
const appStyle = ['App', bgColor].join(' ');
return (
<div className={appStyle}>
<button onClick={toggleBackground}>
Toggle background
</button>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.App {
height: 100vh;
background-color: white;
}
.black { background-color: black; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Use CSS variables and modify the current stylesheet using the CSSStyleDeclaration interface - no state required. We can usefully maintain the toggleBackground function outside of the component because it doesn't rely on state to work.
function toggleBackground() {
const { style } = document.documentElement;
const bgColor = style.getPropertyValue('--bg-color');
if (bgColor === 'white' || bgColor === '') {
style.setProperty('--bg-color', 'black');
} else {
style.setProperty('--bg-color', 'white');
}
}
function Example() {
return (
<div className="App">
<button onClick={toggleBackground}>
Toggle background
</button>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
:root { --bg-color: white; }
.App {
height: 100vh;
background-color: var(--bg-color);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
you can store isActive and color in localStorage
const [isActive, setIsActive] = useState(localStorage.getItem('is_active') || false);
const handleClick = () => {
setIsActive(current => !current);
};
<Button
style={{
backgroundColor: isActive ? 'red' : '',
color: isActive ? localStorage.getItem('bg_color') : '',
}}
onClick={handleClick}
> Test </Button>
The problem with your code is you are acting on the properties of the button. Instead you should act on the element its color you are trying to change.
In this code snippet the button modifies the color of the root div. Be aware that this solution is just to illustrate your problem. As has been stated by other users there are many ways to achieve what you want and some adhere to best practices while others not. Given the context of the question this answer just to points to why it is not working and not how best to do it. Please, refer to Andy's answer for more information about the latter.
const App = () => {
const changeAppColor = () => {
let el = document.getElementById("root");
if (el.style.backgroundColor === "red") {
el.style.backgroundColor = "unset";
} else {
el.style.backgroundColor = "red";
}
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={changeAppColor}>
Change color
</button>
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App />
);
<div id='root'> </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

REACT: Why after clicking on the button color of h1 is not changing? (but if you change color to i.e innerText then it works)

useEffect changes the css property but it takes no effect on the screen. But only so if I change color or bacgroundColor - innerText works just fine.
export function App(props) {
const [state1, setState1] = useState(true);
const hRef = useRef();
const clickHandler = () => {
setState1(!state1);
}
useEffect(()=>{
hRef.current.color=state1?'blue':'green';
setState1(state1);
},[state1]);
return (
<div className='App'>
<h1 ref={hRef}>Hello {`${state1}`}.</h1>
<button onClick={clickHandler}>Change State</button>
</div>
);
}
WRT to not needing to use ref here's an example that stores the "active" state of the button, and uses CSS classes to change the colour of the heading.
const { useState } = React;
// Accept a name prop
function App({ name }) {
// Initialise the state as false
const [ active, setActive ] = useState(false);
// The button updates the state
function handleClick() {
setActive(!active);
}
// We create class string which is determined by
// the current state
const cn = `heading ${active ? 'active' : ''}`;
// And then apply that string to the `className` prop
// of the heading
return (
<div className='App'>
<h1 className={cn}>Hello {name}.</h1>
<button
className="change"
onClick={handleClick}
>Change State
</button>
</div>
);
}
ReactDOM.render(
<App name="Wojtek" />,
document.getElementById('react')
);
.heading { padding: 0.3em; background-color: lightgreen; }
.active { background-color: lightblue; }
.change:hover { cursor: pointer; background-color: #ffffa0; border-radius: 5px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

how to pass arguments up via props in functions with React?

In a class, I would write: this.prop.bind(this,arg)
like this:
<button onClick={this.props.delete.bind(this, id)} id='deletebtn'>
X
</button>
but how would I do the same thing in a function?
I need to use a class or there is a way to do it without it?
ps: did hooks killed classes? if not when are classes really necessary?
You want to create a function that returns a function, like this:
function deleteItem(id) {
return function() {
... <using `id`>
}
}
or with arrow functions:
const deleteItem = (id) => {
return () => {
... <using `id`>
}
}
or even shorter:
const deleteItem = (id) => () => {
... <using `id`>
}
and then you do
<button onClick={deleteItem(id)} id='deletebtn'>
where deleteItem(id) is now a function.
To answer your question about hooks, they did not "kill classes" but provide a arguably better alternative only for React components. Class components are still supported. It is hard to imagine a situation that cannot be handled by the new functional components with hooks, though.
You can pass the removeItem handler down to the child component -
const { useState } = React
const initialItems =
[ "muffins", "cake", "pies" ]
const MyApp = ({ init = initialItems }) => {
const [items, setItems] = useState(init)
const removeItem = (pos = 0) => event =>
setItems([...items.slice(0, pos), ...items.slice(pos + 1)])
const addItem = event =>
event.key === "Enter"
? setItems([...items, event.target.value ])
: null
return <div>
{items.map((name, i) =>
<Item name={name} onDelete={removeItem(i)} />
)}
<div className="item">
<input onKeyDown={addItem} placeholder="Type here and press [ENTER]..." />
</div>
</div>
}
const Item = ({ name = "", onDelete }) =>
<div className="item">
{name}
<button onClick={onDelete}>🗑️</button>
</div>
ReactDOM.render(<MyApp />, document.body)
body {
font-family: monospace;
}
.item {
background-color: ghostwhite;
padding: 0.5rem;
}
input {
display: block;
box-sizing: border-box;
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
Okay, I found a way,
Like #JulienD Said it, I just need it to create a function that returns a function
this is how I did it.
the child sends the argument to the parent
<button onClick={() => {props.delPost(id)}}>delete</button>
the parent sends the argument up one more level
<Post delPost={(id)=>{props.delPost(id)}} key={post.id} postItem={post} />)
finally the root parent make use of the argument
const deletePost = (id) => {
console.log(id);
}
<Feed delPost={deletePost} posts={posts} />

Categories