React JS range slider - using an array for the value? - javascript

I'm wondering how you'd go about getting the value of an index in an array using an input[type="range"] in React, similar to this example?
What I'm trying to do is this: pass in a range of values and be able to print out those values by using the index of the array.
As you'll see from the example code below, I am initially rendering the value I want (in this case, 'Apples') but then when I use the slide it then starts rendering the index of the array, instead of the values.
Here's what I've got so far:
class RangeSlider extends React.Component {
// constructor
constructor(props) {
super(props);
this.state = {
value: props.value[0]
};
}
handleChange(event, index) {
const { value } = this.state;
this.setState({ value: event.target.value});
}
render() {
const { value } = this.state;
const { label } = this.props;
return (
<div className="mb4">
<label className="f4 mt0">
{label} <b className="fw7 pl1">{value}</b>
</label>
<input
className="w-100 appearance-none bg-transparent range-slider-thumb-custom"
type="range"
min={0}
max={this.props.value.length - 1}
step={1}
value={value}
onChange={this.handleChange.bind(this)}
/>
</div>
);
}
}
window.onload = () => {
ReactDOM.render(
<RangeSlider
label={"I would like some "}
value={["Apples", "Oranges", "Pears"]} />,
document.getElementById("main"));
};
Link to a Codepen.

The only problem you were having is that on initial load, your state object was set to access the value in the array correctly. However, everytime the handleChange method fires, it overwrites the state with just an integer, and thus does not do what you are expecting.
If you instead just set the "value" property in your state object to a default value of "0", you can just track the index, and change one more line in your code, and it should work just fine.
First change your state to look like this:
this.state = {
value: 0
};
Next, change to this inside your jsx body:
{label} <b className="fw7 pl1">{this.props.value[value]}</b>
This way, you are always going to print out a value, and not an integer to the screen. I think this results in you having to add far less code.
Working Codepen.

