how do I programmatically create instance of react component? - reactjs

I have a table row component that checks to see if a custom component is specified and is supposed to programmatically take that React component (column.customComponent) and render the data using it. However, I cannot seem to instantiate the components.. all I get is the data as a string.
Here is what I have:
const cellContent = typeof(column.customComponent === 'undefined') ?
rowData[column.columnName] :
{
type: column.customComponent,
props: {
data: rowData[column.columnName],
rowData
}
};
console.log(cellContent); displays something like:
the function representation of the React component
or
function DateCell() ....
I'm not sure if I'm just using the wrong syntax when assigning cellContent or if I should be doing something with factories or what.
How should I do this?

I use this helper method to create components and return the instance.
static async renderComponentAt(componentClass, props, parentElementId){
let componentId = props.id;
if(!componentId){
throw Error('Component has no id property. Please include id:"...xyz..." to component properties.');
}
let parentElement = document.getElementById(parentElementId);
return await new Promise((resolve, reject) => {
props.ref = (component)=>{
if(component){
resolve(component);
}
};
let element = React.createElement(componentClass, props, null);
ReactDOM.render(element, parentElement);
});
}

Related

React createProtal called outsite a JSX component not updating the DOM

I am trying to render a dynamically generated react component in a react app using createProtal.
When I call createProtal from a class the component is not rendered.
Handler.ts the class the contains the business logic
export class Handler {
private element: HTMLElement | null;
constructor(selector: string) {
this.element = document.getElementById(selector);
}
attachedEvent() {
this.element?.addEventListener("mouseenter", () => {
let cancel = setTimeout(() => {
if (this.element != null)
this.attachUi(this.element)
}, 1000)
this.element?.addEventListener('mouseleave', () => {
clearTimeout(cancel)
})
})
}
attachUi(domNode: HTMLElement) {
createPortal(createElement(
'h1',
{className: 'greeting'},
'Hello'
), domNode);
}
}
Main.tsx the react component that uses Handler.ts
const handler = new Handler("test_comp");
export default function Main() {
useEffect(() => {
// #ts-ignore
handler.useAddEventListeners();
});
return (
<>
<div id="test_comp">
<p>Detect Mouse</p>
</div>
</>
)
}
However when I repleace attachUi function with the function below it works
attachUi(domNode: HTMLElement) {
const root = createRoot(domNode);
root.render(createElement(
'h1',
{className: 'greeting'},
'Hello'
));
}
What am I missing?
React uses something called Virtual DOM. Only components that are included in that VDOM are displayed to the screen. A component returns something that React understands and includes to the VDOM.
createPortal(...) returns exactly the same as <SomeComponent ... />
So if you just do: const something = <SomeComponent /> and you don't use that variable anywhere, you can not display it. The same is with createPortal. const something = createPortal(...). Just use that variable somewhere if you want to display it. Add it to VDOM, let some of your components return it.
Your structure is
App
-children
-grand children
-children2
And your portal is somewhere else, that is not attached to that VDOM. You have to include it there, if you want to be displayed.
In your next example using root.render you create new VDOM. It is separated from your main one. This is why it is displayed

React ref that depends by an element's reference does not get passed to the child components

The following code creates an object ref that's called editor, but as you see it depends by the contentDiv element that's a ref to a HTMLElement. After the editor object is created it needs to be passed to the TabularRibbon. The problem is that the editor is always null in tabular component. Even if I add a conditional contentDiv?.current, in front of this, it still remains null...
Anyone has any idea?
export const Editor = () => {
let contentDiv = useRef<HTMLDivElement>(null);
let editor = useRef<Editor>();
useEffect(() => {
let options: EditorOptions = { };
editor.current = new Editor(contentDiv.current, options);
return () => {
editor.current.dispose();
}
}, [contentDiv?.current])
return (
<div >
<TabularRibbon
editor={editor.current}
/>
<div ref={contentDiv} />
..........

How to write a unit test to cover custom PropTypes in reactjs?

I have a reactjs component with a custom properties, that actually represents just a list of allowed extensions.
For example, my component can receive as a prop something like this:
<CustomComponent allowedTypes={['.pdf', '.txt']} />
and the prop types are defined like this
CustomComponent.propTypes = {
allowedTypes: PropTypes.arrayOf(
(propValue, key, componentName, location, propFullName) => {
if (!new RegExp(/^\.[^.]+$/).test(propValue[key])) {
return new Error(
`Invalid prop ${propFullName} for component ${componentName}.`
)
}
}
)
I need to full cover the component with unit test, so also the code in the prop type definition must be covered.
I tried something similar, but it doesn't work
beforeEach(() => {
console.error = jest.fn()
});
it('should check wrong allowed types', () => {
const wrongAllowedTypes = false
try {
const component = new CustomComponent(Object.assign(defaultProps, { allowedTypes: wrongAllowedTypes } ))
} catch (error) {
expect(console.error).toBeCalled()
}
})
Any ideas, thanks in advance
I suggest pulling out the fat arrow function and giving it a name. Then you can call it directly from a test.

Add refs to react component when know the id

I want to make the table on ant design scroll but ant design table does not have the onScroll (event)
Problem:
https://github.com/ant-design/ant-design/issues/5904
And they suggest the solution like this
componentDidMount() {
...
var tableContent = document.querySelector('.ant-table-body')
tableContent.addEventListener('scroll', (event) => {
// checking whether a selector is well defined
console.log('yes, I am listening')
let maxScroll = event.target.scrollHeight - event.target.clientHeight
let currentScroll = event.target.scrollTop
if (currentScroll === maxScroll) {
// load more data
}
})
...
}
He adds the scroll event on element with the id '.ant-table-body';
And also someone said 'better use refs' instead
Could you show me how to solve the problem (add scroll event on ant table) by using refs?
How can I use the refs to refer to the component when I know the id '.ant-table-body'. Note that I cannot access the component '.ant-table-body'.
something like this: tableRef = refs.findById('.ant-table-body')? or tableRefs = refs.addRefToId('.ant-table-body')
First add a ref to your table, like this:
<Table ref={table => { this.table = table }}
Then in componentDidMount you can add the same event listener to it like this:
componentDidMount() {
if(this.table) {
this.table.addEventListener('scroll', (event) => {
// do your stuff here
})
}
}

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