How to use React refs inside FluentUI DetailsList onRender function - office-ui-fabric

I need to use a ref inside the onRender function of a FluentUI DetailsList column. But If I try to use a string ref: e.g: ref={"myref"} React complains that react-dom.development.js:13381 Uncaught (in promise) Error: Function components cannot have string refs. We recommend using useRef() instead. Learn more about using refs safely here:..... And if I try to use useRef I get an error that the onRender function is not a functional component. How can I use refs (multiple) in the FluentUI DetailsList onRender function?

There are several ways to override the rendering of rows/columns within DetailsList.
To define new content for a column, the easiest way is to define an onRender value in the column definition. For instance, in this snippet column3 has a function called every time it renders cells in that column.
const _columns = [
{ key: 'column1', name: 'Name', fieldName: 'name', minWidth: 100, maxWidth: 200, isResizable: true },
{ key: 'column2', name: 'Value', fieldName: 'value', minWidth: 100, maxWidth: 200, isResizable: true },
{ key: 'column3', name: 'Custom value with ref', fieldName: 'value', minWidth: 100, maxWidth: 200, onRender: renderColumn3 },
];
The onRender callback is called with the original item, the index in the list, and the column definition. From there it's trivial to return a react component, which can use any hooks it needs (such as useRef).
This example renders a very simple component that uses refs:
const MyControlForColumn3 = props => {
const myRef = React.useRef();
return <div ref={myRef}><button onClick={() => console.log(myRef.current)}>Print ref.current to console</button>{props.children}</div>;
}
const renderColumn3 = (item: any, itemIndex: any, columnDefinition: any) => {
return <MyControlForColumn3>Item #{itemIndex}</MyControlForColumn3>
}
(Full working example on codepen)

Related

Vue render function for draggable. How can I add v-model

I cannot find how to add v-model to draggable component in Vue2.
found some example in documentation, but it doesn't work.
I tried different variants like adding of list property to component, but it does not work.
const draggableItem = h('draggable', {
class: '',
attrs: {
'group':'people',
ghostClass: 'ghost',
animation: 200,
handle: '.drag-widget'
},
on: {
input: (event) => {
console.log('on input');
this.data.list = event.target.value
this.$emit('data.list', event.target.value)
},
end: () => {
console.log('drag on end');
this.handleMoveEnd()
},
add: (event) => {
console.log('drag on add', this.data.list);
console.log(event.dataTransfer);
this.handleWidgetAdd(event)
}
}
},
[transitionGroupItem]
);
Could somebody help me with it?
Tried to add to attributes list property like list: this.data.list, but it also doesn't work
V-model is basically combination of reactive prop :value and input. Value prop for initializing it and input event to update parent's version of it.
Here is very good guide - Adding V-model to custom vue component
On being added to the list, it should emit('input',data.list) and also there assign to a data value to avoid mutating the prop.

(Vue 3) Error: AG Grid: cannot get grid to draw rows when it is in the middle of drawing rows

-- Initial setup --
Create component
const ButtonAgGrid= {
template: "<button>{{ displayValue }}</button>",
setup(props) {
const displayValue = 'TEST-TEXT';
return {
displayValue,
};
},
};
Register component
<AgGridVue
:components="{
ButtonAgGrid
}"
...
/>
Pass data
const columnDefs = [
{
field: "name"
},
{
field: "button",
cellRenderer: "ButtonAgGrid",
}
]
const rowData = computed(() => {
return {
name: testReactiveValue.value ? 'test', 'test2'
}
})
And when computed "rowData" updated, agGrid send error:
Error: AG Grid: cannot get grid to draw rows when it is in the middle of drawing rows. Your code probably called a grid API method while the grid was in the render stage. To overcome this, put the API call into a timeout, e.g. instead of api.redrawRows(), call setTimeout(function() { api.redrawRows(); }, 0). To see what part of your code that caused the refresh check this stacktrace.
But if we remove cellRenderer: "ButtonAgGrid", all work good
My solution is to manually update rowData.
watchEffect(() => {
gridApi.value?.setRowData(props.rowData);
});
This one works well, but I wish it was originally

Computed not reactive?

