Cannot pass data to child if it is not initialized on definition - javascript

I am passing an array data from parent to child component and I have encountered the following situations:
parent.component.html:
<child-component
...
[options]="students"
>
</child-component>
Status I: When I set the array on definition, everything is ok and I can get the array values on the child component.
parent.component.ts:
export class ParentComponent implements OnInit {
students: any[] = [
{ name: "Mary" },
{ name: "Marta" },
{ name: "Kelly" },
{ name: "John" },
{ name: "Shelley" },
{ name: "Frankstein" },
{ name: "Shierley" },
{ name: "Igor" }
];
}
child.component.ts:
export class ChildComponent implements OnInit {
#Input() options: any[]= [];
}
Status II: However, when I set the array on a method instead of definition, I get the input value as null. Why I want to fill the array in a method is that I fill it by retrieving data from server. So, I struggled with this problem and finally found that the problem is not related to async data. It is related to this definition place. So, how can I perform the data can be passed by its array values?
parent.component.ts:
export class ParentComponent implements OnInit {
students: any[] = [];
ngOnInit() {
this.getStudents();
}
getStudents() {
this.students = [
{ name: "Mary" },
{ name: "Marta" },
{ name: "Kelly" },
{ name: "John" },
{ name: "Shelley" },
{ name: "Frankstein" },
{ name: "Shierley" },
{ name: "Igor" }
];
}
Note: I think assigning null to the students on defining it. But otherwise it throws error and I encounter null value exception on child. Maybe lifcel-ycle related problem, but I have already tried ngOnchanges, ngAfterViewInit, etc.

If you do not directly assign an object which is passed to a child component, initially the input will receive undefined or an empty array since you are assigning an empty array to students variable.
To avoid it use conditional rendering with ngIf:
<child-component
*ngIf="students && students.length > 0"
[options]="students"
>
</child-component>

Related

How do I pass different values depending on the imported data in React?

I want to take data from js files classified as categories such as 'Delivery' and 'Cafe' and deliver different data to different pages.
I thought about how to import it using map(), but I keep getting errors such as 'products' is not defined.'
It must be done, but it is not implemented well with javascript and react weak. If you know how to do it, I'd appreciate it if you could let me know.
Products.js
export const Product = [
{
Delivery: [
{
id: '101',
productName: '허니랩',
summary: '밀랍으로 만든 친환경 식품포장랩 허니랩.',
description:
'~~',
images: ['3k7sH9F'],
companyName: '허니랩',
contact: '02-6082-2720',
email: 'lesslabs#naver.com',
url: 'https://honeywrap.co.kr/',
},
{
id: '102',
productName: '허니포켓',
summary: '밀랍으로 만든 친환경 식품포장랩 허니랩. 주머니형태.',
description:
"~~",
images: ['4zJEqwN'],
companyName: '허니랩',
contact: "02-6082-2720",
email: "lesslabs#naver.com",
url: "https://honeywrap.co.kr/",
},
],
},
{
HouseholdGoods: [
{
id: '201',
productName: '순둥이',
summary: '아기용 친환경 순한 물티슈',
description:
'~',
images: ['4QXJJaz'],
companyName: '수오미',
contact: '080-000-3706',
email: 'help#sumomi.co.kr',
url: 'https://www.suomi.co.kr/main/index.php',
},
{
id: '202',
category: ['HouseholdGoods'],
productName: '순둥이 데일리',
summary: '친환경 순한 물티슈',
description: '품질은 그대로이나 가격을 낮춘 경제적인 생활 물티슈',
images: ['OMplkd2'],
companyName: '수오미',
contact: '080-000-3706',
email: 'help#sumomi.co.kr',
url: 'https://www.suomi.co.kr/main/index.php',
},
],
},
];
Delivery.js
(The file was named temporarily because I did not know how to classify and deliver data without creating a js file separately.)
import React from "react";
function Delivery(
productName,
companyName,
contact,
email,
url,
summary,
description
) {
return (
<div className="Product">
<div className="Product__data">
<h3 className="Product__name">{productName}</h3>
<h4>{companyName}</h4>
<h5>Contact: {contact}</h5>
<h5>Email: {email}</h5>
<h5>URL: {url}</h5>
<p className="Product__summary">{summary}</p>
<p className="Proudct__descriptions">{description}</p>
</div>
</div>
);
}
export default Delivery;
Category.js
import React from "react";
import Delivery from "./Delivery";
import { Product } from "./Products";
class Category extends React.Component {
render() {
state = {
products: [],
};
this.setState(_renderProduct());
return <div>{products ? this._renderProduct() : "nothing"}</div>;
}
_renderProduct = () => {
const { products } = this.state;
const renderProducts = products.map((product, id) => {
return (
<Delivery
productName={Product.productName}
companyName={Product.companyName}
contact={Product.contact}
email={Product.email}
url={Product.url}
summary={Product.summary}
description={Product.description}
/>
);
});
};
}
export default Category;
Sorry and thank you for the long question.
There are quite a few different problems I've found.
First is that you call setState inside render in the Category component, this causes an infinite loop. Instead call setState inside a lifecycle method like componentDidMount or use the useEffect hook if using functional components.
Another problem is that state in Category is also defined inside render. In class components you would normally put this in a class constructor outside of render.
In your setState call you refer to _renderProduct(), this should be this._renderProduct() instead.
Now the main problem here is the structure of your data / how you render this structure.
Products is an array of objects where each object either has a Delivery or HouseholdGoods property which is an array of products. I would advise you to change this structure to something more like this:
export const Product = {
Delivery: [
{
id: "101",
},
{
id: "102",
},
],
HouseholdGoods: [
{
id: "201",
},
{
id: "202",
},
],
};
or this:
export const Product = [
{ id: "101", productType: "Delivery" },
{ id: "102", productType: "Delivery" },
{ id: "201", productType: "HouseholdGoods" },
{ id: "202", productType: "HouseholdGoods" },
];
I personally prefer the second structure, but I've implemented the first as this seems to be what you were going for:
class Category extends React.Component {
constructor(props) {
super(props);
this.state = {
products: null,
};
}
componentDidMount() {
this.setState({ products: Product });
}
render() {
const { products } = this.state;
return (
<div>
{products
? Object.keys(products).map((productKey) => {
return (
<div key={productKey}>
{products[productKey].map((product) => {
return (
<Delivery
key={product.id}
productName={product.productName}
companyName={product.companyName}
contact={product.contact}
email={product.email}
url={product.url}
summary={product.summary}
description={product.description}
/>
);
})}
</div>
);
})
: "no products"}
</div>
);
}
}
We need a nested loop here, because we need to map over each property key and over the array of objects inside each property. If you use the other structure for Product I've shown, you can simply map over Product without needing two loops.
Now the last important problem was that you weren't destructuring the props inside your Delivery component, instead you should do something like this:
function Delivery({
productName,
companyName,
contact,
email,
url,
summary,
description,
}) {
return (
<div className="Product">
<div className="Product__data">
<h3 className="Product__name">{productName}</h3>
<h4>{companyName}</h4>
<h5>Contact: {contact}</h5>
<h5>Email: {email}</h5>
<h5>URL: {url}</h5>
<p className="Product__summary">{summary}</p>
<p className="Proudct__descriptions">{description}</p>
</div>
</div>
);
}
Example Sandbox

React/JSX: Can I use a state variable in another state variable?

I have something like this, where I would like to create array in the state from a variable initialized directly above it. I get the error cards is not defined. Is there a way around this? I need to set this array specifically in the state.
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
cards: [
{
name: "Name 1",
description: "dfdsfaf",
},
{
name: "Name 2",
description: "dsfsfasf",
},
{
name: "Name 3",
description: "daslkdjadlajsd",
},
],
names: cards.map(item => item.name)
};
}
...
}
You can do this in javascript as follows:
const cards = [...]
const names = = cards.map(...)
this.state = { cards: cards, names: names }
You should probably not do this though and set state to only the cards and move the call to calculate the names to your render method

