I'm working on a component that should be able to:
Search by input - Using the input field a function will be called after the onBlur event got triggered. After the onBlur event the startSearch() method will run.
Filter by a selected genre - From an other component the user can select a genre from a list with genres. After the onClick event the startFilter() method will run.
GOOD NEWS:
I got the 2 functions above working.
BAD NEWS:
The above 2 functions don't work correct. Please see the code underneath. The 2 calls underneath work, but only if I comment one of the 2 out. I tried to tweak the startSearch() method in various ways, but I just keep walking to a big fat wall.
//////Searching works
//////this.filter(this.state.searchInput);
//Filtering works
this.startFilter(this.state.searchInput);
QUESTION
How can I get the filter/search method working?. Unfortunately simply putting them in an if/else is not the solution (see comments in the code).
import { Component } from 'preact';
import listData from '../../assets/data.json';
import { Link } from 'preact-router/match';
import style from './style';
export default class List extends Component {
state = {
selectedStreamUrl: "",
searchInput: "",
showDeleteButton: false,
searchByGenre: false,
list: [],
}
startFilter(input, filterByGenre) {
this.setState({
searchByGenre: true,
searchInput: input,
showDeleteButton: true
});
alert("startFilter ")
console.log(this.state.searchByGenre)
/////////---------------------------------
document.getElementById("searchField").disabled = false;
document.getElementById('searchField').value = input
document.getElementById('searchField').focus()
// document.getElementById('searchField').blur()
document.getElementById("searchField").disabled = true;
console.log(input)
this.filter(input);
}
//search
startSearch(input) {
alert("startSearch ")
console.log(this.state.searchByGenre)
//komt uit render()
if (!this.state.searchByGenre) {
//check for input
this.setState({
searchInput: input.target.value,
showDeleteButton: true,
})
//Searching works
//this.filter(this.state.searchInput);
//Filtering works
this.startFilter(this.state.searchInput);
// DOESNT WORK:
// if (this.state.searchInput != "") {
// this.filter(this.state.searchInput);
// } else {
// this.startFilter(this.state.searchInput);
// }
}
}
setAllLists(allLists) {
console.log("setAllLists")
console.log(this.state.searchByGenre)
this.setState({ list: allLists })
//document.body.style.backgroundColor = "red";
}
filter(input) {
let corresondingGenre = [];
let filteredLists = listData.filter(
(item1) => {
var test;
if (this.state.searchByGenre) {
alert("--this.state.searchByGenre")
//filterByGenre
//& item1.properties.genre == input
for (var i = 0; i < item1.properties.genre.length; i++) {
if (item1.properties.genre[i].includes(input)) {
corresondingGenre.push(item1);
test = item1.properties.genre[i].indexOf(input) !== -1;
return test;
}
this.setState({ list: corresondingGenre })
}
} else {
//searchByTitle
alert("--default")
test = item1.title.indexOf(input.charAt(0).toUpperCase()) !== -1;
}
return test;
})
console.log("filterdLists:")
console.log(filteredLists)
console.log("corresondingGenre:")
console.log(corresondingGenre)
//alert(JSON.stringify(filteredLists))
this.setState({ list: filteredLists })
}
removeInput() {
console.log("removeInput ")
console.log(this.state.searchByGenre)
this.setState({ searchInput: "", showDeleteButton: false, searchByGenre: false })
document.getElementById("searchField").disabled = false;
this.filter(this.state.searchInput)
}
render() {
//alle 's komen in deze array, zodat ze gefilterd kunnen worden OBV title.
if (this.state.list === undefined || this.state.list.length == 0 && this.state.searchInput == "") {
//init list
console.log("render ")
console.log(this.state.searchByGenre)
this.filter(this.state.searchInput)
}
return (
<div class={style.list_container}>
<input class={style.searchBar} type="text" id="searchField" placeholder={this.state.searchInput} onBlur={this.startSearch.bind(this)} ></input>
{
this.state.searchByGenre ?
<h1>ja</h1>
:
<h1>nee</h1>
}
{
this.state.showDeleteButton ?
<button class={style.deleteButton} onClick={() => this.removeInput()}>Remove</button>
: null
}
{
this.state.list.map((item, index) => {
return <div>
<p>{item.title}</p>
</div>
})
}
</div>
);
}
}
SetState is an async operation that takes a callback function. I suspect that your second function runs before the first SetState is finished.
Also, you are modifying the DOM yourself. You need to let React do that for you just by modifying state. I don't have time to write up an example now, but hopefully this helps in the meantime.
can you modify your search func,
//search
startSearch(input) {
const { value } = input.target
const { searchInput } = this.state
if (!this.state.searchByGenre) {
this.setState(prevState => ({
searchInput: prevState.searchInput = value,
showDeleteButton: prevState.showDeleteButton = true,
}))
JSON.stringify(value) !== '' ? this.filter(value) : this.startFilter(searchInput)
}
}
Related
I have an table with check boxes . On click i have to collect my data in something like this :
"docList" : {
"docID1" : "docNR1",
"docID2" : "docNR2",
}
and after that i have to make a request to the BE . The BE will return me an ZIP file with all clicked or selected documents . Normally is pretty simple , but i have some issues .
I declare my state into the constructor :
constructor(props) {
super(props);
this.state = {
selected: []
}
};
And then i pass my function into the checkbox :
handleCheckboxClick = (e) => {
if(e.target.checked){
this.setState({
selected: [...this.state.selected, e.target.value],
},()=>{
console.log( this.state.selected)
});
} else {
let remove = this.state.selected.indexOf(e.target.value);
this.setState({
selected: this.state.selected.filter((_, i) => i !== remove)
},()=>{
console.log( this.state.selected)
})
}
}
<Checkbox value={rowData.documentId.toString() + rowData.documentNumber.toString()} onClick={this.handleCheckboxClick} />
And from my console i get this :
["1004212942019-DGD-2000000478"]
Basically this is the docID + docNR combined in one string . So how can i seperate them and return an object with two seprated strings for each .
The result is not what i have expected so i will be glad if someone can give me a hand . Thank you
I would use JSON.stringify() and JSON.parse() as follows:
constructor(props) {
super(props);
this.state = {
docList: {}
};
};
<Checkbox value={JSON.stringify({documentId: rowData.documentId, documentNumber: rowData.documentNumber})} onClick={this.handleCheckboxClick} />
handleCheckboxClick = (e) => {
let parsedVal = JSON.parse(e.target.value);
let newDocList = {...this.state.docList};
if(e.target.checked) {
newDocList[parsedVal.documentId] = parsedVal.documentNumber;
} else {
delete newDocList[parsedVal.documentId];
}
this.setState({
docList: newDocList
}, ()=>{
console.log(this.state.docList)
});
}
You can use data attributes - https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
<Checkbox data-docId={rowData.documentId.toString()} data-docNumber={rowData.documentNumber.toString()} onClick={this.handleCheckboxClick} />
Inside handleCheckboxClick you can get the data values in the event object.
I want to update the todoList in my PARENT COMPONENT after I have added a new item in my child using the AddItem() method. Nothing gets added the first time.
EX. if I add "take test" doesn't get render, then if I add "take shower" doesn't get rendered but now "take test" does. Then if I add "take a leak" "take shower" gets rendered.
PARENT COMPONENT
firstUpdated(changedProperties) {
this.addEventListener('addItem', e => {
this.todoList = e.detail.todoList;
});
}
render() {
return html`
<p>Todo App</p>
<add-item></add-item>//Child item that triggers the add
<list-items todoList=${JSON.stringify(this.todoList)}></list-items>
`;
}
CHILD COMPONENT
AddItem() {
if (this.todoItem.length > 0) {
let storedLocalList = JSON.parse(localStorage.getItem('todo-list'));
storedLocalList = storedLocalList === null ? [] : storedLocalList;
const todoList = [
...storedLocalList,
{
id: this.uuidGenerator(),
item: this.todoItem,
done: false
}
];
localStorage.setItem('todo-list', JSON.stringify(todoList));
this.dispatchEvent(
new CustomEvent('addItem', {
bubbles: true,
composed: true,
detail: { todoList: storedLocalList }
})
);
this.todoItem = '';
}
}
render() {
return html`
<div>
<input .value=${this.todoItem} #keyup=${this.inputKeyup} />
<button #click="${this.AddItem}">Add Item</button>
</div>
`;
}
You need to set properties for todoItem
static get properties() {
return {
todoItem: {
type: Array,
Observer: '_itemsUpdated'
}
}
constructor(){
this.todoItem = []
}
_itemsUpdated(newValue,oldValue){
if(newValue){
-- write your code here.. no event listeners required
}
}
In above code., We need to initialise empty array in constructor.
Observer observe the changes to array & triggers itemsUpdated function which carries oldValue & NewValue. In that function., you can place your logic.
No Event Listeners required as per my assumption
Found my error. I was passing to detail: { todoList : storedLocalList } which is the old array without the updated value.
AddItem() {
if (this.todoItem.length > 0) {
let storedLocalList = JSON.parse(localStorage.getItem('todo-list'));
storedLocalList = storedLocalList === null ? [] : storedLocalList;
const todoList = [
...storedLocalList,
{
id: this.uuidGenerator(),
item: this.todoItem,
done: false
}
];
localStorage.setItem('todo-list', JSON.stringify(todoList));
this.dispatchEvent(
new CustomEvent('addItem', {
bubbles: true,
composed: true,
detail: { todoList: todoList }
})
);
this.todoItem = '';
}
}
Ultimately I am trying to create a piano like application that you can control by click or key down. I want each keyboard key to control a certain note. Each "piano" key is a component which has a keycode property and a note property. When the event.keycode matches the keycode property I want the associated note to play. What is the best strategy to go about this?
I have tried using refs and playing around with focus on componentDidMount. I cant seem to wrap my head around how this should work.
class Pad extends React.Component {
constructor(props) {
super(props)
this.state = {
clicked: false
}
this.clickedMouse = this.clickedMouse.bind(this)
this.unClickedMouse = this.unClickedMouse.bind(this)
this.handleDownKeyPress = this.handleDownKeyPress.bind(this)
this.handleUpKeyPress = this.handleUpKeyPress.bind(this)
}
clickedMouse(e) {
this.setState({ clicked: true })
this.props.onDown(this.props.note)
console.log(e)
}
unClickedMouse(e) {
this.setState({ clicked: false })
this.props.onUp(this.props.note)
}
handleDownKeyPress(e) {
if (e.keyCode === this.props.keyCode && this.state.clicked === false) {
this.setState({ clicked: true })
this.props.onDown(this.props.note)
}
}
handleUpKeyPress(e) {
this.setState({ clicked: false })
this.props.onUp(this.props.note)
}
render() {
return (
<div
className='pad'
onMouseUp={this.unClickedMouse}
onMouseDown={this.clickedMouse}
onKeyDown={this.handleDownKeyPress}
onKeyUp={this.handleUpKeyPress}
tabIndex='0'
/>
);
}
}
export default Pad
class Pads extends React.Component {
constructor(props) {
super(props);
// tone.js build
this.synth = new Tone.Synth().toMaster()
this.vol = new Tone.Volume(0)
this.synth.chain(this.vol, Tone.Master)
// bindings
this.onDownKey = this.onDownKey.bind(this);
this.onUpKey = this.onUpKey.bind(this);
}
onDownKey(note) {
console.log(`${note} played`);
this.synth.triggerAttack(note);
}
onUpKey(note) {
this.synth.triggerRelease();
}
render() {
const { octave } = this.props
return (
<div className="pad-grid">
<Pad
keyCode={65}
note={`C${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
<Pad
keyCode={70}
note={`Db${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
<Pad
keyCode={83}
note={`D${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
<Pad
keyCode={68}
note={`Eb${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
</div>
);
}
}
export default Pads
Heres a codepen so you can check it out in action https://codepen.io/P-FVNK/pen/XopBgW
Final Code: https://codesandbox.io/s/5v89kw6w0n
So I made quite a number of changes to enable the functionality you wanted.
Is there a way to maybe loop through the repeated components and their properties to match event.keyCode to correct keycode property?
To enable this, I decided to create an object array containing the possible keys and their notes. I changed them from the now deprecated KeyboardEvent.keyCode to KeyboardEvent.key instead. You could use KeyboardEvent.code as well.
this.padCodes = [
{
key: "a",
note: "C"
},
{
key: "f",
note: "Db"
},
{
key: "s",
note: "D"
},
{
key: "d",
note: "Eb"
}
];
I moved the keydown event listeners from the individual <Pad />s to the parent component and attached the listener to the document instead.
document.addEventListener("keydown", e => {
let index = this.state.pads.findIndex(item => item.key === e.key);
let { octave } = this.context;
if (this.state.pressed === false) {
this.onDownKey(`${this.state.pads[index].props.note}${octave}`);
this.setState({
activeNote: this.state.pads[index].note,
pressed: true
});
}
});
document.addEventListener("keyup", e => {
this.onUpKey(this.state.activeNote);
this.setState({
activeNote: "",
pressed: false
});
});
You may also notice I decided to use the Context instead of props. That's just personal preference, but I rather have one main reference to the octave state rather than passing the prop to each child component.
After that it was just a matter of ensuring that the functions made the calls to the same function and passed both the note and octave.
To reduce some of the clutter I made an array of <Pad />s which I later rendered instead of typing them out one by one.
let newPadCodes = this.padCodes.map(x => (
<Pad
key={x.key.toString()}
note={`${x.note}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
));
this.setState({
pads: newPads
});
//And later
//Render if this.state.pads.length is > 0
{this.state.pads.length > 0 &&
this.state.pads}
That just about covers all I did. Here's the codesandbox with the modified code: https://codesandbox.io/s/5v89kw6w0n
I am attempting to validate an input manually by passing up the chain the input of a text field. Some magic is supposed to happen whereby the following conditions are checked:
if input text matches that already held in an array - error = "already exists" & the text isn't added to the list
if input text is blank - error = "no text input" & the text isn't added to the list
if input text is not blank and does not already exist - run another method to add text to the list
The error is set to null by default
Currently in the input.js file, the {this.props.renderError} line causes an "underfined" output in the console before anything happens. I understand why this occurs, but I wondered if there was any way to stop it?
Functionality-wise: I can get the error message to output, however this appears to run after the text is already placed in the list of tasks...
Checkout the sandbox for this code
App.js (parent)
const tasks = [
{ name: 'task1', isComplete: false },
{ name: 'task2', isComplete: true },
{ name: 'task3', isComplete: false },
]
class App extends React.Component {
constructor() {
super();
this.state = {
error: null,
}
}
render() {
return (
<div>
<Input
createTask={this.createTask.bind(this)}
renderError={this.renderError.bind(this)}
taskList={this.state.tasks}
throwError={this.throwError.bind(this)}
/>
</div>
)
}
createTask(task, errorMsg) {
this.throwError(errorMsg);
if (this.state.error) {
return;
} else {
this.setState((prevState) => {
prevState.tasks.push({ name: task, isComplete: false });
return {
tasks: prevState.tasks
}
})
}
}
throwError(errorMsg) {
if (errorMsg) {
this.setState((prevState) => {
prevState.error = errorMsg;
return {
error: prevState.error
}
})
}
return;
}
renderError() {
if (this.state.error) {
return <div style={{ color: 'red' }}>{this.state.error}</div>
}
}
Input.js (child)
render() {
return (
<form ref="inputForm" onSubmit={this.handleCreate.bind(this)}>
<TextField placeholder="Input.js"/>
<Button type="submit">Click me</Button>
{this.props.renderError()}
</form>
)
}
validateInput(taskName) {
if (!taskName) {
return '*No task entered';
} else if (this.props.taskList.find(todo => todo.name.toLowerCase() === taskName.toLowerCase())) {
return '*Task already exists'
} else {
return null;
}
}
handleCreate(event) {
event.preventDefault();
// Determine task entered
var newTask = this.refs.inputForm[0].value;
// Constant for error message returned
const validInput = this.validateInput(newTask);
// If error message produced - trigger error to be shown & end
if (newTask) {
this.props.createTask(newTask, validInput);
this.refs.inputForm.reset();
}
}
Update
I have since found that I can make this work if I move the renderError and throwError methods to input.js and also transfer across the state property error.
I have recently started working on react.js, while creating the login page I have used setstate method to set the value of userEmail to text box.
I have created a method which checks the validity of email address and I am calling it every time when user enters a new letter.
handleChangeInEmail(event) {
var value = event.target.value;
console.log("change in email value" + value);
if(validateEmailAddress(value) == true) {
this.setState(function() {
return {
showInvalidEmailError : false,
userEmailForLogin: value,
}
});
} else {
this.setState(function() {
return {
showInvalidEmailError : true,
userEmailForLogin: value
}
});
}
This method and userEmailForLogin state is passed in render method as
<EmailLoginPage
userEmailForLogin = {this.state.userEmailForLogin}
onHandleChangeInEmail= {this.handleChangeInEmail}
/>
I am using the method to validate the email address and the method is
validateEmailAddress : function(emailForLogin) {
if (/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(emailForLogin)) {
return true;
}
return false;
},
I am using this method and state in render of EmailLoginPage as <input type="text" name="" placeholder="Enter Email" className="email-input-txt" onChange={props.onHandleChangeInEmail} value = {props.userEmailForLogin}/>
This is working fine in normal case , but when I try to input a large email addess say yjgykgkykhhkuhkjhgkghjkhgkjhghjkghjghghkghbghbg#gmail.com, it crashes
IMO the frequent change in state is causing this but I couldn't understand what should be done to get rid of this.
I think issue is with the regex only, i tried with other and it's working properly.
Instead of writing the if/else inside change function simply you are write it like this:
change(event) {
var value = event.target.value;
this.setState({
showInvalidEmailError : this.validateEmailAddress(value),
value: value,
});
}
Copied the regex from this answer: How to validate email address in JavaScript?
Check the working solution:
class App extends React.Component {
constructor(){
super();
this.state = {
value: '',
showInvalidEmailError: false
}
this.change = this.change.bind(this);
}
change(event) {
var value = event.target.value;
this.setState(function() {
return {
showInvalidEmailError : this.validateEmailAddress(value),
value: value,
}
});
}
validateEmailAddress(emailForLogin) {
var regex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if(regex.test(emailForLogin)){
return true;
}
return false;
}
render() {
return(
<div>
<input value={this.state.value} onChange={this.change}/>
<br/>
valid email: {this.state.showInvalidEmailError + ''}
</div>
);
}
}
ReactDOM.render(
<App/>,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
You could use Lodash's debounce function so that the check function is not called unless the user stops typing for x amount of time (300ms in my scenario below).
_this.debounceCheck = debounce((value) => {
if(validateEmailAddress(value)) {
this.setState(function() {
return {
showInvalidEmailError : false,
userEmailForLogin: value,
}
});
} else {
this.setState(function() {
return {
showInvalidEmailError : true,
userEmailForLogin: value
}
});
}
}, 300)
handleChangeInEmail(event) {
_this.debounce(event.target.value)
}
A solution using debounce. This way multiple setState can be reduced.
DEMO: https://jsfiddle.net/vedp/kp04015o/6/
class Email extends React.Component {
constructor (props) {
super(props)
this.state = { email: "" }
}
handleChange = debounce((e) => {
this.setState({ email: e.target.value })
}, 1000)
render() {
return (
<div className="widget">
<p>{this.state.email}</p>
<input onChange={this.handleChange} />
</div>
)
}
}
React.render(<Email/>, document.getElementById('container'));
function debounce(callback, wait, context = this) {
let timeout = null
let callbackArgs = null
const later = () => callback.apply(context, callbackArgs)
return function() {
callbackArgs = arguments
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}