Here is the updated code
import React from 'react'
class Range extends React.Component {
// constructor
constructor(props) {
super(props)
this.state = {
value: 0
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({ value: this.props.value[event.target.value]})
}
render() {
const { value } = this.state
const { label } = this.props
return (
<div className="mb4">
<label className="f4 mt0">
{label} <b className="fw7 pl1">{value}</b>
</label>
<input
className="w-100 appearance-none bg-transparent range-slider-thumb-custom"
type="range"
min={0}
max={this.props.value.length - 1}
step={1}
onChange={this.handleChange}
/>
</div>
)
}
}
export default Range

Related

React - state doesn't update in class component even though the message printed on the screen changes

I have an App component which holds an input. Every time I type in the input, the value of the input updates and a Message component prints a different message, depending on how long the input is. At the same time, a third component called Character print to the screen every letter of the string, individually. The desired behavior is that when I click on one of the letters, it gets removed from the string, the new string is displayed on the screen and the input also gets updated with the new string.
I used some console.logs to debug and everything seems to be happening as expected, until the last step when I am trying to update the state, but for some reason, it doesn't get updated.
class App extends React.Component {
constructor(props) {
super(props);
this.state = { text: "" };
}
render() {
const handleUpdateText = event => {
this.setState({
text: event.target.value
});
};
const inputLength = this.state.text.length;
const toArray = this.state.text.split("");
const handleDeleteLetter = index => {
toArray.splice(index, 1);
console.log(toArray);
const updatedArray = toArray.join("");
console.log(updatedArray);
this.setState({ text: updatedArray });
console.log(this.state.text);
};
return (
<>
<input type="text" onChange={handleUpdateText} />
<Message inputLength={inputLength} />
{toArray.map((letter, index) => (
<Character
key={index}
theLetter={letter}
deleteLetter={() => handleDeleteLetter(index)}
/>
))}
</>
);
}
}
class Message extends React.Component {
render() {
const { inputLength } = this.props;
let codeToPrint = "The text is long enough!";
if (inputLength <= 5) {
codeToPrint = "The text is not long enough!";
}
return <p>{codeToPrint}</p>;
}
}
class Character extends React.Component {
render() {
const { theLetter, deleteLetter } = this.props;
return (
<div
style={{
display: "inline-block",
padding: "16px",
textAlign: "center",
margin: "16px",
backgroundColor: "tomato"
}}
onClick={deleteLetter}
>
{theLetter}
</div>
);
}
}
The complete code is here:
https://codesandbox.io/s/react-the-complete-guide-assignment-2-list-conditionals-e6ty6?file=/src/App.js:51-1007
I don't really understand what am I doing wrong and I have a feeling is somehow related to a life cycle method. Any answer could help. Thank you.
State is getting updated, you just need to pass value prop to the input so that input's value can be in sync with your state
<input type="text" value={this.state.text} onChange={handleUpdateText} />
And you're not seeing updated state just after setting it because setState is asynchronous. That's why the console statement just after the setState statement shows the previous value.
Also you should move functions out of your render method, because everytime your component re-renders, new functions would be created. You can declare them as class properties and pass their reference
handleUpdateText = event => {
this.setState({
text: event.target.value
});
};
render() {
.......
return (
<>
<input type="text" onChange={this.handleUpdateText} />

Clearing state and input values between parent and child components in React

Two part question here: First, can anyone explain to me why this.state.taskName and this.state.taskBody and their corresponding inputs aren't clearing after submitting the form? In handleSubmit() I'm using this.setState() to set their states to an empty string, but it doesn't seem to be working. It also wont let me submit more than once, which I suspect might have to do with the state not clearing.
Second, what would be the best way to push a task with multiple key-value pairs into the this.state.tasks array? I tried storing taskName and taskBody as an object in state, and also tried pushing the them into an object and then displaying them, but couldn't get it to work.
Here are parent, child, & sibling files:
import React, { Component } from 'react';
import Task from './Task/Task';
import NewTaskForm from './NewTaskForm/NewTaskForm';
class Board extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.state = {
tasks: [],
taskName: '',
taskBody: ''
};
}
handleSubmit(e) {
e.preventDefault();
let updatedTasks = this.state.tasks;
let taskName = this.state.taskName;
let taskBody = this.state.taskBody;
updatedTasks.push(taskName, taskBody);
let updatedName = '';
let updatedBody = '';
this.setState({ tasks: updatedTasks, taskName: updatedName, taskBody: updatedBody });
};
handleChange(e) {
this.setState({ [e.name]: e.value });
}
render() {
return (
<div>
<NewTaskForm
onSubmit={this.handleSubmit}
onChange={this.handleChange}
/>
<Task
tasks={this.state.tasks}
/>
</div>
);
}
}
export default Board;
import React, { Component } from 'react';
class NewTaskForm extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(e.target);
}
render() {
return (
<form onSubmit={this.props.onSubmit}>
<label>Task Name</label>
<input
name="taskName"
required type="text"
value={this.props.taskName}
onChange={this.handleChange}
placeholder="Enter a task name"
/>
<label>Task Body</label>
<input
name="taskBody"
required type="text"
value={this.props.taskBody}
onChange={this.handleChange}
placeholder="Enter a task body"
/>
<button
type="submit"
className="btn btn-default"
>Add Task
</button>
</form>
);
}
}
export default NewTaskForm;
import React, { Component } from 'react';
class Task extends Component {
render() {
let taskList = this.props.tasks.map((task, i) => {
return (
<li key={i}>
{task}
</li>
);
});
return (
<ul>
{taskList}
</ul>
)
}
}
export default Task;
Thanks!
To address your first question, the reason the inputs aren't clearing is because you are not passing the taskName and taskBody values as props to <NewTaskForm />. The inputs aren't being controlled by React since NewTaskForm isn't receiving them, so they are currently entirely user-controlled. Add them and you'll see them clear after submitting the form.
The best way to hold a taskName/taskBody pair in state is as you said: an object. In your TaskComponent you'll need to change the mapping logic to work with an object, though, as well as make sure you push an object to this.state.tasks in Board. I've linked to a Fiddle that shows the changes I made: https://jsfiddle.net/0z89Lcpw/.
Specifically the changes I made versus your code are:
modified line 21 to push an object with shape {taskName, taskBody}
added lines 37 and 38 to pass taskName and taskBody props to NewTaskForm
changed line 95 (old: line 93) to pull taskName and taskBody off of each task and present both--of course you can present these pieces of data in quite a few different ways to suit your presentational purposes.
Please see your altered code below. Ive added explanations beneath for the main alterations I've made :)
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
tasks: [],
taskName: '',
taskBody: ''
};
}
handleSubmit(e) {
e.preventDefault();
let tasks = this.state.tasks;
let taskName = this.state.taskName;
let taskBody = this.state.taskBody;
tasks.push({taskName, taskBody});
this.setState({tasks, taskName: '', taskBody: ''});
};
handleChange(e) {
const name = e.target.name;
const value = e.target.value;
this.setState({[name]: value});
}
render() {
return (
<div>
<NewTaskForm
taskName={this.state.taskName}
taskBody={this.state.taskBody}
onSubmit={(e) => this.handleSubmit(e)}
onChange={(e) => this.handleChange(e)}
/>
<Tasks
tasks={this.state.tasks}
/>
</div>
);
}
}
class NewTaskForm extends React.Component {
render() {
return (
<form onSubmit={this.props.onSubmit}>
<label>Task Name</label>
<input
name="taskName"
required type="text"
value={this.props.taskName}
onChange={(e) => this.props.onChange(e)}
placeholder="Enter a task name"
/>
<label>Task Body</label>
<input
name="taskBody"
required type="text"
value={this.props.taskBody}
onChange={(e) => this.props.onChange(e)}
placeholder="Enter a task body"
/>
<button type="submit" className="btn btn-default">Add Task</button>
</form>
);
}
}
class Tasks extends React.Component {
render() {
let taskList = this.props.tasks.map((task, i) => {
return (
<li key={i}>
<b>{task.taskName}</b><br/>
{task.taskBody}
</li>
);
});
return (
<ul>
{taskList}
</ul>
)
}
}
Passed through taskName and taskBody as props to your NewTaskForm
component to use in their inputs.
You were pushing the new task to your updated task list incorrectly.
In your Task component you were not showing the properties of the
task, you were attempting to display the task object.
Working fiddle: https://jsfiddle.net/8sLw4phf/2/
I can see couple of issues with the way you have written the code. For starters, you are not passing in taskName and taskBody as props to NewTaskForm, as the component expects the value to be read from the props.
Not a good idea to mutate the state
As name and the body encompasses into a task, maintain a shape for it.
Check this code snippet - https://codesandbox.io/s/ov675m6r7y
I would try something like this:
handleSubmit(e) {
e.preventDefault();
const { taskName, taskBody } = this.state;
this.setState({
tasks: [...this.state.tasks, { taskName, taskBody }]
taskName: '',
taskBody: ''
});
};
This way you are not mutating your state and your array contains one object per task.

