ReactJS + styled components + pseudoClasses - reactjs

Is it possible to use pseudoclasses for styling components. I have checkbox which should display SVG image: checked/ unechecked for displaying state as selected/unselected. I can do this with passing props from parent. But i have been told it is possible by only css(styled components).
part of my code:
const CheckboxInput = styled.input`
&:checked, &:not(:checked) {
display: none;
}`;
const CheckboxLabel = styled.label`
cursor: pointer;
-webkit-user-select: none; /* Chrome/Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+ */
`;
function Checkbox(props) {
return (
<CheckboxLabel htmlFor="id" onChange={() => { props.onchange(); }}>
<CheckboxInput id="id" type="checkbox" checked={props.checked} value="cb" name="cb" />
<Span><SVG glyph={checked} width={17} height={17} /></Span>
<Span><SVG glyph={unchecked} width={17} height={17} /></Span>
{props.children}
</CheckboxLabel>
);
}

With following selector in styled-components you can use the state of the input/checkbox to modify the style with only CSS:
&:checked + ${Label} {
/* your CSS */
}
Here is a full example:
import React from 'react';
import { render } from 'react-dom';
import styled from 'styled-components';
const Label = styled.label`
background: red;
display: block;
padding: 1rem;
`;
const Input = styled.input`
&:checked + ${Label} {
background: blue;
}
`;
const App = () => (
<div>
<Input id="input" type="checkbox" />
<Label for="input" />
</div>
);
render(<App />, document.getElementById('root'));
You can see a preview of it here: https://codesandbox.io/s/oYQ21A4wz
This should give you a basic idea how to change style with CSS only.

Related

Component content not imported when importing styled components

