I have a small component in React which generates two random numbers on render, then asks the user to submit the sum of these numbers and if they are correct, increment their score.
The following code handles this game and works as intended in the browser:
import React, { Component } from 'react';
export const randomNumber = () => {
var maxNumber = 10;
var randomNumber = Math.floor((Math.random() * maxNumber) + 1);
return randomNumber;
}
class Arithmetic extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
numbers: {
x: randomNumber(),
y: randomNumber()
},
score: ''
}
this.updateVals = this.updateVals.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
updateVals() {
this.setState({
numbers: {
x: randomNumber(),
y: randomNumber()
},
score: this.state.score + 1
});
}
componentDidMount() {
this.setState({
score: 0
});
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
var isCorrect = this.state.numbers.x + this.state.numbers.y == this.state.value ? this.updateVals() : alert("Try again");
event.preventDefault();
}
render() {
return (
<section className="arithmetic">
<div className="arithmetic__game">
<div className="row arithmetic__row--details">
<div className="arithmetic__score">
Score: {this.state.score}
</div>
<div className="arithmetic__timer">
</div>
</div>
<div className="row arithmetic__row--main">
<div className="arithmetic__examples">
1 + 1 = 2<br/>
2 + 1 = 3<br />
</div>
<div className="arithmetic__game-container">
What is {this.state.numbers.x} + {this.state.numbers.y}?
<div className="arithmetic__form-container">
<form onSubmit={this.handleSubmit}>
<label>
Answer:
<input className="input-field" type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<button className="btn-submit" type="submit" onClick={(e) => (this.handleSubmit) ? this.handleSubmit(e) : null}>Submit</button>
</form>
</div>
</div>
</div>
</div>
</section>
);
}
};
However, when trying to check whether or not updateVals is called when the sum of both numbers is entered correctly, this fails. I have checked to see if handleSubmit is called on simulation of the "Submit" button being clicked, and it is called. I have also checked the values of the value and numbers props to see if the states have been correctly updated, which they have.
However, when updateVals is called, the score is incremented (and again, this is shown in the browser). But when I try to simulate this in Jest, the score remains at 0 as it is when it is initialised.
My test is as follows:
it("passes with correct input", () => {
const updateVals = jest.fn();
const handleSubmit = jest.fn();
Arithmetic.prototype.updateVals = updateVals;
Arithmetic.prototype.handleSubmit = handleSubmit;
let wrapper = mount(<Arithmetic />);
wrapper.find('.input-field').instance().value = wrapper.update().state().numbers.x + wrapper.update().state().numbers.y;
expect(wrapper.find('.input-field').instance().value).toEqual((wrapper.update().state().numbers.x + wrapper.update().state().numbers.y).toString());
wrapper.find('.input-field').simulate('change');
wrapper.find('.btn-submit').simulate('click');
console.log(wrapper.update().state().numbers.x, wrapper.update().state().numbers.y, wrapper.update().state().value);
expect(updateVals).toHaveBeenCalled();
});
Running tests in the terminal shows that, for instance, if the numbers.x is 1 and numbers.y is 9 then the value key in state is '10'. I'm not sure why when I test handleSubmit, it gets called and the test passes but updateVals does not.
I managed to get this working, I removed any events from the submit button and tested the form itself which already calls onSubmit and tested that the score itself updated in the state.
it("passes with correct input", () => {
const wrapper = shallow(<Arithmetic />);
const preventDefault = jest.fn();
const currentScore = wrapper.update().state().score;
wrapper.find('.input-field').simulate('change', {target: {value: wrapper.update().state().numbers.x + wrapper.update().state().numbers.y}});
wrapper.find('.main-form').simulate('submit', { preventDefault });
expect(wrapper.update().state().score).toEqual(currentScore+1);
});
Related
I'm new to React,
just to get the idea, how would you convert this snippet to React?
<div id="input" contenteditable></div>
<button id="submit">Convert to kg!</button>
<br>
<div id="output"></div>
<script>
const button = document.querySelector('#submit');
const input = document.querySelector('#input');
function convert() {
let x=input.textContent;
if (isNaN(x))
{
alert("Must input numbers");
return false;
} else {
const pounds = document.querySelector('#input').textContent;
let kg = pounds*0.45;
document.querySelector('#output').innerHTML = pounds + " pounds is " + kg + " kg";
}
}
button.addEventListener('click', convert);
</script>
I transformed html to jsx
<div
id="input"
contentEditable
style={{ width: "40%", border: "solid 1px black", margin: "20px}" }}
/>
<button id="submit">Convert to kg!</button>
<br />
<div id="output" style={{ marginTop: 20 }} />
</div>
But, how to go about Javascript, no idea...
Can someone give a direction maybe?
Basic functional component:
weightLb state variable to hold user input
weightKg state variable to hold converted weight
component & logic:
function App() {
const [weightLb, setWeightLb] = useState(0);
const [weightKg, setWeightKg] = useState(0);
const convert = () => {
if (Number.isNaN(weightLb)) {
alert("Must input numbers");
}
setWeightKg(weightLb * 0.45);
}
return (
<div className="App">
<label>
Pounds
<input type="number" onChange={e => setWeightLb(e.target.value)} />
</label>
<button type="button" onClick={convert}>Convert to kg!</button>
<div>Weight (kg): {weightKg}</div>
</div>
);
}
Note: I didn't apply any styling other than default in sandbox.
A simpler abstraction may be to forego the button to convert and just convert input onChange on the fly; requires single piece of state.
function App() {
const [weightLb, setWeightLb] = useState(0);
const convert = () => {
if (Number.isNaN(weightLb)) {
alert("Must input numbers");
}
return weightLb * 0.45;
};
return (
<div className="App">
<label>
Pounds
<input type="number" onChange={e => setWeightLb(e.target.value)} />
</label>
<div>Weight (kg): {convert()}</div>
</div>
);
}
Here are the 6 steps for implementing the React component as per the above requirement:
Define a React Component called App, which extends React Component.
Initialize the 'state' of the component in the constructor method of the component.
The state will contain the variables (weightLb, weightKg)
Define a method (setWeightLb) to change the value of weightLb
Define a method (convert) to calculate weightKg using the formula of
kg = lb x 0.453592
Define 'render' method to show static html of thecomponent
Inside the render method, make calls to setWeightLb and convert on corresponding events.
Here is the working example:
import React, { Component } from "react"
export default class App extends Component {
constructor(props) {
super(props)
this.state = {weightLb: 0.0, weightKg: 0.0}
}
setWeightLb = (value) => {
this.setState({weightLb: value})
}
convert = () => {
this.setState({weightKg: (this.state.weightLb * 0.453592)})
}
render() {
return (
<div>
<label>
Pounds
<input type="number" onChange={e => this.setWeightLb(e.target.value)} />
</label>
<button type="button" onClick={this.convert}>
Convert to kg!
</button>
<div>Weight (kg): {this.state.weightKg}</div>
</div>
)
}
}
Something like this. It may not work as you wished but from this example you can easly create your own answer i guess.
import React from "react";
const convert = (text) => {
if (parseInt(text, 10) > 0) {
const kg = parseInt(text, 10) * 0.45;
document.querySelector("#output").innerHTML =
text + " pounds is " + kg + " kg";
}
alert("You must enter digits");
};
const ExampleComponent = () => {
const [text, setText] = React.useState("");
return (
<section>
<div id="input" onChange={(e) => setText(e.target)}></div>
<button id="submit" onClick={() => convert(text)}>
Convert to kg!
</button>
<br />
<div id="output"> </div>
</section>
);
};
So I am working on a dice application where I have a class component for setting the number of dice and sides of each dice with up and down buttons. My problem is that each time I press up or down button to set number of sides or number of dice, an array of random numbers gets created and displays on screen. However, I want the value to display only when the roll button is clicked.
So is there a way I can change the state of displayDice to false after I have created the array in the render, so that it only becomes true when I click roll button again
You can move logic to componentDidMount. Render is to just render UI. No business logic. It will handle event and delegate to state.
Move generate random to parent component, pass method rollChange from parents to child.
// Dice component
class SideAndDice extends React.Component {
constructor(props) {
super(props);
this.state = { sides: 6, dice: 1, randoms: this.generateRandom() };
}
increaseDice() {
this.setState({ dice: this.state.dice + 1 });
}
decreaseDice() {
if (this.state.dice > 1) {
this.setState({ dice: this.state.dice - 1 });
}
}
increaseSides() {
this.setState({ sides: this.state.sides + 1 });
}
decreaseSides() {
if (this.state.sides > 2) {
this.setState({ sides: this.state.sides - 1 });
}
}
generateRandom() {
let randoms = [];
for (var i = 0; i < this.state.dice; i++) {
var randomValue = Math.floor(Math.random() * this.state.sides + 1);
randoms.push(randomValue);
}
return randoms;
}
onRollDice() {
this.setState({ randoms: this.generateRandom() });
}
render() {
return (
<div>
<h1>Number of Sides</h1>
<h2>{this.state.sides}</h2>
<button onClick={this.increaseSides.bind(this)}>Up</button>
<button onClick={this.decreaseSides.bind(this)}>Down</button>
<h1>Number of Dice</h1>
<h2>{this.state.dice}</h2>
<button onClick={this.increaseDice.bind(this)}>Up</button>
<button onClick={this.decreaseDice.bind(this)}>Down</button>
<CreateScores
randoms={this.state.randoms}
rollChange={this.rollChange.bind(this)}
/>
</div>
);
}
}
class CreateScores extends React.Component {
render() {
return (
<div>
<button onClick={this.props.onRollDice.bind(this)}>Roll</button>
<br />
<br />
{this.props.randoms.map(random => (
<Dice key={i} diceNumber={randomValue} />
))}
</div>
);
}
}
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>
)
}
}
I have a simple react-component which contains of a form. In this form the user has an search-box where to find users.
In order to have a valid form, there must be at least 3 users but not more than 6.
Therefore I've added a hidden field (hidden by surrounding display:none; div) which contains the number of already added users.
This one will show the error-message in case the current number is invalid.
The following code is simplified but shows the main issue I have.
In my render-call I have:
render():JSX.Element {
return (
<form>
<ValidatableInput
input={<Input type="number" value={this.state.users.length.toString()} min={3} max={6} getRef={(el: HTMLInputElement) => this.testElement = el} onChange={() => alert("changed...")} />}
minValueMessage={"Currently there are " + this.state.users.length + " users. A minimum of 3 is needed"}
hidden={true} />
<UserSearch onUserSelected={this.handleUserSelected} />
// ...
</form>
);
}
whereas UserSearch is the component to search for users. This is mainly an autocomplete-input which triggers the handleUserSelected:
private handleUserSelected = (selectedElement: IUser) : void => {
// get a copy of the current state
const newState = { ...this.state };
// add the user
newState.users.push(selectedElement);
// thats what I've tried so far
this.testElement.dispatchEvent(new Event("change"));
this.testElement.dispatchEvent(new Event("change", {bubbles: true}));
this.testElement.onchange(new Event("change"));
this.testElement.onchange(new Event("change"), {bubbles: true});
this.setState(newState, () => // here I want to do the triggering as the input has now the new count as value set)
}
However, the console does not show changed at all.
How can I call/trigger the change-event manually when something other changed on the component?
This is the ValidatableInput-component:
export class ValidatableInput extends React.Component<IValidatableInputProps, IValidatableInputState> {
render(): JSX.Element {
const { labelText, input, requiredMessage, maxLengthMessage, minValueMessage, hidden } = this.props;
input.props.onInvalid = this.handleInvalid;
input.props.onChange = this.handleChange;
if (hidden) {
// set height to 0
}
return (
<FormGroup row >
<Label for={input.props.name} md={3}>{!hidden && labelText}</Label>
<Col md={9}>
<div>
{input}
{this.state.validityState.valueMissing && <div className={classNames("invalid-feedback")}>{requiredMessage || "This field is required"}</div>}
{this.state.validityState.tooLong && <div className={classNames("invalid-feedback")}>{maxLengthMessage || "The field shoud not be longer than " + input.props.maxLength}</div>}
{this.state.validityState.rangeOverflow && <div className={classNames("invalid-feedback")}>{maxLengthMessage || "There are too many items. Maximum allowed: " + input.props.max}</div>}
{this.state.validityState.rangeUnderflow && <div className={classNames("invalid-feedback")}>{minValueMessage || "There are too less items. Minimum needed: " + input.props.min}</div>}
</div>
</Col>
</FormGroup>
);
}
private handleInvalid = (ev: React.FormEvent<HTMLInputElement>): any => {
const input = ev.target as HTMLInputElement;
this.setState({ validityState: input.validity });
}
private handleChange = (ev: React.FormEvent<HTMLInputElement>): any => {
const input = ev.target as HTMLInputElement;
this.setState({ validityState: input.validity });
}
}
Maybe keep it simpler and remove the refs and eventHandling boilerplate.
Your business logic relies on this.state.users.length, right?
So use it on your favor:
handleUserSelected = (selectedElement: IUser) : void => {
this.setState({
users: [
...this.state.users,
selectedElement,
],
})
}
render() {
const { users } = this.state
const isInvalid = users.length < 3 || users.length > 6
return (
<form>
{isInvalid && <span>Your invalid message</span>}
<UserSearch ... />
</form>
)
}
I write application with accessibility using ReactJS.
I have a problem with NVDA and FireFox - NVDA can't read number correctly.
I've found solution (use setTimeout with 0 delay), but I think, that can do it better.
I've created proof of concept to show the problem:
HTML:
<body>
<div id="root"></div>
</body>
JavaScript:
function generateRandomNumber(min = 0, max = 9, toFixed = 2) {
const number = (Math.random() * (max - min) + min).toFixed(toFixed);
return number;
};
function getRandomNumber(min = 0, max = 9, toFixed = 2) {
return generateRandomNumber(101, 400, 2);
};
class Work extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0,
showElement: true
};
}
randomNumber() {
this.setState({
showElement: false,
number: getRandomNumber()
});
setTimeout(() => {
this.setState({
showElement: true
});
}, 0);
}
render() {
return (
<div>
<div aria-live="polite">
{this.state.showElement &&
<p>{this.state.number}</p>}
</div>
<button onClick={() => this.randomNumber()}>Random me!</button>
</div>
);
}
}
class NotWork extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
}
randomNumber() {
this.setState({
number: getRandomNumber()
});
}
render() {
return (
<div>
<div aria-live="polite">
<p>{this.state.number}</p>
</div>
<button onClick={() => this.randomNumber()}>Random me!</button>
</div>
);
}
}
class App extends React.Component {
render(){
return (
<div className="App">
<div>
<div aria-live="polite">
<h3>Works example:</h3>
<Work />
<hr />
<h3>Not works example:</h3>
<NotWork />
</div>
</div>
</div>
)
}
}
React.render( < App / > ,
document.getElementById('root')
);
JSFiddle for run:
https://jsfiddle.net/IceManSpy/1bxu6aau/1/
To reproduce - sad path:
Run jsfiddle on FireFox
Run NVDA (you can open speech viewer)
Click sometimes on Random me! in Not works example
Check results - first value will be ok, but next not.
Sometimes will be (fe. 345.67):
345
67
but sometimes (it missing first digit):
45
67
To reproduce - happy path:
Run jsfiddle on FireFox
Run NVDA (you can open speech viewer)
Click sometimes on Random me! in Works example
Check results - every value will be ok.
How can I resolve this problem without setTimeout ?