Typescript and react conditional render - javascript

I am new to typescript and not an expert in FE development. I've encountered issue that seems pretty basic, but I failed to found any solution. Maybe I just don't know how to google it properly.
In react component I have a button, that is disabled on some condition, which triggers a component's function:
import React, {Component} from 'react';
type DraftCompany = {
id: null
name: string,
};
type Company = Omit<DraftCompany, 'id'> & {
id: number;
};
type Props = {
company: Company | DraftCompany,
onDeleteCompany: (companyId: number) => void,
}
class CompanyRow extends Component <Props> {
handleDeleteCompany = () => {
this.props.onDeleteCompany(this.props.company.id);
};
render = () => {
return (
<div>
<div>{this.props.company.name}</div>
<div>
<button disabled={this.props.company.id === null} onClick={this.handleDeleteCompany}/>
</div>
</div>
)
}
}
export default CompanyRow;
I am getting typescript error on calling this.props.onDeleteCompany(this.props.company.id); that says that there is a chance I will pass null as a parameter. I fully understand why typescript gives me this error, the question is: what would be the best way to deal with this error?
I have found 3 ways:
1) Add 'if' guard
handleDeleteCompany = () => {
if (this.props.company.id) {
this.props.onDeleteCompany(this.props.company.id);
}
};
It works, but I don't like the idea of adding such guards into every function, if someone removes disabled logic, I want to receive console error telling me about it immediately, not to have it be silently swallowed. In my project I have a lot of such code that relies on render, I doubt it is a best practice to add such checks everywhere. Maybe I am wrong.
2) Apply as to field operator:
handleDeleteCompany = () => {
this.props.onDeleteCompany(this.props.company.id as number);
};
It works, but looks kinda hacky.
3) Apply as operator to whole object and pass it to function:
<button disabled={this.props.company.id === null}
onClick={() => this.handleDeleteCompany(this.props.company as Company)}/>
handleDeleteCompany = (company: Company) => {
this.props.onDeleteCompany(company.id as number);
};
It works, but it looks like I am unnecessary passing the value I could have grabbed in function itself from props. I am not sure it is best practice to do such things.
I am sure there should be some pure typescript solution like defining Props type as a union or using conditional types with some combination of any and never. But I haven't figured it out .
Here is a playground:
playground

You can force the compile to assume a value is never null or undefined with the ! operator:
handleDeleteCompany = () => {
this.props.onDeleteCompany(this.props.company.id!);
};

I think based on your requirement
if someone removes disabled logic, I want to receive console error telling me about it immediately
There is a very simple solution that makes perfect sense, simply change your onDeleteCompany type from (companyId: number) => void to (companyId: number | null) => void, then TypeScript will be happy.
It also semantically make sense to you as you want the runtime report this error when companyId is null. Then you should allow companyId with null to be passed in as parameter.

Related

eslint-plugin-react/require-render-return for functional components

Unfortunately eslint-plugin-react/require-render-return works only for Class components. It does not work for functional components, at least on my end.
Why do I need this?
Consider the following example:
const Component = () => <div />
The above functional component returns an empty div.
Now consider that we may have to add a hook:
const Component = () => {
useEffect(() => { /* some side-effect here */ } );
<div />
}
So while switching the arrow function from bodyless to having a function body, the developer forgets to return the DOM and the function returns nothing. The only way to catch this regression is during code review, which involves the human factor, and this could be missed. It would be better if there was a linter rule.
So what do you guys do?
This is a perfect example of why you should use TypeScript and not plain old JavaScript. It would throw an error at compile/write time pointing to that problem.
const SomeComponent: React.FC = () => <div />
// ✅
const AnotherComponent: React.FC = () => {
useEffect(() => {})
}
// 💥 "Type 'void' is not assignable to type 'ReactElement<any, any> | null'."
React combined with TypeScript really is the best in every possible way. I cannot emphasize enough that you should absolutely learn TypeScript, it is worth every second of your time.

How to use #ngrx/store in component?

