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

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;
}

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.

Expose state and method of child Component in parent with React

I know it's not a good pattern to do that, but you will understand why I want to do like that.
I have a HTable, which use a third-party library (react-table)
const HTable = <T extends object>({ columns, data, tableInstance}: Props<T>) {
const instance: TableInstance<T> = useTable<T> (
// Parameters
)
React.useImperativeHandle(tableInstance, () => instance);
}
Now, I want to control columns visibility from parent. I did:
const Parent = () => {
const [tableInstance, setTableInstance] = React.useState<TableInstance<SaleItem>>();
<Table data={data} columns={columns} tableInstance={(instance) => setTableInstance(instance)}
return tableInstance.columns.map((column) => {
<Toggle active={column.isVisible} onClick={() =>column.toggleHiden()}
}
}
The column hides well, but the state doesn't update and neither does the toggle, and I don't understand why. Could you help me to understand?
EDIT:
Adding a sandbox.
https://codesandbox.io/s/react-table-imperative-ref-forked-dilx3?file=/src/App.js
Please note that I cannot use React.forwardRef, because I use typescript and React.forwardRef doesn't allow generic type like this if I use forwardRef
interface TableProps<T extends object> {
data: T[],
columns: Column<T>[],
tableInstance?: React.RefObject<TableInstance<T>>,
}
Your issue is that react-tables useTable() hook always returns the same object as instance wrapper (the ref never changes). So your parent, is re-setting tableInstance to the same object - which does not trigger an update. Actually most of the contained values are also memoized. To get it reactive grab the headerGroups property.
const {
headerGroups,
...otherProperties,
} = instance;
React.useImperativeHandle(
tableInstance,
() => ({ ...properties }), // select properties individually
[headerGroups, ...properties],
);

How to manipulate a global state outside of a React component using Recoil?

I'm using Recoil, and I'd like to access the store outside a component (get/set), from within a utility function.
More generally, how do people write re-usable functions that manipulate a global state with Recoil? Using Redux, we can dispatch events to the store directly, but I haven't found an alternative with Recoil.
Using hooks is a great developer experience, but it's hard to convert a function defined within a component to an external utility function because hooks can only be used within a component.
You can use recoil-nexus, which is a tiny package with code similar to the answer by Vadorequest.
https://www.npmjs.com/package/recoil-nexus
// Loading example
import { loadingState } from "../atoms/loadingState";
import { getRecoil, setRecoil } from "recoil-nexus";
export default function toggleLoading() {
const loading = getRecoil(loadingState);
setRecoil(loadingState, !loading);
}
I managed to adapt https://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777249693 answer and make it work with the Next.js framework. (see below usage example)
This workaround allows to use the Recoil Root as a kind of global state. It only works well if there is only one RecoilRoot component, though.
// RecoilExternalStatePortal.tsx
import {
Loadable,
RecoilState,
RecoilValue,
useRecoilCallback,
useRecoilTransactionObserver_UNSTABLE,
} from 'recoil';
/**
* Returns a Recoil state value, from anywhere in the app.
*
* Can be used outside of the React tree (outside a React component), such as in utility scripts, etc.
* <RecoilExternalStatePortal> must have been previously loaded in the React tree, or it won't work.
* Initialized as a dummy function "() => null", it's reference is updated to a proper Recoil state mutator when RecoilExternalStatePortal is loaded.
*
* #example const lastCreatedUser = getRecoilExternalLoadable(lastCreatedUserState);
*/
export let getRecoilExternalLoadable: <T>(
recoilValue: RecoilValue<T>,
) => Loadable<T> = () => null as any;
/**
* Sets a Recoil state value, from anywhere in the app.
*
* Can be used outside of the React tree (outside a React component), such as in utility scripts, etc.
*
* <RecoilExternalStatePortal> must have been previously loaded in the React tree, or it won't work.
* Initialized as a dummy function "() => null", it's reference is updated to a proper Recoil state mutator when RecoilExternalStatePortal is loaded.
*
* #example setRecoilExternalState(lastCreatedUserState, newUser)
*/
export let setRecoilExternalState: <T>(
recoilState: RecoilState<T>,
valOrUpdater: ((currVal: T) => T) | T,
) => void = () => null as any;
/**
* Utility component allowing to use the Recoil state outside of a React component.
*
* It must be loaded in the _app file, inside the <RecoilRoot> component.
* Once it's been loaded in the React tree, it allows using setRecoilExternalState and getRecoilExternalLoadable from anywhere in the app.
*
* #see https://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777300212
* #see https://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777305884
* #see https://recoiljs.org/docs/api-reference/core/Loadable/
*/
export function RecoilExternalStatePortal() {
// We need to update the getRecoilExternalLoadable every time there's a new snapshot
// Otherwise we will load old values from when the component was mounted
useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => {
getRecoilExternalLoadable = snapshot.getLoadable;
});
// We only need to assign setRecoilExternalState once because it's not temporally dependent like "get" is
useRecoilCallback(({ set }) => {
setRecoilExternalState = set;
return async () => {
};
})();
return <></>;
}
Configuration example using the Next.js framework:
// pages/_app.tsx
import {
NextComponentType,
NextPageContext,
} from 'next';
import { Router } from 'next/router';
import React from 'react';
import { RecoilRoot } from 'recoil';
import { RecoilExternalStatePortal } from '../components/RecoilExternalStatePortal';
type Props = {
Component: NextComponentType<NextPageContext>; // Page component, not provided if pageProps.statusCode is 3xx or 4xx
err?: Error; // Only defined if there was an error
pageProps: any; // Props forwarded to the Page component
router?: Router; // Next.js router state
};
/**
* This file is the entry point for all pages, it initialize all pages.
*
* It can be executed server side or browser side.
*
* #see https://nextjs.org/docs/advanced-features/custom-app Custom _app
* #see https://nextjs.org/docs/basic-features/typescript#custom-app TypeScript for _app
*/
const App: React.FunctionComponent<Props> = (props): JSX.Element => {
const { Component, pageProps} = props;
return (
<RecoilRoot>
<Component {...pageProps} />
<RecoilExternalStatePortal />
</RecoilRoot>
);
};
// Anywhere, e.g: src/utils/user.ts
const createUser = (newUser) => {
setRecoilExternalState(lastCreatedUserState, newUser)
}
Some little hack without any npm package and complex things , but i'm not sure is it good way to do this or not :) but it works great.
In some HOC (high order component) define the ref and imperativeHanle
Outside of component ( on top of component declaration )
// typescript version
export const errorGlobalRef = createRef<{
setErrorObject: (errorObject: ErrorTypes) => void;
}>();
// javascript version
export const errorGlobalRef = createRef();
Inside component
const [errorObject, setErrorObject] = useRecoilState(errorAtom);
//typescript version
useImperativeHandle(errorGlobalRef, () => {
return {
setErrorObject: (errorObject: ErrorTypes) => setErrorObject(errorObject),
};
});
//javascritp version
useImperativeHandle(errorGlobalRef, () => {
return {
setErrorObject: (errorObject) => setErrorObject(errorObject),
};
});
And import and use where you want ;)
In my case:
//axios.config.ts
instance.interceptors.response.use(
(response) => {
return response;
},
async function (error) {
const originalRequest = error.config;
if (error?.response?.data) {
const { data } = error.response;
if (data) {
// set recoil state
errorGlobalRef.current?.setErrorObject({
error: data.error,
message: typeof data.message === 'string' ? [data.message] : data.message,
statusCode: data.statusCode,
});
}
}
return Promise.reject(error);
}
);

