Referencing data in stateless React components - javascript

I made a stateless component with an internal variable to reference an input, as below. This is working fine.
const MyStatelessComp = ({ team, teamProgress, onSet, editing, enableEdit }) => {
let input
return (
<div>
<div className="team__goal-target_header" >Team's Savings Target</div>
<div className="team__goal-target_value" >
M$
<input
ref={ el => input = el }
style={{width: '75px', border: 'none'}}
onChange={() => onSet({teamId: team.id, goalValue: parseInt(input.value, 10) || 0}) }
/>
<div
ref={ el => input }
style={{
display: !input || (!isNaN(parseFloat(input.value)) && isFinite(input.value)) ? 'none' : 'block'
}}
>Must be numeric</div>
</div>
</div>
)
}
I want to validate input and display a notification Must be numeric is the anything that cannot be converted to a number is entered into my input field. That is not working however. How do I make input in the context of the "warning div" reference the value of the input?
Realize that this is not an unorthodox way to working with stateless components, but it would save me lots of pain.
Thank you.

Why use a stateless component when he can be a simple statefull component ?
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ value: event.target.value });
}
render() {
const isNumber = !isNaN(this.state.value);
return (
<div>
<div className="team__goal-target_header">Team's Savings Target</div>
<div className="team__goal-target_value">
M$
<input
style={{ width: "75px", border: "none" }}
onChange={this.handleChange}
value={this.state.value}
/>
{isNumber ? "" : <div>Must be numeric</div>}
</div>
</div>
);
}
}
You can also toggle the div content or create a new alert component and toggle this component.

Related

ReactJS - How to get form value from multiple child component

I know this might have some similar questions but I don't seem to able to find the solution for my situation.
I have a form that will be submitted with the content of the child component, the child component is appended onClick and can be appended infinitely. How can I get the value from all the child component, and to post it.
This is index.js
class ListOfProducts extends React.Component {
constructor()
{
super();
this.appendChild = this.appendChild.bind(this);
this.state = {
children: [],
}
}
appendChild() {
this.setState({
children: [
...this.state.children, <NewComponent/>
]
});
}
render() {
return (
<form>
<div>
<pre><h2 className="h2"> Sales Order</h2></pre>
<div className="box" style={{height: '520px', width: '1300px', position: 'relative', overflow: 'auto', padding: '0'}}>
<div style={{height: '1000px', width: '1000px', padding: '10px'}}>
<div>
{this.state.children.map(child => child )}
</div>
</div>
</div>
<button className="addbut" onClick={() => this.appendChild()}>Add Items</button>
</div>
</form>
)
}
}
This is partial code of NewComponent.JS
<select
name="sel"
className="sel"
value={this.state.selecteditems}
onChange={(e) =>
this.setState({selecteditems: e.target.value})}
>
{this.state.data.map(item =>
<option key={item.productID} value={item.unitPrice}>
{item.itemName}
</option>
)}
</select>
{/*unit price*/}
<p>Unit Price: RM {this.state.selecteditems} </p>
{this.state.selecteditems.length ? (
<p>Quantity: </p>
) : null }
{/*button to add quantity*/}
{this.state.selecteditems.length ? (
<button onClick={this.addPro}> + </button>
) : null }
{/*textbox for quantity*/}
{this.state.selecteditems.length ? (
<input type="text" ref="quan" placeholder="Quantity"
value={this.state.quantity}
onChange={(e) =>
this.setState({quantity: e.target.value})}
>
</input>
) : null }
{/*button to decrease quantity}*/}
{this.state.selecteditems.length ? (
<button onClick={this.decPro}> - </button>
) : null }
{/*subtotal*/}
{this.state.selecteditems.length ? (
<p>Sub Total: RM {this.state.subtot} </p>
) : null }
Thanks in advance!
Quick and dumb: add callback like this
<NewComponent onChange={this.onNewComponentChange}/>
(and implement calling of this onChange callback at every change at NewComponent of course)
There are two ways I can think of getting the value from the child component -
Have a state management system (something like redux) which can actually store the data of all the child components. As and when the child component's data changes, it should be synced to the store and the same can be used by the parent to post the data on submit.
Assign ref to each of the child component when it gets appended. save those ref values in a array. Iterate through those references on submit of the form and call some specific getter function of child component to give you its data.
Preferred way is the first method.

Hiding An Element While User Input Doesn't Exist

