React High Order Components - reactjs

Lets says I have an array of objects where each object have structure like below
obj = {
name:"Name1",
description:"Description1",
activeState:1,
rating:5
}
const User = (obj) => {
let userActiveState = (obj.activeState === 1) ? 'Active' : 'Non Active';
return (
<tr>
<td>{obj.name}</td>
<td>{obj.description}</td>
<td>{userActiveState}</td>
</tr>
);
}
User.propTypes = {
name: PropTypes.string
description: PropTypes.string
activeState: PropTypes.number
}
User.defaultProps = {
name: "Not Available"
description:""
activeState: 0
}
I use this array to create UI using User(a stateless functional react component), but before the stateless functional react component spits out the UI I want to make some modification to the object properties which are required by the
UI (example using text instead of number for activeState) and not all the object properties are required too.
Where would I remove unwanted properties so that I can have defaultProps and proptypes defined only for the required properties and would I use a high order component which transforms and filters the obj properties?

You don't need a HoC here - a simple composition is enough:
const Component = ...;
const ComponentButDifferentFormats = ({propToSkip, propToRename, ...props}) => (
<Component
{...props}
propRenamed={propToRename}
propX={parseFloat(props.propX)}
propY={'' + props.propY}
/>
);
With this approach, you'll decouple the transformation logic from the real UI. It's really useful for example with an API response. Creating a HoC is also an option: it might be parametrized with formats, filters or even the component itself. One more function in the above example: Component => ....

Related

Displaying LIT web components programatically in React

I have the a simple lit web component that renders a input box
import {html} from "lit-element";
import {customElement} from 'lit/decorators.js';
import "./plain-text";
export class MyInputText {
constructor(args){
this.args = args;
}
createPreview(){
const {
colorScheme,
disabled,
labelText,
value,
size,
customizeStyles
} = this.args? this.args: {};
return html`
<plain-text
color-scheme="${colorScheme}"
?disabled="${disabled}"
label-text="${labelText}"
value="${value}"
size="${size}"
customizeStyles="${customizeStyles}"></plain-text>
`;
}
propertyChanged(propertyName, propertyValue){
this.args = {...this.args, [propertyName]:propertyValue}
return this.createPreview();
}
};
I am trying to load it programmatically in react using the following code:
let el = new PlainTextPreviewUI.PlainTextPreview();
el.propertyChanged("width", "300px");
return el;
It returns the following:
With the result above, how can display this web component correctly in React?
I tried rendering it but it gives the following error:
Uncaught Error: Objects are not valid as a React child (found: object with keys {strings, values, type, processor}). If you meant to render a collection of children, use an array instead.
TIA.
React has some support for custom elements already so you can just use the custom element tag name within JSX as long as the custom element definition is loaded and registered.
If you don't need to worry about events, doing something like below is fine.
import './path/to/plain-text.js';
const ReactComponent = () => {
const plainTextProps = {
labelText: 'Text',
disabled: false,
customizeStyles: "",
value:"d"
}
return (
<plain-text {...plainTextProps}></plain-text>
);
}
However, if you need to pass in complex data props or want to declaratively add event listeners, considering using #lit-labs/react for a React component wrapper for the Lit component.

React Typescript: Use a react hook on each item in an array

I'm struggling on using a hook on each item on an array within my React Typescript project.
Without considering the limitations on using hooks, the code would be the following:
const forwardContractArray = useGetForwardArray()
interface ForwardResult {
token: string;
amount: number;
expiryTime: number;
}
const forwardResultArray: Array<ForwardResult> = []
for (let forwardAddress of forwardContractArray) {
const { token, amount, expiryTime} = useForwardInfo(forwardAddress)
forwardResultArray.push({token, amount, expiryTime})
}
Because of the limitations on running hooks in a for loop this is not possible to use a for loop for this.
Please could someone direct me on how to implement such a function within the scopes of React?
Thanks in advance.
React is first and foremost a UI library - so, if you want to show something for each element of an array, then you can have a component per item and inside the component you can use a single hook
return (
<>
{ forwardContractArray
.map(item => <ItemDisplay key={item.token} item={item} />) }
</>
)
and then e.g.
function ItemDisplay({ item } : { item: ForwardResult }) {
const { token, amount, expiryTime} = useForwardInfo(item);
return (
<p>Whatever you want to show for this item</p>
)
}

