react check box not rendering correctly - javascript

I am trying to create a react component that represents a tile.
This component is just a div that's composed of a label and a checkbox.
The problem that I have is that I can click wherever on the component and the states changes like it would normally do (eg: by clicking on the component i can check or uncheck the checkbox). but when I click on the checkbox nothing happens.
Here is my newly created component code:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(()=>{
console.log(selected)
},[selected])
return (
<>
<div className="tile" onClick={ev=>setSelected(curr=>!curr)}>
<label>{title}</label>
<input
type="checkbox"
checked={!!selected}
onChange={ev=>{setSelected(curr=>!curr)}}
></input>
</div>
</>
);
};
and here I use it in my App.js :
return (
<Container>
<Row>
<Col md={4}>
<Tile title="USA"></Tile>
<Tile title="MOROCCO"></Tile>
<Tile title="FRANCE"></Tile>
</Col>
<Col md={8}>
<h1>Hello</h1>
</Col>
</Row>
</Container>
and finally here is my css :
body {
padding-top: 20px;
font-family: "Poppins", sans-serif;
background-color: cornsilk;
}
.tile {
position: relative;
display: block;
width: 100%;
min-height: fit-content;
background: bisque;
padding: 8px;
margin: 1px;
}
.tile input[type="checkbox"] {
position: absolute;
top: 50%;
right: 0%;
transform: translate(-50%, -50%);
}
EDIT: the problem with using the htmlFor fix on the label is that the label is clickable and the checkbox is clickable but the space between them is not. I want the the whole component to be clickable

You don't need the onClick on your div.
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(() => {
console.log(selected);
}, [selected]);
return (
<>
<div className="tile" onClick={() => setSelected((curr) => !curr)}>
<label htmlFor={title}>{title}</label>
<input
id={title}
type="checkbox"
checked={!!selected}
onChange={(ev) => {}}
/>
</div>
</>
);
};
I made a code sandbox to test: https://codesandbox.io/s/optimistic-tharp-czlgp?file=/src/App.js:124-601

When you click on the checkbox, your click event is propagated and handled by both the div and the checkbox inside the div, which results in state being toggled twice and ultimately having the same value as before.
You need to remove one of the onClicks, depending on what you want to be clickable (either the whole div or just the checkbox with the label).
Clickable div:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(() => {
console.log(selected)
}, [selected])
return (
<>
<div className="tile" onClick={() => setSelected(curr => !curr)}>
<label>{title}</label>
<input
type="checkbox"
checked={!!selected}
/>
</div>
</>
);
};
Clickable checkbox and label:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(() => {
console.log(selected)
}, [selected])
return (
<>
<div className="tile">
<label htmlFor="title">{title}</label>
<input
id="title"
type="checkbox"
checked={!!selected}
onChange={() => setSelected(curr => !curr)}
/>
</div>
</>
);
};

Add htmlFor prop to the label and add id to the input matching that htmlFor value.
In your case Tile component would be:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(()=>{
console.log(selected)
},[selected])
return (
<>
<div className="tile" onClick={ev=>setSelected(curr=>!curr)}>
<label htmlFor={title}>{title}</label>
<input
id={title}
type="checkbox"
checked={!!selected}
onChange={ev=>{setSelected(curr=>!curr)}}
></input>
</div>
</>
);
};

Related

How to make some radio buttons disable under condition inside array in reactJs

