I have a relatively simple Bootstrap card layout in React with multiple cards, each of which has a toggle switch on it. However, whichever toggle switch is pressed it's always the handler function for the first card that is called (and hence the first toggle changes state). I can't understand why they aren't calling their own functions.
The structure I have is the card-deck has multiple card children, each of which has a toggle switch child, e.g. card deck -> card -> toggle
Card deck
class CardDeck extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{title: 'Cat 1'},
{title: 'Cat 2'},
{title: 'Cat 3'}
]
}
}
render() {
return (
<div class="card-deck">
{this.state.data.map((item, index) =>
<Card
key={index}
title={item.title}
index={index}
id={index}
/>
)}
</div>
);
}
}
Card
class Card extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: false,
}
this.handleChange = this.handleChange.bind(this);
}
handleChange() {
this.setState({
checked: !this.state.checked,
});
}
render() {
return (
<div class="card" key={this.props.index}>
<div class="card-body">
<h5 class="card-title">{this.props.title}</h5>
<p class="card-text">Text</p>
</div>
<div class="card-footer">
<Toggle checkStatus={this.state.checked} onChange={this.handleChange} key={this.props.index} />
</div>
</div>
);
}
}
Toggle
class Toggle extends React.Component{
render() {
return (
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="customSwitches" onChange={this.props.onChange} checked={this.props.checkStatus} />
<label class="custom-control-label" for="customSwitches">Label</label>
</div>
)
}
}
A simple workaround is to pass an id prop to toggle component because each checkbox control id should be unique but in your case, you are using the same id attribute value for all the three checkboxes so in other to differentiate the checkboxes I passed an id prop to toggle component and append to the id attribute value of each checkbox so as to make them unique.
Toggle Component
class Toggle extends React.Component {
render () {
console.log(this.props.id)
return (
<div className='custom-control custom-switch'>
<input
type='checkbox'
className='custom-control-input'
id={`customSwitches${this.props.id}`}
onChange={this.props.onChange}
checked={this.props.checkStatus}
/>
<label
className='custom-control-label'
htmlFor={`customSwitches${this.props.id}`}
>
Label
</label>
</div>
)
}
}
Card component
class Card extends React.Component {
constructor (props) {
super(props)
this.state = {
checked: false
}
this.handleChange = this.handleChange.bind(this)
}
handleChange () {
this.setState({
checked: !this.state.checked
})
}
render () {
return (
<div className='card' key={this.props.index}>
<div className='card-body'>
<h5 className='card-title'>{this.props.title}</h5>
<p className='card-text'>Text</p>
</div>
<div className='card-footer'>
<Toggle
checkStatus={this.state.checked}
onChange={this.handleChange}
key={this.props.index}
id={this.props.index}
/>
</div>
</div>
)
}
}
Related
I want to do a toggle for the search bar. When I clicked the searchIcon, the searchBar will show or hide. However, i need to lifting up 3 level parent and child. How can I pass the onClick to do the toggle?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activities: activities,
filteredActivities: activities,
};
this.handleSearchChange = this.handleSearchChange.bind(this);
}
filterActivity = searchText => {
//
}
handleSearchChange = inputValue => {
//
};
render() {
const filteredActivities = this.props.filteredActivities;
return(
<div className="notificationsFrame">
<div className="panel">
<Header name={this.props.name} />
<SearchBar inputChanged={this.handleSearchChange} />
<Content activities={this.state.filteredActivities} />
</div>
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div className="header">
<MenuIcon />
<Title name={this.props.name} />
<SearchIcon />
</div>
);
}
}
class SearchIcon extends React.Component {
render() {
return <div className="fa fa-search searchIcon" onClick={}></div>;
}
}
onClick={this.props.onClick}
or
{...props}
Full code:
import React from "react";
import "./styles.css";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleSearchChange = this.handleSearchChange.bind(this);
}
handleSearchChange = inputValue => {
console.log("test");
};
render() {
return (
<div className="notificationsFrame">
<div className="panel">
<Header name={this.props.name} onClick={this.handleSearchChange} />
</div>
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div className="header">
<SearchIcon onClick={this.props.onClick} />
</div>
);
}
}
class SearchIcon extends React.Component {
render() {
return (
<div className="fa fa-search searchIcon" {...this.props}>
XXX
</div>
);
}
}
I would like to know how to handle button click events in multiple places.
I have Button component, used in Home Component multiple times,
When button clicked, it shows Image but are displayed in all places
How to handle click events in multiple places,
class Button extends React.PureComponent{
constructor(props){
super(props);
}
render(){
return(
<button onClick={()=>this.props.toggle();}>Click Me</button>);
}
}
class Button extends React.PureComponent{
constructor(props){
super(props);
this.state={
showImage: true
}
}
toggle(){
this.setState({
showImage: !this.state.showImage
})
}
render(){
return(
<div className="row">
<div className="col-sm-6">
<Button onClick={this.toggle}/>
{this.state.showImage===true ? <img src="xyz.jpg"/> : ""}
</div>
<div className="col-sm-6">
<Button onClick={this.toggle}/>
{this.state.showImage===true ? <img src="xyz.jpg"/> : ""}
</div>
</div>
)
}
}
I would drop the state holding the visibility of the image into a child component. This component would be responsible for 1 image, 1 button and the state of that image.
function ToggleableImage({ imageSrc }) {
const [isVisible, setVisibility] = React.useState(true);
function toggleVisibility() {
setVisibility(currentVisibility => !currentVisibility);
}
return (
<div>
<Button onClick={toggleVisibility} />
{isVisible && <img src={imageSrc} />}
</div>
);
}
This component takes in the image you want to display as a prop. It has the isVisibile state and a toggleVisibility function for updating the state which is called by the button.
I have used functional components and React hooks for the state, but the idea of dropping the state to a child component is the same.
You can then add multiple of these components to the parent.
function Images() {
return (
<div className="row">
<div className="col-sm-6">
<ToggleableImage imageSrc="https://via.placeholder.com/150" />
</div>
<div className="col-sm-6">
<ToggleableImage imageSrc="https://via.placeholder.com/150" />
</div>
</div>
);
}
You can see this working here.
I'm working in a form with React. My idea is to create a reusable Form component that gets the state from a Page component as props, and will hold the logic for updating its own state with children data, send it to parent Page component.
The Page component is this:
class Page extends Component {
constructor(props) {
super(props);
this.state = {
data: {
text1: "Initial text1",
text2: "Initial text2"
}
};
}
render() {
return (
<div className="Page">
<div className="DataPreview">
Data preview in Page component
<div>{this.state.data.text1}</div>
<div>{this.state.data.text2}</div>
</div>
<Form data={this.state.data}>
<Input id="text1" data={this.state.data.text1} />
<Input id="text2" data={this.state.data.text2} />
</Form>
</div>
);
}
}
This is the Form component:
class Form extends Component {
constructor(props) {
super(props);
this.state = this.props.data;
}
render() {
return (
<div className="Parent">
<div>Form component</div>
<div className="DataPreview">
Data preview in Form component
<div>{this.state.text1}</div>
<div>{this.state.text2}</div>
</div>
{this.props.children}
</div>
);
}
}
And this the Input component:
class Input extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="Child" id={this.props.id}>
<div>Input component</div>
<input id={this.props.id} type="text" value={this.props.data} />
</div>
);
}
}
So Input should update Form state, and Form should update Page state. I know how to do it passing a callback when the Input is written Inside Form component, but I cant figure out how to do it when it is written inside Page component, like in this case.
I have a Sandbox for those interested: https://codesandbox.io/s/qx6kqypo09
class Input extends Component {
constructor(props) {
super(props);
}
handleChange(e) {
let data = this.props.this.state.data;
data.text1 = e.target.value;
this.props.this.setState({ data: data });
}
render() {
return (
<div className="Child" id={this.props.id}>
<div>Input component {this.props.id}</div>
<input
id={this.props.id}
type="text"
value={this.props.data}
onChange={e => this.handleChange(e)}
/>
</div>
);
}
}
use your input component as specified and your page component as mentioned below-
class Page extends Component {
constructor(props) {
super(props);
this.state = {
data: {
text1: "Initial text1",
text2: "Initial text2"
}
};
}
render() {
return (
<div className="Page">
<div className="DataPreview">
Data preview in Page component
<div>{this.state.data.text1}</div>
<div>{this.state.data.text2}</div>
</div>
<Form data={this.state.data}>
<Input id="text1" this={this} data={this.state.data.text1} />
<Input id="text2" data={this.state.data.text2} />
</Form>
</div>
);
}
}
I think this will help you
Thanks
As #dashton said, I am holding the same state in different components, and that's not correct. I will look for a different approach instead using only Form component state, and sharing logic via composition. I will open a new question for this.
without using some kind of state management, you would need to create a method that handles the state change in the parent component that you would then pass down to your child component a a prop.
Once you call that method in the child component it will update the state of the parent component.
This is one way of doing what you want to achieve: passing a callback handler for onChange. But, when your app starts to get bigger things can be ugly :) If you are thinking about creating a complex reusable Form component maybe you can examine the present node packages.
An alternative to this method, if you need a simple one, you can study React Context a little bit. It can help you maybe. Other than that Redux or other global state management libraries can do this also.
class Page extends React.Component {
state = {
data: {
text1: "Initial text1",
text2: "Initial text2",
},
};
handleChange = ( e ) => {
const { name, value } = e.target;
this.setState( prevState => ( {
data: { ...prevState.data, [ name ]: value },
} ) );
}
render() {
return (
<div className="Page">
<div className="DataPreview">
Data preview in Page component
<div>{this.state.data.text1}</div>
<div>{this.state.data.text2}</div>
</div>
<Form data={this.state.data}>
<Input name="text1" data={this.state.data.text1} onChange={this.handleChange} />
<Input name="text2" data={this.state.data.text2} onChange={this.handleChange} />
</Form>
</div>
);
}
}
const Form = props => (
<div className="Parent">
<div>Form component</div>
<div className="DataPreview">
Data preview in Form component
<div>{props.data.text1}</div>
<div>{props.data.text2}</div>
</div>
{props.children}
</div>
);
const Input = props => (
<div className="Child" id={props.id}>
<div>Input component {props.id}</div>
<input name={props.name} type="text" value={props.data} onChange={props.onChange} />
</div>
);
const rootElement = document.getElementById("root");
ReactDOM.render(<Page />, rootElement);
.Page {
border: 10px solid blue;
}
.Parent {
border: 10px solid turquoise;
}
.Child {
border: 3px solid tomato;
}
.DataPreview {
border: 3px solid lightgray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
As other people have said, you are holding the same state in different components, which obviously isn't correct.
However, to answer your requirement regarding decoupling child components from the form, you could make your form handle state changes from the inputs by using a render prop which would pass a callback to the inputs, see code and link.
https://codesandbox.io/s/4zyvjm0q64
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class Input extends Component {
constructor(props) {
super(props);
}
handleChange(id, value) {
this.props.onChange(id, value);
}
render() {
return (
<div className="Child" id={this.props.id}>
<div>Input component {this.props.id}</div>
<input
id={this.props.id}
type="text"
value={this.props.data}
onChange={e => this.handleChange(e)}
/>
</div>
);
}
}
class Form extends Component {
constructor(props) {
super(props);
this.state = this.props.data;
}
handleChange = (id, value) => {
this.setState({ [id]: value });
};
render() {
return (
<div className="Parent">
<div>Form component</div>
<div className="DataPreview">
Data preview in Form component
<div>{this.state.text1}</div>
<div>{this.state.text2}</div>
</div>
{this.props.render(this.handleChange)}
</div>
);
}
}
class Page extends Component {
constructor(props) {
super(props);
this.state = {
data: {
text1: "Initial text1",
text2: "Initial text2"
}
};
}
render() {
return (
<div className="Page">
<div className="DataPreview">
Data preview in Page component
<div>{this.state.data.text1}</div>
<div>{this.state.data.text2}</div>
</div>
<Form
data={this.state.data}
render={(handler) => {
return (
<div>
<Input id="text1" onChange={e => handler("text1", e.target.value)} />
<Input id="text2" onChange={e => handler("text2", e.target.value)} />
</div>
);
}}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Page />, rootElement);
This question already has an answer here:
Update state values with props change in reactjs
(1 answer)
Closed 4 years ago.
I have a main component, and when I pass down a prop to another component, it doesn't update the style. The display is still none, whereas it's meant to update to block since I have changed the prop to true. What might be wrong?
class Apps extends Component {
constructor(props) {
super(props);
// Don't do this!
this.state = { showing: true, Login: false, Signup: false, Members: false };
}
render() {
return (
<div>
<div
className="container"
style={{ display: this.state.showing ? "block" : "none" }}
>
<div>A Single Page web application made with react</div>
</div>
<LoginComponent view={this.state.Login} />
<div className="buttons">
<a href="" ref="login" onClick={this.Login_onclick.bind(this)}>
{this.state.Login ? "back" : "Login"}
</a>
<br />
</div>
</div>
);
}
Login_onclick(e) {
this.setState({ Login: !this.state.Login });
e.preventDefault(); //alert(e.target.value);
this.setState({ showing: !this.state.showing });
// this.setState({ref: !ref});
}
}
Login Component
class LoginComponent extends Component {
constructor(props) {
super(props);
this.state = {
show: this.props.view
};
}
render() {
return (
<div
className="login"
style={{ display: this.state.show ? "block" : "none" }}
>
<h3>Login</h3>
<br />
Username: <input type="text" ref="username" />
<br />
Password <input type="password" ref="password" />
<button value="Login">Login</button>
</div>
);
}
}
You are setting this.state = { show: this.props.view }; when the component is created. Changing the view prop after that will have no effect.
There is no need for you to set show in your state if your want it to update when the prop updates.
class LoginComponent extends Component {
render() {
return (
<div className="login" style={{ display: (this.props.view ? 'block' : 'none') }}>
<h3>Login</h3><br/>
Username: <input type="text" ref="username"/><br/>
Password <input type="password" ref="password"/>
<button value="Login" >Login</button>
</div>
);
}
}
I have a bunch of checkbox-list requirements. I will explain in detail. I have a bunch of languages say:
var languages = ["English", "German", "French", "Spanish", "Mandarin", "Tamil"]
I have a parent component which has a form where I have four sections, Say:
class Page extends React.Component {
render() {
return (
<form>
<h1>CanSpeak</h1> <chkboxlist someProp="speak" />
<h1>CanSpeak</h1> <chkboxlist someProp="read" />
<h1>CanSpeak</h1> <chkboxlist someProp="write" />
<h1>CanSpeak</h1> <chkboxlist someProp="understand" />
<button
onClick={e => {
console.log("Need the various chkboxlist values here");
e.preventDefault();
}}
>
Save
</button>
</form>
);
}
}
I want the chkboxlist component to keep track of the list of selected languages in each section and make them available in the "Save" button click handler. I wish to keep track of the state changes (list of selected languages under each section) in the "Page" component.
I do not want to use redux or some such external state management.
Now what is the way to create this chkboxlist component such that the state changes can be tracked in the parent Page component ? Are there any existing components which will fit this requirement and is used widely in the react ecosystem without having to reinvent the wheel ?
I don't know if pulling in a seperate component would be really useful - as it's only a really tiny piece of functionality.
Working fiddle here:
https://jsbin.com/tusakexire/edit?html,js,output
You could do something like:
class Chkboxlist extends React.Component {
constructor(props) {
super(props)
this.state = {}
props.values.map((v, i) => {
this.state[v] = false
})
}
onChange(key, value) {
this.setState({ [key]: value }, (state) => {
this.props.onChange(this.state)
})
}
render() {
return (
<div className="list-group-item form-group">
{this.props.values.map((value, i) => (
<div className="checkbox" key={i}>
<label>
<input
onChange={(e) => this.onChange(value, e.target.checked)}
type='checkbox'
value={this.state[value]}
/>
{value}
</label>
</div>
))}
</div>
)
}
}
class Page extends React.Component {
constructor(props) {
super(props)
this.state = {}
}
onChange(name, values) {
this.setState({ [name]: values })
}
render() {
const languages = ["English", "German", "French", "Spanish", "Mandarin", "Tamil"]
return (
<div className="container">
<div className="row">
<form className="form">
<div className="list-group col-xs-6">
<h4>Can Speak</h4>
<Chkboxlist
onChange={(values) => this.onChange('speak', values)}
values={languages}
/>
</div>
<div className="list-group col-xs-6">
<h4>Can Read</h4>
<Chkboxlist
onChange={(values) => this.onChange('read', values)}
values={languages}
/>
</div>
<div className="list-group col-xs-6">
<h4>Can Write</h4>
<Chkboxlist
onChange={(values) => this.onChange('write', values)}
values={languages}
/>
</div>
<div className="list-group col-xs-6">
<h4>Can Understand</h4>
<Chkboxlist
onChange={(values) => this.onChange('understand', values)}
values={languages}
/>
</div>
<button
className="btn btn-primary"
onClick={(e) => {
console.log(this.state);
e.preventDefault();
}}
>
Save
</button>
</form>
</div>
</div>
);
}
}
ReactDOM.render(<Page />, document.getElementById('app'))