How do I limit the user to only selecting one component? - reactjs

I have the following code that simply constructs blocks for our products and the selected state allows the component to be selected and unselected. How can I figure out which of these components are selected and limit the user to only selecting one at a time. This is ReactJS code
import React from 'react';
export default class singleTile extends React.Component{
constructor(props){
super(props);
this.title = this.props.title;
this.desc = this.props.desc;
this.svg = this.props.svg;
this.id = this.props.id;
this.state = {
selected: false
}
}
selectIndustry = (event) => {
console.log(event.currentTarget.id);
if(this.state.selected === false){
this.setState({
selected:true
})
}
else{
this.setState({
selected:false
})
}
}
render(){
return(
<div id={this.id} onClick={this.selectIndustry}className={this.state.selected ? 'activated': ''}>
<div className="icon-container" >
<div>
{/*?xml version="1.0" encoding="UTF-8"?*/}
{ this.props.svg }
</div>
</div>
<div className="text-container">
<h2>{this.title}</h2>
<span>{this.desc}</span>
</div>
</div>
)
}
}

You need to manage the state of the SingleTile components in the parent component. What i would do is pass two props to the SingleTile components. A onClick prop which accepts a function and a isSelected prop that accepts a boolean. Your parent component would look something like this.
IndustrySelector.js
import React from 'react';
const tileData = [{ id: 1, title: 'foo' }, { id: 2, title: 'bar' }];
class IndustrySelector extends Component {
constructor(props) {
super(props);
this.state = { selectedIndustry: null };
}
selectIndustry(id) {
this.setState({ selectedIndustry: id });
}
isIndustrySelected(id) {
return id === this.state.selectedIndustry;
}
render() {
return (
<div>
{tileData.map((data, key) => (
<SingleTile
key={key}
{...data}
onClick={() => this.selectIndustry(data.id)}
isSelected={this.isIndustrySelected(data.id)}
/>
))}
</div>
);
}
}
The way this works is as follows.
1. Triggering the onClick handler
When a user clicks on an element in SingleTile which triggers the function from the onClick prop, this.selectIndustry in the parent component will be called with the id from the SingleTile component.
Please note that in this example, the id is remembered through a
closure. You could also pass the id as an argument to the function of
the onClick prop.
2. Setting the state in the parent component
When this.selectIndustry is called it changes the selectedIndustry key of the parent component state.
3. Updating the isSelected values form the SIngleTile components
React will automatically re-render the SingleTile components when the state of the parent component changes. By calling this.isIndustrySelected with the id of the SingleTile component, we compare the id with the id that we have stored in the state. This will thus only be equal for the SingleTile that has been clicked for the last time.

Can you post your parent component code?
It's not so important, but you can save some time by using this ES6 feature:
constructor(props){
super(props);
const {title, desc, svg, id, state} = this.props;
this.state = {
selected: false
}
}

Related

State Variable assigned to props data model undefined inside setState React

I have passed a data model as props from Parent Component (MinorStructures) to Child component (PhotoGallery).
The parent Component looks like the following:
export default class MinorStructures extends Component {
constructor(props)
{
super(props);
// This is a super data model class, its main function is to collect
// data from all the children components.
this.state = {
MinorStructures: {
layer: 3,
layerName: 'MinorStructures',
layerId: -1,
agencyId: -1, //to be determined later
galleryModel:{
selectedFile: null
}
}
};
this.panes = [
{
menuItem: 'Photo Gallery', render: () =>
<Tab.Pane>
<PhotoGallery triggerNeedSave={this.props.triggerNeedSave}
disabled={this.props.disabled}
loggedIn = {this.props.loggedin}
minorModel={this.state.MinorStructures}/>
</Tab.Pane>
},
];
}
}
I have removed few codes from the parent class which is not necessary for this problem.
The Child Component is like the following:
export default class PhotoGallery extends Component{
constructor(props) {
super(props)
const {minorModel} = this.props
this.state={
cameraOpen: false,
photoModel: minorModel.galleryModel
}
console.log("Constructor State ", this.state)
}
handleChange = e =>{
this.props.triggerNeedSave();
this.setState({[photoModel.selectedFile]:e.target.files[0]})
console.log(this.state)
}
render() {
const uploadClick = e => {
hiddenFileInput.current.click();
};
return (
<div>
{!this.state.cameraOpen && <Button size='tiny' onClick={uploadClick}
color='brown'
disabled ={this.props.disabled}>Upload Photos from Device</Button>}
<input id="photo" name="selectedFile" type="file"
onChange={this.handleChange}
ref={hiddenFileInput} style={{display:'none'}} />
<Button size='tiny' onClick={checkModel} color='brown'
disabled ={this.props.disabled}>
Click To Check
</Button>
</div>
);
};
return (
<div id="root">
<Gallery />
</div>
)}
}
In the state of PhotoGallery class I have a photoModel that takes the data model from MinorStructures as props. When I select a picture and do setState in the handleChange method of PhotoGallery class it says photoModel is not defined. But I have defined that variable in the state which stores data model passed as props from MinorStructures.
You are using dynamic keys when changing state with [] to access properties on this.state
this.setState({[photoModel.selectedFile]:e.target.files[0]})
The correct way would be to change state with
this.setState({photoModel.selectedFile:e.target.files[0]})
I set a name attribute for the file type input html tag and then used the below code to set the value
this.setState({ [name]: e.target.files[0] });

