Here:
Open the app in desktop width, notice the div has green background
Reduce browser width to mobile, the div background should change to gray
Again, increase browser width to desktop, notice the gray background remains, instead of green
What should have happened
The background in last step should be green as in the first step, isn't it?
Logging value of isMobile does seem to show it is being updated.
Here is also code:
import React from 'react';
import styled from 'styled-components';
import { useMediaQuery } from 'react-responsive';
let MenuItem = styled.div`
height: 100px;
width: 100px;
border:1px solid red;
background-color: green;
// Select those items which are children of .submenu
.submenu & {
background-color: ${({ isMobile }) => {
return isMobile && 'lightgray';
}};
}
`;
function App() {
const isMobile = useMediaQuery({ query: '(max-width: 524px)' });
return (
<div>
<div className="submenu">
<MenuItem isMobile={isMobile}>test</MenuItem>
</div>
</div>
);
}
export default App;
import React, {useEffect, useState} from 'react';
import styled from 'styled-components';
import {useMediaQuery} from 'react-responsive';
let MenuItem = styled.div`
height: 100px;
width: 100px;
border:1px solid red;
background-color: green;
`;
function App() {
const isMobile = useMediaQuery({query: '(max-width: 524px)'});
const [color, setColor] = useState('green');
useEffect(() => {
if (isMobile) setColor('silver');
else setColor('green');
}, [isMobile])
return (
<div>
<div className="submenu">
<MenuItem style={{background: color}} isMobile={isMobile}>test</MenuItem>
</div>
</div>
);
}
export default App;
You could re-write this as:
const MenuItem = styled.div`
height: 100px;
width: 100px;
border:1px solid red;
background-color: green;
`;
const SubMenu = styled.div`
${MenuItem} {
background-color: ${({ isMobile }) => (isMobile ? `red` : 'lightgray')};
}
`;
function App() {
const isMobile = useMediaQuery({ query: '(max-width: 524px)' });
return (
<>
<SubMenu isMobile={isMobile}>
<MenuItem>MenuItem in SubMenu</MenuItem>
</SubMenu>
<MenuItem>MenuItem</MenuItem>
</>
);
}
Stackblitz
It is the correct answer:
https://stackblitz.com/edit/react-t7gqwx?file=src%2FApp.js,src%2Findex.js
You shouldn't use .submenu &.
Related
I have a component like this:
// MyComponent.tsx
export function MyComponent(): React.ReactElement {
return <Wrapper>
<Text>
hello there
</Text>
<AnotherText>
bye bye
</AnotherText>
</Wrapper>
}
export const Wrapper = styled.div`
color: #FEB240;
background: #f5f5f5;
padding-bottom: 5rem;
padding-left: 7rem;
padding-right: 7rem;
gap: 2rem;
`;
export const Text = styled.span`
width: 50%;
cursor: pointer;
color: rgba(28, 33, 120, 1);
`;
export const AnotherText = styled.span`
color: red;
`;
I want to be able to style the wrapper. I tried to like this (from this answer Styling Nested Components in Styled-Components), but I don't see any change:
// AnotherPlace.tsx
const NewlyStyledMyComponent = styled(MyComponent)`
${Wrapper} {
color: brown;
background: magenta;
}
`;
It seems that MyComponent also need to take (generated) className as props and assign it to the root wrapping element to make the nested styles to work as expected.
Simplified live demo: stackblitz
A basic example in MyComponent:
import styled from 'styled-components';
interface Props {
className?: string;
}
export const Wrapper = styled.div`
background-color: hotpink;
`;
export const Text = styled.span`
color: #fff;
`;
function MyComponent({ className }: Props) {
return (
<div className={className}>
<Wrapper>
<Text>Hello</Text>
</Wrapper>
</div>
);
}
export default MyComponent;
And at where it is imported and used:
import styled from 'styled-components';
import MyComponent, { Wrapper, Text } from './MyComponent';
const NewlyStyledMyComponent = styled(MyComponent)`
margin-bottom: 7px;
${Wrapper} {
background-color: indigo;
}
${Text} {
color: gold;
}
`;
function App() {
return (
<div>
<NewlyStyledMyComponent />
<MyComponent />
</div>
);
}
export default App;
There are indeed 2 issues:
To style a custom React component (even just so that its nested components can be styled), you always need to take a className prop and to apply it on one of your rendered elements, as explained in styled-components docs:
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.
To style nested components, the className of the parent element must be applied on a parent DOM element as well; that is why JohnLi's answer has to add an extra <div className={className}> around the <Wrapper>.
But in your case, you could just style MyComponent and apply the className on the <Wrapper>:
export function MyComponent({
className
}: {
className?: string;
}): React.ReactElement {
return (
// Apply className directly on the Wrapper
<Wrapper className={className}>
This text can be re-colored
<Text>hello there can be re-colored if styling nested Text</Text>
<AnotherText>bye bye</AnotherText>
</Wrapper>
);
}
const NewlyStyledMyComponent = styled(MyComponent)`
/* Directly style MyComponent */
color: brown;
background: magenta;
/* Styling of nested components */
${Text} {
color: white;
}
`;
Demo: https://codesandbox.io/s/vibrant-worker-05xmil?file=/src/App.tsx
I am trying to create an element in React, but I cannot get it to be visible. It shows up in the Elements tab of the Chrome developer console, but when I hover over it there it doesn't even show a location. I have other custom elements that work fine, it is this in particular that is not rendering. Here is the code defining the element:
import React from "react";
import ReactDom from "react-dom";
const Modal = (props) => {
const [domReady, setDomReady] = React.useState(false)
React.useEffect(() => {
setDomReady(true)
})
return domReady?ReactDom.createPortal(
<>
<div className="modal">
TEST
</div>
<div onClick={() => {}} className="backdrop">
</div>
</>,
document.getElementById('modal-root')
):null
}
export default Modal;
Here is the CSS that affects it, though it is worth noting that even without this CSS I see nothing:
.modal {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
border-radius: 6px;
background-color: white;
padding: 1rem;
text-align: center;
width: 30rem;
z-index: 10;
position: fixed;
top: 0;
left: 0;
}
.backdrop {
position: fixed;
z-index: 1;
background-color: rgba(0, 0, 0, 0.75);
width: 100%;
height: 100vh;
top: 0;
left: 0;
}
Edit: Here is the file which uses the component:
import { useState } from 'react';
//import Loading from '../Loading/Loading';
import './Income.css';
import { useNavigate } from 'react-router-dom';
import IncomeRow from '../../components/IncomeRow/IncomeRow';
import Button from '../../components/Button/Button';
import Modal from '../../components/Modal/Modal';
const Income = () => {
//Initializing
const navigate = useNavigate();
// modal visibility states
const [editModalVisible, setEditModalVisibility] = useState(false);
const [deleteModalVisible, setDeleteModalVisibility] = useState(false);
const [addModalVisible, setAddModalVisibility] = useState(false);
// show modal to edit income
const onEditClick = () => {
setEditModalVisibility(true);
}
const onDismissEditModal = () => {
setEditModalVisibility(false);
}
//returning JSX
return (
<>
<div className='Incomes'>
{/* TODO: make these autogenerate from database */}
<IncomeRow name="income 1" source="Company 1" date="1/1/2022" amount={123.45} id="1" editFunction={onEditClick}/>
<IncomeRow name="income 2" source="Company 2" date="7/31/2022" amount={420.69} id="2"editFunction={onEditClick}/>
</div>
<Button text="Add Income"/>
<Modal dismissModal={onDismissEditModal}/>
</>
);
}
export default Income
Here is App.js, which holds the base of the layout:
import React, { useState} from 'react';
import './App.css';
import Navbar from './components/Navbar/Navbar';
import 'bootstrap/dist/css/bootstrap.min.css';
//import { ThemeContext, themes } from './context/themeContext';
import { Outlet, useNavigate} from 'react-router-dom';
function App() {
return (
<div className="App">
<Navbar/>
<div className='body'>
<div id='modal-root'>
<Outlet/>
</div>
</div>
</div>
);
}
export default App;
There is a checkbox component:
import React, { InputHTMLAttributes } from "react";
import styled, { css } from "styled-components";
export const CheckBox: React.FC<InputHTMLAttributes<HTMLInputElement>> = (
props
) => {
return <Input type="checkbox" {...props} />;
};
const Input = styled.input`
${({ theme }) => css`
border: 1px solid white;
&:checked {
background-color: green;
border-color: green;
}
`}
`;
I want to set its checked color to another but doesn't work. It's still the default blue background color.
I hope:
Before
After
You can create own your component for that but it takes a lot of time.
Checkout into sandbox
building a component that takes a value (attribute.readonly), and only displays the first 40 characters, unless the <Chevron onClick is triggered which means I then want to show fullDescription as opposed to the shortHeading - ideally this would render similar to how it does currently only doubling the height of the Accordion for example, to allow the rest of the content to fit. I know I'm still a bit off but some pointers for my next steps/ ideas for improving what I have already would be really appreciated!
// #flow
import styled from "styled-components";
import chevron from "../Assets/chevron-icon.svg";
type Props = { attribute: AttributeType, className?: string };
const Accordion = styled.div`
background-color: #e5e9eb;
height: 56px;
width: 612px;
border-radius: 2px;
border: 1px solid #27282a;
margin-bottom: 48px;
display: flex;
align-items: center;
span {
padding-left: 24px;
}
`;
const Chevron = styled.img`
height: 40px;
width: 40px;
float: right;
margin-right: 12px;
`;
const ExpandableString = ({ attribute, className }: Props) => {
const fullDescription = attribute.readonlyvalue;
const shortHeading = fullDescription.substring(0, 40) + '...';
const isExpanded = false;
function toggleContent() {
isExpanded = true;
}
return (
<Accordion className={className}>
<span>{shortHeading}</span>
<Chevron onClick={toggleContent} src={chevron} alt="Expand or collapse content" />
</Accordion>
);
};
export default ExpandableString;
Put your data in the state and toggle it
const ExpandableString = ({ attribute, className }: Props) => {
const [isExpanded, setIsExpanded] = React.useState(false);
function toggleContent() {
setIsExpanded(prev => !prev);
}
return (
<Accordion className={className}>
<span>{shortHeading}</span>
<Chevron onClick={toggleContent} src={chevron} alt="Expand or collapse content" />
</Accordion>
);
};
Then, according to the isExpanded state, you could conditionally render your React component.
I am new to React JS. Now struggled with a little thing that I need to set an element width equal to its height. We can manage with in jQuery something like below:
var divWidth = $('.main').width();
$('.main').height(divWidth);
$( window ).resize(function() {
var divWidth = $('.main').width();
$('.main').height(divWidth);
});
.container {
width: 100%;
max-width: 400px;
}
.main {
width: 100%;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<div class="container">
<diV class="main">
test
</diV>
</div>
How can we do like this in React JS?
You can use the jquery window listener to pass props to a component and set it style. It will have the same effect.
import React, { Fragment, useState, useEffect } from "react";
import ReactDOM from "react-dom";
function LiveVisitors() {
const [dimensions, setDimensions] = useState({
x: document.body.clientWidth,
y: document.body.clientHeight
});
window.addEventListener("resize", () => {
setDimensions({
x: document.body.clientWidth,
y: document.body.clientHeight
});
});
let fraction = dimensions.x / 10;
return (
<div
style={{
border: "1px solid red",
width: fraction,
height: fraction
}}
>
{JSON.stringify(dimensions)}
</div>
);
}
const wrapper = document.getElementById("container");
ReactDOM.render(<LiveVisitors />, wrapper);
You can modify this playground: https://codesandbox.io/s/react-playground-forked-v66j1?file=/index.js:0-719