How to make a step control component? - reactjs

I have a component:
import React from "react";
import styles from "./Clicker.module.scss";
class Clicker extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: props.counter,
step: props.step,
};
}
componentDidMount() {
this.setState({
counter: 0,
step: 1,
});
}
add = () => {
this.setState((previous) => ({
counter: previous.counter + previous.step,
}));
};
changeCountInput = ({ target: { value } }) => {
const toNumber = Number(value);
this.setState(toNumber > 0 ? { step: toNumber } : { step: 1 });
};
render() {
const { counter } = this.state;
return (
<article className={styles.container}>
<h1 className={styles.counter}>{counter}</h1>
<input
onChange={this.changeCountInput}
placeholder="set step (default - 1)"
className={styles.inputCount}
/>
<button onClick={this.add} className={styles.buttonCounter}>
Change counter
</button>
</article>
);
}
}
export default Clicker;
I need to make a step control component.
I don't understand how to do it.
I ask you to explain how to do this. I'm not asking you to do it for me, just theoretically, what should I do to create a step control component?
UPD:
As the teacher said, you need to move this piece of code into a separate component.
<input
onChange={this.changeCountInput}
placeholder="set step (default - 1)"
className={styles.inputCount}
/>

Related

Input doesn't change color according to the validation (rcc)

I want to validate the value that the user write in the input.
The browser works, creating a new room with the click of a button works, but the input doesn't change color according to the validation I set, why?
Inside addRoomName function I created setState for the value inside the room input
addRoomName=(e)=> {
this.setState({ room: e.target.value })
and additionally I created setState for the validation with the conditions
this.setState({ addRoomName: e.target.value });
if (e.target.value.length >= 6){
this.setState({roomNameInputColor:'green'})
} else {
this.setState({roomNameInputColor:'red'})
}
Is that may be the problem? because it seems that the react don't even recognize the validation but just the first setState (the one that bring the value that wrote in the room input)
So why the input doesn't change color?
I shared all the code
thanks!
App.js
import React, { Component } from 'react'
import './App.css';
import Addroom from './components/Addroom.js'
import Room from './components/Room.js'
export default class App extends Component {
state = {
roomsList:[{room:'',color:''}],
}
create = (r, c) => {
this.setState({ roomsList: [...this.state.roomsList, { room: r, color: c }] })
}
render() {
return (
<div>
<h1>My Smart House</h1>
{this.state.roomsList.map((element) => {
return <Room r={element.room} c={element.color} />
})}
<Addroom add={this.create}/>
</div>
)
}
}
Addroom.js
import React, { Component } from 'react'
export default class Addroom extends Component {
constructor(props) {
super(props)
this.state = {
roomNameInputColor:'white',
}
}
addRoomName = (e) => {
this.setState({ room: e.target.value })
this.setState({ addRoomName: e.target.value });
if (e.target.value.length >= 6) {
this.setState({ roomNameInputColor: 'green' })
} else {
this.setState({ roomNameInputColor: 'red' })
}
}
addColor = (e) => {
this.setState({ color: e.target.value })
}
createRoom = () => {
this.props.add(this.state.room, this.state.color);
}
render () {
return (
<div>
<input onChange={this.addRoomName} style={{ backgroundInputColor: this.state.roomNameInputColor }} placeholder='Name Your Room'/>
<br/>
<input onChange={this.addColor} placeholder='Whats The Room Color?'/>
<br/>
<button onClick={this.createRoom}>Create</button>
</div>
)
}
}
Room.js
import React, { Component } from 'react'
export default class Room extends Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
return (
<div>
<h1>Room: {this.props.r} </h1>
<h3>Color: {this.props.c} </h3>
</div>
)
}
}
In your addRoomName function, you are doing multiple setState in a row, where it's often a source of state confusions (that you are probably experiencing here).
Prefer to have a single call to the setState() method in your function like this:
addRoomName = (e) => {
const room = e.target.value;
let roomNameInputColor = '';
if (room.length >= 6) {
roomNameInputColor = 'green';
} else {
roomNameInputColor = 'red';
}
this.setState({ room, addRoomName: room, roomNameInputColor });
}
thanks everyone, now it works, I did like you send guys to have effective code and also I changed this
<input onChange={this.addRoomName} style={{backgroundInputColor:this.state.roomNameInputColor}} placeholder='Name Your Room'/><br/>
To this
<input onChange={this.addRoomName} style={{backgroundColor:this.state.roomNameInputColor}} placeholder='Name Your Room'/><br/>
Because backgroundColor is a reserved word and while I tried to fix the problem I didn't saw that little important thing.. thanks!

