I have built a toggle component that shows one out of two components based on whether the Purchase is true or not. Now I want to select from 3 components and I'm struggling with refactoring this so it's clean and works. What is the best way to do this if I'm adding Component3 both to the toggle selectors and the component import?
import Component1 from "./component1";
import Component2 from "./component2";
class App extends Component {
constructor(props) {
super(props);
// this.toggle = this.toggle.bind(this);
this.state = {
popoverOpen: false,
isPurchase: true,
};
this.switchState = this.switchState.bind(this);
}
switchState(flag) {
this.setState({ isPurchase: flag });
}
render() {
return (
<div className={styles.cardBody}>
<div className={styles.row}>
<div className={styles.col12}>
<div
className={`${
styles.positionRelative
} ${styles.formGroup}`}>
<div
style={{
fontWeight:
"bolder",
color: "#7192a6",
}}>
<span
className={
this.state
.isPurchase
? `${
styles.switchState
}`
: `${
styles.switchState
} ${
styles.unselected
}`
}
onClick={() => {
this.switchState(
true,
);
}}>
Component1{" "}
</span>
/
<span
className={
this.state
.isPurchase
? `${
styles.switchState
} ${
styles.unselected
}`
: `${
styles.switchState
}`
}
onClick={() => {
this.switchState(
false,
);
}}>
{" "}
Component2
</span>
</div>
</div>
</div>
</div>
<div className={styles.row}>
<div className={styles.col12}>
<Component1
isPurchase={
this.state.isPurchase
}
/>
<Component2
isPurchase={
this.state.isPurchase
}
/>
</div>
</div>
</div>
);
}
}
And in the component itself I'm checking this in the state
class Component1 extends Component {
constructor(props) {
super(props);
this.state = {
isPurshase: props.isPurchase,
popoverOpen: false,
};
}
And inside the return display/hide this way
<div style={{ display: this.props.isPurchase ? "" : "none" }}>
You can do something like this to be more clean.
class App extends Component {
state = {
condition: 'condition1',
};
handleSwitch = () => {
const {condition} = this.state;
let newCondition = '';
switch (condition) {
case 'condition1':
newCondition = 'condition2';
break;
case 'condition2':
newCondition = 'condition3';
break;
case 'condition3':
newCondition = 'condition1';
break;
default:
newCondition = 'condition1';
}
this.setState({
condition: newCondition,
});
};
renderSwitch = () => {
<button onClick={() => this.handleSwitch()}> CHANGE </button>;
};
renderComponent = () => {
const {condition} = this.state;
switch (condition) {
case 'condition1':
return (<ComponentOne/>);
case 'condition2':
return (<ComponentTwo/>);
case 'condition3':
return (
<div>
<ComponentOne/>
<ComponentOne/>
</div>
);
default:
return null;
}
}
render() {
return (
<div>
{this.renderSwitch()}
{this.renderComponent()}
</div>
);
}
}
Related
I try to build a to-do-list in react.
I have 2 components so far:
The first one handles the input:
import React from 'react';
import ListItems from './ListItems.js';
class InputComponent extends React.Component {
constructor(){
super();
this.state = {
entries: []
}
this.getText = this.getText.bind(this);
}
getText() {
if(this._inputField.value !== '') {
let newItem = {
text: this._inputField.value,
index: Date.now()
}
this.setState((prevState) => {
return {
entries: prevState.entries.concat(newItem)
}
})
this._inputField.value = '';
this._inputField.focus();
}
}
render() {
return(
<div>
<input ref={ (r) => this._inputField = r } >
</input>
<button onClick={ () => this.getText() }>Go</button>
<div>
<ListItems
entries={this.state.entries}
/>
</div>
</div>
)
}
}
export default InputComponent;
The second one is about the actual entries in the list:
import React from 'react';
class ListItems extends React.Component {
constructor() {
super();
this.lineThrough = this.lineThrough.bind(this);
this.listTasks = this.listTasks.bind(this);
}
lineThrough(item) {
console.log(item);
//item.style = {
// textDecoration: 'line-through'
//}
}
listTasks(item) {
return(
<li key = { item.index }>
<div
ref = { (r) => this._itemText = r }
style = {{
width: 50 + '%',
display: 'inline-block',
backgroundColor: 'teal',
color: 'white',
padding: 10 + 'px',
margin: 5 + 'px',
borderRadius: 5 + 'px'
}}
>
{ item.text }
</div>
<button onClick={ () => this.lineThrough(this._itemText) }>Done!</button>
<button>Dismiss!</button>
</li>
)
}
render() {
let items = this.props.entries;
let listThem = items.map( this.listTasks );
return(
<ul style = {{
listStyle: 'none'
}}>
<div>
{ listThem }
</div>
</ul>
)
}
}
export default ListItems;
As you can see, i want to have two buttons for each entry, one for the text to be line-through, and one to delete the entry.
I am currently stuck at the point where i try to address a specific entry with the "Done!" button to line-through this entry's text.
I set a ref on the div containing the text i want to style and pass that ref to the onClick event handler.
Anyways, the ref seems to be overwritten each time i post a new entry...
Now, always the last of all entries is addressed. How can i properly address each one of the entries?
What would be the best practice to solve such a problem?
you could pass an additional prop with index/key of the todo into every item of your todo list. With passing event object to your handler lineThrough() you can now get the related todo id from the attributes of your event target.
Kind regards
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
}
this.done = this.done.bind(this);
}
done(id) {
this.state.todos[id].done();
}
render() {
return (
this.state.todos.map(t => <Todo item={t} onDone={this.done} />)
);
}
}
const Todo = ({item, onDone}) => {
return (
<div>
<h1>{item.title}</h1>
<button onClick={() => onDone(item.id)}>done</button>
</div>
)
}
You need map listItems then every list item get its own ref
import React from 'react';
import ReactDOM from 'react-dom';
class ListItems extends React.Component {
constructor() {
super();
this.lineThrough = this.lineThrough.bind(this);
}
lineThrough(item) {
item.style.textDecoration = "line-through";
}
render() {
return(
<ul style = {{
listStyle: 'none'
}}>
<div>
<li key={this.props.item.index}>
<div
ref={(r) => this._itemText = r}
style={{
width: 50 + '%',
display: 'inline-block',
backgroundColor: 'teal',
color: 'white',
padding: 10 + 'px',
margin: 5 + 'px',
borderRadius: 5 + 'px'
}}
>
{this.props.item.text}
</div>
<button onClick={() => this.lineThrough(this._itemText)}>Done!</button>
<button>Dismiss!</button>
</li>
</div>
</ul>
)
}
}
class InputComponent extends React.Component {
constructor(){
super();
this.state = {
entries: []
}
this.getText = this.getText.bind(this);
}
getText() {
if(this._inputField.value !== '') {
let newItem = {
text: this._inputField.value,
index: Date.now()
}
this.setState((prevState) => {
return {
entries: prevState.entries.concat(newItem)
}
})
this._inputField.value = '';
this._inputField.focus();
}
}
render() {
return(
<div>
<input ref={ (r) => this._inputField = r } >
</input>
<button onClick={ () => this.getText() }>Go</button>
<div>
{this.state.entries.map((item, index) => {
return <ListItems key={index} item={item} />
})}
</div>
</div>
)
}
}
I'm trying to call a simple method from the grandparent component in my child component but from some reason I can't , I tried every possible way but I think I'm missing something
here's the full code :
import React, { Component } from 'react';
import './App.css';
var todos = [
{
title: "Example2",
completed: true
}
]
const TodoItem = (props) => {
return (
<li
className={props.completed ? "completed" : "uncompleted"}
key={props.index} onClick={props.handleChangeStatus}
>
{props.title}
</li>
);
}
class TodoList extends Component {
constructor(props) {
super(props);
}
render () {
return (
<ul>
{this.props.todosItems.map((item , index) => (
<TodoItem key={index} {...item} {...this.props} handleChangeStatus={this.props.handleChangeStatus} />
))}
</ul>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos ,
text :""
}
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangeStatus = this.handleChangeStatus(this);
}
handleTextChange(e) {
this.setState({
text: e.target.value
});
}
handleChangeStatus(){
console.log("hello");
}
handleSubmit(e) {
e.preventDefault();
const newItem = {
title : this.state.text ,
completed : false
}
this.setState((prevState) => ({
todos : prevState.todos.concat(newItem),
text : ""
}))
}
render() {
return (
<div className="App">
<h1>Todos </h1>
<div>
<form onSubmit={this.handleSubmit}>
< input type="text" onChange={this.handleTextChange} value={this.state.text}/>
</form>
</div>
<div>
<TodoList handleChangeStatus={this.handleChangeStatus} todosItems={this.state.todos} />
</div>
<button type="button">asdsadas</button>
</div>
);
}
}
export default App;
The method im trying to use is handleChangeStatus() from the App component in the TodoItem component
Thank you all for your help
This line is wrong:
this.handleChangeStatus = this.handleChangeStatus(this);
//Change to this and it works
this.handleChangeStatus = this.handleChangeStatus.bind(this);
I am trying to call the function handleToggle() in the child component from the parent and although it seems that everything is fine, this.clickAddGoal(stageContent); appears as undefined
class ParentClass extends Component
{
constructor(props, context)
{
super(props, context);enter code here
this.state = {stageContent: ''};
this.getStageContent = this.getStageContent.bind(this);
}
getStageContent = (stageId) =>
{
let stageContent = '';
switch (stageId)
{
case 1:
stageContent = this.props.data.PPYPL;
break;
case 2:
stageContent = this.props.data.I;
break;
case 3:
stageContent this.props.data.DH;
break;
case 4:
stageContent = this.props.data.R;
break;
}
this.clickAddGoal(stageContent);
this.setState({stageContent: stageContent});
}
renderComponents = () =>
{
return (
<div>
<ChildComponent
subjectContent={this.props.data.name}
onRef={click => this.clickAddGoal = click}
/>
</div>
);
}
}
render()
{
return (
<div style={style}>
<div className="performance-graph">
<div className="container">
<div className="row">
<div className="col-xs-3">
{}
</div>
</div>
<br/>
<div className='subject-unit'>
<StageBar
id={this.props.data.id}
subjectCode={this.props.data.area.color}
progressPercentage={this.props.data.percentageProgress}
onGetStage={this.getStageContent}
/>
{this.renderComponents()}
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) =>
{
return {
data: state.planAreaUnitRed,
};
};
export default connect(mapStateToProps, null)(ParentClass);`
Child Component
import React, {Component} from 'react';
import {connect} from 'react-redux'
class ChildComponent extends Component
{
constructor(props)
{
super(props);
this.state = {editUnit: false, viewContentStage: false, autonomy:''};
}
componentWillMount()
{
this.handleToggle()
}
componentDidMount() {
this.props.onRef(this.handleToggle);
}
handleToggle = () =>
{
this.props.clearStageContent();
this.props.stageContent!=='' this.props.loadContentUnitStage(this.props.stageContent):'';
}
render()
{
return (
<div className='unit-content'>
<div className="container">
<div className="row">
<h3>
{this.props.subjectContent}
</h3>
<div style={{padding: '50px', fontSize: '24px'}}>
{this.props.stageContentData.content}
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) =>
{
return {
stageContentData: state.planAreaUnitRed.stageContent,
};
};
const mapDispatchToProps = (dispatch) =>
{
return {
loadContentUnitStage: (hash) =>
{
dispatch(planAreaUnitActions.fetchContentUnitStage(hash))
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UnitContent);
Is there something I'm doing wrong?
One way I know is,if you are trying to access child component from the parent component then the ref can be applied in the Child component itself. Like
<ChildComponent
subjectContent={this.props.data.name}
ref={click => this.clickAddGoal = click}
/>
So then ref clickAddGoal contains reference to the underlying child component. Then you can handle it from your parent component like
this.clickAddGoal.handleToggle();
I have two components named parent and child.
Child component contains checkbox's and it will send selected checkbox's values to Parent component
Child
class Child extends Component{
state = {
options: [],
selected_options: []
}
handleClose = (e) => {
this.props.add(this.state.selected_options)
}
handleChange = (event) => {
console.log(event.target.name);
if(event.target.checked==true){
event.target.checked=false
this.state.selected_options.slice(this.state.options.indexOf(event.target.value),1)
}
else{
event.target.checked = true
this.state.selected_options.push(event.target.value)
}
}
render() {
return(
<div>
<Grid>
{
this.state.options.map(value => {
return(
<Checkbox onChange={this.handleChange} label={value.name} value={value.name} checked={false} />
)
})
}
<Button color='green' onClick={this.handleClose} inverted>
<Icon name='checkmark' /> Apply
</Button>
</div>
);
}
}
and Parent Component
class Parent extends Component {
constructor(props){
super(props);
this.state = {
selected_options:[],
}
}
addOptions = (options) => {
this.setState({
selected_options: options
})
}
render() {
return(
<div>
<Child selected_options={this.state.selected_options} add={this.addOptions} />
</div>
);
}
}
When a checkbox is selected it must output its corresponding value in the console. but it showing undefined
Directly mutating the state or the event value is not the correct idea
You should be doing it like
class Child extends Component{
state = {
checkedState: []
options: [],
selected_options: []
}
handleClose = (e) => {
this.props.add(this.state.selected_options)
}
handleChange = (index, value) => {
var checkedState = [...this.state.checkedState];
if(checkedState[index] === undefined) {
checkedState.push(true)
}
else {
if(checkedState[index] === true) {
var selected_options=[...this.state.selected_options];
var idx = selected_options.findIndex((obj) => obj.name == value)
selected_options.splice(idx, 1);
checkedState[index] = false
} else {
var selected_options=[...this.state.selected_options];
selected_options.push(value);
}
}
this.setState({checkedState, selected_options})
}
render() {
return(
<div>
<Grid>
{
this.state.options.map((value, index) => {
return(
<Checkbox onChange={() => this.handleChange(index, value.name)} label={value.name} value={value.name} checked={this.state.checkedState[index] || false} />
)
})
}
<Button color='green' onClick={this.handleClose} inverted>
<Icon name='checkmark' /> Apply
</Button>
</div>
);
}
}
and from the Parent render the child and not the Parent
Parent
render() {
return(
<div>
<Child selected_options={this.state.selected_options} add={this.addOptions} />
</div>
);
}
class App extends Component {
constructor(props) {
super(props);
this.state = { Card: Card }
}
HandleEvent = (props) => {
this.SetState({Card: Card.Active}
}
render() {
return (
<Card Card = { this.state.Card } HandleEvent={
this.handleEvent }/>
<Card Card = { this.state.Card } HandleEvent={
this.handleEvent }/>
)
}
}
const Card = props => {
return (
<div style={props.state.Card} onClick={
props.HandleEvent}>Example</div>
)
}
Every time I click on one of the cards all of my elements change states, how do I program this to only change card that I clicked?
Here's a working example
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
0: false,
1: false
};
}
handleEvent(idx) {
const val = !this.state[idx];
this.setState({[idx]: val});
}
render() {
return (
<div>
<Card state={this.state[0]} handleEvent={()=>this.handleEvent(0) } />
<Card state={this.state[1]} handleEvent={()=>this.handleEvent(1) } />
</div>
);
}
}
const Card = (props) => {
return (<div onClick={() => props.handleEvent()}>state: {props.state.toString()}</div>);
}
You can also see it in action here
Obviously this is a contrived example, based on your code, in real world application you wouldn't store hardcoded state like {1: true, 2: false}, but it shows the concept
It's not completely clear from the example what is the Card in the constructor. But here the example of how you can modify clicked element.
Basically you can keep only index of clicked element in parent's state, and then pass it as some property to child component, i.e. isActive here:
const cards = [...arrayOfCards];
class App extends Component {
constructor(props) {
super(props);
this.state = { activeCardIndex: undefined }
}
HandleEvent = (index) => {
this.SetState({
activeCardIndex: index
});
}
render() {
return ({
// cards must be iterable
cards.map((card, index) => {
return (
<Card
key={index}
Card={Card}
isActive={i === this.state.activeCardIndex}
HandleEvent={this.HandleEvent.bind(this, index)}
/>
);
})
});
}
}
const Card = props => {
// style active card
const style = Object.assign({}, props.Card, {
backgroundColor: props.isActive ? 'orange' : 'white',
});
return (
<div style={style} onClick={
props.HandleEvent}>Example</div>
)
}