I'm working on a E-commerce site where I need to show product sizes based on color. Where all available sizes will be clickable and other sizes will be disabled. Here is my code for SizeVariant component.
import React from "react";
import styled from "styled-components";
const SizeVariant = ({ text, ...props }) => {
return (
<StyledSizeVariant {...props}>
<input type="radio" name="text" id={text} value={text} />
<label htmlFor={text}>{text}</label>
</StyledSizeVariant>
);
};
const StyledSizeVariant = styled.div`
input[type="radio"] {
display: none;
&:checked + label {
box-shadow: 0px 0px 2px #7d7d31;
border: 2px solid #FFCD4E;
} //
}
label {
display: inline-block;
padding: 0 1rem;
height: 30px;
margin-right: 10px;
cursor: pointer;
font-weight: 600;
box-shadow: 0px 0px 1px rgba(0, 0, 0, 1);
text-align: center;
display: flex;
align-items: center;
font-size: 16px;
}
`;
export default SizeVariant;
I have a array of sizes.
<FlexBox alignItems="center" mb="16px">
{sizeList && sizeList.map((item) => (
<SizeVariant key={item.attribute_title} text={item.attribute_title} onClick={(e) => handlePrice(item)} />
))}
</FlexBox>
under some conditions, I want to disable some of the sizes(that is radio button) from sizeList array. How can I do that?
You can pass the condition inside input disable attribute.
Here is the code sample from your question:
<FlexBox alignItems="center" mb="16px">
{sizeList && sizeList.map((item) => (
<SizeVariant
key={item.attribute_title}
text={item.attribute_title}
onClick={(e) => handlePrice(item)}
disabled={item.attribute_disable} />
))}
</FlexBox>
// sizevariant
const SizeVariant = ({ text, disabled, ...props }) => {
return (
<StyledSizeVariant {...props}>
<input type="radio" name="text" id={text} value={text} disabled={disabled} />
<label htmlFor={text}>{text}</label>
</StyledSizeVariant>
);
}
In here you can see, I've passed the new props, named disabled, and used this in a SizeVariant Component.
You can logically disabled the input box,
<input type="radio" name="text" id={text} value={text} disabled={something ? true : false} />
In here something can be any condition from your array.
It depends on what information you have in the sizeList items:
If size list only gives you the available sizes then you need to create radio options for all possible sizes and only enable those that are present in the size list, something like
const allSizes = [
{ title: 'S', enabled: false },
{ title: 'M', enabled: false },
{ title: 'L', enabled: false },
]
const Component = () => {
const sizes = useMemo(() => {
return (sizeList || []).reduce((agg, item) => {
agg[item.attribute_title].enabled = true
return agg
}, allSizes)
}, [sizeList])
return (
<FlexBox alignItems="center" mb="16px">
{sizes.map((item) => (
<SizeVariant
key={item.title}
text={item.title}
disabled={item.disabled}
onClick={(e) => handlePrice(item)} // you might need to change this to match the new structure
/>
))}
</FlexBox>
)
}
const SizeVariant = ({ text, ...props }) => {
return (
<StyledSizeVariant {...props}>
<input type="radio" name="text" disabled={props.disabled} id={text} value={text} />
<label htmlFor={text}>{text}</label>
</StyledSizeVariant>
);
};
The sizeList contains all possible sizes with some property to tell if it is available or not. In this case you need to use that flag as the disabled props.
Hope this makes sense :)
Take a state and store the index no of the clicked radio button. Then compare the index no of other radio button with it if index doesn't match then you can disable those buttons.
import React from "react";
import styled from "styled-components";
const SizeVariant = ({ text, ...props }) => {
const [buttonIndex, setButtonIndex] = useState();
return (
<StyledSizeVariant {...props}>
<input onClick={()=>setButtonIndex(index)} disabled={buttonIndex && buttonIndex == indexOfYourArry?false:true} type="radio" name="text" id={text} value={text} />
<label htmlFor={text}>{text}</label>
</StyledSizeVariant>
);
};

How to pass out focus of input when clicked out?