Inheritance in react component

Please consider a following case:
I have created a "baseUser" component which is a form having three fields username, password and name. Now I want this form in three different applications : Application1, Application2 and Application3.
In Application1, User component should use this baseUser component but want only two fields (username, password) from baseUser state and should also have two additional fields which is first-name and last-name.
In Application2 and Application3, User component should work same as the baseUser.
Also all the actions, events, states should be able to work alone and be able to overridden.
The render method should also be overridden as their can be different UIs for different applications.
How can we achieve this functionality using react components? Does inheriting "baseUser" in applications using "extends" cause any issue (Or is it correct way to do it)?
React does not use Inheritance. You can't extend a react component. React basically work on the Composition. Composition means, creating small parts and combine them together to make a complete part.
Now, in your situation. There are only 3 input fields into your baseUsr component and you want to use only two of them into your application1. Simply you can't do this. You have to render complete baseUsr component.
You are getting the concept of components wrong. Think about components like a function or part of UI that is abstract or can be used in standalone.
For example, you can create a Header component because it can be used in isolation and can be used on multiple pages. But an input field in a form can not be used in isolation.
This confusion is created because you create components just like classes in javascript but they are the basic building block of UI.
For more information read Composition VS inheritance on React docs
You can write your baseUser this way:
class BaseUserForm extends Component {
handleChange = ({ currentTarget: input }) => {
const data = { ...this.state.data };
data[input.name] = input.value;
this.setState({ data }); //state is in inheriated form.
};
handleSubmit = e => {
e.preventDefault();
//Form validation
if (!errors) this.doSubmit();
};
renderInput = (name, label, type = "text") => {
return (
<Input
name={name}
label={label}
type={type}
error={this.state.errors[name]}
value={this.state.data[name]}
onChange={this.handleChange}
/>
);
};
renderButton = (label, type = "submit") => {
return (
<button
type={type}
className="btn btn-primary"
// disabled={this.validate()}
>
{label}
</button>
);
};
}
export default BaseUserForm;
Then depending on your Application, you can add/remove input fields as needed.
class AppForm extends BaseUserForm{
state={
data:{username:"",
password:""}
};
doSubmit = () => {
//Do your submit here.
};
render(){
return(
<form onSubmit={this.handleSubmit}>
{this.renderInput("username", "User Name")}
{this.renderInput("password", "Password")}
//Add more field as needed.
</form>
);
}
}

Localize React & React-Native components keeping them as reusable as possible

Recently I've been trying to keep my code as reusable as possible following the Container-Component design pattern in my React and React-Native applications. I usually try to make all the components dummy and let the container do the work and pass down the data using props.
Now I'm trying to localize these components but I want to keep them reusable for further projects. So far, I have come up with to solutions:
1.- Pass every single displayed string into the component as an individual prop. Like this.
<MyAwesomeComponent
...props
string1="String1"
string2="String2"
string3="String3"
/>
2.- Pass the translation as a single object
<MyAwesomeComponent
...props
translation={translation}
/>
I personally find a better solution the first one because it becomes easier to manage default props in the component.
Which one do you think is the best approach and why? Have you find a better approach?
My final approach if it is useful for someone:
I followed #LucasOliveira approach but went a little bit further and this is what I did using ex-react-native-i18n but you can use whatever plugin you feel most comfortable with:
First I declared a helper method inside my component to return a single object with the complete translation
Pass the translation object down to the "Dummy" component
ContaninerComponent.js
class ContainerComponent extends React.Component {
...
// Load translation outside context.
loadTranslation() {
return {
string1: I18n.t('key1'),
string2: I18n.t('key2'),
string3: I18n.t('key3')
}
}
...
render() {
return(
<MyAwesomeComponent
...props
translation={this.loadTranslation()}
/>
);
}
}
Then, in the dummy component I set up a default translation, to fit the case in which the translation is not set and then I created a helper method to handle the possible not handled strings and avoid undefined values:
MyAwesomeComponent.js
const MyAwesomeComponent = ({ ..., translation }) => {
const strings = handleTranslation(translation);
return (
.... some JSX here ....
);
};
const DEFAULT_TRANSLATION = {
string1: 'Your Default String',
string2: 'Your Default String',
string3: 'Your Default String'
}
const handleTranslation = (translation) => {
if (translation === undefined) return DEFAULT_TRANSLATION;
return {
string1: (translation.string1 !== undefined) ?
translation.string1 : DEFAULT_TRANSLATION.string1;
string2: (translation.string2 !== undefined) ?
translation.string2 : DEFAULT_TRANSLATION.string2;
string3: (translation.string3 !== undefined) ?
translation.string3 : DEFAULT_TRANSLATION.string3;
}
};
And now the whole translation is safe to use from the "strings" variable.
Hope it helps!
I would go for the second approach, cause the ability to define your object outside the component declaration, gonna make your component accepts an object, a string, a date, etc... allowing you to treat then later.
Doing that :
<MyAwesomeComponent
...props
translation={translation}
/>
means our code doesn't need to know that it is being rendered , as this will be your component responsibility

