Based on other examples, my regex attempt to replace dashes (-) with letters should work. But it doesn't. I am using React.
I have followed this post (Replace multiple characters in one replace call) and other outside posts but I cannot get regex to replace dashes (-) with certain letters. The app only has one component, App.js. I have also tried writing the line targetNameDashes.replace(/'-'/gi, letter).toUpperCase(); but it hasn't worked either.
GitHub repo: https://github.com/irene-rojas/brooklyn-react
import React, { Component } from 'react';
import './App.css';
class App extends Component {
state = {
names: ['JAKE', 'AMY', 'GINA', 'ROSA', 'CHARLES', 'TERRY', 'HOLT'],
targetName: "",
targetNameDashes: "",
guessRemain: 10,
lettersGuessed: []
}
componentDidMount() {
let targetName = this.state.names[Math.floor(Math.random()*this.state.names.length)];
this.setState({
targetName: targetName,
targetNameDashes: targetName.replace(/[a-zA-Z]/gi , '-').toUpperCase(),
// The flags 'g' and 'i' are for global search and case insensitive search
});
console.log(targetName);
}
onKeyUp = (event) => {
event.preventDefault();
let letter = event.key.toUpperCase();
let targetName = this.state.targetName;
let guessRemain = this.state.guessRemain;
let lettersGuessed = this.state.lettersGuessed;
if (letter) {
this.setState({
guessRemain: guessRemain - 1,
lettersGuessed: lettersGuessed + letter
});
// if letter is in targetName, replace dash with letter
if (targetName.includes(letter)) {
console.log("yup");
let targetNameDashes = this.state.targetNameDashes;
// temporary variable that contains dashes and letters?
targetNameDashes.replace(/-/gi, letter).toUpperCase();
this.setState({
targetNameDashes: targetNameDashes
// does it need a callback to update?
});
}
}
if (guessRemain === 0) {
console.log("too bad");
this.setState({
guessRemain: 10,
lettersGuessed: []
});
this.componentDidMount();
}
console.log(`${letter} end of onKeyUp`);
}
render() {
return (
<div className="App">
<div>
You will be seen by:
<br></br>
{this.state.targetNameDashes}
</div>
<br></br>
<div>
Letters guessed:
<br></br>
<input onKeyUp={this.onKeyUp} />
<br></br>
Letters guessed in this round:
<br></br>
[ {this.state.lettersGuessed} ]
</div>
<br></br>
<div>
Guesses remaining:
<br></br>
{this.state.guessRemain}
</div>
</div>
);
}
}
export default App;
I noticed many issues that I should mention one by one:
Move some of the constants out of the state, for example names. Setting them in state makes no sense because they will not be changed and re-rendered in UI.
You manually called componentDidMount at the end of your key up event handler, which is not they way this should be done. Separate the logic that needs to be re-done in a separate utility function (i.e. gameReset() and call that instead).
The replace logic of the case when targetName.includes(letter) is not correct. It doesn't know which indices to replace.
Think of that this way: your targetName was "EVE", you are asking
a string that looks like "---" to change to wherever "E" is. How can
it know where "E" is without inspecting the original name? A loop
would be a better solution.
setState is asynchronous in nature. You should make use of prevState argument and callback method to ensure you are getting the correct states.
Here I have modified your codes and added comments where necessary, hope it helps:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
// moved it outside state
const names = ["JAKE", "AMY", "GINA", "ROSA", "CHARLES", "TERRY", "HOLT"];
class App extends React.Component {
state = {
targetName: "",
targetNameDashes: "",
guessRemain: 10,
lettersGuessed: []
};
// utility extracted from componentDidMount
// so that it can be re-used later
resetGame = () => {
let targetName = names[Math.floor(Math.random() * names.length)];
this.setState({
guessRemain: 10,
lettersGuessed: [],
targetName: targetName,
targetNameDashes: new Array(targetName.length).fill("-").join("") // fill an array with hyphens
});
};
componentDidMount() {
// call the utility
this.resetGame();
}
onKeyUp = event => {
event.preventDefault();
let letter = event.key.toUpperCase();
// TODO: provide more logic to avoid bad key strokes
// for example backspace should not count
if (letter) {
this.setState(
prevState => {
let modifiedNameDashes = String(prevState.targetNameDashes);
// for each charecter of targetName
for (var i = 0; i < prevState.targetName.length; i++) {
// check if this charecter at index i matched the key
if (prevState.targetName[i] === letter) {
// if it does
// remove a hyphen from modifiedNameDashes at that exact index
modifiedNameDashes =
modifiedNameDashes.substr(0, i) +
letter +
modifiedNameDashes.substr(i + 1);
}
}
return {
targetNameDashes: modifiedNameDashes,
guessRemain: prevState.guessRemain - 1,
lettersGuessed: [...prevState.lettersGuessed, letter]
};
},
// callback after the state update is done
() => {
// won
if (this.state.targetNameDashes === this.state.targetName) {
console.log("Nice!");
}
// lost
if (this.state.guessRemain === 0) {
this.resetGame();
}
}
);
}
};
render() {
return (
<div className="App">
<div>
You will be seen by:
<br />
{this.state.targetNameDashes}
</div>
<br />
<div>
Letters guessed:
<br />
<input onKeyUp={this.onKeyUp} />
<br />
Letters guessed in this round:
<br />[ {this.state.lettersGuessed} ]
</div>
<br />
<div>
Guesses remaining:
<br />
{this.state.guessRemain}
</div>
<code>{JSON.stringify(this.state)}</code>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
targetNameDashes.replace(/-/gi, letter).toUpperCase();
this.setState({
targetNameDashes: targetNameDashes
// does it need a callback to update?
});
This is likely the source of your problem with nothing happening - String.prototype.replace returns a new version of the string, and doesn't modify the parameter string.
This would at least cause it to update so you can continue to work on the logic here.
this.setState({
targetNameDashes: targetNameDashes.replace(/-/gi, letter).toUpperCase()
});
Related
I'm trying to take a user inputted code and compare it to code within my database. Right now I can bring the code and display it outside the map function but when I try to add it, it doesn't work. here is my database:
[
{
"dwelling_code": "ABC-XYZ",
"dwelling_name": "Neves Abode",
"has_superAdmin": true,
"room": []
}
This is the parent component:
class Dwel2 extends Component {
state = {
house: [],
selectedMovie: null,
data: "ABC-XYZ"
}
componentDidMount() {
fetch('Removed for question', {
method: 'GET',
headers: {
}
}).then(resp => resp.json())
.then(resp => this.setState({ house: resp }))
.catch(error => console.log(error))
}
houseClicked = h => {
console.log(h)
}
render() {
return <div>
<EnterCode dataFromParent={this.state.data}
house={this.state.house}
houseClicked={this.house} />
</div>
}
}
This is the child component:
function EnterCode(props) {
return (
<div>
<div>
*THIS BIT DISPLAYS THE CODE*{props.dataFromParent}
</div>
{props.house.map(house => {
var test = house.dwelling_name
var code = house.dwelling_code
if (code === {props.dataFromParent}) {
test = "Test"
}
return (
<React.Fragment>
<div>{test}</div>
</React.Fragment>
)
})}
</div>
)
}
I just want to compare the code in the database to the code defined in the parent component. Here is the error that's coming up this is in the child component.
Line 17:31: 'dataFromParent' is not defined no-undef
You made a tiny mistake in the if statement. You put the props.dataFromParent in brackets, which in the context of JSX would be required, but in the context of JS means creating an object, which is clearly wrong.
if (code === props.dataFromParent) {
test = "Test"
}
Hope this helps :)
I have a search bar that I want to test for user input. I am rendering the view with React and writing the tests using Chai, Mocha and JSDom.
Here are the simple tests I have written so far
describe('Search', () => {
it('renders a search bar', () => {
const component = renderIntoDocument(
<Search />
);
const search_bar = scryRenderedDOMComponentsWithClass(component, 'input-group');
expect(search_bar.length).to.equal(1);
});
it('invokes a callback when the Add button is clicked', () => {
let search_term;
const clickHandler = (term) => search_term = term;
const component = renderIntoDocument(
<Search onClick={clickHandler}/>
)
const add_button = scryRenderedDOMComponentsWithTag(component, 'button');
Simulate.click(add_button[0]);
expect(search_term).to.equal('');
});
it('accepts user input from the search button and performs a regex check for invalid input', () => {
let search_term;
const clickHandler = (term) => search_term = term;
const component = renderIntoDocument(
<Search onClick={clickHandler} />
);
const add_button = scryRenderedDOMComponentsWithTag(component, 'button');
const search_bar = scryRenderedDOMComponentsWithClass(component, 'input-group');
Simulate.change(search_bar[0], {target: {value: 'FB'}});
expect(search_bar[0].state.value).to.equal('FB');
})
})
1) I want to test that the user inputs characters before hitting the Add button.
2) I want to test that the user does not input any numerals or special characters into the search term, the string needs to be only alphabets.
How do I simulate these tests?
Okay, so I have an answer after some debugging. But, it seems to be a little bit of a hack.
Here is the component I am rendering into the DOM
import React, {Component} from 'react';
class Search extends Component {
constructor(props){
super(props);
this.state = {
value: ''
}
}
handleChange(event){
this.setState({value: event.target.value})
}
render() {
return (
<div className='input-group'>
<input ref='input' type='text' className='form-control' placeholder='Enter stock ticker' value={this.state.value}
onChange={this.handleChange}/>
<span className='input-group-btn'>
<button className='btn btn-primary' type='submit'
onClick={() => this.props.onClick(this.state.value)}>
Add
</button>
</span>
</div>
)
}
}
Search.propTypes = {
onClick: React.PropTypes.func.isRequired
}
export default Search;
Here is the unit test to accept user input. Does not include validation
it('accepts user input from the search button', () => {
let search_term;
const clickHandler = (term) => search_term = term;
const component = renderIntoDocument(
<Search onClick={clickHandler} />
);
component.state.value = 'FB';
expect(component.state.value).to.equal('FB');
})
The reason I am not so sure about this is because I am explicitly setting component.state.value = 'FB' and then checking if it is, which it will be. But this is the closest I have gotten to validating that the value actually changes, because trying to run
Simulate.change(component, {state: {value: 'FB'}});
throws an error
TypeError: Cannot read property
'__reactInternalInstance$gvviufwbzvqrhtud00vbo6r' of undefined
Trying the following according to the React docs (https://facebook.github.io/react/docs/test-utils.html#simulate)
it('accepts user input from the search button', () => {
let search_term;
const clickHandler = (term) => search_term = term;
const component = renderIntoDocument(
<Search onClick={clickHandler} />
);
const input = component.refs.input;
input.value = 'FB';
Simulate.change(input);
expect(input.value).to.equal('FB');
// component.state.value = 'FB';
// expect(component.state.value).to.equal('FB');
})
returns an error
TypeError: Cannot read property 'setState' of undefined
presumably because the state has to be set when the Simulate function is called, and the input ref only reads the state.
If anyone has any answers to these various errors, or even how exactly the Simulate functionality works (the docs aren't a great help), do share.
So, I'm going to add another answer because i'd still like answers to the questions about the Simulate object I raised in my previous answer.
The issue I was having had nothing to do with the test I was writing, but the fact that I had not bound this to the handleChange method inside the Search container.
Here is the updated constructor for the Search container
constructor(props){
super(props);
this.state = {
value: ''
}
this.handleChange = this.handleChange.bind(this);
}
And here is the test that will now pass
it('accepts user input from the search button', () => {
let search_term;
const clickHandler = (term) => search_term = term;
const component = renderIntoDocument(
<Search onClick={clickHandler} />
);
const input = component.refs.input;
input.value = 'FB';
Simulate.change(component.refs.input);
expect(input.value).to.equal('FB');
})
I am using Chai, Sinon, and Mocha to test.
I am using Redux-Forms, along with ReactJS.
I want to test what happens after I click submit on a Forgot Password page.
Here's my code so far:
react file:
propTypes: {
fields: React.PropTypes.object.isRequired,
message: React.PropTypes.string.isRequired,
handleSubmit: React.PropTypes.func.isRequired,
},
renderForm: function() {
const { fields: {email}, handleSubmit, isFetching } = this.props;
const iconClass = "fa fa-star-o";
return(
<form form="forgotPassword" name="forgotPassword" className="stack-input">
<ReduxFormTextInput
{...email}
floatingLabelText="Email Address" />
<div className="clearfix" />
<div className="form-footer">
<ButtonSpinner spinner={isFetching} onClick={handleSubmit} ripple={true} raised={true} primary={true} >
<i className={iconClass} />Send
</ButtonSpinner>
</div>
</form>
);
},
renderMessage: function() {
return(
<div>
<i className="fa fa-exclamation-circle" style={{marginRight: '4px'}}/>
If your email is in the system, then it will be sent.
</div>
);
},
//Checks if user has submitted email.
emailSubmit: function(){
var locus = this, props = locus.props;
if(props.message === ''){
return null;
}
else if(props.message === 'SENT'){
return true;
}
return null;
},
render(){
var locus = this, props= locus.props;
return(
<div className="page-wrap">
<div className="page-column">
<h2>Forgot Your Password</h2>
{this.emailSubmit() ? this.renderMessage(): this.renderForm()}
</div>
</div>
);
}
unittest file:
describe('ForgotPasswordForm', () => {
const component = setup({
fields: {
email: {
onChange: spy(),
onBlur: spy(),
onFocus: spy()
}
}, // React.PropTypes.object.isRequired,
message: '', // React.PropTypes.string.isRequired,
handleSubmit: spy() // React.PropTypes.func.isRequired,
//onClearMessage:spy() //, React.PropTypes.func.isRequired
}),
domRoot = TestUtils.findRenderedDOMComponentWithClass(component, 'page-wrap'),
title = TestUtils.findRenderedDOMComponentWithTag(component, 'h2'),
submitButton = TestUtils.findRenderedDOMComponentWithClass(component, 'material-D-button'),
form = TestUtils.findRenderedDOMComponentWithTag(component, 'form'),
inputs = TestUtils.scryRenderedDOMComponentsWithTag(component, 'input'),
emailInput = inputs[0];
This test keeps failing, despite multiple attempts. I am not experienced with Spy(), so I'm not sure if I am suppose to be using calledWith.
it ('should display "If your email is in the system, then it will be sent." on submit', () => {
TestUtils.Simulate.change(emailInput, {target: {value: 'test#email.com'}});
TestUtils.Simulate.click(submitButton);
expect(domColumn.text).to.equal("Forgot Your Password");
});
This is the response I get.
+"If your email is in the system, then it will be sent."
- -"Forgot Your PasswordSend"
I used innerHTML to get a sense of what's being populated after the click, and I don't think the click is even registering.
When I try to do TestUtils.Simulate.change(emailInput, {target: {value: 'test#email.com'}});, it doesn't work. I have to populate the value of the email in the component.
You should be assigning your handleSubmit spy to a constant so you can at least be able to check whether it's getting called. (Probably the same for the other spies).
const handleSubmitSpy = spy()
const component = setup({
...
handleSubmit: handleSubmitSpy
Now you can check expect(handleSubmitSpy).toHaveBeenCalled().
I'd like to transition one element as it changes to another element.
I've got 3 examples:
one that works, but uses a list of items that are kept around (jsfiddle)
one that doesnt work, and only keeps one item around, depending on the state (jsfiddle)
another one that doesn't work, that keeps both items around and hides/shows them (jsfiddle using hide/show)
What I want is more like the second one, which is a very slight variation of the first attempt that works.
Option 1:
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var TodoList = React.createClass({
getInitialState: function() {
return {items: ['hello', 'world', 'click', 'me']};
},
handleAdd: function() {
var newItems =
this.state.items.concat([prompt('Enter some text')]);
this.setState({items: newItems});
},
handleRemove: function(i) {
var newItems = this.state.items;
newItems.splice(i, 1)
this.setState({items: newItems});
},
render: function() {
var items = this.state.items.map(function(item, i) {
return (
<div key={item} onClick={this.handleRemove.bind(this, i)}>
{item}
</div>
);
}.bind(this));
return (
<div>
<div><button onClick={this.handleAdd} /></div>
<ReactTransitionGroup transitionName="example">
{items}
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<TodoList />, document.body);
Option 2:
JSX that doesn't work, but is closer to what I'd like to do (really, hide one view, and show another)
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var Test = React.createClass({
getInitialState: function() {
return {showOne:true}
},
onClick: function() {
this.setState({showOne:! this.state.showOne});
},
render: function() {
var result;
if (this.state.showOne)
{
result = <div ref="a">One</div>
}
else
{
result = <div ref="a">Two</div>
}
return (
<div>
<div><button onClick={this.onClick}>switch state</button></div>
<ReactTransitionGroup transitionName="example">
{result}
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<Test />, document.body);
Option 3:
Uses hide/show to keep the 2 views around, but still doesn't work.
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var Test = React.createClass({
getInitialState: function() {
return {showOne:true}
},
onClick: function() {
this.setState({showOne:! this.state.showOne});
},
render: function() {
var result;
var c1 = this.state.showOne ? "hide" : "show";
var c2 = this.state.showOne ? "show" : "hide";
return (
<div>
<div><button onClick={this.onClick}>switch state</button></div>
<ReactTransitionGroup transitionName="example">
<div className={c1}>One</div>
<div className={c2}>Two</div>
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<Test />, document.body);
So long story short - How can I make a transition execute on switching from one main "component" to another? I don't get why option 1 works, but option 2 doesn't!
React is just changing the content of the DOM because that's all that changed. Give the elements unique keys to make them animate.
if (this.state.showOne)
{
result = <div key="one">One</div>
}
else
{
result = <div key="two">Two</div>
}
JSFiddle
I used Michelle Treys answer to solve a similar problem using React-Router (1.0.1). Its not clear from the api that the key is needed. I was following React-routers suggestion to render a routes children in a parent as follows:
render() {
return (
<div id='app-wrapper'>
<ReactTransitionGroup component='div' className='transition-wrapper'>
{this.props.children}
</ReactTransitionGroup>
</div>
);
}
However the componentWillEnter only triggered on page load. Following Michelle's solution, I cloned a the children as per the react-router updates and added a key as follows:
render() {
const { location } = this.props;
return (
<div id='app-wrapper'>
<ReactTransitionGroup component='div' className='transition-wrapper'>
{React.cloneElement(this.props.children, {
key: location.pathname,
})}
</ReactTransitionGroup>
</div>
);
}
Thanks for the fix. Cheers
I'm trying to implement this (the one at the bottom of the page) RegExp to validate email addresses with jquery validation plugin.
This is my code:
$.validator.addMethod("email_address", function(value, element, param) {
var email_regexp = new RegExp("[a-z0-9!#$%&'*+/=?^_{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_{|}~-]+)*#(?:a-z0-9?.)+(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|asia|jobs|museum)\b", "g");
var result = value.match(email_regexp);
return result ? result.length >= param : false;
}, "Invalid email address");
No JS errors are shown, still it doesn't validate anything! Been playing with it for like an hour and can't get this working!
Is there something wrong?
EDIT: I tried also with // delimiters:
$.validator.addMethod("email_address", function(value, element, param) {
var result = value.match(/[a-z0-9!#$%&'*+/=?^_{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_{|}~-]+)*#(?:a-z0-9?.)+(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|asia|jobs|museum)\b/g);
return result ? result.length >= param : false;
}, "Invalid email address");
~-]+(?:\.[a-z0-9!#$
// ^^
That \. will need escaping again for the Javascript string:
~-]+(?:\\.[a-z0-9!#$
// ^^^
Or, preferably, use // delimeters rather than constructing a RegExp object from a string.
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp
Why are you writing a custom regex function for the jQuery Validate plugin when it already has an email rule built-in?
http://docs.jquery.com/Plugins/Validation/Methods/email
jQuery:
$(document).ready(function() {
$('#myform').validate({
rules: {
field: {
required: true,
email: true
}
}
});
});
HTML:
<form id="myform">
<input type="text" name="field" /> <br/>
<input type="submit" />
</form>
Working Demo:
http://jsfiddle.net/sRwHc/
The default regex function used within the .validate() plugin, FYI:
email: function(value, element) {
// contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))#((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(value);
}