How to build React checkbox tree

I'm trying to work with a checkbox tree component like this: https://www.npmjs.com/package/react-checkbox-tree, except I'm storing the items that I have selected in Redux. Moreover, the only items that I'm actually storing are the leaf nodes in the tree. So for example, I'd have the full options data which would be used to render the tree:
const fam = {
cuz2: {
name: 'cuz2',
children: {
cuzKid2: {
name: 'cuzKid2',
children: {
}
}
}
},
grandpa: {
name: 'grandpa',
children: {
dad: {
name: 'dad',
children: {
me: {
name: 'me',
children: {}
},
sis: {
name: 'sis',
children: {}
}
}
},
aunt: {
name: 'aunt',
children: {
cuz: {
name: 'cuz',
children: {
name: 'cuzkid',
children: {}
}
}
}
}
}
}
and a separate object that stores the items selected. The following would be the only items that would appear if every checkbox was checked:
const selected = {
cuz2: true,
me: true,
sis: true,
cuz: true
}
I seem to be struggling with this method for having the UI determine which boxes to have fully, partially, or un-checked based on the selected object. I was wondering if anyone can recommend another strategy of accomplishing this.
So I have used react-checkbox-tree but I have customised a bit the icons in order to use another icons library.
Check my example on sandbox:
The library provides a basic example of how to render a tree with selected and/or expanded nodes.
All you need to do is:
set up the nodes with a unique 'value'
Choose which items should be selected (it may comes from Redux)
pass nodes & checked list to the CheckBox constructor
also be sure that when user select/unselect, you update the UI properly using the state
Your code should look similar to this:
import React from 'react';
import CheckboxTree from 'react-checkbox-tree';
const nodes = [{
value: '/cuz2',
label: 'cuz2',
children: [],
},
// other nodes
];
class BasicExample extends React.Component {
state = {
checked: [
'/cuz2'
],
expanded: [
'/cuz2',
],
};
constructor(props) {
super(props);
this.onCheck = this.onCheck.bind(this);
this.onExpand = this.onExpand.bind(this);
}
onCheck(checked) {
this.setState({
checked
});
}
onExpand(expanded) {
this.setState({
expanded
});
}
render() {
const {
checked,
expanded
} = this.state;
return (<
CheckboxTree checked={
checked
}
expanded={
expanded
}
nodes={
nodes
}
onCheck={
this.onCheck
}
onExpand={
this.onExpand
}
/>
);
}
}
export default BasicExample;

Reset values within dom-repeat

I'm using a dom-repeat in Polymer. The corresponding list includes an initial value that should be set whenever the list of the dom-repeat is reset. However, when the first element of the list keeps the same initial value, the value is not reset even though I completely empty the list before resetting it to the new value. Here's my minimum example:
<dom-module id="console-app">
<template>
<div id="command-selection">
<paper-dropdown-menu id="command" label="Function">
<paper-listbox slot="dropdown-content" selected="{{_commandIndex}}">
<paper-item>A</paper-item>
<paper-item>B</paper-item>
</paper-listbox>
</paper-dropdown-menu>
</div>
<div id="parameters">
<template is="dom-repeat" items="[[_parameterData]]">
<parameter-block name="[[item.name]]" initial-value="[[item.initialValue]]" ></parameter-block>
</template>
</div>
</template>
<script>
class ConsoleApp extends Polymer.Element {
static get is() {
return 'console-app';
}
static get properties() {
return {
_commandIndex: {
type: Number,
value: -1,
observer: '_onIndexChange'
},
_parameterData: {
type: Array,
value: () => { return []; }
}
};
}
_onIndexChange() {
this.set('_parameterData', []);
switch (this._commandIndex) {
case 0:
this.set('_parameterData', [
{ name: 'AAA', initialValue: '111'},
{ name: 'BBB', initialValue: '123'}
]);
break;
case 1:
this.set('_parameterData', [
{ name: 'CCC', initialValue: '112'}
]);
break;
}
}
}
customElements.define(ConsoleApp.is, ConsoleApp);
</script>
</dom-module>
parameter-block:
<dom-module id="parameter-block">
<template>
<paper-input id="non-bool-value" label="[[name]]"
value="{{_value}}"></paper-input>
</template>
<script>
class ParameterBlock extends Polymer.Element {
static get is() {
return 'parameter-block';
}
static get properties() {
return {
_value: {
type: String,
value: () => { return ''; }
},
initialValue: {
type: String,
value: () => { return ''; },
observer: '_onInitialValueChange'
},
name: {
type: String,
value: () => { return ''; }
}
};
}
_onInitialValueChange() {
this.set('_value', this.initialValue);
}
}
customElements.define(ParameterBlock.is, ParameterBlock);
</script>
</dom-module>
When the index of the dropdown menu changes I reset _parameterData to [] and would assume that after that all future changes to _parameterData are evaluated as new elements. However, it seems like the list remembers the previous initial value after all as the corresponding listener is not called and previous changes to the first text element don't reset to 111 even though I'm changing the selection. If I use different initial values everything works fine, so I assume that I need to tell Polymer somehow to properly reset the elements, but how?
Since, you were only observing the initialValue the application will not know that the initialValue you were changing is for different name. That is why it is not resetting to default values you assigned.
You will need to observe both properties name and initialValue. So, change your observer code to :
static get observers() {
return [
'_onInitialValueChange(name, initialValue)'
]
}
and your method to:
_onInitialValueChange(name, initialValue) {
this.set('_value', this.initialValue);
}
I have updated the plnkr link provided.

Updating VueJS component data attributes when prop updates

I'm building a VueJS component which needs to update the data attributes when a prop is updated however, it's not working as I am expecting.
Basically, the flow is that someone searches for a contact via an autocomplete component I have, and if there's a match an event is emitted to the parent component.
That contact will belong to an organisation and I pass the data down to the organisation component which updates the data attributes. However it's not updating them.
The prop being passed to the organisation component is updated (via the event) but the data attibute values is not showing this change.
This is an illustration of my component's structure...
Here is my code...
Parent component
<template>
<div>
<blink-contact
:contact="contact"
v-on:contactSelected="setContact">
</blink-contact>
<blink-organisation
:organisation="organisation"
v-on:organisationSelected="setOrganisation">
</blink-organisation>
</div>
</template>
<script>
import BlinkContact from './BlinkContact.vue'
import BlinkOrganisation from './BlinkOrganisation.vue'
export default {
components: {BlinkContact, BlinkOrganisation},
props: [
'contact_id', 'contact_name', 'contact_tel', 'contact_email',
'organisation_id', 'organisation_name'
],
data () {
return {
contact: {
id: this.contact_id,
name: this.contact_name,
tel: this.contact_tel,
email: this.contact_email
},
organisation: {
id: this.organisation_id,
name: this.organisation_name
}
}
},
methods: {
setContact (contact) {
this.contact = contact
this.setOrganisation(contact.organisation)
},
setOrganisation (organisation) {
this.organisation = organisation
}
}
}
</script>
Child component (blink-organisation)
<template>
<blink-org-search
field-name="organisation_id"
:values="values"
endpoint="/api/v1/blink/organisations"
:format="format"
:query="getQuery"
v-on:itemSelected="setItem">
</blink-org-search>
</template>
<script>
export default {
props: ['organisation'],
data() {
return {
values: {
id: this.organisation.id,
search: this.organisation.name
},
format: function (items) {
for (let item of items.results) {
item.display = item.name
item.resultsDisplay = item.name
}
return items.results
}
}
},
methods: {
setItem (item) {
this.$emit('organisationSelected', item)
}
}
}
</script>
How can I update the child component's data properties when the prop changes?
Thanks!
Use a watch.
watch: {
organisation(newValue){
this.values.id = newValue.id
this.values.search = newValue.name
}
}
In this case, however, it looks like you could just use a computed instead of a data property because all you are doing is passing values along to your search component.
computed:{
values(){
return {
id: this.organisation.id
search: this.organisation.name
}
}
}

Categories

Resources