I've created a simple star rating component to make users able to review my books.
Here's the component:
import React, { useState } from 'react'
import { FaStar } from 'react-icons/fa'
const StarRating = (props) => {
const [rating, setRating] = useState(null);
return (
<Wrapper>
{[...Array(5)].map((star, i) => {
const ratingValue = i + 0;
return (
<label>
<input
type="radio"
name="rating"
onClick={() => setRating(props.ratingValue)}
/>
<FaStar color={ratingValue < rating ? "#01af93" : "#bbb"} />
</label>
)
})}
</Wrapper>
)
}
export default StarRating
So, if somebody clicks on the Stars the rating will appear (using an onClick handler).
I would like to display the ratings without the onClick handler now.
I've tried simply to add value={props.ratingValue} instead of onClick={() => setRating(props.ratingValue)} but it doesn't work.
Hope someone can help with what I'm doing wrong.
You have to move onClick handler and value to the parent container class. So changing state and keeping current input value must be done in your parent container. Below I share a code snippet for your sample.
import React, { useState } from "react";
import { FaStar } from "react-icons/fa";
const StarRating = (props) => {
console.log(props);
return (
<div>
{Array(5)
.fill(0)
.map((_, idx) => (
<label key={idx}>
<input
type="radio"
name="rating"
onChange={() => props.setRating(idx)}
value={props.ratingValue}
checked={idx === props.ratingValue}
/>
<FaStar color={idx < 3 ? "#01af93" : "#bbb"} />
</label>
))}
</div>
);
};
export const RatingContainer = () => {
const [rate, setRate] = useState(3);
return (
<div>
<StarRating setRating={(val) => setRate(val)} ratingValue={rate} />
</div>
);
};
Related
I want to add to Chip an startIcon={<Icon />}
when click on a Chip.
The state of the icon is managed by chipsState.
In this code,
the state of all chips would change.
How can I change only the chipsState of the element that is clicked?
In this code, the state of all chips will change.
How can I change only the chipsState of the element that is clicked?
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
const [chipsState, setChipsState] = useState(false);
const onChipClick = (element:any) => {
setChipsState(chipsState => !chipsState);
}
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<Chip onClick={() => onChipClick(element)} startIcon={chipsState && <Icon />}>{element.description}</Chip>
</div>
))}
</div>
);
}
export default Modal;
To handle local state (and better testing), you should create a new custom Chip component with dedicated chipState.
interface CustomChipProps {
description: string
}
const CustomChip = (props: CustomChipProps) => {
const [chipState, setChipState] = useState(false);
return <Chip onClick={() => setChipState(prev => !prev)} startIcon={chipState && <Icon />}>{props.description}</Chip>;
}
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<CustomChip description={element.description} />
</div>
))}
</div>
);
}
export default Modal;
You can achieve your desired output by changing chipState state from boolean to object.
So first let's change to object state instead of boolean
const [chipsState, setChipsState] = useState({});
Now we will change onChipClick function to change value of selected chip state
const onChipClick = (element:any) => {
setChipsState({...chipsState, chipsState[element]: !chipsState[element]});
}
And finally we will read correct value of each chipsState element.
<Chip onClick={() => onChipClick(element)} startIcon={chipsState[element] && <Icon />}>{element.description}</Chip>
You can try like the following
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import { Grid, Row } from "react-flexbox-grid";
const ChipSet = ({ symtomsData }) => {
const data = symtomsData.map((symtom) => ({ ...symtom, isSelcted: false }));
const [chipSets, setChipSets] = useState(data);
const onSelectChipSet = useCallback(
(e, index) => {
const updatedChipSets = chipSets.map((chip, i) =>
i === index ? { ...chip, isSelcted: e.target.checked } : chip
);
setChipSets(updatedChipSets);
},
[chipSets]
);
console.log("chipSets", chipSets);
return (
<div>
<h1>Symtoms Data</h1>
{chipSets.map((x, i) => (
<div key={i}>
<label>
<input
onChange={(e) => onSelectChipSet(e, i)}
type="checkbox"
value={x.isSelcted}
/>
{x.description}
</label>
</div>
))}
</div>
);
};
class App extends React.Component {
render() {
const symtomsData = [
{
description: "mild"
},
{
description: "cold"
}
];
return (
<Grid>
<Row>
<ChipSet symtomsData={symtomsData} />
</Row>
</Grid>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
I have two components, the parent and child. Currently I have these codes below. But unfortunately it returns an error:
TypeError: Cannot read property 'click' of null
For some reasons I want when button is click the Item component also will be click. But these codes below produces an error above. Anyone does know how to achieve it?
import React, { useRef } from 'react';
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
{dynamicBoolean ? (
<button onClick={() => itemRef.current.click()}>
click item
</button>
) : (
//more codes here
<Item ref={itemRef} />
)}
</div>
);
};
export default App;
Child component would look like below (demonstration purposes, the code is very lengthly)
import React from 'react';
const Item = (props) => {
return (
<div>
//some design here
</div>
);
};
export default Item;
You need useRef and you have to forward this ref to the Item component.
import React, { forwardRef, useRef } from 'react';
const Item = forwardRef((props, ref) => {
return <li {...props}
onClick={() => alert('clicked on Item')}
ref={ref} >MyItem</li>
})
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
<button onClick={() => itemRef.current.click()}>
click item
</button>
<Item ref={itemRef} />
</div>
);
};
export default App;
import React, { createRef } from "react";
const Hello = (props) => {
const itemRef = createRef();
const hello = () => {
itemRef.current.click();
};
return (
<div>
<button onClick={() => hello()}>click item</button>
<Item ref={itemRef} />
</div>
);
};
const Item = React.forwardRef((props, ref) => {
const myClick = () => {
console.log("this is clicked");
};
return (
<button ref={ref} className="FancyButton" onClick={myClick}>
{props.children}
</button>
);
});
export default Hello;
I'm trying to edit an input value in a child component and send to the parent
:
https://codesandbox.io/s/sleepy-rain-skoss?file=/src/Editlabel.js:0-389
Parent:
import "./styles.css";
import EditLabel from "./Editlabel";
import { useEffect, useState } from "react";
export default function App() {
const [newName, setNewName] = useState();
useEffect(() => {
console.log("newName", newName);
}, [newName]);
return (
<div className="App">
<EditLabel
value={"hello"}
click={(changedName) => {
setNewName(changedName);
}}
/>
</div>
);
}
Child:
import React, { useState } from "react";
const EditLabel = ({ value, click }) => {
const [name, setName] = useState(value);
return (
<>
<input type={"text"} placeholder={name}></input>
<button
onClick={(e) => {
setName(e.target.value);
click(name);
}}
>
Edit
</button>
</>
);
};
export default EditLabel;
However, the console logs "hello" and then it just logs empty strings.
How can I make it work?
try this on your child's input box
<input type={"text"} placeholder={name} onChange={(e) => setName(e.target.value)}>
Change EditLabel to use a ref to capture the input value:
const EditLabel = ({ value, click }) => {
const inputRef = useRef(null);
return (
<>
<input ref={inputRef} type={"text"} placeholder={value}></input>
<button
onClick={() => {
click(inputRef.current.value);
}}
>
Edit
</button>
</>
);
};
Update App to use the values it gets via the click callback:
export default function App() {
const [newName, setNewName] = useState("hello");
useEffect(() => {
console.log("newName", newName);
}, [newName]);
return (
<div className="App">
<EditLabel
value={newName}
click={(changedName) => {
setNewName(changedName);
}}
/>
</div>
);
}
I'm building a React tab navigation component with emotion. I'm having trouble finding a solution that would allow me to:
Initially hide all content except for the buttons and not style the buttons.
When you click on a button activate the style and show the content associated with that button.
And finally when you click outside or the input is empty reset to initial state.
Here is the code:
Code
import React, { useState } from "react";
import ReactDOM from "react-dom";
import styled from "#emotion/styled";
import "./styles.css";
const StyledShowButton = styled("button", {
shouldForwardProp: (prop) => ["active"].indexOf(prop) === -1
})`
color: ${({ active }) => (active ? "red" : "black")};
`;
function App() {
const [active, setActive] = useState(0);
const [showInput, setShowInput] = useState(false);
const handleInputChange = (e) => {
if (e.target.value < 1) {
console.log("Reset Everyting");
}
};
const handleTabClick = (e) => {
const index = parseInt(e.target.id, 0);
if (index !== active) {
setActive(index);
}
if (!showInput) {
setShowInput(!showInput);
}
};
return (
<div className="App">
<StyledShowButton
type="button"
id={0}
active={active === 0}
onClick={handleTabClick}
>
First
</StyledShowButton>
<StyledShowButton
type="button"
id={1}
active={active === 1}
onClick={handleTabClick}
>
Second
</StyledShowButton>
{/* CONTENT */}
{active === 0 ? (
<input placeholder="First input" onChange={handleInputChange} />
) : (
<input placeholder="Second input" onChange={handleInputChange} />
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Just ask if I didn't make my self clear enough,
Thanks beforehand!
Erik
You can hide inpts in this way at first by assigning a null value to the active state.
You can also initialize values from 1 so that id and state state are not confused.
I made the arrangements.
You can review the code below.
You can also view it from this link. Code:
function App() {
const [active, setActive] = useState(null);
const [showInput, setShowInput] = useState(false);
const handleInputChange = (e) => {
if (e.target.value < 1) {
setActive(null);
}
};
const handleTabClick = (e) => {
const index = parseInt(e.target.id, 0);
if (index !== active) {
setActive(index);
}
if (!showInput) {
setShowInput(!showInput);
}
};
return (
<div className="App">
<StyledShowButton
type="button"
id={1}
active={active === 1}
onClick={handleTabClick}
>
First
</StyledShowButton>
<StyledShowButton
type="button"
id={2}
active={active === 2}
onClick={handleTabClick}
>
Second
</StyledShowButton>
{/* CONTENT */}
{active &&
(active === 1 ? (
<>
<input placeholder="First input" onChange={handleInputChange} />
</>
) : (
<input placeholder="Second input" onChange={handleInputChange} />
))}
</div>
);
}
In react, is it possible to manage the state of multiple checkboxes with id attribute or other attributes such as data-*?
For the moment all I'm using is the name attribute, however in my project I need to use the id or preferably data-* due to the complexity of the project, but it seems that in react it's isn't possible.
Or am I not understanding in?
import React, { useState } from 'react';
import someData from './someData'
function Checkbox({ name, id, label, checked, onChange }) {
return (
<span>
<input
type="checkbox"
name={name}
id={id}
checked={checked}
onChange={onChange}
/>
<span>{label}</span>
</span>
);
}
function App() {
const [isChecked, setIsChecked] = useState()
const onCheckboxChange = event => {
const target = event.currentTarget;
const name = target.name
const id = target.id;
const checked = target.checked;
setIsChecked({
...isChecked,
[id]: checked // using "id" seems to not work here.
})
}
return (
someData.map(item => {
return (
<Checkbox
name={item.name}
id={item.id}
label={item.name}
onChange={onCheckboxChange}
checked={isChecked}
/>
);
}
);
}
There's no problem to do what you need in React whatsoever.
You may use dataset API to access data-* attributes values to update your state on checkbox change (e.g. if you assign data-chkboxname attribute to your checkbox):
onCheckboxChange = ({target:{checked, dataset:{chkboxname}}}) => {
setIsChecked({
...isChecked,
[chkboxname]: checked
})
}
Following is a quick proof-of-a-concept live-demo:
const { useState } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const App = () => {
const [isChecked, setIsChecked] = useState({}),
onCheckboxChange = ({target:{checked, dataset:{chkboxname}}}) => {
setIsChecked({
...isChecked,
[chkboxname]: checked
})
},
onFormSubmit = e => {
e.preventDefault()
console.log(isChecked)
}
return (
<form onSubmit={onFormSubmit}>
{
['chckbox1', 'chckbox2', 'chckbox3'].map(checkbox => (
<label key={checkbox}>
{checkbox}
<input
type="checkbox"
data-chkboxname={checkbox}
onChange={onCheckboxChange}
checked={isChecked[checkbox]}
/>
</label>
))
}
<input type="submit" value="submit" />
</form>
)
}
render (
<App />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Try like this
return someData.map((item) => {
return (
<Checkbox
name={item.name}
id={item.id}
label={item.name}
checked={item.checked}
/>
);
});