My goal is to hide one of my divs or all my p tags until user input actually exists. You can see my attempt below which included a method to change the value of my div state to true or false and whether it's true or false, adjust the display to block or none whether or not the user has inputted anything.
I understand that it would be simple to apply this to a button of some sort but my goal here is to allow React to re-render the div or p elements once the user has typed something in.
My vision was to measure the user input's length, and if it was greater than 0, show my div or p tags.
Within my render section of my code, you'll see a div with three p tags inside. I want those p tags, or even the entire div (if it's easier) to not show until the user starts typing something within the input box.
import React from "react";
class UserInput extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
showElements: false
};
}
handleChange = event => {
this.setState({ value: event.target.value });
};
badRobot = () => {
const newInput = this.state.value;
let badInput = "BLA"
.repeat(newInput.length / 3 + 1)
.substring(0, newInput.length);
return badInput;
};
hideElements = () => {
const userValueLength = this.state.value;
if (userValueLength.length !== 0) {
console.log("it worked");
this.setState({ showElements: true });
}
};
render() {
return (
<div>
<form>
<label>
<p>Say Anything</p>
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
</label>
</form>
<div style={{ display: this.state.showElements ? "block" : "none" }}>
<h3>Good Robot</h3>
<p>I hear you saying {this.state.value}. Is that correct?</p>
<h3>Bad Robot</h3>
<p>I hear you saying {this.badRobot()}. Is that correct?</p>
<h3>Kanyebot 5000</h3>
<p>I'm gonna let you finish but Beyonce is {this.state.value}.</p>
</div>
</div>
);
}
}
export default UserInput;
Checking if the value string differs from the empty string sounds like a good condition for showing the div.
Instead of keeping a boolean in state you could check the value directly in the render method.
class UserInput extends React.Component {
state = {
value: ""
};
handleChange = event => {
this.setState({ value: event.target.value });
};
render() {
const { value } = this.state;
const showDiv = value !== "";
const badInput = "BLA"
.repeat(value.length / 3 + 1)
.substring(0, value.length);
return (
<div>
<form>
<label>
<p>Say Anything</p>
<input
type="text"
value={value}
onChange={this.handleChange}
/>
</label>
</form>
<div style={{ display: showDiv ? "block" : "none" }}>
<h3>Good Robot</h3>
<p>I hear you saying {value}. Is that correct?</p>
<h3>Bad Robot</h3>
<p>I hear you saying {badInput}. Is that correct?</p>
<h3>Kanyebot 5000</h3>
<p>I'm gonna let you finish but Beyonce is {value}.</p>
</div>
</div>
);
}
}
ReactDOM.render(<UserInput />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can do conditional rending.
class UserInput extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
showElements: false
};
}
handleChange = (event) => {
const value = event.target.value;
const showElements = value.length > 0 ? true: false;
this.setState({showElements, value});
}
badRobot = () => {
const newInput = this.state.value;
let badInput = 'BLA'.repeat(newInput.length / 3 + 1).substring(0, newInput.length)
return badInput
}
hideElements = () => {
const userValueLength = this.state.value
if (userValueLength.length !== 0) {
console.log("it worked");
this.setState({showElements: true})
}
}
render(){
return(
<div>
<form>
<label>
<p>Say Anything</p>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
</form>
{
this.state.showElements ?
(
<div>
<h3>Good Robot</h3>
<p>I hear you saying {this.state.value}. Is that correct?</p>
<h3>Bad Robot</h3>
<p>I hear you saying {this.badRobot()}. Is that correct?</p>
<h3>Kanyebot 5000</h3>
<p>I'm gonna let you finish but Beyonce is {this.state.value}.</p>
</div>
): null
}
</div>
)
}
}

How to fix error of hiding and showing <div> in React

I am working on a project and i want to display a hidden <div> below another <div> element using an event handler but when i click the icon that is meant to display the div, the whole page becomes blank
This is image I want:
This is what i get
I have tried to check through the internet for some places where i could get the solution. Well i found something similar to what i had done but the error still happens for me.
class PostItTeaser extends Component {
state = {
postIt: false,
moreIt: false,
}
togglePostIt = e => {
e ? e.preventDefault() : null
this.setState({ postIt: !this.state.postIt })
}
_toggle = e => {
e ? e.preventDefault() : null
this.setState({
moreIt: !this.state.moreIt,
})
}
Child = () => <div className="modal">Hello, World!</div>
render() {
let { postIt } = this.state
let { moreIt } = this.state
let {
type,
group,
disabled,
session: { id, username },
} = this.props
return (
<div>
<div
className="post_it inst"
style={{ marginBottom: type == 'group' && 10 }}
>
<img src={`/users/${id}/avatar.jpg`} alt="Your avatar" />
<div className="post_teaser">
<span
className="p_whats_new"
onClick={disabled ? null : this.togglePostIt}
>
What's new with you, #{username}? #cool
</span>
<span className="m_m_exp" data-tip="More" onClick={this._toggle}>
<MaterialIcon icon="expand_more" />
</span>
</div>
</div>
{moreIt && <Child />}
{postIt && (
<PostIt back={this.togglePostIt} type={type} group={group} />
)}
</div>
)
}
}
From skimming through the code I believe you need to bind the scope, since the function you're calling is using this.setState, it needs this to be the react component, not the event you're listening to:
onClick={this._toggle.bind(this)}
You can also bind the functions scope in the constructor. Or, a less memory performant & ugly way:
onClick={() => { this._toggle(); } }