ReactJS: Prop referencing a state var. is not updated after parent's state change

I have a Top component which has a checkbox and also a child MyValidator component. When the checkbox changes, I have a handler in the Top component which sets its State var, this.state.activeItemLocked. The MyValidator child component specified in Top has a Prop with this state var.
However, when the Top Component changes its state, the MyValidator is not refreshed with this new change, even though it should render the current value which is its own state var received from Props:
Top
class Top extends React.Component {
constructor(props) {
super(props);
this.state = {
activeItemLocked: false;
};
}
render() {
return (
<div>
<input type="checkbox" defaultChecked={this.state.activeItemLocked} id="bChkboxLocked" onChange={this.handleCheckboxChange}></input>
<label htmlFor="bChkboxLocked">Locked</label>
<MyValidator value={this.state.activeItemLocked}></MyValidator>
<div>
);
}
// Handler to set State var. in Top upon Checkbox Toggle
handleCheckboxChange = (e) => {
this.setState({
activeItemLocked: e.target.checked ? true : false
});
};
}
MyValidator
class MyValidator extends Component {
constructor(props) {
super(props);
this.state = {
value: this.props.value // Initialize MyValidator's State var. from Props
};
}
render() {
return (
<div style={{"color":"red"}}>Checkbox current state: {this.state.value}</div>
);
}
}
Issue : Upon Top's state change, the Prop-based MyValidator state change does not occur (it does not display the current value).
Keeping a class component, there are many ways you can achieve this. 2 of them are:
Don't copy a props to a state variable. Instead just use the props.
class MyValidator extends Component {
render() {
return (
<div style={{"color":"red"}}>Checkbox current state: {this.props.value}. </div>
);
}
}
Use getDerivedStateFromProps
class MyValidator extends Component {
static getDerivedStateFromProps(props, state) {
if (props.value !== state.value) {
return {value: props.value}
}
return state;
}
render() {
return (
<div style={{"color":"red"}}>Checkbox current state: {this.state.value}</div>
);
}
}
Constructor only runs once. So you need to use getDerivedStateFromProps to update child state before render:
static getDerivedStateFromProps(props){
return {
value: props.value
};
}
also convert the boolean value to a string so that it gets printed out:
{this.state.lol.toString()}
Sandbox: https://codesandbox.io/s/late-https-3eh52?fontsize=14&hidenavigation=1&theme=dark

Render child component in parent after re-rendering sibling component

