For example, I have two lists: income and outcome. And I have two storages (one for income and one for outcome). I am adding these storages in modules into index.js.
I can make one repository for these income and outcome, display it in the list and calculate it. But I want to make a separate store for each.
Now the question is: How can I implement this correctly? I roughly did. But here I show and calculate only INCOME and that's it.
How to do it better? import via ...mapGetters two storages in one component to be calculate and show in the list? Or take data from two storages, and calculate everything in the index.js. Then take this data from the index.js? How do I use multiple modules in one component? I want to show the balance of income and outcome in one component and show in the list.
index.js
import Vue from "vue";
import Vuex from "vuex";
import income from "./modules/income";
import outcome from "./modules/outcome";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
income,
outcome,
},
});
income.js
import Vue from "vue";
const income = {
namespaced: true,
state: {
list: {
1: {
type: "INCOME",
value: 100,
comment: "Some comment",
id: 1,
},
},
},
getters: {
incomeList: ({ list }) => list,
},
mutations: {
},
actions: {
},
},
};
export default income;
outcome.js
// import Vue from "vue";
const outcome = {
namespaced: true,
state: {
list: {
1: {
type: "OUTCOME",
value: -50,
comment: "Some outcome comment",
id: 2,
},
},
},
getters: {
outcomeList: ({ list }) => list,
},
mutations: {
},
actions: {
},
};
export default outcome;
this is my component where i calculate balance
<template>
<div class="total-value" :style="balanceColor">
Balance: {{ totalBalance }}
</div>
</template>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'TBalance',
computed: {
balanceColor: function() {
return {
color: this.totalBalance === 0 ? 'black' : this.totalBalance > 0 ? 'green' : 'red'
}
},
totalBalance() {
return Object.values(this.incomeList).reduce((acc, item) => acc + item.value, 0)
},
...mapGetters("income", ["incomeList"]),
},
methods: {
}
}
</script>
Here is an option for a more correct use of the store with modules.
I also put the calculation in the getter, which makes your component clean.
Try to bring the logic to the store so you can use the balance amount anywhere.
index.js
import Vue from "vue";
import Vuex from "vuex";
import income from "./modules/income";
import outcome from "./modules/outcome";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
income,
outcome,
},
});
income.js
const income = {
namespaced: true,
state: {
list: {
1: {
type: "INCOME",
value: 100,
comment: "Some comment",
id: 1,
},
},
},
getters: {
incomeBalance: state => {
// also, you can move this function into a separate file, and reuse
return Object.values(state.list).reduce((acc, item) => acc + item.value, 0);
},
},
};
export default income;
outcome.js
const outcome = {
namespaced: true,
state: {
list: {
1: {
type: "OUTCOME",
value: -50,
comment: "Some outcome comment",
id: 2,
},
},
},
getters: {
outcomeBalance: state => {
return Object.values(state.list).reduce((acc, item) => acc + item.value, 0);
},
},
};
export default outcome;
It is your component
<template>
<div class="total-value" :style="balanceColor">Balance: {{ incomeBalance }}</div>
</template>
<script>
import { mapGetters, mapState } from 'vuex';
export default {
name: 'TBalance',
computed: {
...mapState('outcome', ['list']), // if you want a list here i added for example
...mapState('income', ['list']), // if you want a list here i added for example
...mapGetters('outcome', ['outcomeBalance']), // also added it for example
...mapGetters('income', ['incomeBalance']),
balanceColor() {
return {
color: this.incomeBalance === 0 ? 'black' : this.incomeBalance > 0 ? 'green' : 'red',
};
},
},
methods: {},
};
</script>
Let's see we have the simple component ToggleButton:
const ButtonComponent = Vue.component('ButtonComponent', {
props: {
value: Boolean
},
methods: {
handleClick() {
this.$emit('toggle');
}
},
template: `
<button
:class="value ? 'on' : 'off'"
#click="handleClick"
>
Toggle
</button>`
});
And the story for that component:
import ToggleButton from './ToggleButton.vue';
export default {
title: 'ToggleButton',
component: ToggleButton,
argTypes: {
onToggle: {
action: 'toggle' // <-- instead of logging "toggle" I'd like to mutate `args.value` here
}
}
};
export const Default = (_args, { argTypes }) => ({
components: { ToggleButton },
props: Object.keys(argTypes),
template: `
<ToggleButton
:value="value"
:toggle="onToggle"
/>
`
});
Default.args = {
value: false
}
What I want to achieve is to handle toggle action inside the story and change value that I've used in Default.args object to change the button style by changing the class name from .off to .on.
I had the same exact issue, and kept looking for days, till I stumbled upon this github post:
https://github.com/storybookjs/storybook/issues/12006
Currently in my React (am sure vue approach will be similar), I do following:
import React from 'react';
import CheckboxGroupElement from '../CheckboxGroup';
import { STORYBOOK_CATEGORIES } from 'elements/storybook.categories';
import { useArgs } from '#storybook/client-api';
export default {
component: CheckboxGroupElement,
title: 'Components/CheckboxGroup',
argTypes: {
onChange: {
control: 'func',
table: {
category: STORYBOOK_CATEGORIES.EVENTS,
},
},
},
parameters: { actions: { argTypesRegex: '^on.*' } },
};
const Template = (args) => {
const [_, updateArgs] = useArgs();
const handle = (e, f) => {
// inside this function I am updating arguments, but you can call it anywhere according to your demand, the key solution here is using `useArgs()`
// As you see I am updating list of options with new state here
console.log(e, f);
updateArgs({ ...args, options: e });
};
return <CheckboxGroupElement {...args} onChange={handle} />;
};
export const CheckboxGroup = Template.bind({});
CheckboxGroup.storyName = 'CheckboxGroup';
CheckboxGroup.args = {
//Here you define default args for your story (initial ones)
controller: { label: 'Group controller' },
options: [
{ label: 'option 1', checked: true },
{ label: 'option 2', checked: false },
{ label: 'option 3', checked: false },
],
mode: 'nested',
};
I have the following Sharepoint Framework webpart, which basically renders a grid depending on a dropdown property which has the list names.
import * as React from "react";
import * as ReactDom from "react-dom";
import { Version } from "#microsoft/sp-core-library";
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneDropdown,
IPropertyPaneDropdownOption,
IPropertyPaneField,
PropertyPaneLabel
} from "#microsoft/sp-webpart-base";
import * as strings from "FactoryMethodWebPartStrings";
import FactoryMethod from "./components/FactoryMethod";
import { IFactoryMethodProps } from "./components/IFactoryMethodProps";
import { IFactoryMethodWebPartProps } from "./IFactoryMethodWebPartProps";
import * as lodash from "#microsoft/sp-lodash-subset";
import List from "./components/models/List";
import { Environment, EnvironmentType } from "#microsoft/sp-core-library";
import IDataProvider from "./components/dataproviders/IDataProvider";
import MockDataProvider from "./test/MockDataProvider";
import SharePointDataProvider from "./components/dataproviders/SharepointDataProvider";
export default class FactoryMethodWebPart extends BaseClientSideWebPart<IFactoryMethodWebPartProps> {
private _dropdownOptions: IPropertyPaneDropdownOption[];
private _selectedList: List;
private _disableDropdown: boolean;
private _dataProvider: IDataProvider;
private _factorymethodContainerComponent: FactoryMethod;
protected onInit(): Promise<void> {
this.context.statusRenderer.displayLoadingIndicator(this.domElement, "Todo");
/*
Create the appropriate data provider depending on where the web part is running.
The DEBUG flag will ensure the mock data provider is not bundled with the web part when you package the
solution for distribution, that is, using the --ship flag with the package-solution gulp command.
*/
if (DEBUG && Environment.type === EnvironmentType.Local) {
this._dataProvider = new MockDataProvider();
} else {
this._dataProvider = new SharePointDataProvider();
this._dataProvider.webPartContext = this.context;
}
this.openPropertyPane = this.openPropertyPane.bind(this);
/*
Get the list of tasks lists from the current site and populate the property pane dropdown field with the values.
*/
this.loadLists()
.then(() => {
/*
If a list is already selected, then we would have stored the list Id in the associated web part property.
So, check to see if we do have a selected list for the web part. If we do, then we set that as the selected list
in the property pane dropdown field.
*/
if (this.properties.spListIndex) {
this.setSelectedList(this.properties.spListIndex.toString());
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
}
});
return super.onInit();
}
// render method of the webpart, actually calls Component
public render(): void {
const element: React.ReactElement<IFactoryMethodProps > = React.createElement(
FactoryMethod,
{
spHttpClient: this.context.spHttpClient,
siteUrl: this.context.pageContext.web.absoluteUrl,
listName: this._dataProvider.selectedList === undefined ? "GenericList" : this._dataProvider.selectedList.Title,
dataProvider: this._dataProvider,
configureStartCallback: this.openPropertyPane
}
);
// reactDom.render(element, this.domElement);
this._factorymethodContainerComponent = <FactoryMethod>ReactDom.render(element, this.domElement);
}
// loads lists from the site and fill the dropdown.
private loadLists(): Promise<any> {
return this._dataProvider.getLists()
.then((lists: List[]) => {
// disable dropdown field if there are no results from the server.
this._disableDropdown = lists.length === 0;
if (lists.length !== 0) {
this._dropdownOptions = lists.map((list: List) => {
return {
key: list.Id,
text: list.Title
};
});
}
});
}
protected get dataVersion(): Version {
return Version.parse("1.0");
}
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
/*
Check the property path to see which property pane feld changed. If the property path matches the dropdown, then we set that list
as the selected list for the web part.
*/
if (propertyPath === "spListIndex") {
this.setSelectedList(newValue);
}
/*
Finally, tell property pane to re-render the web part.
This is valid for reactive property pane.
*/
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
}
// sets the selected list based on the selection from the dropdownlist
private setSelectedList(value: string): void {
const selectedIndex: number = lodash.findIndex(this._dropdownOptions,
(item: IPropertyPaneDropdownOption) => item.key === value
);
const selectedDropDownOption: IPropertyPaneDropdownOption = this._dropdownOptions[selectedIndex];
if (selectedDropDownOption) {
this._selectedList = {
Title: selectedDropDownOption.text,
Id: selectedDropDownOption.key.toString()
};
this._dataProvider.selectedList = this._selectedList;
}
}
// we add fields dynamically to the property pane, in this case its only the list field which we will render
private getGroupFields(): IPropertyPaneField<any>[] {
const fields: IPropertyPaneField<any>[] = [];
// we add the options from the dropdownoptions variable that was populated during init to the dropdown here.
fields.push(PropertyPaneDropdown("spListIndex", {
label: "Select a list",
disabled: this._disableDropdown,
options: this._dropdownOptions
}));
/*
When we do not have any lists returned from the server, we disable the dropdown. If that is the case,
we also add a label field displaying the appropriate message.
*/
if (this._disableDropdown) {
fields.push(PropertyPaneLabel(null, {
text: "Could not find tasks lists in your site. Create one or more tasks list and then try using the web part."
}));
}
return fields;
}
private openPropertyPane(): void {
this.context.propertyPane.open();
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
/*
Instead of creating the fields here, we call a method that will return the set of property fields to render.
*/
groupFields: this.getGroupFields()
}
]
}
]
};
}
}
my component.tsx
//#region Imports
import * as React from "react";
import styles from "./FactoryMethod.module.scss";
import { IFactoryMethodProps } from "./IFactoryMethodProps";
import {
IDetailsListItemState,
IDetailsNewsListItemState,
IDetailsDirectoryListItemState,
IDetailsAnnouncementListItemState,
IFactoryMethodState
} from "./IFactoryMethodState";
import { IListItem } from "./models/IListItem";
import { IAnnouncementListItem } from "./models/IAnnouncementListItem";
import { INewsListItem } from "./models/INewsListItem";
import { IDirectoryListItem } from "./models/IDirectoryListItem";
import { escape } from "#microsoft/sp-lodash-subset";
import { SPHttpClient, SPHttpClientResponse } from "#microsoft/sp-http";
import { ListItemFactory} from "./ListItemFactory";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import {
DetailsList,
DetailsListLayoutMode,
Selection,
buildColumns,
IColumn
} from "office-ui-fabric-react/lib/DetailsList";
import { MarqueeSelection } from "office-ui-fabric-react/lib/MarqueeSelection";
import { autobind } from "office-ui-fabric-react/lib/Utilities";
import PropTypes from "prop-types";
//#endregion
export default class FactoryMethod extends React.Component<IFactoryMethodProps, IFactoryMethodState> {
constructor(props: IFactoryMethodProps, state: any) {
super(props);
this.setInitialState();
}
// lifecycle help here: https://staminaloops.github.io/undefinedisnotafunction/understanding-react/
//#region Mouting events lifecycle
// the data returned from render is neither a string nor a DOM node.
// it's a lightweight description of what the DOM should look like.
// inspects this.state and this.props and create the markup.
// when your data changes, the render method is called again.
// react diff the return value from the previous call to render with
// the new one, and generate a minimal set of changes to be applied to the DOM.
public render(): React.ReactElement<IFactoryMethodProps> {
switch(this.props.listName) {
case "GenericList":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items={this.state.DetailsListItemState.items} columns={this.state.columns} />;
case "News":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items={this.state.DetailsNewsListItemState.items} columns={this.state.columns}/>;
case "Announcements":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items={this.state.DetailsAnnouncementListItemState.items} columns={this.state.columns}/>;
case "Directory":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items={this.state.DetailsDirectoryListItemState.items} columns={this.state.columns}/>;
default:
return null;
}
}
// invoked once, only on the client (not on the server), immediately AFTER the initial rendering occurs.
public componentDidMount(): void {
// you can access any refs to your children
// (e.g., to access the underlying DOM representation - ReactDOM.findDOMNode).
// the componentDidMount() method of child components is invoked before that of parent components.
// if you want to integrate with other JavaScript frameworks,
// set timers using setTimeout or setInterval,
// or send AJAX requests, perform those operations in this method.
this._configureWebPart = this._configureWebPart.bind(this);
this.readItemsAndSetStatus("");
}
//#endregion
//#region Props changes lifecycle events (after a property changes from parent component)
public componentWillReceiveProps(nextProps: IFactoryMethodProps): void {
if(nextProps.listName !== this.props.listName) {
this.readItemsAndSetStatus(nextProps.listName);
}
}
//#endregion
//#region private methods
private _configureWebPart(): void {
this.props.configureStartCallback();
}
public setInitialState(): void {
this.state = {
type: "ListItem",
status: this.listNotConfigured(this.props)
? "Please configure list in Web Part properties"
: "Ready",
columns:[],
DetailsListItemState:{
items:[]
},
DetailsNewsListItemState:{
items:[]
},
DetailsDirectoryListItemState:{
items:[]
},
DetailsAnnouncementListItemState:{
items:[]
},
};
}
// reusable inline component
private ListMarqueeSelection = (itemState: {columns: IColumn[], items: IListItem[] }) => (
<div>
<DetailsList
items={ itemState.items }
columns={ itemState.columns }
setKey="set"
layoutMode={ DetailsListLayoutMode.fixedColumns }
selectionPreservedOnEmptyClick={ true }
compact={ true }>
</DetailsList>
</div>
)
// read items using factory method pattern and sets state accordingly
private readItemsAndSetStatus(nextListName: string): void {
this.setState({
status: "Loading all items..."
});
const factory: ListItemFactory = new ListItemFactory();
factory.getItems(this.props.spHttpClient, this.props.siteUrl, nextListName)
.then((items: any[]) => {
const keyPart: string = this.props.listName === "GenericList" ? "" : nextListName;
// the explicit specification of the type argument `keyof {}` is bad and
// it should not be required.
this.setState<keyof {}>({
status: `Successfully loaded ${items.length} items`,
["Details" + keyPart + "ListItemState"] : {
items
},
columns: buildColumns(items)
});
});
}
private listNotConfigured(props: IFactoryMethodProps): boolean {
return props.listName === undefined ||
props.listName === null ||
props.listName.length === 0;
}
//#endregion
}
and my factory object
import { SPHttpClient, SPHttpClientResponse } from "#microsoft/sp-http";
import { IWebPartContext } from "#microsoft/sp-webpart-base";
import { IListItem} from "./models/IListItem";
import { IFactory } from "./IFactory";
import { INewsListItem } from "./models/INewsListItem";
import { IDirectoryListItem } from "./models/IDirectoryListItem";
import { IAnnouncementListItem } from "./models/IAnnouncementListItem";
export class ListItemFactory implements IFactory {
private _listItems: IListItem[];
public getItems(requester: SPHttpClient, siteUrl: string, listName: string): Promise<IListItem[]> {
if(listName === ""){
listName = "GenericList";
}
switch(listName) {
case "GenericList":
let items: IListItem[];
// tslint:disable-next-line:max-line-length
return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Author/Title,Editor/Title&$expand=Author,Editor`,
SPHttpClient.configurations.v1,
{
headers: {
"Accept": "application/json;odata=nometadata",
"odata-version": ""
}
})
.then((response: SPHttpClientResponse): Promise<{ value: IListItem[] }> => {
return response.json();
})
.then((json: { value: IListItem[] }) => {
console.log(JSON.stringify(json.value));
return items=json.value.map((v,i)=>(
{
//key: v.id,
id: v.Id,
title: v.Title,
created: v.Created,
createdby: v.Author.Title,
modified: v.Modified,
modifiedby: v.Editor.Title
}
));
});
case "News":
let newsitems: INewsListItem[];
// tslint:disable-next-line:max-line-length
return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Created By,Modified By,newsheader,newsbody,expiryDate`,
SPHttpClient.configurations.v1,
{
headers: {
"Accept": "application/json;odata=nometadata",
"odata-version": ""
}
})
.then((response: SPHttpClientResponse): Promise<{ value: INewsListItem[] }> => {
return response.json();
})
.then((json: { value: INewsListItem[] }) => {
return items=json.value.map((v,i)=>(
{
id: v.Id,
title: v.Title,
created: v.Created,
createdby: v.Author.Title,
modified: v.Modified,
modifiedby: v.Editor.Title,
newsheader: v.newsheader,
newsbody: v.newsbody,
expiryDate: v.expiryDate
}
));
});
case "Announcements":
let announcementitems: IAnnouncementListItem[];
return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id`,
SPHttpClient.configurations.v1,
{
headers: {
"Accept": "application/json;odata=nometadata",
"odata-version": ""
}
})
.then((response: SPHttpClientResponse): Promise<{ value: IAnnouncementListItem[] }> => {
return response.json();
})
.then((json: { value: IAnnouncementListItem[] }) => {
return items=json.value.map((v,i)=>(
{
id: v.Id,
title: v.Title,
created: v.Created,
createdby: v.Author.Title,
modified: v.Modified,
modifiedby: v.Editor.Title,
announcementBody: v.announcementBody,
expiryDate: v.expiryDate
}
));
});
case "Directory":
let directoryitems: IDirectoryListItem[];
return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id`,
SPHttpClient.configurations.v1,
{
headers: {
"Accept": "application/json;odata=nometadata",
"odata-version": ""
}
})
.then((response: SPHttpClientResponse): Promise<{ value: IDirectoryListItem[] }> => {
return response.json();
})
.then((json: { value: IDirectoryListItem[] }) => {
return items=json.value.map((v,i)=>(
{
id: v.Id,
title: v.Title,
created: v.Created,
createdby: v.Author.Title,
modified: v.Modified,
modifiedby: v.Editor.Title,
firstName: v.firstName,
lastName: v.lastName,
mobileNumber: v.mobileNumber,
internalNumber: v.internalNumber
}
));
});
default:
// tslint:disable-next-line:max-line-length
return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Author/Title,Editor/Title&$expand=Author,Editor`,
SPHttpClient.configurations.v1,
{
headers: {
"Accept": "application/json;odata=nometadata",
"odata-version": ""
}
})
.then((response: SPHttpClientResponse): Promise<{ value: IListItem[] }> => {
return response.json();
})
.then((json: { value: IListItem[] }) => {
console.log(JSON.stringify(json.value));
return items=json.value.map((v,i)=>(
{
//key: v.id,
id: v.Id,
title: v.Title,
created: v.Created,
createdby: v.Author.Title,
modified: v.Modified,
modifiedby: v.Editor.Title
}
));
});
}
}
}
The problem is that the first time it renders, the listname is undefined, so it reads data from the GenericList and I can see the grid rendered which is desired behavior
But whenever I select another list, I get an empty string as parameter on the getlistitems parameter, its not taking either Announcements, News, etc
What am I missing here?
Please Note: I do not use the SharePoint framework.
I believe your issue lies in the integration between the SharePoint framework WebPart and React. You don't see a change in the React app after the dropdown selection because the React app never receives a change in its properties.
There are two options:
1) Manually call FactoryMethodWebPart.render() when the dropdown changes during the setSelectedList(). Since I do not use the SharePoint framework, I do not know if this is best practice. I do know that this would re-build the React app and insert it into the DOM again instead of triggering a re-render.
2) Use a higher order component that allows you to pass new props to the already built React app. This solution is based on this article. Idea is to build a function that returns a wrapper around a Component. This function accepts both the Component to render and a subscribe function that in turn accepts the function that can be used to pass new props to the component. When the Component is created, it calls the subscribe() method, and simply renders the Component passed to it.
The HOC:
const connect = (Component, subscribe) => class extends React.Component {
constructor(props) {
super(props);
const rerender = props => this.setState(props);
subscribe(rerender);
}
render() {
const props = { ...this.props, ...this.state };
return <Component {...props} />;
}
}
The use case:
export default class FactoryMethodWebPart extends BaseClientSideWebPart<IFactoryMethodWebPartProps> {
// other private fields
private _factoryMethodComponent;
private _setFactoryMethodProps;
protected onInit(): Promise<void> {
// ...
this._factoryMethodComponent = connect(FactoryMethod, rerender => this._setFactoryMethodProps = rerender);
}
public render(): void {
const element = React.createElement(
this._factoryMethodComponent, // use the connected component instead of FactoryMethod
{ ... }
] );
this._factorymethodContainerComponent = ReactDom.render(element, this.domElement);
}
private setSelectedList(value: string): void {
// ...
this._setFactoryMethodProps({ listName: selectedDropDownOption ? selectedDropDownOption.text : 'GenericList' });
}
}
I've use this in my main component:
<contacts :corporation="corporation"></contacts>
contacts component:
export default {
props: {
corporation: {
default: () => []
}
},
data () {
return {
contacts: []
}
},
created() {
this.fetchContacts();
},
methods: {
fetchContacts() {
console.log(this.corporation.slug); // undefined!
CorporationService.users(this.corporation.slug)
.then(({data}) => {
this.contacts = data.contacts;
});
}
}
}
I'm trying to fetch contacts in the contacts component. The problem is that if I console.log(this.corporation.slug);in the method fetchContacts(); the corporation.slug is undefined!
But when I look into vue devtools the corporation prop is being set properly!
What could be going on? Already tried to change:
created() {
this.fetchContacts();
}
to
mounted() {
this.fetchContacts();
}
But that's not working.
Can you use a watch?
Something like this.
watch: {
'corporation.slug': function(slug) {
if(slug){
this.fetchContacts();
}
}
}
Now if parent component changes corporation.slug your child component will fetch contacts automatically.
Your prop default value is whether a value, or a function. But if it is a function, it HAS TO return something:
props: {
corporation: {
default () {
return []
}
}
},