I created a todo list by using react. I get some problem that I want to create checkbox but my checkbox it does not work and I cannot solve :( I don't know what's wrong with that.
I set the data for each task and then I need to change the completed of some task, but it cannot click and change the completed task
This is my code
class App extends React.Component {
constructor() {
super()
this.state = {
todos: todoData,
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if(todo.id === id) {
todo.completed = !todo.completed
// console.log(todo.completed)
}
return todo
})
return {
todos: updatedTodos
}
})
}
render() {
const todoItem = this.state.todos.map(item => <TodoItem key={item.id} item={item}
handleChange={this.handleChange}/>)
return (
<div>
<h1 className="header">My Todo Lists</h1>
{todoItem}
</div>
)
}
}
function TodoItem(props) {
let textItem = props.item.completed === true ?
<del>{props.item.text}</del> : props.item.text
return (
<div className="list">
<input
type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<p className="item">{textItem}</p>
</div>
)
}
And this is my data
const todoData = [
{
id: 1,
text: "Practice coding",
completed: false
},
{
id: 2,
text: "Grocery shopping",
completed: true
},
{
id: 3,
text: "Wash the dishes",
completed: true
},
{
id: 4,
text: "Take out the trash",
completed: false
},
{
id: 5,
text: "Teach my brother homework",
completed: false
}
]
Thank you for helping :)
Looks like on your handleChange you are mutating the existing state on your map transformation. you must return a new state instead.
Replace your handleChange with the following code:
handleChange(id) {
this.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) => {
return {
...todo,
completed: todo.id === id ? !todo.completed : todo.completed
};
});
return {
todos: updatedTodos
};
});
}
Related
I have some troubles with asyncselect. Idea is to make a loop with many select fields with names as html array. Like fields[index], field_condition[index]. So for now it loads data into first select, but it doesn't set a value, and it's not loading second select(it take field it and loads data based on that field id), I've checked and all requests seems to be good. Two questions: How to make html arrays using asyncselect and why it doesn't load into second select?
Request for first asyncselect:
loadFields(inputValue) {
const { cookies, getFields } = this.props;
return new Promise(resolve => {
getFields({
headers: {
token: cookies.get('token')
},
per_page: 20,
page: 1,
dispatchAction: false,
resolve: (response) => {
resolve(filter(inputValue, response.data.results.map((field) => ({
...field,
value: field.id,
label: field.name,
}))));
},
reject: (error) => {
resolve(error);
}
});
});
}
Here is my second request that take additional argument(not working at all):
loadFieldConditions(inputValue, filter_id) {
const { cookies, getFieldConditions } = this.props;
return new Promise(resolve => {
getFieldConditions({
headers: {
token: cookies.get('token')
},
field_id: filter_id,
per_page: 20,
page: 1,
dispatchAction: false,
resolve: (response) => {
resolve(filter(inputValue, response.data.results.map((condition) => ({
...condition,
value: condition.id,
label: condition.name,
}))));
},
reject: (error) => {
resolve(error);
}
});
});
}
filter.fields.map((item, index) => {
return <div>
<div className="filter-item">
{/* <span class="missive-column-auto text-e">•</span> */}
<div className="filter-field">
<AsyncSelect
value={ fields[index] }
// onMenuClose={ closeDropdown.bind(null, item.field.id) }
onChange={ onChange.bind(null, 'fields['+index+']') }
className="react-select-container"
ref={(node) => this.fields[index] = node }
loadOptions={ loadFields }
classNamePrefix="react-select"
placeholder="Select field"
blurInputOnSelect={ true }
defaultOptions
cacheOptions
isClearable
/>
</div>
<div className="filter-field">
<AsyncSelect
value={ item.field_condition.id }
// onMenuClose={ closeDropdown.bind(null, item.field.id) }
// onChange={ onChange.bind(null, 'fields', item.field.id) }
className="react-select-container"
// ref={(node) => item.field.id = node }
loadOptions={(inputValue) => { loadFieldConditions(inputValue, item.field.id) }}
classNamePrefix="react-select"
placeholder="Select condition"
blurInputOnSelect={ true }
defaultOptions
cacheOptions
isClearable
/>
</div>
Currently i am using buefy autocomplete.But there are a few issues with it.
DepartmentDetail.vue
<template slot-scope="props">
<div class="container is-fluid">
<b-loading :is-full-page="true" :active.sync="this.isLoading"></b-loading>
<b-field label="Business Unit">
<b-autocomplete
:data="dataBusinessUnit"
placeholder="select a business unit..."
field="businessUnit"
:loading="isFetching"
:value="this.objectData.businessUnit"
#typing="getAsyncDataBusinessUnit"
#select="(option) => {updateValue(option.id,'businessUnit')}"
>
<template slot-scope="props">
<div class="container">
<p>
<b>ID:</b>
{{props.option.id}}
</p>
<p>
<b>Description:</b>
{{props.option.description}}
</p>
</div>
</template>
<template slot="empty">No results found</template>
</b-autocomplete>
</b-field>
</div>
</template>
Function that fetches the results based on user input-
getAsyncDataBusinessUnit: debounce(function(name) {
// console.log('getAsyncDataBusinessUnit you typed'+name);
if (!name.length) {
this.dataBusinessUnit = [];
return;
}
this.isFetching = true;
api
.getSearchData(this.sessionData.key,`/businessunit/${name}`)
.then(response => {
this.dataBusinessUnit = [];
response.forEach(item => {
this.dataBusinessUnit.push(item);
});
})
.catch(error => {
this.dataBusinessUnit = [];
throw error;
})
.finally(() => {
this.isFetching = false;
});
}, 500),
So instead of using the buefy's b-autocomplete i want to create and use my own autocomplete component.
So i went ahead and created my own autocomplete component and called it from the DepartmentDetail vue page like this-
DepartmentDetail.vue
<b-field label="Custom Business Unit ">
<AutoComplete :method="getAsyncDataBusinessUnit" title='businessUnit' :autocompleteData="dataBusinessUnit" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
AutoComplete.vue
<template>
<div class="autocomplete">
<input style="font-size: 12pt; height: 36px; width:1800px; " type="text" v-model="this.objectData[this.title]" #input="getAsyncDataBusinessUnit"/>
<ul v-show="isFetching" >
<li v-for="(dataBusinessUnit, i) in dataBusinessUnit" :key="i" #click="setResult(dataBusinessUnit)" >
<!-- {{ autocompleteData }} -->
<template v-if="title!='manager'">
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>Description:</b>
{{dataBusinessUnit.description}}
</p>
</div>
</template>
<template v-else>
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>First Name:</b>
{{dataBusinessUnit.firstName}}
</p>
<p>
<b>Last Name:</b>
{{dataBusinessUnit.lastName}}
</p>
</div>
</template>
</li>
</ul>
</div>
</template>
<script>
import { viewMixin } from "../viewMixin.js";
import schemaData from '../store/schema';
import debounce from "lodash/debounce";
import api from "../store/api";
const ViewName = "AutoComplete";
var passedview;
export default {
name: "AutoComplete",
props: {
method: {
type: Function
},
title: String,
viewname:String,
autocompleteData: {
type: Array,
required: true
}
},
data() {
return {
// results: [],
dataBusinessUnit: [],
isFetching: false
// vignesh: this.objectData[this.title]
};
},
computed: {
viewData() {
return this.$store.getters.getViewData('DepartmentDetail')
},
objectData() {
return this.$store.getters.getApiData(this.viewData.api_id).data
},
sessionData() {
return this.$store.getters.getSessionData()
},
isLoading() {
return this.$store.getters.getApiData(this.viewData.api_id).isLoading
},
newRecord() {
return this.$route.params.id === null;
},
getTitle() {
return this.title
}
},
mounted() {
},
methods: {
setResult(result) {
this.updateValue(result.id,this.title);
// localStorage.setItem(this.title,result.id );
this.isFetching = false;
},
updateValue(newValue, fieldName) {
var val;
var schema = schemaData[this.viewData.schema];
if(typeof schema!=='undefined' && schema['properties'][fieldName]['type'] == 'date'){
val = this.formatDate(newValue);
} else {
val = newValue;
}
this.$store.dispatch('updateDataObjectField', {
key: this.viewData.api_id,
field: fieldName,
value: val
});
},
getAsyncDataBusinessUnit: debounce(function(name) {
console.log('getAsyncDataBusinessUnit you typed'+name.target.value);
if (!name.target.value.length) {
this.dataBusinessUnit = [];
this.isFetching = false;
return;
}
// this.isFetching = true;
api
.getSearchData(this.sessionData.key,`/businessunit/${name.target.value}`)
.then(response => {
this.dataBusinessUnit = [];
if (!response.length)
{
console.log('inside if')
this.isFetching = false;
}
else{
console.log('inside else')
response.forEach(item => {
this.dataBusinessUnit.push(item);
});
this.isFetching = true;
}
console.log('length of dataBusinessUnit is '+(this.dataBusinessUnit).length)
console.log('contents of dataBusinessUnit array '+JSON.stringify(this.dataBusinessUnit))
})
.catch(error => {
this.dataBusinessUnit = [];
throw error;
})
.finally(() => {
// this.isFetching = true;
});
}, 500),
},
components: {
}
};
</script>
The issue iam facing is whenever i start to type anything into the Custom Business Unit input field, then immediately after 1-2 seconds the value gets reset(with the value coming from the store getters ).This however does not happen if i remove the line this.dataBusinessUnit = []; in the getAsyncDataBusinessUnit function . Why is this happening? I even tried using :input instead of v-model for the input field , but i am still facing the same issue.And also second issue is when i click an existing record under DepartmentDetail page, the value that should be set for the custom business unit input field coming from the store getters (this.objectData.businessUnit) is not showing up sometimes? please help
The basic logic of an autocomplete is not really hard:
Vue.component('Autocomplete', {
props: ['list'],
data() {
return {
input: null
}
},
template: `<div><input v-model="input" #input="handleInput"><div class="bordered" v-if="input"><ul><li v-for="(item, i) in list" :key="i" #click="setInput(item)">{{ item }}</li></ul></div></div>`,
methods: {
handleInput(e) {
this.$emit('input', e.target.value)
},
setInput(value) {
this.input = value
this.$emit('input', value)
}
}
})
new Vue({
el: "#app",
computed: {
filteredList() {
if (this.filterInput) {
return this.list.filter(e => e.toLowerCase().indexOf(this.filterInput.toLowerCase()) !== -1)
} else {
return this.list
}
}
},
data: {
filterInput: null,
list: [
"First",
"Second",
"Third",
"Fourth",
"Fifth",
"Sixth",
"Seventh"
]
},
methods: {
handleInput(e) {
this.filterInput = e
}
}
})
.bordered {
border: 1px solid black;
display: block;
}
ul li {
cursor: pointer;
}
ul li:hover {
background: rgba(0, 0, 0, 0.3)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<autocomplete :list="filteredList" #input="handleInput" />
</div>
I wouldn't handle the data in the presentational component (autocomplete is a presentational component in the structure I created), but in its parent-container. That way the autocomplete just gets a list as a prop and displays it; every action is $emitted to the parent, who does the data-handling.
It's easier to control displayed data this way - even with an async data source.
How to close autocomplete dropdown when clicked on any outside area? Currently i am calling my custom autocomplete component(child presentation component) 3 times with different labels from another vue page(parent) called departmentDetail.vue.
Example:
departmentDetail.vue
<b-field label="Custom Business Unit ">
<AutoComplete :method="getAsyncDataBusinessUnit" title='businessUnit' :autocompleteData="dataBusinessUnit" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
<b-field label="Custom Managers">
<AutoComplete :method="getAsyncData" title='manager' :autocompleteData="dataManager" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
<b-field label="Custom Location">
<AutoComplete :method=" getAsyncDataLocation" title='location' :autocompleteData="dataLocation" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
AutoComplete.vue (Custom component created by me)
<template>
<div class="autocomplete">
<input style="font-size: 12pt; height: 36px; width:1800px; " type="text" v-model="objectData[title]" #focus="getAsyncDataBusinessUnit" #input="getAsyncDataBusinessUnit"/>
<ul v-show="isFetching" >
<li v-for="(dataBusinessUnit, i) in dataBusinessUnit" :key="i" #click="setResult(dataBusinessUnit)" >
<template v-if="title!='manager'">
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>Description:</b>
{{dataBusinessUnit.description}}
</p>
</div>
</template>
<template v-else>
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>First Name:</b>
{{dataBusinessUnit.firstName}}
</p>
<p>
<b>Last Name:</b>
{{dataBusinessUnit.lastName}}
</p>
</div>
</template>
</li>
</ul>
</div>
</template>
<script>
import { viewMixin } from "../viewMixin.js";
import schemaData from '../store/schema';
import debounce from "lodash/debounce";
import api from "../store/api";
const ViewName = "AutoComplete";
var passedview;
export default {
name: "AutoComplete",
props: {
method: {
type: Function
},
title: String,
viewname:String,
autocompleteData: {
type: Array,
required: true
}
},
data() {
return {
// results: [],
dataBusinessUnit: [],
results: [],
isFetching: false
// vignesh: this.objectData[this.title]
};
},
computed: {
viewData() {
return this.$store.getters.getViewData('DepartmentDetail')
},
objectData() {
return this.$store.getters.getApiData(this.viewData.api_id).data
},
sessionData() {
return this.$store.getters.getSessionData()
},
isLoading() {
return this.$store.getters.getApiData(this.viewData.api_id).isLoading
},
newRecord() {
return this.$route.params.id === null;
},
getTitle() {
return this.title
}
},
mounted() {
},
methods: {
setResult(result) {
this.updateValue(result.id,this.title);
// localStorage.setItem(this.title,result.id );
this.isFetching = false;
},
updateValue(newValue, fieldName) {
var val;
var schema = schemaData[this.viewData.schema];
if(typeof schema!=='undefined' && schema['properties'][fieldName]['type'] == 'date'){
val = this.formatDate(newValue);
} else {
val = newValue;
}
this.$store.dispatch('updateDataObjectField', {
key: this.viewData.api_id,
field: fieldName,
value: val
});
},
getAsyncDataBusinessUnit: debounce(function(name) {
console.log('getAsyncDataBusinessUnit you typed'+name.target.value);
if (!name.target.value.length) {
// this.results = [];
// this.dataBusinessUnit = [...this.results];
// this.isFetching = false;
// return;
}
// this.isFetching = true;
api
.getSearchData(this.sessionData.key,`/businessunit/`,{ filter: `{id}like'%${name.target.value}%'` })
.then(response => {
this.results = [];
if (!response.length)
{
console.log('inside if ')
this.isFetching = false;
}
else{
console.log('inside else')
response.forEach(item => {
this.results.push(item);
});
// this.dataBusinessUnit=this.results
this.dataBusinessUnit = [...this.results];
this.isFetching = true;
}
console.log('length of dataBusinessUnit is '+(this.dataBusinessUnit).length)
console.log('contents of dataBusinessUnit array '+JSON.stringify(this.dataBusinessUnit))
})
.catch(error => {
//this.dataBusinessUnit = [];
throw error;
})
.finally(() => {
// this.isFetching = true;
});
}, 500),
},
components: {
}
};
</script>
Screenshot of the Department screen
And why is it that when the page loads sometimes the values dont show up in these input fields? But upon focus or if i type anything then sudddenly the value shows up?Any idea why this is happening?
About your last question:
"And why is it that when the page loads sometimes the values dont show up in these input fields? But upon focus or if i type anything then sudddenly the value shows up?Any idea why this is happening?"
It looks like you are usesing API requests on computed props.
Computed props are pre renderd values. If your API works async then the computed is renderd "empty" before the full request is resolved.
you could try data props and set them with the API setter in Mounted() or Created().
EDIT:
It could look something like this:
data() {
return {
// results: [],
dataBusinessUnit: [],
results: [],
viewData: [],
objectData:[],
sessionData:[],
isLoading: [],
isFetching: false
// vignesh: this.objectData[this.title]
};
},
computed: {
newRecord() {
return this.$route.params.id === null;
},
getTitle() {
return this.title
}
},
mounted() {
this.viewData = this.$store.getters.getViewData('DepartmentDetail');
this.objectData = this.$store.getters.getApiData(this.viewData.api_id).data;
this.sessionData = this.$store.getters.getSessionData();
this.isLoading = this.$store.getters.getApiData(this.viewData.api_id).isLoading;
},
I'm trying to add and remove product when clicking a button, and each button is in different component and the data that I'm getting from is in storeData component where inside there is an object with a true/false status if the status is true the product should display in Cart component if false it will remove the product.
now in ProductList component when I click the add button the status is changing to true, but it's not changing the actual status in storeData component so the result when i go to Cart component nothing is displayed
I know I'm doing this the wrong way, so how can I perform this add and remove operation, i'm new in React.js so please any help would really be appreciated.
ProductList component
import itemlist from "../storeData/storeData";
import { Link } from "react-router-dom";
class ProductList extends Component {
state = {
items: itemlist.items,
addToCart: null
};
addItem(id) {
let itemArray = [];
itemlist.cartItems.filter(target => {
return id === target.id ? itemArray.push(target) : null;
});
const addToCart = itemArray[0];
addToCart.status = false;
this.setState({ addToCart });
}
render() {
return (
<div className="list-wrap">
{this.state.items.map(item => {
return (
<div key={item.id}>
<Link to={{ pathname: "/productdetail", itemdetail: item }}>
<img alt="item img" src={item.posterUrl} />
</Link>
<h2>{item.title}</h2>
<h3>${item.price}</h3>
<button onClick={() => this.addItem(item.id)}>Add to Cart</button>
</div>
);
})}
</div>
);
}
}
export default ProductList;
Cart component
import itemlist from "../storeData/storeData";
class Cart extends Component {
state = {
cart: itemlist.cartItems,
remove: null
};
removeItem(id) {
let itemArray = [];
itemlist.cartItems.filter(target => {
return id === target.id ? itemArray.push(target) : null;
});
let remove = itemArray[0];
remove.status = false;
this.setState({ remove });
}
render() {
return (
<div>
{this.state.cart.map(itm => {
return itm.status === false ? null : (
<div key={itm.id} className="cart-layout">
<img alt="img" src={itm.posterUrl} />
<h4>{itm.title}</h4>
<h4>{itm.price}</h4>
<button onClick={() => this.removeItem(itm.id)}>Remove</button>
</div>
);
})}
</div>
);
}
}
storeData component
let itemlist = {
items: [
{
id: 1,
title: "name 1",
price: "232",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMjIxNTU4MzY4MF5BMl5BanBnXkFtZTgwMzM4ODI3MjE#._V1_SX300.jpg"
},
{
id: 2,
title: "name 2",
price: "65",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY5NTc2NjYwOV5BMl5BanBnXkFtZTcwMzk5OTY0MQ##._V1_SX300.jpg"
},
],
cartItems: [
{
id: 1,
status: false,
title: "name 1",
price: "232",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMjIxNTU4MzY4MF5BMl5BanBnXkFtZTgwMzM4ODI3MjE#._V1_SX300.jpg"
},
{
id: 2,
status: false,
title: "name 2",
price: "65",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY5NTc2NjYwOV5BMl5BanBnXkFtZTcwMzk5OTY0MQ##._V1_SX300.jpg"
},
]
};
I don't think you are using filter correctly here, in either component. You are confusing the filter test with the action of composing your array. All you need with the filter is a test that will return a boolean and that will construct the array for you.
Try changing:
let itemArray = [];
itemlist.cartItems.filter(target => {
return id === target.id ? itemArray.push(target) : null;
});
To
const itemArray = itemlist.cartItems.filter(target => id === target.id);
And similarly in the cart component.
For more detail on filter see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
I have json data passed as props from the Redux store, the json has this format:
{
"label1.label2.label3": {
"option1":[
],
"option2": "value",
"option3": [
],
...
}
...
}
I need to split the label part and create a nested menu from the data as follows:
label1
label2
label3
option1
value of option1
option2
value of option2
option3
...
I couldn't figure out how to map into the data, my component code looks like this:
class MyDropdown extends React.Component {
componentDidMount(){
//A component dispatches ACTIONS
this.props.dispatch(fetchDataWithRedux());
}
render(){
const {error, loading, elements} = this.props;
if (loading){
return <div>Loading...</div>;
}
if (error){
return <div> Error! {error.message}</div>;
}
console.log(this.props.elements); //outputs "undefined"
return(
<div>
{this.props.elements && this.props.elements.map(this.createMenu)}
</div>
);
}
createMenu() {
return this.props.elements.map((item) => {
return(
);
});
}
}
const mapStateToProps = state => ({
elements: state.items,
loading: state.loading,
error: state.error
});
export default connect(mapStateToProps)(MyDropdown);
I'm getting real close with this. It's pretty tricky to get the nesting right though.
const data = {
"label1.label2.label3": {
"option1":"value1",
"option2": "value2",
"option3": "value3"
},
"label1.label2.label3.label4": {
"option1":"value1",
"option2": "value2"
},
}
class App extends React.Component {
createMenu = () => {
const options = Object.keys(data).reduce( (options, key, i) => {
const d = data[key];
let levels = key.split('.').map( (label, i) => {
return {
label: label,
options: []
};
});
levels[levels.length -1].options = Object.keys(d).map( option => {
return {
label: option,
value: d[option]
}
});
options.push(levels);
return options;
}, []);
const mapOptions = o => {
let menu = [];
o.forEach( (option, i) => {
if(!option.options.length) {
// Nest next option
menu.push(
<li>
{option.label}
</li>
);
}else {
// display options
menu.push(
<li>
{option.label}
{option.options.map( nested => {
return <li>{nested.label}: {nested.value}</li>
})}
</li>
);
}
});
return menu;
};
return options.map( option => {
return mapOptions(option);
});
};
render() {
return (
<div>
{this.createMenu()}
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
li li {
margin-left: 15px;
}
<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="root"></div>