I am still new to React and have been doing small projects lately to get better. I am currently working on a nutrition webpage that sets calorie goals and obtains foods from an API. This project consists of 2 components FoodItem and Main.
The Main component calculates calories and displays the search results from the API. Here lies the problem. When the search bar first receives a name, it displays nothing. However, it displays the intended search results after a backspace (deleting one letter from the word). This is seen in the screenshots.
Full word:
After deleting one letter:
Here is the function responsible for displaying the Search Results:
updateResult(name) {
console.log(name);
if (name == "") {
this.setState({
foodResult: []
})
return;
}
let result = [];
let url = 'https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=key&ingr=' + name;
fetch(url)
.then(
function(response) {
return response.json();
}
).then(function(jsonData) {
for (let i = 0; i < jsonData.hints.length; i++) {
foods.push({
name: jsonData.hints[i].food.label,
calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL)
})
}
})
console.log(foods);
foods = removeDuplicates(foods);
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
result.push(
<FoodItem name ={foods[i].name} calories ={foods[i].calories} updateFoods = {this.displayEatenFoods} isEaten = {false} checkItem = {this.checkItem}/>)
}
}
console.log(result);
this.setState({
foodResult: result
});
}
Full code:
import React from "react";
import ReactDOM from "react-dom";
//////////////////
let foods = [];
function removeDuplicates(arr) {
var unique = [];
for (let i = 0; i < arr.length; i++) {
let current = arr[i].name;
let add = true;
for (let i = 0; i < unique.length; i++) {
if (current == unique[i].name) add = false;
}
if (add) unique.push(arr[i]);
}
return unique;
}
///////////////////
class FoodItem extends React.Component {
constructor(props) {
super(props);
this.state = { addDone: false, disable: false };
this.addEaten = this.addEaten.bind(this);
}
addEaten() {
if (this.props.updateFoods(this.props.name))
this.setState({ addDone: false, disable: true });
else this.setState({ addDone: true });
}
render() {
if (this.props.isEaten) {
return (
<div>
{this.props.name}
Calories :{this.props.calories}
</div>
);
}
if (!this.state.addDone) {
return (
<div>
{this.props.name}
Calories :{this.props.calories}
<button primary onClick={this.addEaten} disabled={this.state.disable}>
Eat
</button>
</div>
);
}
return null;
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
goal: " ",
remaining: " ",
goalEntered: false,
foodSearch: "",
foodResult: [],
EatensFoods: [],
allowance: " ",
calories: ""
};
this.setGoal = this.setGoal.bind(this);
this.changeGoal = this.changeGoal.bind(this);
this.changeFoodSearch = this.changeFoodSearch.bind(this);
this.displayEatenFoods = this.displayEatenFoods.bind(this);
this.checkItem = this.checkItem.bind(this);
this.changeCalorieSearch = this.changeCalorieSearch.bind(this);
}
changeGoal(event) {
this.setState({ goal: event.target.value });
}
setGoal(event) {
this.setState({ goalEntered: true, remaining: this.state.goal });
event.preventDefault();
}
changeFoodSearch(event) {
this.setState({ foodSearch: event.target.value });
this.updateResult(event.target.value);
}
changeCalorieSearch(event) {
this.setState({ calories: event.target.value });
}
updateResult(name) {
console.log(name);
if (name == "") {
this.setState({ foodResult: [] });
return;
}
let result = [];
let url =
"https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=key&ingr=" +
name;
fetch(url)
.then(function(response) {
return response.json();
})
.then(function(jsonData) {
for (let i = 0; i < jsonData.hints.length; i++) {
foods.push({
name: jsonData.hints[i].food.label,
calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL)
});
}
});
console.log(foods);
foods = removeDuplicates(foods);
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
result.push(
<FoodItem
name={foods[i].name}
calories={foods[i].calories}
updateFoods={this.displayEatenFoods}
isEaten={false}
checkItem={this.checkItem}
/>
);
}
}
console.log(result);
this.setState({ foodResult: result });
}
displayEatenFoods(name) {
let tempEaten = [];
let disableFlag = false;
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase() == name.toUpperCase()) {
if (this.checkItem(foods[i].calories, foods[i].name)) {
tempEaten.push(
<FoodItem
name={foods[i].name}
calories={foods[i].calories}
updateFoods={this.displayEatenFoods}
isEaten={true}
checkItem={this.checkItem}
/>
);
} else {
disableFlag = true;
}
}
}
tempEaten = removeDuplicates(tempEaten);
tempEaten = this.state.EatensFoods.concat(tempEaten);
this.setState({ EatensFoods: tempEaten });
return disableFlag;
}
checkItem(cal, name) {
let newRemainder = this.state.remaining - cal;
if (newRemainder < 0) {
this.setState({ allowance: "You can't eat " + name });
return false;
}
this.setState({
remaining: newRemainder,
allowance: "You can eat " + name
});
return true;
}
render() {
if (!this.state.goalEntered) {
return (
<center>
<form onSubmit={this.setGoal}>
<label>
Please Enter your desired calories
<input
type="text"
value={this.state.goal}
onChange={this.changeGoal}
/>
</label>
<input type="submit" value="OK" />
</form>
</center>
);
}
return (
<div>
<center>
<h1>Maximum Calories:{this.state.goal}</h1>
<h2>Remaining Calories:{this.state.remaining}</h2>
<h3>{this.state.allowance}</h3>
<form>
<label>
Search foods
<input
type="text"
placeholder="Enter Name"
value={this.state.foodSearch}
ref={a => {
this.searchValue = a;
}}
onChange={this.changeFoodSearch}
/>
<input
type="text"
placeholder="Calories,Min+,Max,Min-Max"
value={this.state.calories}
onChange={this.changeCalorieSearch}
/>
</label>
</form>
{this.state.foodResult}
<h2>Eaten Foods:</h2>
{this.state.EatensFoods}
</center>
</div>
);
}
}
ReactDOM.render(<Main />, document.getElementById("root"));
First of all I can't examine all your code if there should be some best practices instead of your logic but your problem is your updateResult function doing an async job but you are not waiting it to finish. This is your main problem. Deleting one word or deleting anything does not trigger the problem. Just type "o" then wait a little bit, then write anything and see the same problem occurs. Make your updateResult function async and put an await before your fetch.
async updateResult(name) {
console.log(name);
if (name == "") {
this.setState({ foodResult: [] })
return;
}
let result = [];
let url = 'https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=somekeyhere&ingr=' + name;
await fetch(url)
.then(
function (response) {
return response.json();
}
).then(function (jsonData) {
for (let i = 0; i < jsonData.hints.length; i++) {
foods.push({ name: jsonData.hints[i].food.label, calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL) })
}
})
console.log(foods);
foods = removeDuplicates(foods);
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
result.push(
<FoodItem name={foods[i].name} calories={foods[i].calories} updateFoods={this.displayEatenFoods} isEaten={false} checkItem={this.checkItem} />)
}
}
console.log(result);
this.setState({ foodResult: result });
}
Instead of making your function an async one, you can continue the code with another .then method or methods. Just do not forget to return what you need for those .then methods from the previous ones.
Related
When I update 'classProficienciesChoices' in my state using setState() it is updating not only that property, but also where I derived the 'classProficienciesChoices' info from in the 'classSelected' property, AND ALSO from where I derived the classSelected info from in the 'classesInfo' property.
The same function I update 'classProficienciesChoices' I also update 'classProficiencies', and it updates properly in the one property I tell it to, and not the elements where the information was derived from.
Any insight would be helpful. The Create component has other components nested and none of them have state and only use props passed. There are navigation, selection, and information display components nested.
import React, { Component } from 'react'
import Create from './Create'
class App extends Component {
constructor(props) {
super(props);
const url = 'http://www.dnd5eapi.co/api/';
fetch(url + 'classes')
.then(result => result.json())
.then(result => { this.setState({ classes: result, }, this.getInfo(result)) });
}
state = {
classes: {}, //assigned value from API call in constructor
classesInfo: [], //assigned value from API call in getInfo()
classSelected: {}, //assigned a value derived from classInfo in displayClassInfo()
classProficiencies: [], //assigned a value derived from classSelected in setStartingProficiencies()
classProficienciesChoices: [], //assigned a value derived from classSelected in setStartingProficiencies()
}
getInfo(data) {
let info = []
const url = 'http://www.dnd5eapi.co'
for (var i = 0; i < data.results.length; i++) {
fetch(url + data.results[i].url)
.then(result => result.json())
.then(result => info.push(result))
}
this.setState({ classesInfo: info, })
}
}
setStartingProficiencies(chosenClass) {
const profs = chosenClass.proficiencies.map((prof) => {
return prof;
});
const proChoice = chosenClass.proficiency_choices.map((choices) => {
return choices;
});
this.setState({ classProficiencies: profs, classProficienciesChoices: proChoice, });
}
addProficiency = (proficiencyName) => {
const { classProficienciesChoices } = this.state
// classProficienciesChoices: [
// { choose: 2, type: 'proficiencies', from: [{ name: 'someName', url: 'someUrl' }, { name: 'someName', url: 'someUrl' }] },
// ]
// different classes have more objects in the parent array
let newChoiceArray = classProficienciesChoices.map((choices) => {
return choices
})
for (var i = 0; i < newChoiceArray.length; i++) {
for (var j = 0; j < newChoiceArray[i].from.length; j++) {
if (newChoiceArray[i].from[j].name === proficiencyName) {
let newChoices = newChoiceArray[i].from.filter(function (proficiency) { return proficiency.name !== pIndex })
let newProficiency = newChoiceArray[i].from.filter(function (proficiency) { return proficiency.name === pIndex })
newChoiceArray[i].from = newChoices //I think this is the problem
this.setState(state => ({
classProficiencies: [...state.classProficiencies, newProficiency[0]],
proficienciesChoices: newChoiceArray,
}))
}
}
}
}
displayClassInfo = index => {
const { classesInfo } = this.state
for (let i = 0; i < classesInfo.length; i++) {
if (classesInfo[i].index === index) {
const classSelected = classesInfo.filter(function (cClass) { return cClass.name === classesInfo[i].name })
this.setState({ classSelected: classSelected[0], isClassSelected: true }, this.setStartingProficiencies(classSelected[0]),)
break;
}
}
}
render() {
const { classes, classesInfo, classSelected, isClassSelected, classProficiencies, classProficienciesChoices } = this.state
return (<Create classes={classes} classesInfo={classesInfo} displayClassInfo={this.displayClassInfo} classSelected={classSelected} isClassSelected={isClassSelected} category='classes' classProficiencies={classProficiencies} classProficienciesChoices={classProficienciesChoices} addProficiency={this.addProficiency} />);
}
}
export default App
You call setState four times, of course it will update the state multiple times.
SOLVED!!! Stumbled across turning the array into a string and then back. It breaks the reference and creates an entirely new array. Reference type got me.
setStartingProficiencies(chosenClass) {
const proficiencies = JSON.parse(JSON.stringify(chosenClass.proficiencies))
const proficienciesChoices = JSON.parse(JSON.stringify(chosenClass.proficiency_choices))
this.setState({ classProficiencies: proficiencies, classProficienciesChoices: proficienciesChoices, });
}
I'm trying to write a todo list project with pure JS using MVC pattern. It's my first project with MVC and I have a real problem that I can't solve.
I have three buttons, each button gets a value as filter value (in Model class) for complete, active and all task. default value for filter is 0 that refers to all button.
when complete button is active, new todo adds to page, but that page is only for complete todo and new todo must add to all pages while complete page is still active.
I write some methods for handle it, but they doesn't work and I can't understand where is the problem.
how can I solve it?
this is my code:
class Model {
constructor() {
this.todoS = [];
this.filter = 0;
}
bindTodoListChanged(callback) {
this.onTodoListChanged = callback;
}
_commit(todoS) {
this.onTodoListChanged(todoS);
}
addTodo(todoText) {
var todo = {
id:
this.todoS.length > 0
? this.todoS[this.todoS.length - 1].id + 1
: 0,
text: todoText,
complete: false
};
this.todoS.push(todo);
this._commit(this.todoS);
}
toggleTodo(id) {
this.todoS = this.todoS.map(todo =>
todo.id === id
? {
id: todo.id,
text: todo.text,
complete: !todo.complete
}
: todo
);
this._commit(this.todoS);
}
filterTodo(filter) {
this.todoS.filter(todo => {
if (filter === 0) return true;
return filter === 1 ? !todo.complete : todo.complete;
});
}
}
class View {
constructor() {
this.form = document.querySelector("#taskForm");
this.input = document.getElementById("taskInput");
this.list = document.querySelector("#taskList");
this.filterBtnS = document.getElementById("filterButtons");
this.allBtn = document.querySelector(".all");
this.activeBtn = document.querySelector(".active");
this.completeBtn = document.querySelector(".complete");
}
createElement(tag, className) {
var element = document.createElement(tag);
if (className) element.classList.add(className);
return element;
}
getElement(selector) {
var element = document.querySelector(selector);
return element;
}
get _todoText() {
return this.input.value;
}
_resetInput() {
this.input.value = "";
}
displayTodoS(todoS) {
// Faster way for clear tasks
while (this.list.firstChild) {
this.list.removeChild(this.list.firstChild);
}
if (todoS.length !== 0) {
todoS.forEach(todo => {
var li = this.createElement("li", "task"),
span = this.createElement("span");
li.id = todo.id;
var checkbox = this.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = todo.complete;
if (todo.complete) {
var strike = this.createElement("s");
strike.textContent = todo.text;
span.innerHTML = "";
span.append(strike);
} else {
span.textContent = todo.text;
}
li.append(checkbox, span);
this.list.append(li);
});
}
}
bindAddTodo(handler) {
this.form.addEventListener("submit", e => {
e.preventDefault();
if (this._todoText) {
handler(this._todoText);
this._resetInput();
}
});
}
bindToggleTodo(handler) {
this.list.addEventListener("change", event => {
if (event.target.type === "checkbox") {
var id = +event.target.parentElement.id;
handler(id);
}
});
}
bindFilterTodo(handler) {
this.filterBtnS.addEventListener("click", e => {
var filter = +e.target.getAttribute("value");
handler(filter);
});
}
}
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
this.model.bindTodoListChanged(this.onTodoListChanged);
this.view.bindAddTodo(this.handleAddTodo);
this.view.bindToggleTodo(this.handleToggleTodo);
this.view.bindFilterTodo(this.handleFilterTodo);
this.onTodoListChanged(this.model.todoS);
}
onTodoListChanged = todoS => {
this.view.displayTodoS(todoS);
};
handleAddTodo = todoText => {
this.model.addTodo(todoText);
};
handleToggleTodo = id => {
this.model.toggleTodo(id);
};
handleFilterTodo = filter => {
this.model.filterTodo(filter);
};
}
var app = new Controller(new Model(), new View());
<div id="main">
<h2>Task List</h2>
<form id="taskForm">
<input
id="taskInput"
placeholder="New task..."
autocomplete="off"
/>
<input class="submit" type="submit" value="Add Task" />
</form>
<div id="filterButtons" class="buttons">
<div class="all" value="0">All</div>
<div class="active" value="1">Active</div>
<div class="complete" value="2">Completed</div>
</div>
<ul id="taskList"></ul>
</div>
The issue is that in filterTodo, you just filter the tasks but not _commit the change.
So
Store the filter in the model
Move the filter to _commit (so it will keep filter for any other actions such as add todo)
class Model {
constructor() {
this.todoS = [];
this.filter = 0;
}
bindTodoListChanged(callback) {
this.onTodoListChanged = callback;
}
_commit(todoS = this.todoS) {
this.onTodoListChanged(todoS.filter(todo => {
if (this.filter === 0) return true;
return this.filter === 1 ? !todo.complete : todo.complete;
}));
}
addTodo(todoText) {
var todo = {
id:
this.todoS.length > 0
? this.todoS[this.todoS.length - 1].id + 1
: 0,
text: todoText,
complete: false
};
this.todoS.push(todo);
this._commit(this.todoS);
}
toggleTodo(id) {
this.todoS = this.todoS.map(todo =>
todo.id === id
? {
id: todo.id,
text: todo.text,
complete: !todo.complete
}
: todo
);
this._commit(this.todoS);
}
filterTodo(filter) {
this.filter = filter;
this._commit();
}
}
class View {
constructor() {
this.form = document.querySelector("#taskForm");
this.input = document.getElementById("taskInput");
this.list = document.querySelector("#taskList");
this.filterBtnS = document.getElementById("filterButtons");
this.allBtn = document.querySelector(".all");
this.activeBtn = document.querySelector(".active");
this.completeBtn = document.querySelector(".complete");
}
createElement(tag, className) {
var element = document.createElement(tag);
if (className) element.classList.add(className);
return element;
}
getElement(selector) {
var element = document.querySelector(selector);
return element;
}
get _todoText() {
return this.input.value;
}
_resetInput() {
this.input.value = "";
}
displayTodoS(todoS) {
// Faster way for clear tasks
while (this.list.firstChild) {
this.list.removeChild(this.list.firstChild);
}
if (todoS.length !== 0) {
todoS.forEach(todo => {
var li = this.createElement("li", "task"),
span = this.createElement("span");
li.id = todo.id;
var checkbox = this.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = todo.complete;
if (todo.complete) {
var strike = this.createElement("s");
strike.textContent = todo.text;
span.innerHTML = "";
span.append(strike);
} else {
span.textContent = todo.text;
}
li.append(checkbox, span);
this.list.append(li);
});
}
}
bindAddTodo(handler) {
this.form.addEventListener("submit", e => {
e.preventDefault();
if (this._todoText) {
handler(this._todoText);
this._resetInput();
}
});
}
bindToggleTodo(handler) {
this.list.addEventListener("change", event => {
if (event.target.type === "checkbox") {
var id = +event.target.parentElement.id;
handler(id);
}
});
}
bindFilterTodo(handler) {
this.filterBtnS.addEventListener("click", e => {
var filter = +e.target.getAttribute("value");
handler(filter);
});
}
}
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
this.model.bindTodoListChanged(this.onTodoListChanged);
this.view.bindAddTodo(this.handleAddTodo);
this.view.bindToggleTodo(this.handleToggleTodo);
this.view.bindFilterTodo(this.handleFilterTodo);
this.onTodoListChanged(this.model.todoS);
}
onTodoListChanged = todoS => {
console.log(todoS);
this.view.displayTodoS(todoS);
};
handleAddTodo = todoText => {
this.model.addTodo(todoText);
};
handleToggleTodo = id => {
this.model.toggleTodo(id);
};
handleFilterTodo = filter => {
this.model.filterTodo(filter);
};
}
var app = new Controller(new Model(), new View());
<div id="main">
<h2>Task List</h2>
<form id="taskForm">
<input id="taskInput" placeholder="New task..." autocomplete="off" />
<input class="submit" type="submit" value="Add Task" />
</form>
<div id="filterButtons" class="buttons">
<div class="all" value="0">All</div>
<div class="active" value="1">Active</div>
<div class="complete" value="2">Completed</div>
</div>
<ul id="taskList"></ul>
</div>
https://stackblitz.com/edit/js-1u6dxi
I suspect the biggest issues in the returning of values. For example this:
handleFilterTodo = filter => {
this.model.filterTodo(filter);
};
This code does not return anything, it only calls this.model.filterTodo. In the corresponding method filterTodo, you are creating a new array by using this.todoS.filter but you do not return it anywhere:
filterTodo(filter) {
this.todoS.filter(todo => {
if (filter === 0) return true;
return filter === 1 ? !todo.complete : todo.complete;
});
}
You could do something like what you did with toggleTodo and the map function here:
filterTodo(filter) {
this.todoS = this.todoS.filter(todo => {
if (filter === 0) return true;
return filter === 1 ? !todo.complete : todo.complete;
});
}
...but that would only work once, as setting the filter would remove the other todos from your database.
From my understanding of the code (without trying it out), I'd probably set the filter only and whenever _commit gets called, pass the filtered version of the todos based on the selected filter
constructor() {
...
this.possibleFilters = {
0: () => true,
1: todo => !todo.completed,
2: todo => todo.completed
};
}
filterTodo(filter) {
this.filter = filter;
}
_commit(todoS) {
const selectedFilter = this.possibleFilters[this.filter];
this.onTodoListChanged(todoS.filter(selectedFilter));
}
I have this component DynamicSelect (child compoenent) and I'm using it in an another component (parent) but when I try to validate my childe component, it deliver always the value as null so the validation is always false
DynamicSelect Component:
<template>
<a-select
:showSearch="true"
:placeholder=placeholder
:value="selectedValue"
#search="searchRegex($event)"
#change="$emit('changed-item', setChangedItem($event))"
#select="$emit('selected-item', setSelectedItem($event))"
:filterOption="filterOption"
>
<a-select-option
v-for="(item,idx) in dropdownData"
:value="idx"
:key="idx"
>{{item.text}}</a-select-option>
</a-select>
</template>
<script>
export default {
name: "DynamicSelect",
data(){
return{
dropdownData: [],
copyDropdownData:[],
selectedValue: undefined
}
},
props:{
//Input data collection
dataSrc:Array,
//Placeholder for input field
placeholder: String,
//if true the dropdown will be automatically cleared after element selected
resetAfterSelect: false,
// List of id to filter the dropdown list
lookFor:Array,
// Data to display in the dropdown if not set, lookFor list will be displayed
displayedValues: Array,
//Default Value
defaultValues:String,
},
beforeMount(){
this.checkDefaultVariable();
},
watch:{
dataSrc:function(newVar,oldVar) { // watch it
this.checkDefaultVariable()
}
},
methods:{
//Search for search term in the data collection 'lookFor' elements to set the dropdown list
async searchRegex(term){
if(term.length>2) {
let searchTerm = new RegExp(term.toUpperCase());
this.dropdownData = await this.filterData(searchTerm);
this.copyDropdownData = JSON.parse(JSON.stringify(this.dropdownData));
}
else
{
this.dropdownData = [];
this.copyDropdownData = [];
}
},
filterData(searchTerm){
return this.dataSrc.filter(x => {
let filtered= [];
for (let i=0; i<this.lookFor.length;i++){
if(x[this.lookFor[i]])
{
if(searchTerm.test(x[this.lookFor[i]].toUpperCase()))
{
let text = '';
if(this.displayedValues !== undefined)
{
for (let k=0; k<this.displayedValues.length;k++)
{
text += x[this.displayedValues[k]];
if(k < this.displayedValues.length-1)
text += ', '
}
}
else {
for (let k=0; k<this.lookFor.length;k++)
{
text += x[this.lookFor[k]];
if(k < this.lookFor.length-1)
text += ', '
}
}
x.text = text;
filtered.push(x);
}
}
}
return filtered.length>0
});
},
// just a logger
logger(event){
console.log(event);
},
async checkDefaultVariable(){
if (this.defaultValues !== '' && this.defaultValues !== undefined && this.dataSrc.length>0 ){
// console.log('DATA',this.dataSrc);
await this.searchRegex(this.defaultValues);
let selected = await this.setSelectedItem(0);
this.$emit('selected-item', selected)
}
},
// return the selected Item as an Object
setSelectedItem(id){
// console.log('ON SELECT');
let temp = JSON.parse(JSON.stringify(this.dropdownData[id]));
delete temp.text;
if(this.resetAfterSelect)
{
this.dropdownData = [];
this.selectedValue = undefined;
}
else {
this.selectedValue = id;
}
return temp
},
setChangedItem(id){
let temp = JSON.parse(JSON.stringify(this.copyDropdownData[id]));
delete temp.text;
if(this.resetAfterSelect)
{
this.copyDropdownData = [];
this.selectedValue = undefined;
}
else {
this.selectedValue = id;
}
return temp
},
// search in the dropdown list
filterOption(input, option) {
let searchTerm = new RegExp(input.toUpperCase());
if(searchTerm.test(this.dropdownData[option.key].text.toUpperCase()))
return true;
else {
for(let i=0;i<this.lookFor.length;i++){
if(searchTerm.test(this.dropdownData[option.key][this.lookFor[i]].toUpperCase()))
return true;
else if(i >= this.lookFor.length)
return false;
}
}
}
}
}
</script>
parent component:
<template>
<dynamic-select
:dataSrc="users"
placeholder="Lastname, Firstname"
#selected-item="onSelectUser($event)"
#changed-item="onSelectUser($event)"
:lookFor="['lastname','firstname']"
v-decorator="['contact', {valuePropName:'selectedValue',
rules: [{ required: true,
validator: userExists,
message: 'Error'}]}]"
>
</dynamic-select>
</template>
<script>
.
.
.
methods: {
userExists(rule, value, callback) {
console.log('VALUE', value); //always undefined
console.log('RULES',rule);
console.log('CALLBACK',callback)
return value !== null && value !== undefined && value.length > 2;
},
onSelectUser(user) {
console.log("user: " , user); // set with the selected value
}
},
.
.
.
</script>
I expect that the child component returns the selected value like when emitting an event, I also tried with models but it hasn't helped
thanks :)
You can easily communicate between components
Vue.config.debug = true;
// Parent
let App = new Vue({
el: "#the-parent",
data(){
return{ msg: "Nothing.." };
},
methods:{
receivedFromChild(request){
this.msg = request;
}
},
// Children
components: {
'children': {
template: `
<div><button #click="request">Send to parent!</button>` + `<input type="text" v-model="text"></div>`,
props: [ 'childrenRequest' ],
data() {
return {
text: 'this is value'
}
},
methods: {
request(){
console.log('work!');
this.$emit('received', this.text);
}
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="the-parent">
<h3>The children want me to say: {{ msg }}</h3>
<children #received="receivedFromChild"></children>
</div>
export class EstimateForm extends React.Component<IEstimateFormProps,
IEstimateFormState> {
state: IEstimateFormState = {
cellUpdateCss: 'red',
toRow: null,
fromRow: null,
estimateList: null,
estimateItemList: [],
poseList: null,
levelList: null,
partList: null,
selectedEstimate: null,
totalEstimateItems: 0,
selectedIndexes: [],
totalEstimateAmount: 0,
grid: null,
projectId: 0,
};
constructor(props, context) {
super(props, context);
this.state.estimateList = this.props.estimateList;
}
rowGetter = i => {
const row = this.state.estimateItemList[i];
const selectRevison = this.state.selectedEstimate.revision;
if (row['pose.poseName']) {
const poseCode = row['pose.poseName'].substring(row['pose.poseName'].lastIndexOf('[') + 1, row['pose.poseName'].lastIndexOf(']'));
for (const pose of this.state.poseList) {
if (pose.poseCode === poseCode) {
row.pose = pose;
}
}
}
if (row['level.levelName']) {
const levelCode = row['level.levelName'].substring(
row['level.levelName'].lastIndexOf('[') + 1,
row['level.levelName'].lastIndexOf(']')
);
for (const level of this.state.levelList) {
if (level.levelCode === levelCode) {
row.level = level;
}
}
}
if (row['level.part.partName']) {
const partCode = row['level.part.partName'].substring(
row['level.part.partName'].lastIndexOf('[') + 1,
row['level.part.partName'].lastIndexOf(']')
);
for (const part of this.state.partList) {
if (part.partCode === partCode) {
row.part = part;
}
}
}
row.get = key => eval('row.' + key);
row.totalCost = (row.materialCost + row.laborCost) * row.amount;
if (row.revision > selectRevison) {
for (let i = 0; i < row.length; i++) {
row[i].style.color = 'red'; // here color is nor change
}
return row;
} else {
return row;
}
}
handleGridRowsUpdated = ({ fromRow, toRow, updated }) => {
const rows = this.state.estimateItemList.slice();
for (let i = fromRow; i <= toRow; i++) {
const rowToUpdate = rows[i];
const updatedRow = update(rowToUpdate, { $merge: updated });
rows[i] = updatedRow;
}
this.setState({ estimateItemList: rows, fromRow: (fromRow), toRow: (toRow)
}, () => {
});
};
saveEstimateItems = () => {
if (this.state.selectedEstimate == null) {
toast.warn(<Translate
contentKey="bpmApp.estimateForm.pleaseSelectEstimate">Please select an
estimate</Translate>);
return;
}
render() {
return ()
}
I wanna to change the row color when the condition row.revision > this.state.selectedEstimate.revision . How can I prevent the change of this.color. However im not get any error but row color is not change. how can i solve this problem it is my first project in react and i dont know where is the problemThanks for your feedback guys.
export class EstimateForm extends React.Component<IEstimateFormProps,
IEstimateFormState> {
state: IEstimateFormState = {
cellUpdateCss: 'red',
toRow: null,
fromRow: null,
estimateList: null,
estimateItemList: [],
poseList: null,
levelList: null,
partList: null,
selectedEstimate: null,
totalEstimateItems: 0,
selectedIndexes: [],
totalEstimateAmount: 0,
grid: null,
projectId: 0,
};
constructor(props, context) {
super(props, context);
this.state.estimateList = this.props.estimateList;
}
rowGetter = i => {
const row = this.state.estimateItemList[i];
const selectRevison = this.state.selectedEstimate.revision;
if (row['pose.poseName']) {
const poseCode =
row['pose.poseName'].substring(row['pose.poseName'].lastIndexOf('[') + 1,
row['pose.poseName'].lastIndexOf(']'));
for (const pose of this.state.poseList) {
if (pose.poseCode === poseCode) {
row.pose = pose;
}
}
}
if (row['level.levelName']) {
const levelCode = row['level.levelName'].substring(
row['level.levelName'].lastIndexOf('[') + 1,
row['level.levelName'].lastIndexOf(']')
);
for (const level of this.state.levelList) {
if (level.levelCode === levelCode) {
row.level = level;
}
}
}
if (row['level.part.partName']) {
const partCode = row['level.part.partName'].substring(
row['level.part.partName'].lastIndexOf('[') + 1,
row['level.part.partName'].lastIndexOf(']')
);
for (const part of this.state.partList) {
if (part.partCode === partCode) {
row.part = part;
}
}
}
row.get = key => eval('row.' + key);
row.totalCost = (row.materialCost + row.laborCost) * row.amount;
const changeColor = {
backgroundcolor: 'red'
};
const all = document.getElementsByClassName('react-grid-Row') as
HTMLCollectionOf<HTMLElement>;
debugger; if (row.revision > selectRevison) {
for (let i = this.state.fromRow; i <= this.state.toRow; i++) {
all[i].style.color = 'red'; //HERE
}
return row;
}
}
handleGridRowsUpdated = ({ fromRow, toRow, updated }) => {
const rows = this.state.estimateItemList.slice();
for (let i = fromRow; i <= toRow; i++) {
const rowToUpdate = rows[i];
const updatedRow = update(rowToUpdate, { $merge: updated });
rows[i] = updatedRow;
}
this.setState({ estimateItemList: rows, fromRow: (fromRow), toRow: (toRow)
}, () => {
});
};
saveEstimateItems = () => {
if (this.state.selectedEstimate == null) {
toast.warn(<Translate
contentKey="bpmApp.estimateForm.pleaseSelectEstimate">Please select an
estimate</Translate>);
return;
}
render() {
return ()
}
I wanna to change the row color when the condition row.revision > this.state.selectedEstimate.revision . How can I prevent the change of this.color. However TypeError: Cannot read property 'style' of undefined get error but row color is not change. how can i solve this problem it is my first project in react and i dont know where is the problemThanks for your feedback guys.
Okay, so without the rest of the context because your pasted code is difficult to read and understand, the simplest reason for your issue is in this chunk:
const all = document.getElementsByClassName('react-grid-Row') as
HTMLCollectionOf<HTMLElement>;
debugger; if (row.revision > selectRevison) {
for (let i = this.state.fromRow; i <= this.state.toRow; i++) {
all[i].style.color = 'red'; //HERE
}
Essentially there's multiple things that could go wrong here, but most likely there are either no rows with that class on the page, or less than your this.state.fromRow, I see you've got the debugger in there, but you are missing a few things:
You aren't doing a null check on all to make sure you are finding something
You aren't checking whether all.length > this.state.fromRow
You aren't breaking the for loop if all.length < this.state.toRow
It's failing because all[i] doesn't exist, or there's no values:
all = [0, 1]
and you are looking for all[3] for example
Throw in those fallbacks and check what all is on page load and you should be able to figure it out.