Increment / Decrement function in ReactJS

I created a increment/decrement function, but I have a doubt.
I can decrement count clicking in same button that include a increment count?
How to create that function?
Code:
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
clicks: 0,
show: true
};
}
IncrementItem = () => {
this.setState({ clicks: this.state.clicks + 1 });
}
DecreaseItem = () => {
this.setState({ clicks: this.state.clicks - 1 });
}
ToggleClick = () => {
this.setState({ show: !this.state.show });
}
render() {
return (
<div>
<button onClick={this.IncrementItem}>Click to increment by 1</button>
<button onClick={this.DecreaseItem}>Click to decrease by 1</button>
<button onClick={this.ToggleClick}>
{ this.state.show ? 'Hide number' : 'Show number' }
</button>
{ this.state.show ? <h2>{ this.state.clicks }</h2> : '' }
</div>
);
}
}
export default App;
You could set a conditional in the function you trigger when you click on the button. Maybe something like this:
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
clicked: false,
};
}
toggleClicked = () => {
const counter = this.state.clicked ? counter +1 : counter - 1;
const clicked = !this.state.clicked;
this.setState({ counter, clicked })
}
render() {
return (
<div>
<button onClick={this.toggleClicked}>Click</button>
{ this.state.counter ? <h2>{ this.state.counter }</h2> : '' }
</div>
);
}
}
export default App;
This way if you have already clicked the counter will decrease by 1 and viceversa.

React trying to make a list of dynamic inputs

