I am trying to use the react-table as a checkbox table. The first column will be the checkboxes, when a checkbox gets selected i want to save the id that was defined in the accessor in the state.
I have been looking at the examples and made my way trough the official documentation, so far without much luck.
My code so far:
import React from 'react';
import ReactTable from "react-table";
import 'react-table/react-table.css'
export default class TableAccessor extends React.Component {
constructor(props){
super(props);
this.state = { personId: null }
//Data to show inside the table, when an item gets selected the ID has to be set in the state
this.data= [
{id: 1, first_name: 'Emma', last_name: 'Smith', email: 'emma#example.com'},
{id: 2, first_name: 'Liam', last_name: 'Miller', email: 'liam#example.com'}
];
//Header data for the table
this.columns = [
{Header: '#', accessor: 'id', Cell: () => <input onChange={() => {
//Here i want to get the ID of the selected item defined in this.data
this.setState({personId: this.data.id});
}} type="checkbox"></input>},
{Header: 'First name', accessor: 'first_name'},
{Header: 'Last name', accessor: 'last_name'},
{Header: 'Email', accessor: 'email'}
];
}
logger = () => {
console.log("personId: " + this.state.personId)
}
render() {
return(
<div className="wrapper">
<button onClick={this.logger}>Print</button>
<ReactTable data={this.data} columns={this.columns} />
</div>
);
}
}
I also want to be able to only check one checkbox at the time. I saw people solving this with radio buttons, however when i change the type="checkbox" to type="radio" i can still select more than one item.
Who can help me overcome this issue and explain me what the best way is to save the accessor value in the state?
Thanks in advance :)
First of all, why would you use react-table package? What is the benefit of using 3rd party package instead of writing it yourself?
Steps I'd take:
Write static HTML table with stuff you need
Map the data array to the table rows and populate it. Make sure to add data-id attribute and pass the id attribute from the array to each element
Add onClick handlers which get the data-id attribute when clicked.
You can use computed property names (example here) and generate state like this: this.setState( {[savedId]: value} )
And that should be it.
Related
I want to allow the user to choose a teacher's name from a drop down and insert that teacher's "teacher_name" and "teacher_id" as 2 separate fields in Firestore database.
I have the following input field which creates a drop down by "teacher_name". Now I can either pass "teacher_name" OR "teacher_id" under optionValue to insert that field. Is there a way to insert both "teacher_name" AND "teacher_id" as 2 separate fields?
<ReferenceInput
label="Teacher"
source="teacher_name"
reference="teachers"
>
<AutocompleteInput
optionText="teacher_name"
optionValue="teacher_name"
defaultValue={null}
/>
</ReferenceInput>
My Firestore looks like this:
Collection name : teachers
Document Structure :
{
teacher_id : "XXX",
teacher_name : "XXX",
other_fields : "XXX",
}
As mentioned in the Answer which explains How to have the whole object act as optionValue in a SelctInput in react-admin as :
This is possible using parse and format. Format makes sure the
form options in the html are just the name strings. parse translates
the selected option into the format you db needs.
Example :
const choices = [
{ id: '1', name: 'Programming' },
{ id: '2', name: 'Lifestyle' },
{ id: '3', name: 'Photography' },
];
<SelectInput
source="category"
choices={choices}
format={(c) => c.name}
parse={(name) => choices.find((c) => c.name === name)}
/>
There is another answer which explains how to input and create two (or more than two) fields using React-Admin as:
allows users to select an existing record related
to the current one (e.g. choosing the author for a post). if that you
want to create a new record instead. You can do so via the onCreate
prop, as explained in the doc:
import { AutocompleteInput, Create, SimpleForm, TextInput } from 'react-admin';
const PostCreate = () => {
const categories = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
]; return (
<Create>
<SimpleForm>
<TextInput source="title" />
<AutocompleteInput
onCreate={(filter) => {
const newCategoryName = window.prompt('Enter a new category', filter);
const newCategory = { id: categories.length + 1, name: newCategoryName };
categories.push(newCategory);
return newCategory;
}}
source="category"
choices={categories}
/>
</SimpleForm>
</Create>
);
}
For more information, you can refer to the Official documentation which explains about the input components and common input props.
I was folling this tutorial for my own tree view with a recursive component in vuejs.
So the input array looks like this:
let tree = {
label: 'root',
nodes: [
{
label: 'item1',
nodes: [
{
label: 'item1.1'
},
{
label: 'item1.2',
nodes: [
{
label: 'item1.2.1'
}
]
}
]
},
{
label: 'item2'
}
]
}
<template>
<div>
...
<tree-menu
v-for="node in nodes"
:nodes="node.nodes"
:label="node.label" />
...
</div>
<template
<script>
export default {
props: [ 'label', 'nodes' ],
name: 'tree-menu'
}
</script>
So basically a label and a subarray of nodes is passed to a child node. Now I want to update or delete a node (e.g. item1.1), but reflect this change in the outmost array (here tree), because I want to send this updated structure to the server. How can I achive this? If I change the label of a node, this will be rendered in the DOM, but the tree array is not updated.
Here's how you can use the .sync modifier to update recursively:
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('tree-node', {
template: `
<div style="margin-left: 5px;">
<input :value="label"
type="text"
#input="$emit('update:label', $event.target.value)" />
<tree-node v-for="(node, key) in nodes"
:key="key"
v-bind.sync="node" />
</div>
`,
props: ['label', 'nodes']
});
let tree = {
label: 'root',
nodes: [{
label: 'item 1',
nodes: [
{ label: 'item 1.1' },
{ label: 'item 1.2',
nodes: [
{ label: 'item 1.2.1' }
]
}
]
},
{ label: 'item 2' }
]
};
new Vue({
el: '#app',
data: () => ({
tree
})
})
#app {
display: flex;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<div id="app">
<div>
<tree-node v-bind.sync="tree" />
</div>
<pre v-html="tree" />
</div>
v-bind.sync="node" is shorthand for :label.sync="node.label" :nodes.sync="node.nodes". v-bind unwraps all object members as attributes of the tag, resulting in props for the component.
The other half of the solution is replacing v-model on the input with :value + an $emit('update:propName', $event.target.value) call on #input which updates the .sync-ed property in the parent. To conceptualize it, it's a DIY v-model exposed by Vue so it could be customized (you decide when to call the update and what to update with). You can replace the <input> with any other type of input, depending on what you're binding/modifying (checkboxes, textarea, select, or any fancier input wrapper your framework might feature). Depending on type of input you'll want to customize the listener: #change, #someCustomEvent, etc...
.sync makes everything reactive at each individual level. Since everything is :key-ed, no re-rendering actually happens (Vue only re-renders DOM elements which actually changed). If that wasn't the case, the input would lose focus upon re-rendering.
The update principle is: instead of making the change at child level you update the parent property which, through v-bind, sends it back to the child.
It's the same exact principle used by Vuex. Rather than changing some local prop you call a store mutation which comes back through getters and modifies the local value but it happens for any component using that store data, not just for current one.
Have a Parent Component which certain props such as name, queryValue and image to my Child Component which is a Custom Dropdown where as of now i am displaying the names based on a condition check for the default display in the dropdown, but now more data will be shown along with flags. I need to show the name based on the value from the parent .
Parent.jsx
<SelectCountry
options={[
{
name: 'English (USA)',
queryValue: 'en',
flagIcon: USA,
},
{
name: 'Bahasa Indonesia',
queryValue: 'id',
flagIcon: ID,
},
]}
value={localStorage.getItem('locale')} />
SelectCountry.jsx
<div className={`${baseClassName}__selected-flag-container`}>
<img
src={require(value === "en" ? '../../assets/USA.svg' : '../../assets/indonesia.svg')}
alt='desc'
/>
{value === "en" ? options[0].name : options[1].name}
</div>
//COde for the Custom DropDown will be here
As above, HAve put the condition check and its working since there were only two languages, now when more languages are added, not sure how to pick the name and also setting the src in img tag dynamically.
You are doing all in a bit of wrong way your Parent.jsx is the container component so it should hold all your logic so why not pass the appropriate data.
<SelectCountry
options={[
{
name: 'English (USA)',
queryValue: 'en',
flagIcon: USA,
src: '../../assets/USA.svg'
},
{
name: 'Bahasa Indonesia',
queryValue: 'id',
flagIcon: ID,
src: '../../assets/indonesia.svg'
},
]}
value={localStorage.getItem('locale')}
/>
& in your presentation component SelectCountry.jsx you just present.
const { options } = require('joi');
options.map((option) => {
<div className={`${baseClassName}__selected-flag-container`}>
<img src={option.src} alt='desc' />
{option.name}
</div>;
});
I haven't tested so you might need to adjust the code. Dan has some fantastic free courses on Redux where he teaches more about container & presentation component you can check it out here https://egghead.io/instructors/dan-abramov. Hope this will help you 🙂️🙂️
single config example shows what configuration I use for inputs.
export const age = [
{
value: '',
type: 'radio',
name: 'age',
items: [
{value: 'kid', label: 'Im less than 18',
{value: 'adult', label: 'Im 18!',
],
},
];
export const someDate = [
{
type: 'date',
value: null,
name: 'someDate',
label: 'Enter date',
format: 'dd/MM/yyyy',
minDate: new Date('2019-12-31'),
maxDate: new Date('2020-03-31'),
validators: required.required.date
}
];
RFForm is a wrapper for rect-final-form Form component and I pass all components inside as children.
<RFForm
config={[...age, ...someDate, ...somethingElse]}
submit={submit}
>
<SectionBox title="Your age">
<Box mb={5}>
<InputList inputs={age}/>
</Box>
<SectionTitle title="Date"/>
<Box maxWidth="50%">
<InputList inputs={someDate}/>
</Box>
</SectionBox>
<SectionBox title="Something else">
<Box maxWidth="50%">
<InputList inputs={somethingElse}/>
</Box>
</SectionBox>
{
conditionToHideOrShowSection && <SectionWithInputs />
}
{
buttons
}
</RFForm>
I want to add a new section that will hide or show depends on what the user picks. The section should be ONLY present when the second radio button value is selected (adult). The section will contain a component with inputs from the configuration. All the fields should be required. This should only be validated when the section is present.
First question - how should I check if the specific radio button is checked. I don't have access to useForm from here and I need to hide or show section.
Second question - how to dynamically add new inputs when other inputs are changing. Remember that I have to add it to config (RFForm props) because initial value must be updated.
Actually you have access to it, but in other way via render method link
This sample shows how to conditionally show inputs link. Be aware if input hide back the data won't gone, it's still there , so to cleanup you should use change('inputName', undefined) (at least it helps me in my case)
At last you also should checkout about subscription prop. link
Well,
I'm trying to do a project of an a Shopping cart with vue.js, and the browser Console is showing this error:
vue.common.js:576 [Vue warn]: Error in created hook: "TypeError: Cannot use 'in' operator to search for '[object Array]' in products"
// App.vue
<template>
<div class="container">
<div class="products">
<div class="clearfix">
<product v-for="product in products" :key="product"></product>
</div>
</div>
<div class="shopping-cart">
<shopping-cart></shopping-cart>
</div>
</div>
</template>
<script>
import ShoppingCart from './components/ShoppingCart.vue'
import Product from './components/Product.vue'
export default {
created () {
// dados mockados
var dummy = [
{id: 1, title: 'Name of Product 1', price: 40, image: 'product.png'},
{id: 2, title: 'Name of Product 2', price: 90, image: 'product.png'},
{id: 3, title: 'Name of Product 3', price: 10, image: 'product.png'},
{id: 4, title: 'Name of Product 4', price: 20, image: 'product.png'}
];
this.$set('products', dummy)
},
data () {
return {
products: []
}
},
components: { Product, ShoppingCart }
}
</script>
What can I do?
I tried a lot of things and still without success =(
First of all you component name in template is "product" and also the key in for loop is also "product". Either you change Component name to suitable name like.
And you must have forgot to give a name(assign a name of component for tepmplate) to component which you imported. You cannot use imported component just like that without giving it reference name to use it in template.
components: { Product:productName, ShoppingCart: shoppingCart }
This way you use <product-name> </product-name> in template and so after that in for loop, the product in prodcuts will work.
Also products array should not work with this way. It should be in computed hook.
computed ={}
Or I should suggest you should directly asssign it in data()
for better working , in the $set method in VUE
the first arg for pass 'this' keyword
some thing like this
this.$set(this,'your_object', value)
and notice second arg must be String
you must use
this.products = dummy
instead of
this.$set('products', dummy)
and if you create your array in mounted () better than created () in your single app
I think the problem is with $set method, you need to specify the object as 1st parameter, see full doc here
so you need to do something like this:this.$set(this.products, dummy)
also this will not give you 4 products in the v-for loop. I would suggest to assign the products directly in data()