React High Order Components

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 => ....

Passing more values under same prop name and test it with Jest

I am using Jest and Enzyme to test my React-Mobx app. I have come to a problem where I am accessing different values under the same prop name. As you can see I am accessing different properties under the same FilterssAction prop name.
#inject('RootStore', 'FiltersAction')
#observer
class ExpenseListFilters extends Component {
state = {
calendarFocused: null
}
onDatesChange = ({startDate, endDate}) => {
this.props.FiltersAction.setStartDate(startDate)
this.props.FiltersAction.setEndDate(endDate)
}
onTextChange = (e) => {
this.props.FiltersAction.setTextFilter(e.target.value)
}
...
When I write my test it fails. I need to pass props to shallow rendered component. So I am passing different values under same. This is not working, I keep getting an error TypeError: _this.props.FiltersAction.setTextFilter is not a function
How do I test this?
let setStartDateSpy, setEndDateSpy, setTextFilterSpy, sortByDateSpy, sortByAmountSpy, FiltersStore, wrapper
beforeEach(() => {
setStartDateSpy = {setStartDate: jest.fn()}
setEndDateSpy = {setEndDate: jest.fn()}
setTextFilterSpy = {setTextFilter: jest.fn()}
FiltersStore = {FiltersStore: {filters: filters}}
wrapper = shallow(
<ExpenseListFilters.wrappedComponent
FiltersAction = {
setStartDateSpy,
setEndDateSpy,
setTextFilterSpy
}
RootStore = {FiltersStore}
/>
)
})
test('should handle text change', () => {
const value = 'rent'
wrapper.find('input').at(0).simulate('change', {
target: {value}
})
expect(setTextFilterSpy).toHaveBeenLastCalledWith(value)
})
It looks like your component is expecting FiltersAction to be an object whose values are functions.
In your application code, you can see that it as accessing the FiltersActions prop with dot-notation, and executing it as a function. For example, this.props.FiltersAction.setEndDate(endDate)
To pass a prop as an object, you need to wrap it again with curly braces. So, in your test, try passing FiltersAction as an object like below:
wrapper = shallow(
<ExpenseListFilters.wrappedComponent
FiltersAction = {{
setStartDate: setStartDateSpy,
setDentDate: setEndDateSpy,
setTextFilter: setTextFilterSpy
}}
RootStore = {FiltersStore}
/>
)
Note that in your example, you were:
Not double-wrapping your object w/ curly braces
Creating the object literal with computed property keys. That is, when you don't explicitly provide a property name, the resulting object will create the property name based on the variable name. Here is a good resource for learning more about enhanced object literals in ES6 So your generated object did not include the properties your application code was expecting and was attempting to call undefined as a function

Resources