I'm trying to make a piece of code a little more efficient. The concept is that when you focus the textarea, you are shown a paragraph tag that tells you the characters remaining. Currently i have:
import React, { Component } from "react";
class Idea extends Component {
state = {
showCharactersRemaining: false
};
calculateRemaining() {
return 15 - this.props.body.length;
}
onTextAreaFocus = () => {
this.setState(state => {
return { showCharactersRemaining: true };
});
};
onBlur = () => {
this.setState(state => {
return { showCharactersRemaining: false };
});
};
render() {
const { title, body } = this.props;
const { showCharactersRemaining } = this.state;
return (
<div className="idea">
<input type="text" name="title" value={title} />
<textarea
rows="3"
name="body"
maxlength="15"
value={body}
onFocus={this.onTextAreaFocus}
onBlur={this.onBlur}
/>
{showCharactersRemaining && (
<p>{this.calculateRemaining()} characters remaining.</p>
)}
</div>
);
}
}
export default Idea;
Which works but also relies on having two methods attached to said textarea to make it work. Is there a smarter way in react of doing this?
CSS can handle it for you, removing the necessity for state and either event handler. Run the snippet to see it work (I removed the logic for counting characters to keep this example simple)
.charcount {
display: none;
}
textarea:focus+.charcount {
display: block;
}
<div className="idea">
<textarea rows="3" name="body" maxlength="15" value="foo"></textarea>
<p class="charcount">XX characters remaining.</p>
</div>
Related
import React, { Component, createElement } from "react";
export default class TodoList extends Component {
state = {
todo: [],
inputValue: "",
};
addTodo = () => {
this.setState({ todo: [...this.state.todo, this.state.inputValue] });
// I want to insert separate paragraph tags (todos from this.state.todo) into the list element here
};
handleKeyDown = (e) => {
if (e.keyCode === 13) {
if (this.state.inputValue === "") return;
this.addTodo();
}
};
handleChange = (e) => {
this.setState({ inputValue: e.target.value });
};
render() {
return (
<div className="list"></div>
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
);
}
}
I am creating a Todo List where the user types into an input, and the todo is then inserted into the div with class list element. I'm new to React so I don't know the best way I should go about doing this.
Any help is appreciated, thanks!
You can map the array, inside the .list div, and render each todo item, by wrapping it in p tag. I have added a button element, to handle the addTodo() function.
Also, you may want to move the .list div, below the input.
import React, { Component, createElement } from "react";
export default class TodoList extends Component {
state = {
todo: [],
inputValue: ""
};
addTodo = () => {
this.setState({ todo: [...this.state.todo, this.state.inputValue] });
// I want to insert separate paragraph tags (todos from this.state.todo) into the list element here
};
handleKeyDown = (e) => {
if (e.keyCode === 13) {
if (this.state.inputValue === "") return;
this.addTodo();
}
};
handleChange = (e) => {
this.setState({ inputValue: e.target.value });
};
render() {
return (
<div>
<div className="list">
{this.state.todo.map((todo) => {
return <p>{todo}</p>;
})}
</div>
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
<button onClick={this.addTodo}>Add Todo</button>
</div>
);
}
}
Codesandbox link - https://codesandbox.io/s/busy-pascal-txh55?file=/src/App.js
class TodoList extends Component {
state = {
todo: [],
inputValue: "",
};
addTodo = () => {
// I want to insert separate paragraph tags (todos from this.state.todo) into the list element here
// Hint: when you want to add a todo, you simply set input value to empty here.
this.setState({
todo: [...this.state.todo, this.state.inputValue],
inputValue: "",
});
};
handleKeyDown = (e) => {
if (e.keyCode === 13) {
if (this.state.inputValue === "") return;
this.addTodo();
}
};
handleChange = (e) => {
// Hint: I prefer adding "...this.state" every time before updating.
this.setState({ ...this.state, inputValue: e.target.value });
};
render() {
return (
<>
{
// Hint: use React fragment ("<> ... </>") when there's
more than one element in the first level.
}
<div className="list">
{
// Hint: Adding the current list with map in here
}
<ul>
{this.state.todo.map((t, i) => (
<li key={i}>{t}</li>
))}
</ul>
</div>
{
// I would prefer adding it inside a form element and instead of onKeyDown, use onSubmit on the form
// (on enter it will submit automatically, but you will have to do an e.preventDefault() to not refresh the page).
}
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
</>
);
}
}
This is a working example with a few comments. Also setState runs asyncrounously so it's not a good idea to run multiple one at the same time. Hope this helps.
Using map like TechySharnav mentioned is a quick way of doing it. But if you need to do some more complex operations/layout stuff, then writing a custom function and calling it in the render jsx might be cleaner. So, you could have a function like:
renderItems() {
var rows = []
this.state.todo.forEach((elem, idx) => {
// for example
rows.push(
<p>{elem}</p>
)
});
return rows;
}
Then call it inside render:
//...
<div className="list">
{this.renderItems()}
</div>
//...
js map will certainly solve the problem.
this small snippet for printing the list,
render() {
return (
<div className="list">
{ this.state.todo.map((item) => {
return <p>{item}</p>
})}
</div>
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
);
}
Ok so here's the deal I'm working in react and I got this website me and team are working on for a senior project.
So i've got this functioning login page with its code below called component.jsx its got all i need including text inputs for customers. however We want to add validation to it so that when the text is empty(like if a customer just forget and hit submit) it would send a message letting them know.
essentially Im asking for a conditional only issue is that jsx dosent rly do conditionals like IF/else
so I wanted to do it in a sepereate js file (validate.js) and I've looked everywhere I cant find anything about how to make an effective function/class or whatever for an input in a JS file then impart that into the jsx file where the code is.
So right now Im looking fr guidance on how to achieve that goal and was wondering if anybody had any ideas(websites with this, similar questions or just know the answer).
If you do just know the answer on how to do this please keep it simple that way I can look back at it latter to understand incase I miss you
currently the JS file(validate.js) I have is empty
login website
JSX code
Here is a little exemple, there is plenty of way to manage errors, here is one.
const Login = () => {
// our values state
const [values, setValues] = React.useState({email: "", password: ""});
// our errors state
const [errors, setErrors] = React.useState({email: "", password: ""});
const handleChange = (event) => {
// onChange return an event object containing the name of the input and its current value
const { name, value } = event.target;
// update values in state
setValues({...values, [name]: value})
// reset error for that input on change
if(errors[name].length > 0) setErrors({ ...errors, [name]: "" })
}
const handleSubmit = (event) => {
// on submit we prevent default behavior
event.preventDefault();
// check if our inputs are valid
const {hasErrors, errors} = validateInputs();
if(!hasErrors) {
// input are not empty
// you can do whatever you want here
} else {
// there is errors, update our state to display them on next render
setErrors(errors);
}
}
const isEmpty = (value) => {
return value === "";
}
const validateInputs = () => {
// iterate each input value
const errors = Object.keys(values).reduce((errors, current) => {
// you can add any check here
if(isEmpty(values[current])) {
errors[current] = `${current.charAt(0).toUpperCase() + current.slice(1)} is required.`;
}
return errors;
}, {});
const hasErrors = Object.keys(errors).length > 0;
return { errors, hasErrors };
}
return (
<form onSubmit={handleSubmit}>
<label>Email</label>
<input type="text" placeholder="Email" name="email" onChange={handleChange} />
{ errors.email.length > 0 && ( <div className="error-message" >{errors.email}</div> ) }
<label>Password</label>
<input type="password" placeholder="Password" name="password" onChange={handleChange} />
{ errors.password.length > 0 && ( <div className="error-message" >{errors.password}</div> ) }
<button type="submit" >Log in</button>
</form>
)
};
ReactDOM.render(
<Login />,
document.getElementById("root")
);
form {
display: flex;
flex-direction:column;
}
input, button {
width: 200px;
margin: 0.3rem;
}
.error-message {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The simple and easiest way is to add required:{true} into your attribute.
example here:
<Form.Control
type="text"
placeholder="Enter Skills"
value={Skill}
required={true}
onChange={(e) => setSkill(e.target.value)}
></Form.Control>
I have two (or more) HTML elements and I want to execute a callback whenever any of them loses focus except when the focus is moved between them. In other words I want to treat those elements like a single one from the focus point of view and execute a group-onBlur callback.
Using React I tried to keep track of the the focus of each element on a state, but when moving the focus from element a to element b, onBlur on a always happens before onFocus on b. Also since the state update may be async I'm not sure about the consistency of the state.
You can store refs to each of the inputs that you want to "share" focus and then check if either is focused in your onBlur function before taking any actions. Note, however that the focused element is the body if you check immediately when the blur happens. To get around this you can wrap your onBlur logic in a setTimeout call with a delay of 0ms.
Here's an example:
function MyComponent() {
const aRef = React.useRef(null);
const bRef = React.useRef(null);
function onBlurGroup() {
window.setTimeout(() => {
if (document.activeElement === aRef.current || document.activeElement === bRef.current) {
console.log("a or b is focused");
return;
}
console.log("focus lost from group");
// do blur handling
}, 0)
}
return (
<div>
<input ref={aRef} name="a" type="text" onBlur={onBlurGroup}/>
<input ref={bRef} name="b" type="text" onBlur={onBlurGroup}/>
<input name="other" type="text"/>
</div>
);
}
And a functioning sample (using a class-based component since Stack doesn't support hooks yet):
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.aRef = React.createRef();
this.bRef = React.createRef();
}
onBlurGroup = () => {
window.setTimeout(() => {
if (document.activeElement === this.aRef.current || document.activeElement === this.bRef.current) {
console.log("a or b is focused");
return;
}
console.log("focus lost from group");
// do stuff
}, 0)
}
render() {
return (
<div>
<input ref={this.aRef} name="a" placeholder="A" type="text" onBlur={this.onBlurGroup}/>
<input ref={this.bRef} name="b" placeholder="B" type="text" onBlur={this.onBlurGroup}/>
<input name="other" placeholder="Other" type="text"/>
</div>
);
}
}
ReactDOM.render(<MyComponent/>, document.querySelector("#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"/>
Typically you pull it off with a timeout and wait for a few milliseconds before firing text step. Quick and dirty react code.
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
blurTimer: null
}
this.handleBlur = this.handleBlur.bind(this);
this.handleFocus = this.handleFocus.bind(this);
}
clearTimer() {
if (this.state.blurTimer) {
window.clearTimeout(this.state.blurTimer);
this.setState({ blurTimer: null });
}
}
processIt() {
console.log('Process it!!!!');
}
handleBlur() {
this.clearTimer();
const blurTimer = window.setTimeout(() => this.processIt(), 100);
this.setState({ blurTimer });
}
handleFocus() {
this.clearTimer();
}
render() {
return (
<div>
<h2>foo</h2>
<input type="text" onBlur={this.handleBlur} onFocus={this.handleFocus} />
<input type="text" onBlur={this.handleBlur} onFocus={this.handleFocus} />
</div>
)
}
}
ReactDOM.render(<Test />, document.querySelector("#app"))
<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="app"></div>
I feel like I've done this a million times, but possibly not with a mapping function. I have a collection of activities that I'm creating checkboxes with the code is as follows (__map is coming from Lodash by the way):
<div className="activity-blocks">
{
__map(this.state.activities, (activity, i) => {
return (
<div key={ i } className="single-activity">
<input id={ `register-activity-${ i }` }
type="checkbox" className="register-multiselect"
name="main_activity" checked={ !!activity.checked }
onChange={ (e) => this.toggleActivity(e, activity.id) }
/>
<label htmlFor={ `register-activity-${ i }` }>{ activity.name }</label>
</div>
)
})
}
My onChange handler looks like this:
toggleActivity(e, activityId) {
let activities = { ...this.state.activities };
__forEach(this.state.activities, (activity) => {
if (activityId === activity.id) {
activity = __assign(activity, { checked: e.target.checked });
return false;
}
});
this.setState({ activities: activities });
}
The handler works just fine, it's for reference mostly. My issue is that the label is not firing the handler. I have tested only the checkbox input with no label and it fires off the handler. Does anyone know why the label is not doing so?
More info: I've had this problem before but it usually turns out to be an id mismatch or something simple like that. I don't believe so this time.
Edit
I have deleted pretty much all the code from my file and incorporated Slawa's answer below and it still doesn't behave as expected. Here is the code including all imports, the class structure and all unnecessary code stripped out, including styles:
import React from 'react';
import __forEach from 'lodash/forEach';
import __assign from 'lodash/assign';
import __map from 'lodash/map';
export default class RegisterActivities extends React.Component {
constructor(props) {
super(props);
this.state = {
activities: [
{
id: 1,
name: 'label-1'
},
{
id: 2,
name: 'label-2'
},
{
id: 3,
name: 'label-3'
}
],
}
}
toggleActivity(e, activityId) {
const { activities } = this.state;
const updated = activities.map((activity) => {
if (activity.id === activityId){
return {
...activity,
checked: e.target.checked
}
}
return activity;
});
}
render() {
return (
<div>
<p>what do you do?</p>
<div>
{
__map(this.state.activities, (activity, i) => {
return (
<div key={ i }>
<input id={ `register-activity-${ i }` }
type="checkbox"
name="main_activity" checked={ !!activity.checked }
onChange={ (e) => this.toggleActivity(e, activity.id) }
/>
<label htmlFor={ `register-activity-${ i }` }>{ activity.name }</label>
</div>
)
})
}
</div>
</div>
);
}
}
Edit2
I'm playing around and decided to replce onChange with onClick and now I'm able to click the actual checkbox to hit toggleActivity. Does anyone have an idea on why onChange isn't firing?
The answer to my question was found on this SO post. It turns out that my label's onClick event (behind the scenes of html/React, not coded myself) was bubbling up to the parent instead of handling the input. I needed to tell the label to stop propagating by doing onClick={ e => e.stopPropagation() }.
I think, you have an error in your toggleActivity function, activities is array and try to convert it to object, you can find my full solution of this problem here: https://codesandbox.io/s/wqyolw50vl
toggleActivity(e, activityId) {
const { activities } = this.state;
const updated = activities.map( activity => {
if (activity.id === activityId){
return {
...activity,
checked: !activity.checked
}
}
return activity;
})
this.setState({ activities: updated });
}
I was having this same issue. It was driving me mad, but what my team and I discovered is there must be some bug with the htmlFor they tell you to use in the docs. We removed it from our checkbox prop and now it functions as expected with no weird side effects. I'm opening up a GH issue on it. I'll edit later with the link/update
Code that didn't work:
<label htmlFor={`${label}-checkbox`} className={checked ? "checked data-filter-checkbox" : "data-filter-checkbox"} >
<input id={`${label}-checkbox`} type="checkbox" onChange={(event) => handleCheck(event)} value={label} name={label} checked={checked} />
{label}
<span>{count}</span>
</label>
Code I'm using now:
<label className={checked ? "checked data-filter-checkbox" : "data-filter-checkbox"} >
<input id={`${label}-checkbox`} type="checkbox" onChange={(event) => handleCheck(event)} value={label} name={label} checked={checked} />
{label}
<span>{count}</span>
</label>
I wonder why following snippet is not updating DOM
const { hyper, wire } = hyperHTML;
class Input extends hyper.Component {
get defaultState() {
return { error: false };
}
onclick() {
this.setState(prev => ({ error: !prev.error }));
}
render() {
const textField = wire()
`
<div onconnected=${this} ondisconnected=${this}>
<input type="text" value="foo" onclick=${this}>
<label>bar</label>
</div>
`;
let finalNode;
if (this.state.error) {
finalNode = this.html `
<div>
${textField}
<p>
some name
</p>
</div>
`;
} else {
finalNode = this.html `
<div>
${textField}
</div>
`;
}
return finalNode;
}
}
document
.getElementById('root')
.appendChild(new Input().render());
I would expect it would it to render textField first and upon click to render p element along. I can see that render call is made but resulting element does not end up in DOM.
With hyper.Component the best way to go is to make the content conditional, not the root as a whole.
In order to do that, you can simply use a ternary, or an array.concat(...) or any other way that will update the component.
It's like the difference between updating an instance and replacing it.
From the inside, the component has no power to replace itself on its parent, unless you explicitly do so.
However, this example is easier than it looks like, and you can see it working on this Code Pen.
Moreover, if you don't specify onconnected and ondisconnected callbacks in the class, there's no need to specify them on the node.
const { hyper, wire } = hyperHTML;
class Input extends hyper.Component {
get defaultState() { return { error: false }; }
onclick() {
this.setState(prev => ({ error: !prev.error }));
}
render() { return this.html`
<div>
<div>
<input type="text" value="foo" onclick=${this}>
<label>bar</label>
</div>
${this.state.error ?
wire(this, ':extra')
`<p> some name </p>` :
null
}
</div>`;
}
}
document.body.appendChild(new Input().render());
I hope this helped.