I'm using next js for my application, and I am designing a search field. The suggestions start coming in once the user types something, but I would like to hide it when the input bar is not in focus.
When the user clicks on it again, I would like to show it back. The search suggestions contain routes, so I am not able use onFocus and onBlur as the element loses focus when I register a click and the route happens only when I release it.
I tried css too, but I'm not able to register the focus out, or is there a way?
Please help me out!!
Here is my sample code:
const [suggestionState,setSuggestionState] = useState(false);
<input type="input"
ref={inputRef}
autoFocus
className={styles['search-bar-input']}
onFocus={()=>{setSuggestionState(true)}}
onBlur={()=>{setSuggestionState(false)}}
placeholder="Search Bloggo"
onChange={(e)=>{
var trimmedQuery = e.target.value;
trimmedQuery = trimmedQuery.trim();
setSearchQuery(trimmedQuery);
getSuggestions(trimmedQuery)
}}
onKeyDown={(e)=>{handleKeypress(e)}}
/>
{
searchQuery.length == 0 || suggestionState == false? '':
<div className={styles['search-bar-suggestions']}>
<Link>... </Link>
</div>
}
You could do this with css :focus-within
.suggestions {
display: none;
}
form:focus-within .suggestions {
display: block;
}
input:focus~.suggestions {
display: block;
}
<form>
<input type="input" placeholder="Search Bloggo" value="">
<div class="suggestions">Suggestions...
<div>Suggestion 1</div>
<div>Suggestion 2</div>
<div>Suggestion 3</div>
<div>Suggestion 4</div>
</div>
</form>
Applying the above in react might looks something like this:
import "./styles.css";
import { useState, useEffect } from "react";
export default function App() {
const [searchQuery, setSearchQuery] = useState("");
const [results, setResults] = useState([]);
useEffect(() => {
if (!searchQuery) {
setResults([]);
return;
}
fetch(`https://rickandmortyapi.com/api/character/?name=${searchQuery}`)
.then((response) => response.json())
.then(({ results }) => setResults(results));
}, [searchQuery]);
return (
<form>
<input
value={searchQuery}
type="input"
autoFocus
placeholder="Search Bloggo"
onChange={(e) => {
setSearchQuery(e.target.value);
}}
/>
{!!results.length && (
<div className={`suggestions `}>
<h3>Suggestions</h3>
{results.map((result) => {
return (
<Link key={result.id} url={result.url}>
{result.name}
</Link>
);
})}
</div>
)}
</form>
);
}
const Link = ({ url, children }) => (
<div>
<a href={url}>{children}</a>
</div>
);

How to animate a sliding cart with react spring with a toggle button