My understanding after reading the documentation is that in order to obtain values from an NgRx store, we have to use selectors. So far, I've had luck doing that, only with a small issue regarding typing, which makes me worried if my implementation is incorrect in any way.
So, let's say I want to retrieve a number value from the store, id for example. In my componentName.component.ts I would access this value like this:
id$ = this.store.select(selectId);
where selectId is defined from another file as:
export const selectData = (state: AppState) => state.data;
export const selectId = createSelector(
selectData,
(state: DataState) => state.id,
)
I am able to access id$ in my HTML component easily by doing {{id$ | async}}, but accessing it from the component class itself has proven a bit more difficult. For starters, the variable type is Observable<number> instead of just number, and that makes it hard to use it in cases where it needs to be of number type, such as when comparing it:
ngOnInit(): void {
console.log(this.id$ === 0);
}
The TypeScript error I get from the code above is:
TS2367: This condition will always return 'false' since the types
'number' and 'Observable ' have no overlap.
And console-logging id$ itself confirms that it is indeed of Observable type, so this leads me to believe I am doing something wrong, but I'm unsure exactly what. Any help is appreciated!!!
id$ is an Observable, and you can access its value by subscribing to it from the component class using subscribe function, or from the component template using async pipe.
In Component Class:
ngOnInit(): void {
this.id$.subscribe(id => console.log(id));
}
In Component Template:
<span>{{ id$ | async }}</span>
Check the official docs of Angular about Observable(s):
https://angular.io/guide/observables

Errors in a React.js project