I am building a site using React and have decided to use Styled Components. I have a component that is styled with Styled Components and is receiving a prop from my homepage where I am using an instance of this component. I have the styling being pulled into the homepage, but it is not pulling in the content of the component. When if I just pull in the whole component it pulls in the content fine, but it doesn't allow me to pass the prop, but if I import the component styling it doesn't pull in the content of the component, but the prop passes properly. So I need some help with how to pull in the content and styling and get the prop to pass.
I am pretty new to Gatsby and Styled Components so thanks for any help.
My Component Code
import React from 'react'
import styled from "styled-components"
import { useStaticQuery, graphql } from 'gatsby'
export const WhyChooseSection = styled.section`
border-bottom: 1px solid var(--charcoal);
`
export const WhyChooseH2 = styled.h2`
display: ${(props) => props.displayH2};
text-align: center;
`
export const WhyChooseContent = styled.div`
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
width: 100%;
max-width: 1160px;
margin: 0 auto;
padding-top: 40px;
padding-bottom: 40px;
column-gap: 100px;
row-gap: 30px;
#media(min-width: 992px) {
justify-content: space-between;
column-gap: 0;
}
.why-icon {
width: 180px;
height: 145px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
p {
margin-bottom: 0;
font-size: 19px;
font-weight: var(--font-bold);
}
}
`
export function WhyChooseIcons() {
const data = useStaticQuery(graphql`
{
whyChooseIcons: allWp {
nodes {
acfOptionsOfficeSettings {
whyChooseInteriorDetails {
whyChooseIcons {
whyChooseIconGraphic {
altText
id
sourceUrl
}
whyChooseIconTitle
}
}
}
}
}
}
`)
return (
<>
{data.whyChooseIcons.nodes.map((node, index) => (
<WhyChooseSection key={index}>
<WhyChooseH2>Why Choose Interior Details?</WhyChooseH2>
<WhyChooseContent>
{node.acfOptionsOfficeSettings.whyChooseInteriorDetails.whyChooseIcons && node.acfOptionsOfficeSettings.whyChooseInteriorDetails.whyChooseIcons.map((item) => (
<div className='why-icon' key={item.whyChooseIconGraphic.id}>
<img src={item.whyChooseIconGraphic.sourceUrl} alt={item.whyChooseIconGraphic.altText} />
<p>{item.whyChooseIconTitle}</p>
</div>
))}
</WhyChooseContent>
</WhyChooseSection>
))}
</>
)
}
export default WhyChooseIcons
My Homepage Code
import * as React from "react"
// import styled from "styled-components"
import { graphql } from 'gatsby'
import Seo from "../components/seo"
import Hero from '../components/heroComponent'
import { WhyChooseSection } from '../components/whyChooseIcons'
import { WhyChooseH2 } from '../components/whyChooseIcons'
import { WhyChooseContent } from '../components/whyChooseIcons'
import HomeProducts from '../components/homeProducts'
import HomeTestimonials from '../components/testimonials'
import HomeNewHomes from '../components/homeNewHomes'
import HomeFamilyTreatment from '../components/homeFamilyTreatment'
import HomeSolutions from '../components/homeSolutions'
import HomeMotorization from '../components/motorizationComps'
import HomeBrands from '../components/homeBrands'
import CTAOne from '../components/ctaOne'
const IndexPage = (hero) => {
const heroData = hero.data.homeHero
return (
<>
{heroData.edges.map(({ node }, index) => (
<Hero key={index} heroTitle={node.template.homePage.heroH1} heroSubTitle={node.template.homePage.heroParagraph} heroBg={node.template.homePage.heroBackground.gatsbyImage} heroBtnOneText={node.template.homePage.heroButton1Text} heroBtnOneURL={node.template.homePage.heroButton1Url} heroBtnTwoText={node.template.homePage.heroButton2Text} heroBtnTwoURL={node.template.homePage.heroButton2Url} />
))}
<WhyChooseSection>
<WhyChooseH2 displayH2="none"></WhyChooseH2>
<WhyChooseContent></WhyChooseContent>
</WhyChooseSection>
<HomeProducts />
<HomeTestimonials />
<HomeNewHomes />
<HomeFamilyTreatment />
<HomeSolutions />
<HomeMotorization />
<HomeBrands />
<CTAOne />
</>
)
}
export default IndexPage
export const Head = () => <Seo title="Home" />
export const HeroContent = graphql`
{
homeHero: allWpPage(filter: {template: {templateName: {eq: "Home Template"}}}) {
edges {
node {
template {
... on WpHomeTemplate {
homePage {
heroH1
heroParagraph
heroButton1Text
heroButton1Url
heroButton2Text
heroButton2Url
heroBackground {
gatsbyImage(placeholder: BLURRED, width: 1920)
altText
}
}
}
}
}
}
}
}
`
Since you only want to pass a prop into your <WhyChooseIcons> component and that the component itself renders its own content, there is no need to provide any children node to it in the consuming parent.
Simply update your <WhyChooseIcons> component to accept a prop that it will pass to its inner child. Let's name it displayH2 and we can simply pass it into the <WhyChooseH2> component by doing this: <WhyChooseH2 displayH2={displayH2}>. So your component definition now looks like this:
export function WhyChooseIcons({ displayH2 }) {
// Rest of the component logic here
return (
<>
{data.whyChooseIcons.nodes.map((node, index) => (
<WhyChooseSection key={index}>
<WhyChooseH2 displayH2={displayH2}>Why Choose Interior Details?</WhyChooseH2>
{/* Other content here */}
</WhyChooseSection>
))}
</>
)l
};
Then in the file where you consume this component, it is just a matter of passing "none" to the prop:
<WhyChooseSection displayH2="none">
HOWEVER this feel like a XY problem to me, because all you want is to control the conditional rendering of this component. What you can do is to create a prop on your component that determines whether <WhyChosseH2> should be rendered or not.
export const WhyChooseH2 = styled.h2`
text-align: center;
`
export function WhyChooseIcons({ shouldHideH2 }) {
return (
<>
{data.whyChooseIcons.nodes.map((node, index) => (
<WhyChooseSection key={index}>
{!shouldHideH2 && <WhyChooseH2>Why Choose Interior Details?</WhyChooseH2>}
{/* Other content here */}
</WhyChooseSection>
))}
</>
)
}
Then you can just control H2 rendering with a true/false boolean:
{/* To show it, just don't set the prop */}
<WhyChooseIcons />
{/* To hide it, both ways are valid */}
<WhyChooseIcons shouldHideH2 />
<WhyChooseIcons shouldHideH2={true} />