I have almost got this workign but not quite sure what I am doing wrong. It will slide in when I click the toggle button, but it wont slide out when I click it again, it will just rerun the slide in animation.
Any help would be great
I have the following state and toggle function
const [close, setClose] = useState(false)
const toggleCart = () => {
setClose(!close)
}
following component
<CartItems close={close} location={location} />
import React, { useState } from "react"
import tw, { styled } from "twin.macro"
import { useTransition, animated } from "react-spring"
const CartWrapper = styled.div`
.test {
position: fixed;
top: 0px;
z-index: 5000;
right: 0;
height: 100vh;
background: lightgrey;
padding: 25px;
}
`
export function CartItems({ location, close }) {
const transitions = useTransition(close, null, {
enter: { transform: "translate3d(100%,0,0)" },
leave: { transform: "translate3d(0%,0,0)" },
})
return (
<>
<CartWrapper>
{transitions.map(({ props }) => {
return (
<animated.div className="test" style={props}>
<h2>Shopping Cart</h2>
{cart}
<p>Total: {formattedTotalPrice}</p>
<form onSubmit={handleSubmitCheckout}>
{/* include validation with required or other standard HTML validation rules */}
<input
name="name"
placeholder="Name:"
type="text"
onChange={e => setName(e.target.value)}
/>
<input
name="giftMessage"
placeholder="Gift Message:"
type="text"
onChange={e => setGiftMessage(e.target.value)}
/>
<input type="submit" />
</form>
<button onClick={clearCart}>Remove all items</button>
</animated.div>
)
})}
{/* <button onClick={handleSubmit}>Checkout</button> */}
</CartWrapper>
</>
)
}
In your example there is a second item during the transition, one entering, and one leaving. That's why you see always the entering animation.
If you use a boolean instead of array in the useTransition you have to insert a condition in the render method to prevent the second item. Just like the third example in the useTransition doc. https://www.react-spring.io/docs/hooks/use-transition
transitions.map(({ item, props, key }) => {
return (
item && <animated.div className="test" style={props} key={key}>
Now it basically works, but a slight modification in the useTransition is necessary.
const transitions = useTransition(close, null, {
from: { transform: "translate3d(100%,0,0)" },
enter: { transform: "translate3d(0%,0,0)" },
leave: { transform: "translate3d(100%,0,0)" }
});
I have a working example here: https://codesandbox.io/s/toggle-react-spring-transition-ju2jd

React - displaying images side by side

QUESTION
I have a few photos saved as png files that are in the same folder as the React component and are imported correctly as well.
How and what would be a good practice way to display all the images, let's say there are 4 images, in their proper box shown in the picture below and have them be displayed side by side, along with their name/price aligned below the image.
Similar to craigslist's gallery setting when looking at posts with images.
Ex:
<img src={Logo} alt=“website logo”/>
<img src={Mogo} alt=“website mogo”/>
<img src={Jogo} alt=“website jogo”/>
<img src={Gogo} alt=“website gogo”/>
Could I do something with products.map((product, index, image?))...?
CODE
const Product = props => {
const { product, children } = props;
return (
<div className="products">
{product.name} ${product.price}
{children}
</div>
);
};
function App() {
const [products] = useState([
{ name: "Superman Poster", price: 10 },
{ name: "Spider Poster", price: 20 },
{ name: "Bat Poster", price: 30 }
]);
const [cart, setCart] = useState([]);
const addToCart = index => {
setCart(cart.concat(products[index]));
};
const calculatePrice = () => {
return cart.reduce((price, product) => price + product.price, 0);
};
return (
<div className="App">
<h2>Shopping cart example using React Hooks</h2>
<hr />
{products.map((product, index) => (
<Product key={index} product={product}>
<button onClick={() => addToCart(index)}>Add to cart</button>
</Product>
))}
YOUR CART TOTAL: ${calculatePrice()}
{cart.map((product, index) => (
<Product key={index} product={product}>
{" "}
</Product>
))}
</div>
);
}
Wrap the list of products with a div (<div className="productsContainer">), and display it as a flex with wrap.
Set the width of the items (products) to 50% or less.
To render the image, render the <img> tag as one of the children, or add it directly to the product. Also change the data to include the src.
const { useState } = React;
const Product = ({ product, children }) => (
<div className="products">
{product.name} ${product.price}
{children}
</div>
);
function App() {
const [products] = useState([
{ name: "Superman Poster", price: 10, logo: 'https://picsum.photos/150/150?1' },
{ name: "Spider Poster", price: 20, logo: 'https://picsum.photos/150/150?2' },
{ name: "Bat Poster", price: 30, logo: 'https://picsum.photos/150/150?3' }
]);
const [cart, setCart] = useState([]);
const addToCart = index => {
setCart(cart.concat(products[index]));
};
const calculatePrice = () => {
return cart.reduce((price, product) => price + product.price, 0);
};
return (
<div className="App">
<h2>Shopping cart example using React Hooks</h2>
<hr />
<div className="productsContainer">
{products.map((product, index) => (
<Product key={index} product={product}>
<img src={product.logo} alt="website logo" />
<button onClick={() => addToCart(index)}>Add to cart</button>
</Product>
))}
</div>
YOUR CART TOTAL: ${calculatePrice()}
{cart.map((product, index) => (
<Product key={index} product={product}>
{" "}
</Product>
))}
</div>
);
}
ReactDOM.render(
<App />,
root
);
* {
box-sizing: border-box;
}
.productsContainer {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.products {
display: flex;
flex-direction: column;
align-items: center;
width: 45%;
margin: 0 0 1em 0;
padding: 1em;
border: 1px solid black;
}
.products img {
margin: 0.5em 0;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Best way to display those side by side you can display it by giving css classes flex-row for horizontal view and flex-column for vertical view in the main div component
const Product = props => {
const { product, children, image } = props;
return (
<div className="products">
{product.name} ${product.price} ${product.image}
{children}
</div>
);
};
products.map((product, index, image?))...?
Something along the lines of this?

Reactjs dropdown menu not displaying when hovered over the word

can someone tells me why the dropdown menu is not displaying in this demo? the dropdown menu should show when I hover over the word 'collective'?
https://codesandbox.io/s/funny-river-c76hu
For the app to work, you would have to type in the input box "collective", click analyse, then a progressbar will show, click on the blue line in the progressbar, an underline would show under the word "collective" then you should hover over "collective" word and a drop down menu should be displayed but the whole window disappears when I hover over the word "collective" instead of the drop down menu
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { Content, Dropdown, Label, Progress, Button, Box } from "rbx";
import "rbx/index.css";
function App() {
const [serverResponse, setServerResponse] = useState(null);
const [text, setText] = useState([]);
const [loading, setLoading] = useState(false);
const [modifiedText, setModifiedText] = useState(null);
const [selectedSentiment, setSentiment] = useState(null);
const [dropdownContent, setDropdownContent] = useState([]);
const [isCorrected, setIsCorrected] = useState(false);
const [displayDrop, setDisplayDrop] = useState(false);
useEffect(() => {
if (serverResponse && selectedSentiment) {
const newText = Object.entries(serverResponse[selectedSentiment]).map(
([word, recommendations]) => {
const parts = text[0].split(word);
const newText = [];
parts.forEach((part, index) => {
newText.push(part);
if (index !== parts.length - 1) {
newText.push(
<u
className="dropbtn"
data-replaces={word}
onMouseOver={() => {
setDropdownContent(recommendations);
setDisplayDrop(true);
}}
>
{word}
</u>
);
}
});
return newText;
}
);
setModifiedText(newText.flat());
}
}, [serverResponse, text, selectedSentiment]);
const handleAnalysis = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
setServerResponse({ joy: { collective: ["inner", "constant"] } });
}, 1500);
};
const handleTextChange = event => {
setText([event.target.innerText]);
};
const replaceText = wordToReplaceWith => {
const replacedWord = Object.entries(serverResponse[selectedSentiment]).find(
([word, recommendations]) => recommendations.includes(wordToReplaceWith)
)[0];
setText([
text[0].replace(new RegExp(replacedWord, "g"), wordToReplaceWith)
]);
setModifiedText(null);
setServerResponse(null);
setIsCorrected(true);
setDropdownContent([]);
};
const hasResponse = serverResponse !== null;
return (
<Box>
<Content>
<div
onInput={handleTextChange}
contentEditable={!hasResponse}
style={{ border: "1px solid red" }}
>
{hasResponse && modifiedText
? modifiedText.map((text, index) => <span key={index}>{text}</span>)
: isCorrected
? text[0]
: ""}
</div>
<br />
{displayDrop ? (
<div
id="myDropdown"
class="dropdown-content"
onClick={() => setDisplayDrop(false)}
>
dropdownContent.map((content, index) => (
<>
<strong onClick={() => replaceText(content)} key={index}>
{content}
</strong>{" "}
</>
))
</div>
) : null}
<br />
<Button
color="primary"
onClick={handleAnalysis}
disabled={loading || text.length === 0}
>
analyze
</Button>
<hr />
{hasResponse && (
<Label>
Joy{" "}
<Progress
value={Math.random() * 100}
color="info"
onClick={() => setSentiment("joy")}
/>
</Label>
)}
</Content>
</Box>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
css file
.App {
font-family: sans-serif;
text-align: center;
}
.highlight {
background: red;
text-decoration: underline;
}
.dropbtn {
color: white;
font-size: 16px;
border: none;
cursor: pointer;
}
.dropbtn:hover,
.dropbtn:focus {
background-color: #2980b9;
}
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
position: relative;
background-color: #f1f1f1;
min-width: 160px;
overflow: auto;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
}
.show {
display: block;
}
The problem is this:
{displayDrop ? (
<div
id="myDropdown"
class="dropdown-content"
onClick={() => setDisplayDrop(false)}
>
dropdownContent.map((content, index) => (
<>
<strong onClick={() => replaceText(content)} key={index}>
{content}
</strong>{" "}
</>
))
</div>
) : null}
You are missing a pair of curly brackets around dropdownContent. It should be:
{displayDrop ? (
<div
id="myDropdown"
class="dropdown-content"
onClick={() => setDisplayDrop(false)}
>
{dropdownContent.map((content, index) => (
<>
<strong onClick={() => replaceText(content)} key={index}>
{content}
</strong>{" "}
</>
))}
</div>
) : null}
A working sandbox here https://codesandbox.io/embed/fast-feather-lvpk7 which is now displaying this content.

Categories

Resources