React: TypeError: Unable to get property 'handler' of undefined or null reference

I am new to React.js and cannot seem to get any event handlers to work. I get the above-referenced error anytime I make an event handler outside of the render function instead of as an annonymous inner class. Can someone point out what I'm doing wrong please?
class Checkboxes extends Component{
constructor(props) {
super(props);
this.state = { checked: [true, true],
frame: [values, values],
title: ['A', 'B'] };
this.handleChange= this.handleChange.bind(this);
}
handleChange(e) {
let index= e.target.id;
let newState = this.state.checked.slice();
newState[index] = !this.state.checked[index];
this.setState ({checked: newState});
}
render(){
const selectionBoxes = this.state.title.map(function(title, index) {
return (
<div className="w3-checkbox" id={index} onClick={this.handleChange}>
<input
type="checkbox"
label={'Division '+title}
value={this.state.checked[index]}
checked={!this.state.checked[index]}
onChange= {this.handleChange}
id={index} />
<label id={index} onClick={this.handleChange}>
{'Division '+ title}
</label>
</div>
);
});
const frameDisplay = this.state.frame.map(function(frame, index) {
return (
<div>
{this.state.checked[index]} ? null :
<DivFrame frameId={frame} width={this.state.width} height={this.state.height} title={this.state.title[index]} />
</div>
);
});
return (
{selectionBoxes}
);
}
};
export default Checkboxes;
It seems that, even though you may have bound the handleChange to this in the constructor, it is still not in scope within the this.state.title.map(function(title, index) {... function. I have fixed this problem by simply changing this.state.title.map(function(title, index) {... to this.state.title.map((title, index) => {...
This is the ES6 syntax and does the binding for me automatically.
Have a look here for a demo: https://codesandbox.io/s/py2zp8z1kj
Please note that for the sake of simplicity I have removed the code pertaining to the frames component since it's not really related to the problem and to cut down on the work required having to implement that component. To have the event handler working on that component as well though you will have to change the callback of the mapping function to the ES6 syntax as well.
Also, you will notice that in the return of the render() method, I have removed the curly braces because you do not need them since selectionBoxes is raw html and needs not to be evaluated. Actually evaluating it ({ selectionBoxes}) converts the value of selectionBoxes to an object which is NOT a valid react child. So rather put selectionBoxes as is in the render block.
In case you are not able to view the demo, this is how the code looks like:
class Checkboxes extends Component {
constructor(props) {
super(props);
this.state = {
checked: [true, true],
frame: [0, 1],
title: ['A', 'B']
};
}
handleChange(e) {
let index = e.target.id;
let newState = this.state.checked.slice();
newState[index] = !this.state.checked[index];
this.setState({ checked: newState });
}
render() {
const selectionBoxes = this.state.title.map( (title, index) => {
return (
<div className="w3-checkbox" id={index} onClick={this.handleChange}>
<input
type="checkbox"
label={'Division ' + title}
value={this.state.checked[index]}
checked={!this.state.checked[index]}
onChange={this.handleChange}
id={index} />
<label id={index} onClick={this.handleChange}>
{'Division ' + title}
</label>
</div>
);
});
return (
selectionBoxes
);
}
};
export default Checkboxes;

Controlled component input field onChange behaviour

I have a controlled component with an input field representing a day value. Since this day value can have 1-2 digits, e.g. one has to to delete 12 to enter 21.
Since the onChange react-event behaves like the input DOM-event, I can only delete or enter one digit and the event is fired, hence the whole model gets updated too early with day set to one after I deleted a digit.
<input
name={name}
type="number"
value={value}
onChange={(e) => { onChange(e.target.value) } }
/>
Thanks to defaultValue change does not re-render input I can handle this with an uncontrolled component input with an onBlur event. Using key ensures, that a new rendering happens, when the model is changed by other means:
<input
name={name}
type="number"
defaultValue={value}
key={value}
onBlur={(e) => { onChange(e.target.value); }}
/>
But honestly, this feels like a hack. How do you pros solve this scenario? Have I overlooked something simpler? Do you use a timeout-function before updating the model (thus waiting for complete user-input) aka debounce?
Why can't you use both onChange and onBlur ?
class NumberChooser extends React.Component{
constructor(props){
super(props);
this.state = {
fieldValue: props.value,
time: ''
}
}
onChange(e){
this.setState({fieldValue: e.target.value});
}
render(){
return (
<input
name={this.props.name}
type="number"
value={this.state.fieldValue}
//key={value} not sure what do with this
onChange={this.onChange}
onBlur={(e) => this.props.onChange(e.target.value)}
/>
);
}
}
export default NumberChooser;
Thanks to Andrew's input it came to me, that using local state could be a solution for my problem. Now the component is a class and not a functional component anymore. Still it feels a bit awkward to store the displayed value of a field locally just to be able to use onChange without midst-editing field updates coming from the main state. But it seems to be the way, if one wants to use controlled components with a single source of truth and I'll just consider the local state as UI-state ;-)
export default class NumberChooser extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = { value: this.props.value };
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.props.value) {
this.setState({ value: nextProps.value });
}
}
render() {
return (
<div className="col" name={`NumberChooser_${this.props.name}`} style={ isDebug ? debug.borderStyle : {} } >
<IncButton
onButtonClicked={() => this.props.onChange(this.state.value+1)}
/>
<input
name={this.props.name}
type="number"
value={this.state.value}
onChange={(e) => { this.setState({ value: e.target.value }); } }
onBlur={(e) => { this.props.onChange(e.target.value); }}
/>
<DecButton
onButtonClicked={() => this.props.onChange(this.state.value-1)}
/>
</div>
);
}
}

How to stop re-rendering all Childs in React render function when I change just one child value?

I have some Card (more than 10 Card component) in a Cards component and each Card has a form with more than 10 textField components. When I'm typing in textFields, It has delay between type and update value of textField. After spending more than 2 days, I found my problem. I think it's related to re-rendering all Childs (all Card components) when I set my value in statein value update... .
I want to know where am I wrong? If my codes is standard, is there any way to stop re-rendering all Childs after changing state for just one textField?
My codes are like as follow:
MainComponent:
export default class MainComponent extends Component {
constructor(props) {
super(props);
this.state = {
value : {}
};
}
static PropTypes = {
results: PropTypes.array.isRequired
};
handleChange(ref, e) {
this.state.value[ref] = e;
this.setState(this.state);
}
render() {
const { results } = this.props;
<Cards>
{
results.map((result, index) => {
var ref_taxtfield1 = result.id + "taxtfield1";
var ref_taxtfield2 = result.id + "taxtfield2";
var ref_taxtfield3 = result.id + "taxtfield3";
var ref_taxtfield4 = result.id + "taxtfield4";
var ref_taxtfield5 = result.id + "taxtfield5";
return <Card key={ result.id } style={ styles.card }>
<Form>
<div style={ styles.innerContainer }>
<Textfield
name="taxtfield1"
label="My Label 1"
ref={ref_taxtfield1}
onValueChange={this.handleChange.bind(this, ref_taxtfield1)}
value={this.state.value[ref_taxtfield1]}
/>
<Textfield
name="taxtfield2"
label="My Label 2"
ref={ref_taxtfield2}
onValueChange={this.handleChange.bind(this, ref_taxtfield2)}
value={this.state.value[ref_taxtfield2]}
/>
<Textfield
name="taxtfield3"
label="My Label 3"
ref={ref_taxtfield3}
onValueChange={this.handleChange.bind(this, ref_taxtfield3)}
value={this.state.value[ref_taxtfield3]}
/>
<Textfield
name="taxtfield4"
label="My Label 4"
ref={ref_taxtfield4}
onValueChange={this.handleChange.bind(this, ref_taxtfield4)}
value={this.state.value[ref_taxtfield4]}
/>
<Textfield
name="taxtfield5"
label="My Label 5"
ref={ref_taxtfield5}
onValueChange={this.handleChange.bind(this, ref_taxtfield5)}
value={this.state.value[ref_taxtfield5]}
/>
</div>
</Form>
</Card>})}
</Cards>
}
}
My TextField Component
export default class Textfield extends Input {
static defaultProps = {
initialCount: 0,
value: "",
defaultValue: "",
onValueChange: null,
label: ""
};
state = { focused: false };
onChange = this.onChange.bind(this);
onChange(e) {
if(this.props.onValueChange){
this.props.onValueChange(e.target.value);
}
}
handleOnBlur = this.handleOnBlur.bind(this);
handleOnBlur(e){
this.setState({focused: false});
if(this.props.onBlur){
this.props.onBlur(e);
}
}
render() {
const { focused } = this.state;
const { value, disabled } = this.props;
return (
<div>
<label>{this.props.label}</label>
<input
{ ...this.inputProps() }
type="text"
placeholder={this.props.placeholder}
defaultValue={this.props.defaultValue}
onChange={this.onChange}
onBlur={this.handleOnBlur}
value={ isCurrency ? formatData.currency(value) : value}
disabled={disabled}
/>
</div>
)
}
}
My Card and Cards Component
export class Cards extends Component {
render() {
const { children, ...props } = this.props;
return <div {...props} >{ children }</div>;
}
};
export class Card extends Component {
render() {
const { ...props } = this.props;
return <div {...props} } />
}
}
I use ES6 syntax and also remove all style tags from my code to simplify.
You are passing a new function to every Textfield component on render:
onValueChange={this.handleChange.bind(this, ref_taxtfield1)}
.bind returns a new function every time, causing every Textfield to render on each update.
Two possible solutions:
Don't use .bind inside .render. .bind the method once in the constructor and let Textfield pass an identifier to this.props.onValueChange.
Implement shouldComponentUpdate in Textfield, returning false if only this.props.onValueChange changed.

Categories

Resources