Use Navigate function not working (React)

I'm following a tutorial and everything was going great until I tried to implement Navigation through a search input. For instance, If I am on http://localhost:3000/searched/profile. Typing out an input of 'names' in the search bar should take me to http://localhost:3000/searched/names. In the tutorial it worked that way and I believe I did the same thing but it doesn't work for me
First below is the Search component for the search bar and its input
And then the Pages where my routing is done. My Browser Router is in the App.js.
import styled from "styled-components"
import { FaSearch } from 'react-icons/fa'
import { useState } from 'react'
import {useNavigate} from 'react-router-dom'
function Search() {
const [input, setInput] = useState('');
const navigate = useNavigate();
const submitHandler = (e) => {
e.preventDefault();
navigate('/searched/' + input) (I GUESS THIS IS WHAT IS NOT WORKING)
};
return (
<FormStyle onSubmit={submitHandler}>
<div>
<FaSearch></FaSearch>
<input onChange={(e) => setInput(e.target.value)} type="text" value={input}/>
</div>
<h1>{input}</h1>
</FormStyle>
)
}
const FormStyle = styled.div`
margin: 0 20rem;
div{
width: 100%;
position: relative;
}
input{
border: none;
background: linear-gradient(35deg, #494949, #313131);
border-radius: 1rem;
outline: none;
font-size: 1.5rem;
padding: 1rem 3rem;
color: white;
width: 100%;
}
svg{
position: absolute;
top: 50%;
left: 0%;
transform: translate(100%, -50%);
color: white;
}
`
export default Search
Pages
import Home from "./Home"
import { Route, Routes } from 'react-router-dom'
import Cuisine from "./Cuisine"
import Searched from "./Searched"
function Pages() {
return (
<Routes>
<Route path='/' element={<Home/>} />
<Route path='/cuisine/:type' element={<Cuisine/>} />
<Route path='/searched/:search' element={<Searched/>} />
</Routes>
)
}
export default Pages
The FormStyle component is a styled div element instead of a form element, so the onSubmit handler is meaningless on the div. To resolve you should use the form element so the form submission works as you are expecting.
Search.js Example:
import styled from "styled-components";
import { FaSearch } from "react-icons/fa";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
function Search() {
const [input, setInput] = useState("");
const navigate = useNavigate();
const submitHandler = (e) => {
e.preventDefault();
navigate("/searched/" + input);
};
return (
<FormStyle onSubmit={submitHandler}> // <-- (2) onSubmit works now
<div>
<FaSearch></FaSearch>
<input
onChange={(e) => setInput(e.target.value)}
type="text"
value={input}
/>
</div>
<h1>{input}</h1>
</FormStyle>
);
}
const FormStyle = styled.form` // <-- (1) switch to form element
margin: 0 20rem;
div {
width: 100%;
position: relative;
}
input {
border: none;
background: linear-gradient(35deg, #494949, #313131);
border-radius: 1rem;
outline: none;
font-size: 1.5rem;
padding: 1rem 3rem;
color: white;
width: 100%;
}
svg {
position: absolute;
top: 50%;
left: 0%;
transform: translate(100%, -50%);
color: white;
}
`;
export default Search;

Not able to type in the styled input component react js

I have created styled component to input the text. I am not able to type into the same when I render it. Please let me know what am I missing on.I tried console.logging on onChange.It seems the event is not getting fired.
Component:
export default function Containers() {
const [supplier, setSupplier] = useState("");
const onChange = e => {
setSupplier(e.target.value);
};
return (
<>
<Background />
<CenterAlignedColumnContainer>
<CompleteWidthDivider />
<InputBox
type="text"
value={supplier}
onChange={e => onChange(e)}
name="supplier"
/>
<PartialWidthDivider />
<GradientButton>
<SacramentoText fontSize="36px">Submit</SacramentoText>
</GradientButton>
</CenterAlignedColumnContainer>
</>
);
}
Styled component
export const InputBox = styled.input`
display: flex;
width: 45%;
height: 2em;
margin-top: 1em;
background: linear-gradient(
360deg,
#b0a7e6 0%,
rgba(176, 167, 230, 0) 169.23%
);
border-radius: 5px;
`;
Check this working example of your code - https://codesandbox.io/s/upbeat-euler-smcjf?file=/src/index.js
Looks like this is not a problem with styled-components, check the component CenterAlignedColumnContainer