React list choosing option

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

How to get DOM element within React component?

I'm rendering multiple of the same component, each with their own tooltip. Can I write code that will only look within the HTML of each component, so I'm not affecting all the other tooltips with the same class name? I'm using stateless components. Here is the code:
OptionsComponent.js:
import React from 'react';
const OptionsComponent = () => {
const toggleTooltip = event => {
document.getElementsByClassName('listings-table-options-tooltip').classList.toggle('tooltip-hide');
event.stopPropagation();
};
return (
<div className="inline-block">
<span onClick={toggleTooltip} className="icon icon-options listings-table-options-icon"> </span>
<div className="tooltip listings-table-options-tooltip">
Tooltip content
</div>
</div>
);
};
Backbone.js has something like this, allowing you to scope your document query to begin within the view element (analogous to a React component).
With React, you don't want to modify the DOM. You just re-render your component with new state whenever something happens. In your case, since you want the OptionsComponent to track its own tooltip state, it really isn't even stateless. It is stateful, so make it a component.
It would look something like this:
class OptionsComponent extends React.Component {
state = {
hide: false
};
toggleTooltip = (ev) => this.setState({ hide: !this.state.hide });
render() {
const ttShowHide = this.state.hide ? "tooltip-hide" : "";
const ttClass = `tooltip listings-table-options-tooltip ${ttShowHide}`;
return (
<div className="inline-block">
<span onClick={this.toggleTooltip} className="icon icon-options listings-table-options-icon"> </span>
<div className={ttClass}>
Tooltip content
</div>
</div>
);
// Alternatively, instead of toggling the tooltip show/hide, just don't render it!
return (
<div className="inline-block">
<span onClick={this.toggleTooltip} className="icon icon-options listings-table-options-icon"> </span>
{/* do not render the tooltip if hide is true */}
{!this.state.hide &&
<div className="tooltip listings-table-options-tooltip">
Tooltip content
</div>
}
</div>
);
}
}
You should use refs.
Slightly modified from React docs:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.focus = this.focus.bind(this);
}
focus() {
var underlyingDOMNode = this.textInput; // This is your DOM element
underlyingDOMNode.focus();
}
render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in this.textInput.
return (
<div>
<input
type="text"
ref={(input) => this.textInput = input} />
<input
type="button"
value="Focus the text input"
onClick={this.focus}
/>
</div>
);
}
}
A comfortable approach would be modifying your toggleTooltip method this way:
...
const toggleTooltip = event => {
event.target.parentNode.querySelector('.tooltip').classList.toggle('tooltip-hide');
};
...
I would however recommend having a state to represent the tooltip displaying or not.
With https://github.com/fckt/react-layer-stack you can do alike:
import React, { Component } from 'react';
import { Layer, LayerContext } from 'react-layer-stack';
import FixedLayer from './demo/components/FixedLayer';
class Demo extends Component {
render() {
return (
<div>
<Layer id="lightbox2">{ (_, content) =>
<FixedLayer style={ { marginRight: '15px', marginBottom: '15px' } }>
{ content }
</FixedLayer>
}</Layer>
<LayerContext id="lightbox2">{({ showMe, hideMe }) => (
<button onMouseLeave={ hideMe } onMouseMove={ ({ pageX, pageY }) => {
showMe(
<div style={{
left: pageX, top: pageY + 20, position: "absolute",
padding: '10px',
background: 'rgba(0,0,0,0.7)', color: '#fff', borderRadius: '5px',
boxShadow: '0px 0px 50px 0px rgba(0,0,0,0.60)'}}>
“There has to be message triage. If you say three things, you don’t say anything.”
</div>)
}}>Yet another button. Move your pointer to it.</button> )}
</LayerContext>
</div>
)
}
}

Categories

Resources