typescript + react - mixed variable in state - javascript

I have this code in my react application, as per interface, a variable items should be an array.
But, in my initial state, there it is initialized as null (for I need as null as initial state).
Within the interface declaration I could have written ?Array, but it would mean, that the key items may not be in the state at all - and provided that this key is present within the object, it is going to be an array.
What are options? Is my architecture bad? Or how to declare required variable as mixed array|null?
declare interface StateInterface {
items: Array
}
class MyComponent extends Component {
state: StateInterface = {
items: null
};
}

You could initialize it to an empty array:
class MyComponent extends Component<{}, StateInterface> {
constructor() {
this.state = { items: [] };
}
}

Related

Function to get object from list of implementations in class by type of implementation. (typescript generics)

I have a list of Components in a class Entity. These components extend the interface Component.
class Entity {
...
const components: Component[] = [];
...
}
Where specific components implements the interface Component
class SpecificComponent0 implements Component { ... }
Now I want to query the entity instance e and get a component if it matches the type fed into the query, something like this:
const specificComponent0 = e.getSpecificComponent<SpecificComponentClass0>();
Or perhaps like this
const specificComponent0 = e.getSpecificComponent(instanceof SpecificComponentClass0)
But I can't seem to figure out a way to do it in the entity's get function.
This is a tricky one as you are mixing runtime and build-time concerns. Referring to the examples you suggested:
const specificComponent0 = e.getSpecificComponent<SpecificComponentClass0>();
This definitely isn't going to work, because the angle brackets specify a "Type Parameter", which only exists at build time. Since what you are trying to do involves logic, you need to pass something into the function at runtime to help it pick the correct element.
const specificComponent0 = e.getSpecificComponent(instanceof SpecificComponentClass0)
The return value of the instanceof operator is a boolean value. You are passing either true or false into this function, which isn't very useful.
You have two problems here.
You want to pass something into the function that will allow you to select the right component
I am assuming you want the function to return a component narrowed to the correct type, rather than typed generically as Component
Problem 1 can be solved by passing in the type Constructor function and then matching it with the constructor property of the instantiated Component
class Entity {
constructor(private components: Component[]) {}
getSpecificComponent(thing: new () => Component): Component | undefined {
return this.components.find(component => component.constructor === thing)
}
}
This works perfectly fine, but your getSpecificComponent function is going to return a value typed as Component | undefined, which isn't very useful if you want to use properties that only exist on one of the specific types.
To solve Problem 2 (without casting the return value, which you really shouldn't do), we need to
Make the function generic and
Turn the predicate that is passed into find into a user defined type guard to give the compiler a hint that if that function returns true, it can safely narrow the type down to the generic type
class Component {}
class OtherThing1 extends Component { name = 'thing1' }
class OtherThing2 extends Component { name = 'thing2' }
class OtherThing3 extends Component { name = 'thing3' }
const getNarrower = <T extends Component>(thingConstructor: new () => T) =>
(thing: Component): thing is T => thing.constructor === thingConstructor
class Entity {
constructor(private components: Component[]) {}
getSpecificComponent<T extends Component>(thing: new () => T): T | undefined {
return this.components.find(getNarrower(thing))
}
}
const e = new Entity([new OtherThing1(), new OtherThing2()])
const thing = e.getSpecificComponent(OtherThing1)
console.log(thing) // [LOG]: OtherThing1: { "name": "thing1" }
const thingNotHere = e.getSpecificComponent(OtherThing3)
console.log(thingNotHere) // [LOG]: undefined

React child component receives object. Why there is no visual update upon change of just one attribute of the object?

I have a Javascript complex data structure with 2 person fields - customer and payer (both are of type Person)
{
invoice: {
id: 123,
warehouseId: 456;
customer: {
id: 777,
name: "Coco"
}
payer: {
id: 778,
name: "Roro"
}
}
}
I am using child component for displaying Person object:
class ConnectedPersonFieldSet extends Component {
render () {
return
<div>
<div>{this.props.label}</div>
<div>{this.props.data.id}</div>
<div>{this.props.data.name}</div>
</div>
}
}
const PersonFieldSet = connect(mapStateToProps, mapDispatchToProps)(ConnectedPersonFieldSet);
export default PersonFieldSet;
And I have parent component that display full Invoice object and which has 2 child components for customer and payer respectively:
class ConnectedInvoice extends Component {
render () {
return
<div>
<div>{this.props.invoice.id}</div>
<div>{this.props.invoice.warehouseId}</div>
<PersonFieldSet label={"Customer" + /* this.props.customer.name */ } data={this.props.customer}></PersonFieldSet>
<PersonFieldSet label="Payer" data={this.props.payer}></PersonFieldSet>
</div>
}
}
const Invoice = connect(mapStateToProps, mapDispatchToProps)(ConnectedInvoice);
export default Invoice;
I have also complex logic that changes just invoice.customer.name. The updated customer name becomes visible in the Invoice component:
<div>{this.props.invoice.id}</div>
But, unfortunately, the
<PersonFieldSet label={"Customer" + /* this.props.customer.name */ } data={this.props.customer}></PersonFieldSet>
stays the same. If I uncomment /* this.props.customer.name */ then the updated customer.name becomes visible both in the label and in the name subcomponent of the PersonFieldSet.
So - my question is - why the child component, which receives the object, can not detect the change of the one attribute of this object and hence, does not update visual data upon the change of the one attribute of the object?
If the child component is able to feel somehow (e.g. via label={"..." + this.props.customer.name}) that the update of the attribute happened, then the child component displays the full update of all the attributes.
How to press the child component to detect that attributes can change the forwarded object?
I have read (e.g. React: why child component doesn't update when prop changes) that there is a trick with (more or less redundant) key attribute of the child element, but is this really my case?
My understanding is that React should support the hierarchical composition of both visual components and data components and do it without tricks or any other intrigues, but how to handle my situation? Should I really start to use hacks (key or others) in this situation that is pretty standard?
Added:
I am using Redux architecture for making updates - currently I am testing update of just one field - name:
const rootReducer = (state = initialState, action) => {
switch(action.type) {
case UPDATE_INVOICE_CUSTOMER: {
let person_id = action.payload.person_id;
let data = {
invoice: state.invoice
}
let newData = updateInvoiceByCustomer(data, person_id);
return {...state,
invoice: newData.invoice,
}
}
}
}
export function updateInvoiceByCustomer(data, person_id) {
let newData = {
invoice: data.invoice,
}
/* This will be replaced by the complex business logic, that retrieves
customer from the database using person_id and afterwards complex
calculations are done on the invoice, e.g. discounts and taxes
are assigned according to the rules relevant for the specific
customer. Possible all this code will have to be moved to the chain
of promises */
newData.invoice.customer.name='Test';
return newData;
}
Thanks #Yoshi for comments on my question and for persisting to check my Redux update logic. Indeed, when I have removed all the copying-update logic (which should be corrected to use cloning) and replaced it by:
return {
...state,
['invoice']: {...state['invoice'],
['customer']: {...state['invoice']['customer'],
['name']: 'REAL-TEST',
}
}
}
Then child component started to re-render and to show the actual value without any hacks or use of key-attributes. So, that was the cause of error.

Problem with acces to function with react javascript child component

They would like to function from the component from the child component. This method call: TypeError: Pizza__WEBPACK_IMPORTED_MODULE_2_.default.valid is not a function.
I try to add static function but it will not get the value.
I can add code of pizza to orders, but this not I will.
Can anyone help?
I want to get dish_details from Pizza and Show Pizza form underneath.
In .js no .tsx
Parend class:
class Orders extends React.Component {
constructor(props) {
super(props);
this.order = {
name: "",
preparation_time: "00:00:00",
type: "",
}
}
kind(){
switch (this.order.type) {
case 'pizza':
return <Pizza/>;
}
}
submit(){
console.log(Pizza.dishDetails()); // return error
}
render() {
return (<div>
<div>{this.state.selected ? this.kind() : ""}</div>
<button className={styles.order_submit} onClick={this.submit.bind(this)}>Submit</button>
</div>
);
}
Kids class:
class Pizza extends React.Component{
constructor(props) {
super(props);
this.state = {
noOfSlices : 0,
diameter : 0
}
}
dishDetails(){
return this.state;
}
noOfSlices(e){
this.setState({noOfSlices : e.target.value});
}
If you want your components to have a state you need to declare it with
this.state.[the name of the variable]
That's how react knows that you want to store state inside a component. The error you get probably is because you declared the state of the pizza component wrongly
dish_details = { //Not correct
noOfSlices : 0,
diameter : 0
}
Here you declare it inside the constructor, and that is correct, but in order to work you need to use the component state.
constructor(props) { /
super(props);
this.state.order = {
name: "",
preparation_time: "00:00:00",
type: "",
}
}
Check out the docs on state.
You have several issues here
you say something is static, but you have not created a static function!
submit(){
console.log(Pizza.dishDetails()); // return error or undefined when static
}
This is a call to a static function. To create a static function you would do this:
// ES5
Pizza.dishDetails = function(){ /* do something that does not touch `this` */ }
or in modern ES2015+:
class Pizza {
static dishDetailsfunction(){
/* do something that does not touch `this` */
}
}
The dishDetailsfunction function is not static, but more importantly, it cannot be static, since it uses this. A static function has no reference to this - that's the definition of static.
So you need to reorganize a bit ...
You are not allowed to access the inner state of a component from an outer component, so you need to either do your data and external actions handling outside of your components (like Redux), use some kind of callback logic, or delegate the logic for handling submits down to Pizza.
Here is one way to do it using a callback:
In the order component
renderPizza() {
// the `this` in the callback references the Orders (parent) component
return <Pizza onChange={(pizzaOrder) => this.setState({order: pizzaOrder}) }/>} />
}
In the pizza component:
updateNoOfSlices(e){
this.setState({noOfSlices : e.target.value});
this.prop.onChange({this.state});
}
I removed all the logic that is not necessary for the point, but you should see how a callback solves this easily.
P.S. If you centralize your data handling in one place (for instance a component) you will get simpler and more easily testable code. For instance, you can remove all state from the Pizza class and just let it have noOfSlices and diameter passed to it as props from the Orders class.
I have answer. I create clas Static with static value, and this is working for me.
static
class Static {
static defaultProps = {}
}
export default Static;
order
submit(){
console.log(Static.defaultProps)
pizza
noOfSlices(e){
Static.defaultProps = {noOfSlices : e.target.value};
}

How can I, in Vue, define a local data property that uses a prop as its initial value, in Typescript syntax?

When passing a prop to a child component in Vue, the documentation says:
In addition, every time the parent component is updated, all props in the child component will be refreshed with the latest value. This means you should not attempt to mutate a prop inside a child component. If you do, Vue will warn you in the console.
The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards. In this case, it’s best to define a local data property that uses the prop as its initial value:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
We are using typescript. The syntax for "defining a local data property" is as follows (to my understanding):
<script lang="ts">
import Vue from 'vue'
import { Component } from 'vue-property-decorator'
#Component
export default class App extends Vue {
// Data property
myDataProperty: string;
</script>
And the syntax for a prop is:
#Component
export default class App extends Vue {
// Makes a "exampleProperty" a component prop with the default value of 'Example'
#Prop({default: 'Example'})
exampleProperty: string
}
So, we tried to follow the documentation, and ended up with:
parentComponent.vue
<template>
<childComponent testProperty='test' />
</template>
childComponent.vue
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class childComponent extends Vue {
#Prop(
{
default: 'notTest',
validator: (component) => {
return [
'notTest',
'test',
].indexOf(component) > -1;
},
},
)
testProperty!: string;
testProperty = this.testProperty;
</script>
That, predictably, errored with `Duplicate identifier testProperty.
So, we tried
...
testProperty!: this.testProperty;
...
which resulted in
Duplicate identifier 'testProperty'.
Property 'testProperty' has no initializer and is not definitely assigned in the constructor.
Subsequent property declarations must have the same type. Property 'testProperty' must be of type 'this', but here has type 'any'.
So, I decided to try the "vue-class-component" decorator.
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component({
data: function(){
return {
testProperty: this.testProperty,
}
}
})
export default class childComponent extends Vue {
#Prop(
{
default: 'notTest',
validator: (component) => {
return [
'notTest',
'test',
].indexOf(component) > -1;
},
},
)
testProperty!: string;
testProperty = this.testProperty;
</script>
This resulted in the error Property 'testProperty' does not exist on type 'Vue'.
I would like to, in a handler, do this.testProperty = 'newProperty' at some point, but cannot, because that would be directly modifying a prop.
How can I define a local data property that uses a prop as its initial value in Typescript?
EDIT:
If I do none of the above, and simply define the prop, with no attempt to define a local data property that uses the prop as its initial value, and then do
this.testProperty = 'test'
in a handler, this error is displayed in the chrome console:
vue.runtime.esm.js[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "testProperty"
I will summarise my comments into a single coherent answer: the problem you are seeing is that you have already defined this.testProperty by declaring it as a prop: doing testProperty = this.testProperty is a circular reference at best. Using the #Prop decorator alone will do the mapping of the attribute in the template to the variable.
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class childComponent extends Vue {
#Prop(
{
default: 'notTest',
validator: (component) => {
return [
'notTest',
'test',
].indexOf(component) > -1;
},
},
)
testProperty!: string;
// Map prop to local data property
testPropertyLocal = this.testProperty;
</script>
Also, remember this caveat: VueJS properties must be kebab-case in templates and camelCase in JS. So, you need to update your child component reference to:
<template>
<childComponent test-property='test' />
</template>

What is the proper way of initializing nullable state in React/TypeScript apps?

What is the proper way to initialize initial empty(null) state in React, using TypeScript interfaces/types?
For example, I have an interface:
interface IObject {
name: string,
age: number,
info: IAnotherObject
}
and a simple component, where I want to define initial information state as null(w/o creating a class that implements my interface and shape default values for all properties), but with IObject interface
interface IState {
information: null|IObject;
}
class App extends React.Component<{}, IState> {
state = {
information: null
};
componentDidMount() {
// fetch some data and update `information` state
}
render() {
<div>
{this.state.information &&
<div>
<div>{this.state.information.name}</div>
<div>//other object data</div>
</div>
</div>
}
Do we have another way to initialize nullable state w/o using union type:
// worked way, but with union null type
interface IState {
information: null|IObject;
}
// some another way(no union), but with initial `null` or `undefined`
interface IState {
information: IObject;
}
state = {
information: null
}
(it's seems not very "smart" for me to annotate with null every object which I want to have initial empty value)type object in state?
If you want to have initial empty state, you should to do this,
Mark information as empty,
interface IState {
information?: IObject;
}
now you can initialize it as empty.
state = {}
You should rely on undefined instead of null for missing properties. From typescript style guide, (link)
Use undefined. Do not use null.

Categories