I have a React component called beercard which populates an item for me and adds a like button, here's the Beercard file:
function Beercard(props) {
const [active, setActive] = useState(false);
const changeLike = () => {
setActive(!active)
}
return (
<BeerCard>
<Content>
<Lockup text={props.description} tag="h3" title={props.title}/>
</Content>
<ImagContainer>
<Like liked={active} />
<Image url={props.image}/>
</ImagContainer>
</BeerCard>
)
}
Then I have the Like component, which I will use to handle the onClick:
const Icon = styled.svg`
width: 32px;
height: 32px;
stroke: white;
stroke-width: 2px;
fill: ${props => props.liked ? 'white' : 'transparent'};
`
const like = (props) => {
return (
<LikeButton onClick={props.action}>
<Icon liked={props.liked}>
<path
id="heart-icon"
d="M16,28.261c0,0-14-7.926-14-17.046c0-9.356,13.159-10.399,14-0.454c1.011-9.938,14-8.903,14,0.454
C30,20.335,16,28.261,16,28.261z"
/>
</Icon>
</LikeButton>
)
}
It all works fine ie with the css for filled and unfilled, then if I set the state to true in the beercard file it sets the heart to active/fills it. However the onClick function is not doing anything and I'm not sure how to communicate between the two files to add this event. It's a simple on/off toggle with true and false but I don't know how to handle the event, can somebody see what I need to do?
If I understand correctly, you just need to pass along the desired function as a property to the Like component. You're actually 90% there, but you're calling props.action in onClick, but passing nothing to the action property.
Since you already have everything, you can change the line where you use your Like component to:
<Like liked={active} action={changeLike} />
Related
I'm trying to recreate the effect shown at https://hexed.it/
When you hover over either list the corresponding byte in the other list is also highlighted. I figured a panel with each list inside it that had a state with the current hovered byte would do but it seems that React wants to re-render the entire list or do something strange every time resulting in larger files being unbearably slow.
I see a lot of "use memo! use the useCallback hook!" when searching and I've tried... it's still slow and I'm not sure why. It seems like it's only rendering the updated HexByte but it's still unacceptably slow for large files.
Sandbox: https://codesandbox.io/s/flamboyant-ellis-btfk5s
Can someone help me quicken/smooth out the hovering?
I solved it using this answer: Prevent DOM element re-render in virtualized tree component using react-window
In short the things I've learned:
memo has no effect if a component has a useState in it
Large lists of data should be rendered using a library like react-window
The cell rendering function as mentioned in the answer above can't be part of a parent component
As an example for anyone coming here, the new HexPanel class looks like so
import Box from '#mui/material/Box';
import { memo } from 'react';
import { FixedSizeGrid as Grid, areEqual } from 'react-window';
const HexByte = memo(function HexByte(props) {
const onMouseEnter = () => {
props.onHover(props.index);
//setInside(true);
}
const onMouseLeave = () => {
//setInside(false);
}
const onClick = () => {
//setClicked(true);
}
return (
<span
style={{
display: 'inline-block',
padding: '5px',
backgroundColor: props.hoverIndex == props.index ? '#777' : 'transparent',
color: 'darkblue'
}}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{props.byte}
</span>
)
}, (prevProps, nextProps) => nextProps.hoverIndex != nextProps.index);
const Cell = memo(function({ data, columnIndex, rowIndex, style }) {
return (
<div style={style}>
<HexByte byte={data.hex[rowIndex][columnIndex]} onHover={data.onHover} hoverIndex={data.hoverIndex} index={`${rowIndex}${columnIndex}`} />
</div>
)
}, areEqual);
const HexPanel = (props) => {
return (
<Box
sx={{
fontFamily: 'Source Code Pro',
display: 'flex',
flexDirection: 'column',
}}
>
<Grid
columnCount={16}
columnWidth={30}
height={900}
itemData={props}
rowCount={props.hex.length}
rowHeight={35}
width={500}
>
{Cell}
</Grid>
</Box>
)
}
export default HexPanel;
I would like to build out my functional component BoldButton in which when the user clicks either the lowercase a or uppercase A, the text of the button is changed to bold. Below is my work in progress.
import React, { useEffect, useState } from 'react';
const BoldButon = () => {
const [color, setColor] = useState("black")
useEffect(() => {
})
const changeColor = () => {
setColor("black")
}
return (
<div>
<button text={{style:color}} onClick={changeColor}>
A
</button>
<button text={{style:color}} onClick={changeColor}>
a
</button>
</div>
)
}
export default BoldButon;
This was one problem I had when doing a mock technical interview so I would like to know how to solve it when using a functional component. **Not sure if I need to utilize useEffect in order to solve this.
Currently, there are a few problems with syntax to address:
text={{style:color}} not sure what this is supposed to do: text is not a standard or custom prop that you have set anywhere on the button component. Also, say you were to change the color of the button's text (which you could do with style={{color}} then you would be able to change it from let's say black to gray or black to yellow for different emphasis, but you seemed to say that you wanted different font-weight (bold vs not bold) is controlled by the CSS property font-weight (and that can be set in HTML or JSX through the style property that all elements can have.
if you have nothing in useEffect probably you can/should get rid of it. useEffect is useful for if the state of something changes and you need a side effect you can put it there (or it can be used as a strange componentDidMount which will only run after all parents have had their useEffect run.
For something like this it might be better to use a class and then you can set a number of style properties with a single string: like is-emphasized is a commonly used class that can change the font-weight to 600 and the color to black from a dark gray (if necessary).
You probably want individual states for each individual button (as you want each button to be bold if it was clicked: it seems based on your question).
.is-emphasized {
font-weight: 600;
color: #000;
}
But if not the simplest fastest way to approach this is:
<button
style={{fontWeight: bold ? 'bold' : 'normal'}}
onClick={changeWeight}
>
A
</button>
<button
style={{fontWeight: !bold ? 'bold' : 'normal'}}
onClick={changeWeight}
>
a
</button>
function BoldButon() {
const [capitalABold, setCapitalABold] = React.useState(false)
const [aBold, setaBold] = React.useState(false);
const changeColor = () => {
setColor("black")
}
return (
<div>
<button
className={capitalABold ? 'is-emphasized' : ''}
onClick={() => setCapitalABold(!capitalABold)}
>
A
</button>
<button
className={aBold ? 'is-emphasized' : ''}
onClick={() => setaBold(!aBold)}
>
a
</button>
</div>
)
}
ReactDOM.render(<BoldButon/>, document.getElementById("root"));
* {
color: #333;
font-size: 14px;
}
.is-emphasized {
color: #000;
font-weight: 600;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You don't need useEffect for this kind of functionality, you can utilize the useState hook to manage the variable that determines if the user has clicked on the button. Also, you can use the useMemo hook to keep the style object which returns either bold or normal. Please note that the toggleBold toggles the font weight so if a user is clicking multiple times it will switch between the bold and normal.
import React, { useState, useMemo } from 'react';
const BoldButon = () => {
const [boldA, setBoldA] = useState(false)
const [boldB, setBoldB] = useState(false)
const toggleBold = (buttonType) => () => {
if(buttonType === 'firstButton') {
setBoldA(prevState => !prevState)
} else {
setBoldB(prevState => !prevState)
}
}
const buttonStyleA = useMemo(() => {
return {fontWeight: boldA ? 'bold' : 'normal'}
}, [boldA])
const buttonStyleB = useMemo(() => {
return {fontWeight: boldB ? 'bold' : 'normal'}
}, [boldB])
return (
<div>
<button style={buttonStyleA} onClick={toggleBold('firstButton')}>
A
</button>
<button style={buttonStyleB} onClick={toggleBold('secondButton')}>
B
</button>
</div>
)
}
export default BoldButon;
Basic scenario is such: I have a component which has width: 100% as defined in a stylesheet. Therefore it should retain the width of its parent component. I want to calculate the width of my component and apply it to my child component because I am rendering it via createPortal and I would like them to be the same width. This works in the browser. However, in my test, I am finding that window.getComputedStyle(component) is not returning any of the styles applied from the stylesheet.
As suggested, I could mock the javascript window, but that's actually counter to what I'm hoping to do, I think. I want to verify the behavior that is present in the browser, that window.getComputedStyle() returns all styles applied, not just the inline styles.
I have put a simple example into a codesandbox: https://codesandbox.io/s/goofy-wilson-6v4dp
Also here:
function App() {
return (
<div className="App">
<WidthComponent />
</div>
)
}
function WidthComponent() {
const myInput = useRef();
const [inputWidth, setInputWidth] = useState(0);
useEffect(() => {
console.log("in handleLoad");
const width = myInput.current ? myInput.current.offsetWidth : 0;
setInputWidth(width);
}, [myInput]);
return (
<div className="inherited-width" ref={myInput}>
<div style={{ width: inputWidth }} className="child-element">
Hello
</div>
</div>
);
}
// test
test("width is inherited", () => {
const { rerender } = render(
<div style={{ width: "452px" }}>
<WidthComponent />
</div>
);
const element = document.getElementsByClassName("child-element").item(0);
rerender(
<div style={{ width: "452px" }}>
<WidthComponent />
</div>
);
expect(window.getComputedStyle(element).width).toBe("452px");
});
.App {
font-family: sans-serif;
text-align: center;
width: 500px;
}
.inherited-width {
width: inherit;
}
Any help is appreciated.
However, in my test, I am finding that window.getComputedStyle(component) is not returning any of the styles applied from the stylesheet.
Note that if you're running your tests in JSDOM (i.e. every Jest test) then CSS isn't fully implemented. Specifically, the cascade part of CSS is not implemented (https://github.com/jsdom/jsdom/pull/2690). Inheritance is only partially implemented (display and visibility) (https://github.com/jsdom/jsdom/issues/2160).
I would suggest running tests that assert on computed styles only in browsers, not JSDOM. A codesandbox test is not running in an actual browser environment.
I have a simple message board with nested comments built in React. I'm trying to add a favorite button (just a button that toggles filled/unfilled contingent on its boolean value). Every tutorial I find involves making a new component, but I'd like to include this in my primary app code (using a hook/useState).
I've tried some various CSS stuff like active, etc. I'm rusty with CSS and a bit lost using it on JSX. Ideally, it'd be a star button that fills/unfills, but I want to figure out the simple basics here first.
function Toggle(props) {
const [toggleState, setToggleState] = useState(false);
function toggle() {
setToggleState(toggleState === false? true : false);
}
return (
<div {...props}>
<Button
className={`switch ${toggleState}`}
onClick={toggle}>
Favorite
</Button>
</div>
)
}
Toggle=styled(Toggle)`
//dunno what to put here
The button shows up just fine but nothing I try in the style will make it toggle colors (or filled/unfilled) on click. How can I do this?
First of all this syntax is more readable
function toggle() {
setToggleState(!toggleState);
}
then you can use this to change the class of your button
className={`switch ${toggleState ? "some_class" : "some_other_class"}`}
If you use styled-components, you can customize Button directly. In Tagged templates, switch styles according to prop.
Demo
const Button = styled(Icon)`
color: ${props => (props.filled ? 'pink' : 'transparent')};
-webkit-text-stroke: 2px pink;
overflow: hidden;
margin: 0 0.5em;
cursor: pointer;
`
function Toggle(props) {
const [toggleState, setToggleState] = useState(false)
function toggle() {
setToggleState(toggleState => !toggleState)
}
return (
<Button filled={toggleState} onClick={toggle}>
favorite
</Button>
)
}
Adapting based on props
Following your code:
const StyledToggle=styled(Toggle)`
&.true {
//true styles
}
&.false {
//false styles
}
`;
As you are using css in js you could also pass the state as a prop to the component and read it inside the tagged template:
return (
<div {...props}>
<Button
toggleState={toggleState}
onClick={toggle}>
Favorite
</Button>
</div>
)
}
const StyledToggle = styled(Toggle)`
font-weight: ${({toggleState}) => toggleState && "bold"};
`;
I use a list of styled components for displaying some info. I want this info to be sortable. The real problem I'm trying to solve is actually way more complex than what I'm demonstrating here. So any odd design choices are very specific to what I'm trying to do. I'm just mentioning it because the code I'm showing will be very simplified but it will also show some of these at first glance odd design choices.
I've read this article: https://medium.com/the-andela-way/react-drag-and-drop-7411d14894b9
Temitope Emmanuel (the author) did what I'm trying to achieve but with just a plain div. I don't know whether he tested all of what he proposes in his article.
Off to some code:
import React, { Component, Fragment } from 'react';
import styled from 'styled-components';
export default class SomeList extends Component {
constructor(props) {
super(props);
// in real problem all of these are props
// pulled off the state of a parent
this.state = {
dragging: false,
listOfChildrenInOrder: ['1', '2', '3'],
itemComponent: styled.div`
border: 1px solid black;
`,
};
}
render() {
const {
dragging,
listOfChildrenInOrder,
itemComponent: ItemComponent,
} = this.state;
const {
children,
} = this.props;
const Container = styled.div`
display: grid;
grid-template-rows: max-content;
grid-template-columns: repeat(${listOfChildrenInOrder.length}, max-content) 1fr;
`;
const Droppable = styled.div`
&:hover {
background-color: rgba(0,0,0,0.4);
}
`;
return (
<Container>
<Fragment>
{listOfChildrenInOrder.map(((cid, i) => (
<ItemComponent
draggable
key={`ic-${cid}`}
style={{
gridArea: `1 / ${i + 1} / span 1 / span 1`,
}}
onDragStart={(e) => {
this.setState({ dragging: true });
e.dataTransfer.setData('text/plain', `${cid}`);
}}
onDragEnd={() => {
this.setState({ dragging: false });
// doesn't even fire anymore
}}
>
{children.find(c => c.key === cid)}
</ItemComponent>
)))}
</Fragment>
<Fragment>
{dragging && listOfChildrenInOrder.map(((cid, i) => (
<Droppable
key={`d-${cid}`}
style={{
gridArea: `1 / ${i + 1} / span 1 / span 1`,
}}
onDragOver={(e) => {
e.stopPropagation();
e.preventDefault();
}}
onDrop={() => {
// do whatever (out of scope), doesn't get called anyway
}}
>
{children.find(c => c.key === cid)}
</Droppable>
)))}
</Fragment>
</Container>
);
}
}
I'm expecting the reconciler (Fiber) to update the DOM node without straight out replacing it in the middle of a drag operation. I'm using these things to act as highlighters. The real Problem I'm trying to solve actually makes a difference on where exactly stuff gets dropped, so the grid in the real problem is finer, with more droppables and one item component spaning multiple grid columns. Like I said: odd choices, but not without purpose.
Okay, I know now what was causing this whole operation to fail. The reason was dynamically creating new styled components in the render loop. Never do that. Just another rule of thumb learned.