I have built this site
https://supsurvey.herokuapp.com/surveycreate/
now I am trying to move the fronted to React so I can learn React in the process.
with vanila js it was much easier to create elements dynamically.
I just did createElement and after that when I clicked "submit" button
I loop throw all the elements of Options and take each target.value input.
so I loop only 1 time in the end when I click Submit and that's it I have now a list of all the inputs.
in react every change in each input field calls the "OnChange" method and bubbling the e.targe.value to the parent and in the parent I have to copy the current array of the options and rewrite it every change in every field.
is there other way? because it seems crazy to work like that.
Options.jsx
```import React, { Component } from "react";
class Option extends Component {
constructor(props) {
super(props);
this.state = { inputValue: "", index: props.index };
}
myChangeHandler = event => {
this.setState({ inputValue: event.target.value });
this.props.onChange(this.state.index, event.target.value);
};
render() {
return (
<input
className="survey-answer-group"
type="text"
placeholder="Add Option..."
onChange={this.myChangeHandler}
/>
);
}
}
export default Option;
______________________________________________________________________________
Options.jsx````
```import React, { Component } from "react";
import Option from "./option";
class Options extends Component {
render() {
console.log(this.props);
return <div>{this.createOptions()}</div>;
}
createOptions = () => {
let options = [];
for (let index = 0; index < this.props.numOfOptions; index++) {
options.push(
<Option key={index} onChange={this.props.onChange} index={index} />
);
}
return options;
};
}
export default Options;```
______________________________________________________________________________
App.jsx
```import React from "react";
import OptionList from "./components/Options";
import AddButton from "./components/add-button";
import "./App.css";
class App extends React.Component {
state = {
numOfOptions: 2,
options: [{ id: 0, value: "" }, { id: 1, value: "" }]
};
handleChange = (index, value) => {
const options = [...this.state.options];
console.log("from App", value);
options[index].value = value;
this.setState({
options: options
});
console.log(this.state);
};
addOption = () => {
const options = [...this.state.options];
options.push({ id: this.state.numOfOptions + 1, value: "" });
this.setState({
numOfOptions: this.state.numOfOptions + 1,
options: options
});
};
submitButton = () => {};
render() {
return (
<div className="poll-create-grid">
<div id="poll-create-options">
<OptionList
onChange={this.handleChange}
numOfOptions={this.state.numOfOptions}
/>
</div>
<button
className="surveyCreate-main-btn-group"
onClick={this.addOption}
>
Add
</button>
<button
className="surveyCreate-main-btn-group"
onClick={this.submitButton}
>
Submit
</button>
</div>
);
}
}
export default App;
```
So firstly,
The issue is with the way your OptionList component is defined.
Would be nice to pass in the options from the state into the component rather than the number of options
<OptionList
onChange={this.handleChange}
options={this.state.options}
/>
The you basically just render the options in the OptionsList component (I'm assuming it's same as the Options one here
class Options extends Component {
...
render() {
return
(<div>{Array.isArray(this.props.options) &&
this.props.options.map((option) => <Option
key={option.id}
index={option.id}
onChange={this.props.onChange}
value={option.value}
/>)}
</div>);
}
...
}
You would want to use the value in the Option component as well.
this.props.onChange(this.state.index, event.target.value); No need using the state here to be honest
this.props.onChange(this.props.index, event.target.value); is fine

ReactJS. Infinity loop

I'am getting props from child in getCount function. And set it prop into state. Than i try set it in component and get infinity loop. How can i fix that?
There is code of parent component:
import React, { Component } from "react";
import Message from "./Message/Message";
export default class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
color: {
s: 30,
l: 60,
a: 1
},
counter: 0
};
}
getCount = count => this.setState(state => ({
counter: count
}));
getColor = color => {
console.log(`the color is ${color}`);
};
render() {
const counter = this.state.counter;
return (
<div>
<Message
getColor={this.getColor}
getCount={this.getCount}
color={this.state.color}
>
{undefined || `Hello World!`}
</Message>
{counter}
</div>
);
}
}
child:
import React, { Component } from "react";
export default class Message extends React.Component {
constructor(props) {
super(props);
this.changeColor = this.changeColor.bind(this);
this.state = { h: 0 };
this.counter = 0;
}
changeColor = () => {
this.setState(state => ({
h: Math.random()
}));
};
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
this.props.getCount(this.counter);
}
render() {
this.counter++;
const { children } = this.props;
const { s, l, a } = this.props.color;
this.color = `hsla(${this.state.h}, ${s}%, ${l}%, ${a})`;
return (
<p
className="Message"
onClick={this.changeColor}
style={{ color: this.color }}
>
{children}
</p>
);
}
}
The problem lies in your Message component.
You are using getCount() inside your componentDidUpdate() method. This causes your parent to re-render, and in turn your Message component to re-render. Each re-render triggers another re-render and the loop never stops.
You probably want to add a check to only run the function if the props have changed. Something like:
componentDidUpdate(prevProps) {
if(prevProps.color !== this.props.color) {
this.props.getColor(this.color);
this.props.getCount(this.counter);
}
}
This will keep the functionality you need, but prevent, not only the infinity-loop, but also unnecessary updates.

How to use dynamically created classes in react components

I have a class of this form:
export default class FixedMem {
constructor(totalMem){
this._totalMem = totalMem
}
get totalMem(){
return this._totalMem
}
addMem(mem){
this._totalMem += mem
}
}
I import it into my react component like this :
import Fixed from '../somewhere'
If i want to create a new classes with varying parameters based on input from a textbox and display its values. How do i call its methods from inside the render method ?. This somewhat illustrates my problem
class fixedBlock extends Component {
constructor(){
super()
this.state = {
"textInput":"",
"totalMem":0,
"fixed":null
}
}
handleInputChanged(e){
this.setState({
"textInput":e.target.value
})
}
handleButtonPressed(){
this.setState({"fixed":new Fixed(parseInt(this.state.textInput))})
}
incrementButtonPressed(){
this.state.fixed.addMem(2)
}
render(){
return(
<div>
<input type="button" onClick={this.handleInputChanged} value=
{this.state.textInput}>
<button onClick={this.handleButtonPressed}>create</button>
<button onClick={this.incrementButtonPressed}> increment </button>
<p>{this.state.fixed.totalMem}</p>
</div>
)
}
}
this doesn't work, another approach i had to solve this problem was using closures, so inside my react component :
class fixedBlock extends Component{
constructor(){//stuff here}
FixedMem () {
var FixedObj = null
return {
initFixed: function (totalMem) {
FixedObj = new Fixed(totalMem, divisions)
},
totalMem: function () {
return FixedObj.totalMem
},
increment: function(){
FixedObj.addMem(2)
}
render(){//stuff here}
}
How do i even use this in the render method ?
There are several issues with your code example. Missing closing tags and rebinding of methods missing.
Here's an example of dynamically usage of a class instance in a React component. However I can not recommend to use this approach. This is mainly as proof of concept.
class MyValue {
constructor(val) {
this._val = parseInt(val, 10) || 0;
}
get total() {
return this._val;
}
set total(val) {
this.val = val;
}
add(val) {
this._val += val;
}
subtract(val) {
this._val -= val;
}
}
Here's the React component
class Block extends React.Component {
constructor() {
super();
this.state = {
textInput: "",
myValue: new MyValue()
};
}
handleInputChanged(e) {
this.setState({
textInput: e.target.value
});
}
handleButtonPressed() {
this.setState({ myValue: new MyValue(this.state.textInput) });
}
incrementButtonPressed() {
this.state.myValue.add(2);
this.forceUpdate(); /* React does not know the state has updated, force update */
}
render() {
return (
<div>
<input type="number" step="1" onChange={this.handleInputChanged.bind(this)} />
<button onClick={this.handleButtonPressed.bind(this)}>create</button>
<button onClick={this.incrementButtonPressed.bind(this)}>increment</button>
<p>{this.state.myValue.total}</p>
</div>
);
}
}
As an alternative approach. You could use a pattern where you separate logic from presentation. Here's an example using function as child. The Calculator handles the calculation and Presentation uses the calculator and present the GUI.
class Calculator extends React.Component {
constructor() {
super();
this.state = {value: 0};
}
add(value){
this.setState(prevState => ({value: prevState.value + value}));
}
subtract(value){
this.setState(prevState => ({value: prevState.value - value}));
}
set(){
this.setState(prevState => ({value: parseInt(prevState.input, 10) || 0}));
}
input(value){
this.setState({input: value});
}
render() {
return this.props.children(
{
value: this.state.value,
add: this.add.bind(this),
subtract: this.subtract.bind(this),
set: this.set.bind(this),
input: this.input.bind(this),
});
}
}
const Presentation = props => (
<Calculator>
{ ({value,add,subtract,set,input}) => (
<div>
<button onClick={() => add(2)}>add 2</button>
<button onClick={() => subtract(3)}>subtract 3</button>
<input type="number" step="1" onChange={e => input(e.target.value)} />
<button onClick={set}>set</button>
<p>{value}</p>
</div>)
}
</Calculator>);
The problem with the first attempt is that you are mutating a Component's state without letting React know about it. You need to use setState() or forceUpdate(). One way to still have FixedMem manage your state while letting React know could be:
class FixedBlock extends Component {
constructor(props) {
super(props);
this.state = {
textInput: '',
totalMem: 0
};
this.fixedMem = new FixedMem(0);
this.sync = this.sync.bind(this);
}
sync() {
const totalMem = this.fixedMem.totalMem;
this.setState({ totalMem });
}
handleInputChanged(evt) {
this.setState({ textInput: evt.target.value });
}
handleButtonPressed() {
this.fixedMem = new FixedMem(parseInt(this.state.textInput));
this.sync();
}
incrementButtonPressed() {
this.fixedMem.addMem(2);
this.sync();
}
render() {
return (
<div>
<input type="text" onChange={this.handleInputChanged.bind(this)} />
<button onClick={this.handleButtonPressed.bind(this)}>create</button>
<button onClick={this.incrementButtonPressed.bind(this)}>increment</button>
<p>{this.state.totalMem}</p>
</div>
);
}
}

Resources