Strange stuff with useState, useEffect and fetch

I'm trying to fetch some data inside useEffect and when the data is received I want to set a certain state with useState. Data correctly returns from the server. However this doesn't work. Here's the code:
const [sharingLink, setSharingLink] = React.useState(null);
React.useEffect(() => {
client.query({
query: queryGetReferalData
}).then(result => {
console.warn(result); // correct response
setSharingLink(result.data.referralsystem.shareUrl);
console.warn(sharingLink); // null
});
}, []);
Here's the whole component:
import React from 'react';
import styled from 'styled-components';
import { i18n } from 'Helpers';
import { Button } from './Button';
import { ButtonLink } from './ButtonLink';
import { Heading } from './Heading';
import { Input } from './Input';
import Copy from './icons/Copy';
import Facebook from './icons/Facebook';
import Twitter from './icons/Twitter';
import WhatsApp from './icons/WhatsApp';
import client from '#client/apollo';
import queryGetReferalData from './schemas/queryGetReferalData.graphql';
const Root = styled.div`
padding: 48px;
padding-top: 32px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0px 10px 30px rgba(27, 50, 85, 0.1);
border-radius: 4px;
background-color: #FFFFFF;
`;
const Pane = styled.div`
`;
const Row = styled.div`
display: flex;
& > * + * {
margin-left: 10px;
}
`;
export const Form = () => {
const [sharingLink, setSharingLink] = React.useState(null);
const facebookSharingLink =
sharingLink && `https://www.facebook.com/sharer/sharer.php?${encodeURIComponent(sharingLink)}`;
const twitterSharingLink =
sharingLink && `http://www.twitter.com/share?url=${encodeURIComponent(sharingLink)}`;
const whatsAppSharingLink =
sharingLink && `whatsapp://send?text=${encodeURIComponent(sharingLink)}`;
React.useEffect(() => {
client.query({
query: queryGetReferalData
}).then(result => {
console.warn(result);
setSharingLink(result.data.referralsystem.shareUrl);
console.warn(sharingLink);
});
}, []);
return (
<Root>
<Pane>
<Heading>
{ i18n._('Your invitational link') }
</Heading>
<Row>
<Input disabled={sharingLink === null} value={sharingLink} />
<Button icon={<Copy />}>
{ i18n._('COPY') }
</Button>
</Row>
</Pane>
<Pane>
<Heading>
{ i18n._('Or share via social') }
</Heading>
<Row>
<ButtonLink
backgroundColor='#5A79B5'
icon={<Facebook />}
href={facebookSharingLink}
>
{ i18n._('Facebook') }
</ButtonLink>
<ButtonLink
backgroundColor='#52A6DB'
icon={<Twitter />}
href={twitterSharingLink}
>
{ i18n._('Twitter') }
</ButtonLink>
<ButtonLink
backgroundColor='#0DC455'
icon={<WhatsApp />}
href={whatsAppSharingLink}
>
{ i18n._('WhatsApp') }
</ButtonLink>
</Row>
</Pane>
</Root>
);
};
The component also renders like sharingLink is null.
Why is this happening?
What do I do to make this work?
I had some code that was adding to the DOM in the parent component. When I remove it, everything works.
Adding to the DOM in the component, doesn't matter if it's into useEffect or not, somehow messes up hooks even though you add HTML to something completely unrelated to react I guess.
I had this structure:
<body>
<div id="page">
<div id="root">
<CustomReactElement />
</div>
</div>
<body>
The code inside CustomReactElement was adding markup to the 'page'. I changed the code from setting an innerHTML to appendChild() and everything worked.

React Context API returns undefined

