Validating a form using Styled-components doesn't behave like CSS - css

I've found a very simple and efficient tutorial to handle form validation and errors handling with just React and CSS : https://medium.com/#everdimension/how-to-handle-forms-with-just-react-ac066c48bd4f
This solution is working like a charm. But I wanted to reproduce the exact same behavior using the css-in-js library styled-components.
The solution to handle the errors in the tutorial was to switch the class of the FORM and target the pseudo class :invalid of the input child to apply a red border around the inputs. And it works perfectly using just CSS classes. I tried to use the same system with styled-components, but it went wrong.
I did 2 fiddle to show you the difference in behavior
Only CSS => fiddle with css
js
const Form = () => {
const [error, setError] = React.useState(false)
const handleSubmit = React.useCallback((event) => {
event.preventDefault()
if (!event.target.checkValidity()) {
setError(true);
return;
}
setError(false)
console.log('Do something')
}, [error, setError])
return(
<div>
<h1>With pure CSS</h1>
<h2>Good behavior : </h2>
<h2>1 . Press submit => the input goes red </h2>
<h2>2 . Fill one of the input => cleans the red border individually</h2>
<form
onSubmit={handleSubmit}
noValidate
className={error ? "displayErrors" : ""}
>
<input className='input' required></input>
<input className='input' required></input>
<input type="submit" value="Submit!"></input>
</form>
</div>
)
}
ReactDOM.render(<Form />, document.querySelector("#app"))
css
.displayErrors input:invalid {
border-color: red;
}
Using styled-components => Fiddle with styled-components
const StyledInput = styled.input`
margin-right: 5px;
display: table-cell;
margin-bottom: 10px;
outline: none;
`
const StyledForm = styled.form`
&:invalid ${StyledInput} {
border-color: ${props => {
console.log(props.error)
return props.error ? "red" : "none"
}};
}
`;
const Form = () => {
const [error, setError] = React.useState(false)
const handleSubmit = React.useCallback((event) => {
event.preventDefault()
if (!event.target.checkValidity()) {
setError(true);
return;
}
setError(false)
console.log('Do something')
}, [error, setError])
return(
<div>
<h1>With Styled components</h1>
<h2>To reproduce the issue : </h2>
<h2>1 - press submit, the inputs goes red </h2>
<h2>2 - fill one input => ISSUE => the input (which is no more invalid) stays red </h2>
<StyledForm
onSubmit={handleSubmit}
noValidate
error={error}
>
<StyledInput className='input' required></StyledInput>
<StyledInput className='input' required></StyledInput>
<input type="submit" value="Submit!"></input>
</StyledForm>
</div>
)
}
ReactDOM.render(<Form />, document.querySelector("#app"))
The expected behavior on the first fiddle (CSS):
Press submit, the inputs goes red
Fill one input => The red border is cleaned individually (which means one input status doesn't depend on the other)
To reproduce the issue on the second fiddle (Styled-components):
Press submit, the inputs goes red
Fill one input => ISSUE => the input (which is no more invalid) stays red as long as the second input is still invalid (which means the two input are related).
My problem is that in the styled-components version the red borders of the inputs are cleaned when the both of the inputs are valid. But I want the inputs to act individually like in the CSS version.
I know there is 100 of solutions to solve the problem but I really liked the easy and clean solution of the tutorial. I expected to have the same behavior on both sides, could be a bug? Is there something i'm missing? Please help <3

Related

Input is always `valid` in React

I have a react component like so
export const SearchBar = ({
searchInput,
updateSearchKeyword,
}: SearchBarProps) => {
const dummy = "";
return (
<div className={`${styles.container} `}>
<input
className={`${styles["search-bar"]} `}
type={"text"}
value={dummy}
title="search"
placeholder="Search..."
onChange={(e) => updateSearchKeyword(e.target.value)}
/>
</div>
);
};
And I want to make come CSS changes when the Input filed has text in it. So, my CSS is like below
.search-bar:valid {
color: red;
}
However, the color is always red! In other words, in put is always 'valid'
I tried
.search-bar[type="text"]:valid {
color: red;
}
and even adding a pattern in the component. It is not working how do I fix this?
PS: I am using modules.css, hence the class names are a bit strange but they are working as expected for other properties.
you only declare
className={`${styles["search-bar"]} `}
you never update this, so yes, are gona be always red.
then, another mistake when you put:
const dummy = "";
value={dummy}
You will be not able to change the value of this input. You have to use like this:
const [dummy, setDummy] = useState('')
and then on the input:
value={dummy}
onChange={(e)=>setDummy(e.target.value)}
but this not solves you problem, but you can:
onChange={(e)=>handleChange(e.target.value)}
and just declare this:
const handleChange = (value) => {
setDummy(value)
if (dummy !== '') {
// here change the style of ur input (the color)
}
}
###################
to change the style u can:
document.getelemntbyId... bla bla bla (booooring)
or BETTER: declare the style on a state like this:
const [myStyle, setMyStyle] = useState(`${styles["search-bar"]}`)
and then when u want to change ur style just actualize using
setMyStyle(`${styles["search-bar-blue"]}`)
Then for the validation you need to declare a css pattern to check if true or false:
<input pattern="[a-z]+" type.../>
On the example is gona validate true ONLY if input are ONLY lowercase letters

React - Hover Button?

How can I create a Button with hover effect I can't really get it and I have used bootstrap
Button Component:
import React from "react";
import "./button.css";
const Button = ({ title, colorStyle }) => {
return (
<>
<button type="submit" className={colorStyle}>
{title}
</button>
</> );
};
export default Button;
And import line is::
<Button title="Shop" colorStyle="bg-dark text-white" />
in your button.css add
button::hover {
css declarations;
}
Just use react-bootstrap components. The button of it already has a hover effect in default state: https://react-bootstrap.github.io/components/buttons/
Well, the hover effect has nothing to do with react. What you can do is rely on plain old CSS for this. The class that you have added on the button can use used to do this. Your code would look something like this if you wanted a background-changing effect.
bg-dark {
background: black;
}
bg-dark:hover {
background: red;
}
In the above example, your button's background colour will switch to red on hover.
Let me know if you have any follow up questions.

NavLink returns active to all links and no solution in SO seems to work

It seems none of the solutions presented elsewhere works, so I decided to ask. No matter what I do, I can't get the NavLinks to be rendered differently if they are active or not. I took the ternary operator straight from the part about NavLinks in react-router-dom's documentation and I'd try to create a function to avoid having all this code written three times, but before that I'd like to at least have the correct rendering working.
(side question: why does it break if I remove the Router tags from the return statement? They're there because, before I needed the NavLinks for different styling, I was using regular routes, but if I take them out it breaks with a message about useHref, something like that)
Here's my return statement and the styled-components below it:
return (
<>
<GlobalStyles />
<Router>
<Header />
<Wrapper>
<NavBar>
<NavLink
to="/search"
exact
className={(isActive) =>
"nav-link" + (!isActive ? " inactive" : "")
}>
Search song
</NavLink>
<NavLink
to="/weekly-thread"
exact
className={(isActive) =>
"nav-link" + (!isActive ? " inactive" : "")
}>
Weekly thread
</NavLink>
<NavLink
to="/game"
exact
className={(isActive) =>
"nav-link" + (!isActive ? " inactive" : "")
}>
Game
</NavLink>
</NavBar>
</Wrapper>
</Router>
</>
);
};
const Wrapper = styled.div`
width: 100vw;
height: 100vh;
position: absolute;
z-index: -1;
`;
const NavBar = styled.div`
display: flex;
padding: 20px 20px;
position: relative;
gap: 20px;
font-size: 18px;
a {
text-decoration: none;
}
.nav-link {
color: blue;
&:inactive {
color: black;
}
}
`;
EDIT: it's getting weirder. After even simply pasting the code from the answer below and not getting any results, I tried to mess with the font size for the active NavLink. To my surprise, now something changed, but in totally bizarre way, as it made the first element stay as it were before and second and third got transformed just in terms of font size, but nothing else. When I click on the other NavLinks, the URL changes, but they remain the same: the first in the original size, the other two altered.
SECOND EDIT: Trying to debug this a little further, I've done this to one of my NavLinks and the result is very weird:
<NavLink to="/search" exact className={(isActive) => {
console.log(isActive);
isActive === false ? console.log(isActive) : console.log("foo");
}}>Search song</NavLink>
Now, the first console.log returns the actual value of isActive, but the second and third console.logs always return, "foo", which is the second value in the ternary operator, even if I change "isActive === false" to "isActive === true", which should swap them. I have no idea what is going on here.
After console.logs here, there and everywhere, something struck my attention: isActive appears as an object on the console. That's why I was always getting the same result.
When I changed the condition on the ternary operator to isActive.isActive ? I started getting the expected results.
Router
You might want to read up on the docs.
The primary components page explains why you would need a router.
It provides information for the other components; IE what the current route is.
NavLink
NavLink should be able to set their active class themselves, based on if the router is matching on the to props of the NavLink
see this CodePen for an example.
Note that you would still need need to provide the CSS for the class that is applied. If you're using a framework like bootstrap, or material-ui; they would usually have a .active css styling already in there by default.
CSS
The css rule might be incorrect, there seems to be a colon at &:inactive which probable should be .inactive class:
.nav-link {
color: blue;
&.inactive {
color: black;
}
}
codepen
You should be able to remove your className property from NavLink.
Remove:
className={(isActive) =>
"nav-link" + (!isActive ? " inactive" : "")
}>
It's unnecessary as React-Router already adds the .active class to the active link, so what you're doing with that property is no doubt creating the weirdness.
You can just style the a.active however you want.

How to dynamically add css style to pseudo classes in React

I'm trying to build a React component that shows multiple images stored in a database, where below each thumbnail image there is a button linking to its dedicated page. Now, I would like each button to show the dominant color of the image as background on hover. I already have the colors stored in database (artwork.palette.DOMINANT), but struggles to pass the hex code to the React component.
The problem is, inline style cannot set properties of pseudo selectors like a:hover, and because the color is dynamically fetched with the artwork object, I can not set it in a global and static css. Is there a way to set component-scoped style in React? Presumably like <style scoped>...</style>.
Here is my code, I only kept the gist of it for simplicity's sake.
const ImageBox = ({ artwork }) => {
return (
<Fragment>
<div className="image">
<img src={artwork.resources.THUMBNAIL} alt={artwork.title} />
</div>
<div className="caption">
<span>By {artwork.artist}.</span>
</div>
<div className="button">
<a href="!#" style={{ color: artwork.palette.DOMINANT }}>
Details
</a>
</div>
</Fragment>
);
};
You could use JS to modify the global properties of CSS.
Declare properties in index.css or App.css and add your basic styles that will utilize these variables.
:root {
--color-surface: white;
}
button {
background: var(--color-surface);
}
Modify these properties using JS(onMouseEnter and onMouseLeave). i.e.
//onMouseEnter
document.documentElement.style.setProperty("--color-surface", "black");
//onMouseLeave
document.documentElement.style.setProperty("--color-surface", "white")
There a few references you can follow:
Blog and CodSandBox
Note: Not sure if it's a good practice(I haven't seen in projects I've worked on), I would recommend using CSS-in-JS or libraries such as styled component.
Thanks to #PramodMali, I found the CSS-in-JS approach as a elegant way to solve this problem. For anyone who stumbles upon the same struggle in the future, here's how I solved it with react-css:
import { createUseStyles } from "react-jss";
const useStyles = (dominantColor = "#fff") =>
createUseStyles({
toDetailPage: {
color: dominantColor,
"&:hover": {
color: "#000",
background: dominantColor,
}
}
});
After defining the style generator, use it to dynamically set classes in component:
const ImageBox = ({ artwork }) => {
const classes = useStyles(artwork.palette.dominant)();
return (
<Fragment>
<div className="image">
<img src={artwork.resources.THUMBNAIL} alt={artwork.title} />
</div>
<div className="caption">
<span>By {artwork.artist}.</span>
</div>
<div className="button">
<a href="!#" className={classes.toDetailPage}>
Details
</a>
</div>
</Fragment>
);
};
This will then generate dynamic classes on render, for instance detailsButton-0-2-7, which applies the properties passed to the generator function when defining classes.
I was looking for how to set styles dynamically. My main intention was to write a style for hover. Using state to track whether it is hovered or not is good but that will cost render for each update. So I thought How I can solve it with out any state change. I end up doing this and it works perfectly.
<a
target='_blank'
href="#"
onMouseOver={(e) => e.target.style.color = 'red'}
onMouseOut={(e) => e.target.style.color = ''}
>
This is my link
</a>

position absolute - float within screen limits (React)

I'm trying to create an App with a global dictionary; so that when a word that appears in the dictionary is hovered than a small box appears next to it with a definition.
The problem is that the text in the dictionary can appear any where on the screen, and I need to align the floating box so that it will not be displayed out side of the screen
Similar to this
only that I need to be able to style the floating box, like this
Note that the box display outside of the screen:
I tired to use ui material ToolTip
but it throws
TypeError
Cannot read property 'className' of undefined
I solved a similar problem before with jQuery, where I dynamically calculated the position of the box, relative to the screen and the current element.
but I don't know how to do it in react, mainly since I don't know how to get the position of the current element dynamical.
Please help
To give an idea where to start, have a look at useCallback and refs for React. With the given information from node.getBoundingClientRect(), you could calculate if your tooltip is outside the visible area of the browser.
// edit: useCallback won't work in this case, because the visibility is triggered by a css hover and the dimensions are not yet available for the hidden tooltip. Here is a possible solution with useRef and use useEffect though:
function ToolTip({ word, description }) {
const [left, setLeft] = useState(0);
const [hover, setHover] = useState(false);
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
const { right } = ref.current.getBoundingClientRect();
if (window.innerWidth < right) {
setLeft(window.innerWidth - right);
}
}
}, [hover]);
return (
<span
desc={description}
className="dashed"
onMouseEnter={() => setHover(true)}
onMouseLeave={() => {
setHover(false);
setLeft(0);
}}
>
{word}
<div ref={ref} className="explain" style={{ left }}>
<h2>{word}</h2>
{description}
</div>
</span>
);
}
Codepen example: https://codesandbox.io/s/lk60yj307
I was able to do it with
https://www.npmjs.com/package/#syncfusion/ej2-react-popups
But I still wonder what is the correct way to do it in code.

Resources