I wrote this code to return a list of skills. If the user already has a specific skill, the list-item should be updated to active = false.
This is my initial code:
setup () {
const user = ref ({
id: null,
skills: []
});
const available_skills = ref ([
{value: 'css', label: 'CSS', active: true},
{value: 'html', label: 'HTML', active: true},
{value: 'php', label: 'PHP', active: true},
{value: 'python', label: 'Python', active: true},
{value: 'sql', label: 'SQL', active: true},
]);
const computed_skills = computed (() => {
let result = available_skills.value.map ((skill) => {
if (user.value.skills.map ((sk) => {
return sk.name;
}).includes (skill.label)) {
skill.active = false;
}
return skill;
});
return result;
})
return {
user, computed_skills
}
},
This works fine on the initial rendering. But if I remove a skill from the user doing
user.skills.splice(index, 1) the computed_skills are not being updated.
Why is that the case?
In JavaScript user or an object is a refence to the object which is the pointer itself will not change upon changing the underling properties hence the computed is not triggered
kid of like computed property for an array and if that array get pushed with new values, the pointer of the array does not change but the underling reference only changes.
Work around:
try and reassign user by shadowing the variable
The computed prop is actually being recomputed when you update user.skills, but the mapping of available_skills produces the same result, so there's no apparent change.
Assuming user.skills contains the full skill set from available_skills, the first computation sets all skill.active to false. When the user clicks the skill to remove it, the re-computation doesn't set skill.active again (there's no else clause).
let result = available_skills.value.map((skill) => {
if (
user.value.skills
.map((sk) => {
return sk.name;
})
.includes(skill.label)
) {
skill.active = false;
}
// ❌ no else to set `skill.active`
return skill;
});
However, your computed prop has a side effect of mutating the original data (i.e., in skill.active = false), which should be avoided. The mapping above should clone the original skill item, and insert a new active property:
const skills = user.value.skills.map(sk => sk.name);
let result = available_skills.value.map((skill) => {
return {
...skill,
active: skills.includes(skill.label)
}
});
demo
slice just returns a copy of the changed array, it doesn't change the original instance..hence computed property is not reactive
Try using below code
user.skills = user.skills.splice(index, 1);

Vue / Vuex: How to properly make a mutation for adding a property nested in object

I have a mutation that sets my state of products from a backend API when the page loads. Below is how the data is structured when the mutation runs and populates the products.
state:
products: {
title: "Television",
desc: "Here is a tv",
order_products: [
{
inventory_id: 44,
color: "red"
},
{
inventory_id: 45,
color: "blue"
},
{
inventory_id: 46,
color: green,
}
]
}
I need to periodically find a nested product by the inventory_id and a add a property scanned (eg. scanned: true) under the nested color field for each inventory item. I am reading on the proper way to use a mutation to add a property to an Object using Vuex such as this method: state.obj = { ...state.obj, newProp: 123 } (https://vuex.vuejs.org/guide/mutations.html)
but I don't know how that would work because I am adding a nested property , not just a property to the root of the Object.
There are multiple ways to add a reactive property.
Here's one way, with Vue.set
const id = '...'
const product = products.order_products.find(p => p.inventory_id === id)
Vue.set(product, 'scanned', true)

How to set Vue component that is imported globally to have preset attributes?

For example, I'm using vue-numeric.
Every time I need it, I always use at least this classes and attributes.
<vue-numeric
class="form-control border-input text-right"
:separator="."
:minus=false
/>
Is it possible to declare some of the attributes when registering the component, so that I don't need to type it each time I need vue-numeric?
And also that maybe one day I decided to change the class, I just need to change it in 1 file, e.g in main.js. I don't want to change it from node_modules.
I would like to do something like this.
Vue.component('vue-numeric', VueNumeric, {
class:"form-control border-input text-right",
separator:".",
minus: false
}
so then in template, I just need to add some specific attributes.
<vue-numeric v-model="price" :min=0 />
Create a functional wrapper component which applies the default prop values:
Vue.component('vue-numeric-default', {
functional: true,
render(h, ctx) {
return h('vue-numeric', Object.assign({}, ctx.data, {
// Apply default class only if no class was defined
staticClass: ctx.data.class || ctx.data.staticClass
? ctx.data.staticClass
: 'form-control border-input text-right',
// Apply default props
props: Object.assign({
separator: '.',
minus: false,
}, ctx.props),
}), ctx.children);
},
});
You could also use
{ separator: '.', ...ctx.props }
syntax instead of
Object.assign({ separator: '.' }, ctx.props)
if it is supported.
You can specify the classes in component's root element. And apply default props in mounted hook.
You can define the default value to the props methods in a component and if you
want to override it must be called it else it has the default value,
you should add below code to the "vue-numeric" component file
props: {
class: {
type: String,
default: 'form-control border-input text-right'
},
separator: {
type: String,
default: '.'
}
minus: {
type: Boolean,
default: false
}
}