I have a parent component housing two children components(AddPersonForm and PeopleList). When I submit a name via the AddPersonForm, I expect it to be rendered in the PeopleList component, but it doesn't.
Here is my AddPersonForm:
class AddPersonForm extends React.Component {
state = {
person: ""
}
handleChange = (e) => this.setState({person: e.target.value});
handleSubmit = (e) => {
if(this.state.person != '') {
this.props.parentMethod(this.state.person);
this.setState({person: ""});
}
e.preventDefault();
}
render() {
return (
<form onSubmit={this. handleSubmit}>
<input type="text" placeholder="Add new contact" onChange={this.handleChange} value={this.state.person} />
<button type="submit">Add</button>
</form>
);
}
My PeopleList component:
class PeopleList extends React.Component {
constructor(props) {
super(props);
const arr = this.props.data;
this.state = {
listItems: arr.map((val, index) => <li key={index}>{val}</li> );
}
}
render() {
return <ul>{this.state.listItems}</ul>;
}
}
Now the parent component, ContactManager:
class ContactManager extends React.Component {
state = {
contacts: this.props.data
}
addPerson = (name) => {
this.setState({contacts: [... this.state.contacts, name]});
render() {
return (
<div>
<AddPersonForm parentMethod={this. addPerson}×/>
<PeopleList data={this.state.contacts} />
</div>
);
Please what I'm I doing wrong, or not doing?
The issue is in your PeopleList component. The state object which renders your list is created in the constructor when the component mounts, but you have no way of updating it when it recieves new values. It will always give you the initial value.
You could introduce a lifecycle method, componentDidUpdate, which would allow you to compare the previous props to the new props when they arrive, and update the state accordingly. I would recommend you not do this for two reasons:
Storing props directly in a components state is not good practice. You are just creating a copy of the state in the component above and that creates opportunities for confusion and stale values when one of them updates. Ideally, each piece of data should live in only one place.
If all PeopleList is doing is rendering your data, then it doesn't need any state at all. It can act as a display component that maps your props in place and doesn't have to worry about updating itself or managing its own data. This would actually make it a good candidate for conversion into a functional component.
class PeopleList extends React.Component {
render() {
return (
<ul>
{this.props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}
}
You are initializing PeopleList with props when its created and mounted but then you are not using new values of props for updating it.
To fix your issue use current value of prop when rendering:
class PeopleList extends React.Component {
render() {
return <ul>{ this.props.data.map((val, index) => <li key={index}>{val}</li>) }</ul>;
}
}

props in not passing value to Child Component in React

I am developing a simple food app.
Firstly, it will show dishDetails in MenuComponent
and onClick it will pass Id of a selected dish to a function named as
getDish(dishdetail)
Here i want to send props or state to my CartComponent where it will show details of selected Dish.
Problem-1
Props is not passing to Cart (undefined value)
but dishdetail name,id is showing if i do console.log in MenuComponent
How i can pass props/state to Cart kindly guide me.
//Here im binding my function
class Menu extends Component {
constructor(props) {
super(props);
this.getDish = this.getDish.bind(this);
}
//This is my getDish function(in which i want to send props to Cart)
getDish(dishDetail) {
return (
<React.Fragment>
<Cart dishdetail={dishDetail}/> **//undefined in Cart**
{console.log({dishDetail.name})} **//it is working perfectly**
</React.Fragment>
);
}
Working Fine
From Where I am sending data onClick function
<button
onClick={() => this.getDish(this.props.dishes[index])}
></button>
All components should be rendered from render method. And there behaviour can be controlled using state.
// class Menu
constructor(props) {
super(props);
this.state = {
dishDetail: null
};
this.getDish = this.getDish.bind(this);
}
getDish(selectedDish) {
this.setState({
dishDetail: selectedDish
});
}
render() {
return (
<>
<button onClick={() => this.getDish(this.props.dishes[index]])}>Click Me</button>
{/*Cart is called from render and value passed from state*/}
<Cart dishdetail={this.state.dishDetail}/>
</>
);
}
And your cart class will be re-rendered with your new data
class Cart extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log('cart', this.props);
return (
<div>You added {this.props.dishdetail} to cart</div>
);
}
}

LocalStorage in state of the component

I have two components Parent and Children. I want to see on my screen actual value of localStorage.getItem("myEl"). Parent state is storage:localStorage.getItem("myEl"). I change the "myEl" in localeStorage in Children component. Unfotunately Parent component not re-renders after "myEl" is changed but it works after I perform some action, such as changing the state again. I know that the problem is that setState is asinc but i don't know how to fix the problem.
For example,
Parent:
class Parent extends React.Component {
constructor() {
super();
this.state = {storage:localStorage.getItem("myEl")};
}
render(){
return <div>
<Child/>
<p>{this.state.storage}</p>
</div>
}
}
Child:
let i=0;
class Child extends React.Component {
render() {
return (
<button onClick={() => {
localStorage.setItem("myEl",i);
i++;
}}>click me</button>
);
}
}
react is not listening to changes in localStorage that is why parent component don't know when child component changes the value in localStorage.
To fix this you have to path your child component onClick function from parent this way:
class Parent extends React.Component {
constructor() {
super();
this.state = {storage:localStorage.getItem("myEl")};
}
handleChildClick = (count) => {
localStorage.setItem("myEl", count);
this.setState({ storage:localStorage.getItem("myEl") });
}
render(){
return <div>
<Child onClick={this.handleClick} />
<p>{this.state.storage}</p>
</div>
}
}
let i=0;
class Child extends React.Component {
render() {
return (
<button onClick={() => {
this.props.onClick(i);
i++;
}}>click me</button>
);
}
}
in case you need this value in other components consider using redux with react-redux containers to have a global storage available to you in any place of the react app.
Component should receive an state or prop in order to rerender itself, in your case it receive none of them. You should not update the localStorage and expect that your component is going to be reRendered with a new value from local storage, you could write a handler for your button in order to save the incremented value into your localstorage. Like below:
class App extends React.Component {
constructor() {
super()
this.state = { _val: 0 }
}
componentDidMount = () => {
const valFromLocalStorage = localStorage.getItem("myEl") || this.state._val
this.setState({ _val: valFromLocalStorage })
}
handleINC = e => {
const _valFromState = this.state._val
const _val = _valFromState++
localStorage.setItem("myEl", _val)
}
render() {
return(
<div>
<button onClick={this.handleINC}>increment value!</button>
</div>
)
}
}
By the way, in componentDidMount you get the value from localStorage or if it was falsy you get the default value from your state. Then in button handler function you get the value from state and increment it and set it in your localStorage in case of component use cases in future, when user closes the tab and opens our website after a while the localstorage data is not been cleared, then this component will get the value from there.

Resources