I am working on integrating maps in my react project. I am getting some errors. I used typescript in my project.
Below is a code which shows error:
handleChange = (e:any) => {
this.setState({location: this.state.location})
function initAutocomplete() {
var input = document.getElementById('pac-input');
var searchBox = new window.google.maps.places.SearchBox(input);
searchBox.addListener('places_changed', function() {
this.setState({ PlaceName: document.getElementById('pac-input').value });
});
}
initAutocomplete();
}
This are errors which I am facing:
1.This error is shown at line no.5 in var searchbox last bracket (input)
Argument of type 'HTMLElement | null' is not assignable to parameter of type 'HTMLInputElement'.
Type 'null' is not assignable to type 'HTMLInputElement'.ts(2345)
This error is shown at line no.7 in this.setSatate
Property 'setState' does not exist on type 'SearchBox'.ts(2339)
This error is shown at line no.7 in document.getElementById('pac-input')
Object is possibly 'null'.ts(2531)
This error is shown at line no.7 in last value
Property 'value' does not exist on type 'HTMLElement'.ts(2339)
TypeScript is the most powerful, useful and wonderful thing if you know what you're doing and how to use it. If you don't it will be your worst nightmare.
This is why 3/4 of your errors are because you are using TS wrong with React.
Let me rewrite your code so it will work with TypeScript:
handleChange = (e:any)/* #1 */ => {
this.setState({location: this.state.location}) // #2
function initAutocomplete() {
const input = document.getElementById('pac-input')! as HTMLInputElement //#3 #4;
const searchBox = new window.google.maps.places.SearchBox(input);
searchBox.addListener('change', function() { // THERE IS NO SUCH THING "places_changed" as an event. ~ Would "change" help? ~
this.setState({ PlaceName: document.getElementById('pac-input').value #5 });
});
}
initAutocomplete();
}
#1 Why are you setting event to any? it is not efficient and if you don't use event inside your code - don't put it inside your code - it would be much more nicer to look and read.
#2 Why are you setting the state to it current value? it would reduce the performance of your code because you're trying to render it twice(!) with one function.
There are two "setState" in one function instead of one. each of them re rendering the page.
#3 Why are you using var and not const or let? there are a lot of problems with using var, and since ES2015 const and let replaced var. You should consider not using it.
#4 when you want to assign an element from the DOM TS don't knows if it exits or not so it would return HTMLElement | null. But because you're writing the code you knows there is an Element so you add ! at the end .getElementById('myID')!.
Now it return HTMLElement and not HTMLElement | null.
#5 You should declare Elements before you're using "setState".
like:
const pacInput = document.getElementById('pac-input')! as HTMLInputElement
Now you should tell TS what the Element you are using if it a div, an input, a button etc... so you're writing the type of the element "as HTML(ELEMENT_TYPE_FROM_THE_LIST)";
So it should looks like:
document.getElementById("myID")! as HTMLDivElement;
// Now TS knows the Element exist and a div Element
This code would fix 3 of your code errors.
Let me know about the setState error.
Extra:
If you want to run a function on the fly you should write it as:
(function(){
--DO SOME STUFF
})
Because it is a function on the fly, you don't need to declare nor call it. It will automatically executed even inside other functions.
I really recommend you to learn how to use TS with React. This website has a lot of great sources!
BTW:
If you're using React, Why are you writing elements inside the HTML? This is what JSX for.
You should read more about React JSX here.
Your problems start with the fact that you are not working on a React project, in other words you are not writing your JavaScript as a React frontend but rather a plain JavaScript application.
What do I mean?
In the React view of things, your search bar would be a component, or in other words, your input, would be its own component called SearchBar, so instead of this:
const input = document.getElementById('pac-input')! as HTMLInputElement //#3 #4;
You would write a whole component like this:
import React from 'react';
class SearchBar extends React.Component {
state = { term: '' };
onInputChange = (event) => {
this.setState({ term: event.target.value });
};
onFormSubmit = (event) => {
event.preventDefault();
this.props.onFormSubmit(this.state.term);
}
render() {
return (
<div className="search-bar ui segment">
<form onSubmit={this.onFormSubmit} className="ui form">
<div className="field">
<label>Some Input Field</label>
<input type="text" value={this.state.term} onChange={this.onInputChange} />
</div>
</form>
</div>
);
}
}
export default SearchBar;
Give that a try and if you run into trouble come back and ask a question.

Cannot Get Values from Prop in Twin.macro

You can see an example of what I am trying to do here: https://codesandbox.io/s/vibrant-leaf-qj8vz
Note: this particular example is using Twin.macro with Styled Components. On my local computer I tried the same thing with the same results using Twin.macro with emotion/next.js.
Here is a sample component illustrating what I am trying to do:
import React from 'react'
import tw from 'twin.macro'
const Acme = ({ children, type }) => <div css={[tw`${type}`]}>{children}</div>
export default Acme
Here is how I would use that component: <Acme type="text-2xl">Text Goes Here</Acme>
My expectation is that I will be able to style this instance of the <Acme /> component via the tailwind css classes that I pass into the type prop. Instead, I get the following error message:
/src/components/Acme.js: twin.macro: Property value expected type of string but got null Learn more: https://www.npmjs.com/package/twin.macro
When trying to figure this out, I noticed something interesting that may be relevant. Here is a variation of the code that does work:
const Acme = ({ children, type }) => {
const typeClass = 'text-2xl'
const typeObj = {
class: 'text-2xl',
}
return <div css={[tw`${typeClass}`]}>{children}</div>
}
export default Acme
Note that I have created a variable typeClass and set it to the same tailwind css class. Note, in particular, the following line of code:
css={[tw`${typeClass}`]}
I have replace the prop type with the variable typeClass. This works. But now, instead of using the variable typeClass let's use the object typeObj that I have created as follows:
const Acme = ({ children, type }) => {
const typeClass = 'text-2xl'
const typeObj = {
class: 'text-2xl',
}
return <div css={[tw`${typeObj.class}`]}>{children}</div>
}
export default Acme
This does not work and produces the same error:
/src/components/Acme.js: twin.macro: Property value expected type of string but got null Learn more: https://www.npmjs.com/package/twin.macro
This is so even though typeClass === typeObj.class evaluates to true.
I don't know if this is helpful, but perhaps it can help indicate a solution. If I can get the type prop to behave like the typeClass variable then hopefully this would work.
Either way, any idea why this is not working and how to fix it?
Thanks.
I found the answer (meaning that someone else answered it on a different site). Here is is. I have to rewrite both the Component and the usage of the component as follows:
// Acme.js
const Acme = ({ children, type }) => <div css={[type]}>{children}</div>
---
// App.js
import tw from "twin.macro"
<Acme type={tw`text-2xl`}>Text Goes Here</Acme>
I have tried this out and it works.

What prop-type should I use for a material-ui icon? [duplicate]

I have a component that I wish to accept another component as a prop, and render that. I wish for that passed component to be optional, and render nothing in that case.
The following code works perfectly:
const Component = ({ Inner }) => (
<div style={{ borderStyle: "solid" }}>
<Inner />
</div>
);
Component.propTypes = {
Inner: PropTypes.element
};
Component.defaultProps = {
Inner: () => null
};
However, on the first load of the page, prop-types complains:
Warning: Failed prop type: Invalid prop Inner of type function supplied to Component, expected a single ReactElement.
My current solution is to redefine the propType as PropTypes.oneOfType([PropTypes.element, PropTypes.func]), but this seems entirely incorrect.
What should my propType or defaultProp actually be?
SSCCE in a sandbox.
This question is similar to If proptype of element what is the default?, but that has accepted an answer that doesn't work, and even if it did, it's not a great deal of help to me, as I'm using react native for real. I've not framed the question in a manner pertaining to react native, as like I said, my example works, it's just prop-types being a big meanie.
Using the type of elementType will resolve your problem.
Component.propTypes = {
Inner: PropTypes.elementType
};
Since your prop is a react component thus we use elementType. If you ever open the source code for the prop-types you will find this there.
Hope so this is helpful to you.

Categories