I'm quite new to React, and i'm trying to make a ToDoList. I have a Modal with a submit button that when pressed should add a ToDoItem. But since i didn't want to prop drill my way through this i wanted to use the Context API. The Context API confuses me quite a bit, maybe i'm just a moron, but i have a hard time understanding why i have to make a hook and pass that as the value in the provider. I thought that in the ToDoContext that i already defined the default value as a empty array, so i just did it again.
In the console at line 62, which is my initial render it says that it's undefined, after the pressing the Add ToDo I get the same message.
App.jsx
import React, { useState } from "react";
import { render } from "react-dom";
import { ThemeProvider } from "emotion-theming";
import { defaultTheme } from "./theme";
import { Global, css } from "#emotion/core";
import Header from "./components/Header";
import ToDoList from "./components/ToDoList";
import AddBtn from "./components/AddBtn";
import ToDoContext from "./ToDoContext";
const App = () => {
const [toDoItems] = useState([]);
return (
<>
{/*Global styling*/}
<Global
styles={css`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
list-style: none;
text-decoration: none;
}
`}
/>
{/*App render start from here*/}
<ThemeProvider theme={defaultTheme}>
<ToDoContext.Provider value={toDoItems}>
<Header />
<main>
<ToDoList />
<AddBtn />
</main>
</ToDoContext.Provider>
</ThemeProvider>
</>
);
};
render(<App />, document.getElementById("root"));
ToDoContext.jsx
import { createContext } from "react";
const ToDoContext = createContext([[], () => {}]);
export default ToDoContext;
AddBtn.jsx
import React, { useState, useContext } from "react";
import { css } from "emotion";
import Modal from "../Modal";
import ToDoContext from "../ToDoContext";
const BtnStyle = css`
position: fixed;
bottom: 0;
right: 0;
cursor: pointer;
display: block;
font-size: 7rem;
`;
const ModalDiv = css`
position: fixed;
left: 50%;
background-color: #e6e6e6;
width: 60%;
padding: 20px 20px 100px 20px;
display: flex;
flex-direction: column;
align-items: center;
max-width: 400px;
height: 50%;
transform: translate(-50%, -50%);
border-radius: 20px;
top: 50%;
`;
const textareaStyle = css`
resize: none;
width: 100%;
height: 200px;
font-size: 1.25rem;
padding: 5px 10px;
`;
const timeStyle = css`
font-size: 3rem;
display: block;
`;
const modalSubmit = css`
width: 100%;
font-size: 3rem;
cursor: pointer;
margin-top: auto;
`;
const Label = css`
font-size: 2rem;
text-align: center;
display: inline-block;
margin-bottom: 50px;
`;
const AddBtn = () => {
const [showModal, setShowModal] = useState(true);
const [time, setTime] = useState("01:00");
const [toDoItems, setToDoItems] = useContext(ToDoContext);
console.log(toDoItems);
return (
<>
<div className={BtnStyle} onClick={() => setShowModal(!showModal)}>
<ion-icon name="add-circle-outline"></ion-icon>
</div>
{showModal ? (
<Modal>
<div className={ModalDiv}>
<div>
<label className={Label} htmlFor="time">
Time
<input
className={timeStyle}
type="time"
name="time"
value={time}
onChange={(e) => setTime(e.target.value)}
/>
</label>
</div>
<label className={Label} htmlFor="desc">
Description
<textarea
className={textareaStyle}
name="desc"
placeholder={`Notify yourself this message in ${time}`}
></textarea>
</label>
<button
className={modalSubmit}
onClick={() => {
setToDoItems(
toDoItems.push({
time,
})
);
}}
>
Add ToDo
</button>
</div>
</Modal>
) : null}
</>
);
};
export default AddBtn;
There are few issues in your code to fix:
useState returns a value and a setter. With this line of code, const [toDoItems] = useState([]);, you are just passing an empty array to your context.
So do this:
const toDoItems = useState([]);
In your ToDoContext.js, just pass an empty array as argument (initial value)
const ToDoContext = createContext([]);
Working copy of your code is here. (see console logs)
Also, I noticed that you are pushing the todo in setTodoItems in AddBtn.js.
Don't do this:
onClick={() => {
setToDoItems(
toDoItems.push({
time
})
);
}}
Do this:
onClick={() => {
setToDoItems(
toDoItems.concat([
{
time
}
])
);
}}

Resources