React.js: How to get all props component expects?

I'm starting to unit test a React.js application and one of the challenge I'm facing is to determine all the props a component needs to mount it properly. Is there any utility to check what all is needed to mount the component successfully? Additionally the data type of those props to initialize them appropriately for component rendering.
Like one of my component is getting props from parent using spread {...props} operator. And the parent is also getting these using spread operator and then adds some additional props and passes it to child. Which makes it very difficult for me to get all props a components expects. Is there any legit way to get the list of props?
An Interesting task.
I start with:
import React from "react";
import * as PropTypes from "prop-types";
function Hello(props) {
const { boo, foo } = props;
return (
<div>
<h1>{boo}</h1>
<h2>{foo}</h2>
</div>
);
}
Hello.propTypes = {
boo: PropTypes.string,
foo: PropTypes.number
};
export default Hello;
I found this article https://blog.jim-nielsen.com/2020/proptypes-outside-of-react-in-template-literal-components/ with function:
/**
* Anytime you want to check prop types, wrap in this
* #param {function} Component
* #param {Object} propTypes
* #return {string} result of calling the component
*/
function withPropTypeChecks(Component) {
return props => {
if (Component.propTypes) {
Object.keys(props).forEach(key => {
PropTypes.checkPropTypes(
Component.propTypes,
props,
key,
Component.name
);
});
}
return Component(props);
};
}
Then I wrote another one:
const getPropsInfo = (component) => {
const result = {};
const mock = Object.keys(component.propTypes).reduce(
(acc, p) => ({ ...acc, [p]: Symbol() }),
{}
);
const catching = (arg) => {
const [, , prop, type] = `${arg}`.match(
/Warning: Failed (.*) type: Invalid .* `(.*)` of type `symbol` supplied to.*, expected `(.*)`./
);
result[prop] = type;
};
const oldConsoleError = console.error.bind(console.error);
console.error = (...arg) => catching(arg);
withPropTypeChecks(component)(mock);
console.error = oldConsoleError;
return result;
};
I chose Symbol as the less expected type.
And called it:
const propsInfo = getPropsInfo(Hello);
console.log(propsInfo);
As result I got: {boo: "string", foo: "number"}
P.S.: I have not tested this on other types. Just for fun! :)
For unit testing this will be totally OK for your parent component to check only that properties which it adds to its child. Because in unit testing you just test functionality of a particular unit. In this case you want to check that your parent component adds all the needed properties to its child and passes all the other properties that it takes (whatever they are).
In parallel you test your child component and check its functionality.
To check that two or more components interact with each other correctly you should use E2E/functional testing. In this case you will test some functionality parts of your working app. If you have some issues with component interaction they will pop up.
One way to solve this problem will be to use decorators. I understand that they are not yet fully here and as for now you might need to use typescript or such. But it is a solution to the stated problem that will allow you to decorate and attach any required information to the properties of your component. Below is an example in typescript:
#PropContainer("Textbox")
export class TextboxProps
{
#Prop(PropertyType.Mandatory)
public propA: string;
#Prop(PropertyType.Optional)
public propB?: number;
}

Resources