Fairly new with react. I'm creating a dropdown button for a Gatsby project. The button toggle works, but I'm having trouble getting the selected value to the parent where I need it.
-Tried lifting the state up, but this resulted in the button not appearing at all. I was a bit confused here so maybe I was doing something wrong.
-Also tried using refs although I wasn't sure if this was the right use case, it worked, however it seems the value is grabbed before it's updated in the child component and I'm not sure how to change or work around this. (the code is currently set up for this)
Are either of these options right? or could anybody steer me in the right direction, thanks.
Dropdown in parent:
this.dropdownRef1 = React.createRef();
componentDidUpdate(){
console.log("Color Option:" + this.dropdownRef1.current.state.ColorOption)
}
<DropdownBtn ref={this.dropdownRef1} mainText="Color" options={this.props.pageContext.colors || ['']} />
DropdownBtn:
export default class refineBtn extends React.Component {
constructor(props) {
super(props);
}
state = {
open: false,
[this.props.mainText + "Option"]: "all",
};
dropdownBtnToggle = () => {
this.setState((prevState)=> {
return{open: !prevState.open};
});
};
optionClickHandler = (option) => {
this.setState(() => {
console.log(this.props.mainText + " updated to " + option)
return {[this.props.mainText + "Option"] : option}
});
};
render(){
const options = this.props.options
console.log("open: " + this.state.open)
return(
<div>
<button onClick={this.dropdownBtnToggle} >
{this.props.mainText}:
</button>
<div className={this.state.open ? 'option open' : "option"} >
<p key={"all"} onClick={() => this.optionClickHandler("all")}> all</p>
{options.map(option => (
<p key={option} onClick={() => this.optionClickHandler(option)}>{option}</p>
))}
</div>
</div>
);
}
}
You can respond to selection by allowing your component to accept a callback.
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {open: false, value: ''}
}
render() {
return (
<div>
<div onClick={() => this.setState({open: true})}>{this.state.value}</div>
<div style={{display: this.state.open ? 'block' : 'none'}}>
{this.props.options.map((option) => {
const handleClick = () => {
this.setState({open: false, value: option})
this.props.onChange(option)
}
return (
<div key={option} onClick={handleClick} className={this.state.value === option ? 'active' : undefined}>{option}</div>
)
})}
</div>
</div>
)
}
}
<MyComponent onChange={console.log} options={...}/>
Related
I would like to explain my problem of the day.
the code works correctly ,
the only problem is
when i click on my navbar to select a tab ,
my activeclassname works fine except that when i click elsewhere on the same page i lose my activeclassname
I would like him to be active permanently
Do you have an idea of how to fix this?
class MainHome extends Component {
constructor(props) {
super(props);
this.state = {
};
// preserve the initial state in a new object
this.toggle = this.toggle.bind(this);
this.cgtChange1= this.cgtChange1.bind(this);
this.cgtChange2= this.cgtChange2.bind(this);
}
cgtChange1() {
console.log('bière clicked')
this.setState({
cgt : 1
})
}
cgtChange2() {
console.log('cocktails clicked')
this.setState({
cgt :2
})
}
render() {
let addedItems = this.props.items.length ?
(
this.props.items.filter(item => item.ctg === this.state.cgt).map(item => {
return (
<div key={item.id}>
{item.title}
</div>
)
})) :
(
<div></div>
)
return (
<div>
<Header text='Carte'/>
<NavBar
onClick1={this.cgtChange1}
onClick2={this.cgtChange2}
/>
{addedItems}
</div>
)
}
}
NavBar
export default class FocusOnSelect extends Component {
render() {
return (
<div>
<button onClick={this.props.onClick1}
activeClasseName="selected" className='inactive' > Bières
</button>
</div>
<div >
<button onClick={this.props.onClick2} activeClasseName="selected" className='inactive' > Cocktails </button>
</div>
</div>
);
}
}
I have a react component in which user can upload Image and he's also shown the preview of uploaded image. He can delete the image by clicking delete button corresponding to Image. I am using react-dropzone for it. Here's the code:
class UploadImage extends React.Component {
constructor(props) {
super(props);
this.onDrop = this.onDrop.bind(this);
this.deleteImage = this.deleteImage.bind(this);
this.state = {
filesToBeSent: [],
filesPreview: [],
printCount: 10,
};
}
onDrop(acceptedFiles, rejectedFiles) {
const filesToBeSent = this.state.filesToBeSent;
if (filesToBeSent.length < this.state.printCount) {
this.setState(prevState => ({
filesToBeSent: prevState.filesToBeSent.concat([{acceptedFiles}])
}));
console.log(filesToBeSent.length);
for (var i in filesToBeSent) {
console.log(filesToBeSent[i]);
this.setState(prevState => ({
filesPreview: prevState.filesPreview.concat([
<div>
<img src={filesToBeSent[i][0]}/>
<Button variant="fab" aria-label="Delete" onClick={(e) => this.deleteImage(e,i)}>
<DeleteIcon/>
</Button>
</div>
])
}));
}
} else {
alert("you have reached the limit of printing at a time")
}
}
deleteImage(e, id) {
console.log(id);
e.preventDefault();
this.setState({filesToBeSent: this.state.filesToBeSent.filter(function(fid) {
return fid !== id
})});
}
render() {
return(
<div>
<Dropzone onDrop={(files) => this.onDrop(files)}>
<div>
Upload your Property Images Here
</div>
</Dropzone>
{this.state.filesToBeSent.length > 0 ? (
<div>
<h2>
Uploading{this.state.filesToBeSent.length} files...
</h2>
</div>
) : null}
<div>
Files to be printed are: {this.state.filesPreview}
</div>
</div>
)
}
}
export default UploadImage;
My Question is my component is not re-rendering even after adding or removing an Image. Also, I've taken care of not mutating state arrays directly. Somebody, please help.
Try like this, I have used ES6
.
I am new to react and have started following through the book learning react by Alex Banks and Eve Porcello. I have got to a section that creates a star rating system but at the moment I cant get it working can anyone tell me why? As far as I can tell I have copied the code correctly and everything looks like it should work as intended. Here is the code I have:
const Star = ({ selected=false, onClick=f=>f }) =>
<div className={ (selected) ? "star selected" : "star" } onClick={onClick}>
</div>
Star.propTypes = {
selected: PropTypes.bool,
onClick: PropTypes.func
}
class StarRating extends React.Component {
constructor(props) {
super(props)
this.state = { starsSelected: 0 }
this.change = this.change.bind(this)
}
change(starsSelected) {
this.setState({ starsSelected: starsSelected })
}
render() {
const { totalStars } = this.props
const { starsSelected } = this.state
return(
<div className="star-rating">
{[...Array(totalStars)].map((n,i) =>
<Star
key={i}
selected={i<starsSelected}
onClick={() => this.change(i+1)}
/>
)}
<p>{starsSelected} of {totalStars} stars</p>
</div>
)
}
}
StarRating.propTypes = {
totalStars: PropTypes.number
}
StarRating.defaultProps = {
totalStars: 5
}
When inspecting the components in react-dev-tools it seems that the stars selected prop is always set to false and I cant figure out why. Thanks for any help anyone can offer
It can be 2 things:
this line: {[...Array(totalStars)].map((n,i) =>
You need to pass totalStars as a Number and not a string:
<StarRating totalStars={5} />
or
you are not display anything in your Star component. Try this:
const Star = ({ selected = false, onClick = f => f }) => (
<div className={selected ? "star selected" : "star"} onClick={onClick}>
Star
</div>
);
See a working example:
https://codesandbox.io/s/52ox6wmwwk
The code shared is perfectly fine. Just render something from this code on which the click event would be fired.
const Star = ({ selected=false, onClick=f=>f }) =>
<div className={ (selected) ? "star selected" : "star" } onClick={onClick}>
// Something to render maybe?
</div>
Inject : npm i star-based-rating directly.
For Reference on this react package: star-based-rating
import { FaStar } from "react-icons/fa";
const CreateArray = (length) => [...Array(length)]; // This will create Array with the length provided.
// Star Component
function Star({ selected = false, onSelect }) {
return (
<FaStar color={selected ? "orange" : "lightgray"} onClick={onSelect} />
);
}
// Star Rating component
function StarRating({ totalStars = 5 }) {
const [selectedStars, setSelectedStars] = useState(0);
return (
<>
{CreateArray(totalStars).map((n, i) => (
<Star
key={i}
selected={selectedStars > i}
onSelect={() => setSelectedStars(i + 1)}
/>
))}
<p>
{selectedStars} of {totalStars} // To display selected stars out of total stars
</p>
</>
);
}
function App() {
return <StarRating totalStars={5} />; // This will display 5 stars
}
Working example - Star Rating - CodeSandbox
Your code is working absolutely fine. Check out this link : https://codesandbox.io/s/01wqmzrpjl
Make sure while declaring css classes star is above selected else it will not show the selected style fields if it has same style-field in star className.
Example in my link if I declare selected class above star class then red background (selected style property) is override by grey background (star style property).
I have an location app which can save name of locations.
I am trying to get each saved location a red border by clicking on it.
What it does is changing the border color of all the categories.
How can I apply that?
class Categories extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
categories: [],
selectedCategories: [],
hidden: true,
checkboxState: true
};
}
toggle(e) {
this.setState({
checkboxState: !this.state.checkboxState
})
}
onChange = (event) => {
this.setState({ term: event.target.value });
}
addCategory = (event) => {
if (this.state.term === '') {
alert('Please name your category!')
} else {
event.preventDefault();
this.setState({
term: '',
categories: [...this.state.categories, this.state.term]
});
}
}
render() {
return (
<div className="categories">
<h1>Categories</h1>
<div className='actions'>
<button className="delete" onClick={this.deleteCategory}>Delete</button>
<button className="edit" onClick={this.editCategory}>Edit</button>
</div>
<p>To add new category, please enter category name</p>
<form className="App" onSubmit={this.addCategory}>
<input value={this.state.term} onChange={this.onChange} />
<button>Add</button>
</form>
{this.state.categories.map((category, index) =>
<button
key={index}
style={this.state.checkboxState ? { borderColor: '' } : { borderColor: 'red' }}
checked={this.state.isChecked}
onClick={this.toggle.bind(this)}>
{category}</button>
)}
</div >
);
}
}
I want to be able to control each selected category seperatly, to be able to delete and edit theme as well.
You can set the state based on index and retrieve the similar way,
Code:
{this.state.categories.map((category, index) =>
<button
key={index}
id={`checkboxState${index}`}
style={!this.state[`checkboxState${index}`] ?
{ borderColor: '' } : { border: '2px solid red' }}
checked={this.state.isChecked}
onClick={this.toggle}>
{category}</button>
)}
You can see how I am checking the state dynamically this.state[`checkboxState${index}`] and also I have assigned an id to it.
In toggle method:
toggle = (e) => {
const id = e.target.id;
this.setState({
[id]: !this.state[id]
})
}
FYI, this is a working code, you can see it
https://codesandbox.io/s/vy3r73jkrl
Let me know if this helps you :)
Here's a really bad example using react. I'd more than likely use this.props.children instead of just cramming them in there. This would allow it to be more dynamic. And instead of using state names we could then just use indexes. But you'll observe, that the parent container decides which child is red by passing a method to each child. On click, the child fires the method from the parent. How you implement it can vary in a million different ways, but the overall idea should work.
class ChildContainer extends React.Component
{
constructor(props)
{
super(props);
}
render() {
let color = this.props.backgroundColor;
return(
<section
className={'child'}
style={{backgroundColor: color}}
onClick={this.props.selectMe}
>
</section>
)
}
}
class Parent extends React.Component
{
constructor(props)
{
super(props)
this.state = {
first : 'Pink',
second : 'Pink',
third : 'Pink',
previous: null
}
this.updateChild = this.updateChild.bind(this);
}
updateChild(name)
{
let {state} = this;
let previous = state.previous;
if(previous)
{
state[previous] = 'Pink';
}
state[name] = 'Red';
state.previous = name;
this.setState(state);
}
render()
{
console.log(this)
return(
<section id={'parent'}>
<ChildContainer
selectMe={() => this.updateChild('first')}
backgroundColor = {this.state.first}
/>
<ChildContainer
selectMe={() => this.updateChild('second')}
backgroundColor = {this.state.second}
/>
<ChildContainer
selectMe={() => this.updateChild('third')}
backgroundColor = {this.state.third}
/>
</section>
)
}
}
class App extends React.Component
{
constructor(props)
{
super(props)
}
render()
{
return(
<section>
<Parent/>
</section>
)
}
}
React.render(<App />, document.getElementById('root'));
You need to track the state of every checkbox, possibly have an array with all currently checked checkboxes.
Then instead of this.state.checkboxState in this.state.checkboxState ? { borderColor: '' } : { borderColor: 'red' } you need to check if current category is in the currently checked categories array.
Hope this helps
I have an accordion in React formed of a row component which is looped inside a body parent component. In the row I'm toggling the state showDetails to show/hide the details for each row, effectively opening the accordion item. But, since the state is for each row, how do I close one accordion item when I open another one?
Body:
export default class Body extends React.Component {
render() {
const {modelProps, showInfo, linkedRow} = this.props;
return (
<div className="c-table__body">
{this.props.model.map(
(subModel, i) =>
linkedRow ?
<LinkedRow
key={`${i}`}
model={subModel}
modelProps={modelProps}
/>
:
<Row
key={`${i}_${subModel.username}`}
model={subModel}
modelProps={modelProps}
showInfo={showInfo}
handleStatusChanged={this.props.handleStatusChanged}
/>
)}
</div>
);
}
}
Row:
class Row extends React.Component {
constructor(props) {
super(props);
this.state = {
userId: '',
showDetails: false,
showModal: false,
status: '',
value: '',
showInfo: false
};
render() {
const { model, modelProps, showInfo } = this.props;
return (
<div className="c-table__row">
<div className="c-table__row-wrapper">
{modelProps.map((p, i) => (
<div className={`c-table__item ${this.isStatusCell(model[p]) ? model[p] : p}`} key={i}>{this.isStatusCell(model[p]) ? this.toTitleCase(model[p]) : model[p]}</div>
))}
{showInfo ? (
<div className="c-table__item c-table__item-sm">
<a
name="view-user"
onClick={this.showDetailsPanel}
className={this.state.showDetails ? 'info showing' : 'info'}
>
<Icon yicon="Expand_Cross_30_by_30" />
</a>
</div>
) : (
''
)}
</div>
{this.state.showDetails ? (<ConnectedDetails user={model} statusToggle={this.handleStatusChange}/>) : null}
</div>
);
}
}
export default Row;
Not really sure how to approach this, maybe something in the body that check is there's any row open according to the showDetails state in the rows?
Thanks in advance
The approach is to lift the state of which <Row /> is open to the <Body /> component.
Also the method that switch between opened <Row /> is on the <Body /> component.
toggleOpen = (idx) => {
this.setState({ openRowIndex: idx });
}
then when you rendered your <Row />s you can pass a prop isOpen:
<Row
key={`${i}_${subModel.username}`}
model={subModel}
modelProps={modelProps}
showInfo={showInfo}
handleStatusChanged={this.props.handleStatusChanged}
isOpen={this.state.openRowIndex === i}
onToggle={_ => this.toggleOpen(i)}
/>