What do you call this React Component pattern? - javascript

I am having a hard time finding some documentation on this pattern.
Does it have a name?
TextBase is a styled component. so I can extend it as following:
Text.H1 = withComponent('h1') however I want html attributes to be passed as well. Hence the function component. However when I extend my Text component the props are being overridden, resulting with all components being h1's.
const Text = (props) => {
const { children, testid, ...rest } = props;
return <TextBase data-testid={testid} {...rest}>{children}</TextBase>
}
Text.defaultProps = {color: 'red'}
Text.H1 = Text
Text.H1.defaultProps = { as: 'h1'}
Text.H2 = Text
Text.H2.defaultProps = { as: 'h2'}
Text.H3 = Text
Text.H3.defaultProps = { as: 'h3'}
🙏🏽

Try binding the function call using this or use arrow function or manually bind the function using bind. I am sure it will work. Reference fault is all this is

Related

How to call functions set as properties of child functional components in React?

I have an app with multiple filters that share the same logic but with different inputs. Every filter has a different combination of inputs and some of those inputs are reused in different places, so I created some reusable components to be used like this:
<Filter>
<Filter.Input1 />
<Filter.Input2 />
</Filter>
Filter component manipulates the query string and every Input component uses different letters and logic to set it. For now, the logic related to query string is inside Filter:
const Filter = ({children}) => {
const searchParams = getParams(); // returns an object with the query string params
// Simplified version of current code to populate defaultValues
const defaultValues = {
a: searchParams?.a, // query string param for Input1
b: searchParams?.b, // query string param for Input2
}
// Here is a hook that receives the defaultValues
// On a second render this hook will ignore any new defaultValues
const onSubmit = (values) => {
// More logic related to query string params
}
return (
<form onSubmit={onSubmit}>
{React.children.map(...) // Set some props on Input elements
</form>
)
}
The problem here is that I have to include the logic to populate the defaultValues for every possible Input component, no matter if it's being used and now it's growing in complexity. I want to split that logic into the child components and loop through them to invoke functions with that logic, so now I'm trying to do something like this:
const Input1 = () => {...}
Input1.parseParams = (stringParams) => ({ a: stringParams.?a }) // Some components have a more complex logic
const Input2 = () => {...}
Input2.parseParams = (stringParams) => ({ b: stringParams.?b })
const Filter = ({children}) => {
const searchParams = getParams();
const defaultValues = React.children.map(children, child => {
if (child?.type && child?.type?.parseParams) {
return child.type.parseParams(searchParams);
}
})
}
It works, but I didn't see anything on the docs or somewhere else about the property type inside child. Also, I was aware of it containing the parseParams function after doing a console.log. I have never seen an approach like this, so my questions are:
Where can I find documentation about type?
Is this a valid approach or there is a better way to do it? I'm afraid that type could be an internal property that is not intended to be used like this.
PD: I think using refs will not work here because I need to do the form initialization on the first render and refs are assigned to the children on the return.
I haven't seen earlier such approach or use of type, but I have a suggestion,
Since you wrote some specific logic for each component (Input1, Input2), why don't you just pass searchParams to them and let them decide what to do with it?
You can make something like this:
React.children.map(children, child => {
if (child?.type && child?.type?.parseParams) {
return React.cloneElement(child, { searchParams });
}
})
UPD: I noticed that you might want just to get some defaultValues in your parent filter component, I think you can provide your child components a method like "setDefaultValue" and pass searchParams to it. After your child component render, it will make all necessary logic and call setDefaultValue which will set state in your parent component. It should work

Is there a way to reference a DOM element in Svelte component created using Client-side component API?

I am creating a svelte component in use:action function and I need to get the DOM element from the svelte component. I can't figure out how to bind or just get reference to DOM element in this scenario. One way I thought was to give the element a unique attribute, class or any identifier to use document.querySelect grab it but I feel like there should be way to get the element since I just create it right there. I am looking for api to equivalent of bind:this.
Example code
import ToolTip from '$lib/components/tooltips/Plain.svelte';
export default function (node: HTMLElement) {
const title = node.getAttribute('title');
let tooltipComponent = new ToolTip({
props: {
title,
},
target: node,
});
const tooltip = undefined; // ?? how to get the DOM element here
let arrow = tooltip.querySelector('#tooltip-arrow');
let update = () => {
compute(node, tooltip, arrow);
};
node.addEventListener('mouseenter', update);
node.addEventListener('focus', update);
return {
update() {
},
destroy() {
node.removeEventListener('mouseenter', update);
node.removeEventListener('focus', update);
tooltipComponent.$destroy();
},
};
}
One way I thought to do it
const uniqueId = generateUniqueId();
let tooltipComponent = new ToolTip({
props: {
title,
class: `tooltip-${uniqueId}`,
},
target: node,
});
const tooltip = tooltip.querySelector(`.tooltip-${uniqueId}`);
Alternatively to querying the element a reference inside the component could be set with bind:this and either made accessible via <svelte:options accessors={true}/> or a function REPL
<script>
import Tooltip from './Tooltip.svelte'
function addTooltip(node) {
const title = node.getAttribute('title');
const tooltipComp = new Tooltip({ target: node , props: {title}});
const tooltipElem = tooltipComp.tooltip
tooltipElem.style.background = 'lightblue'
const tooltipElemFn = tooltipComp.getTooltipElem()
tooltipElemFn.style.border = '2px solid purple'
}
</script>
<div use:addTooltip title="tooltip">
I have a tooltip
</div>
<svelte:options accessors={true}/>
<script>
export let title
// export variable with option accessors={true} OR make accessible via function
export let tooltip
export function getTooltipElem() {
return tooltip
}
</script>
<div bind:this={tooltip}>
{title}
</div>
There is no guaranteed way of finding any elements because components do not necessarily create any. Using some unique identifier or the DOM hierarchy can work if you know the structure of the component you create.
Just make sure to look within the target that you mount the component on. So use something like node.querySelector(...).
REPL example

Pass props and use ID again

I have a component that passes props to another component. Inside the component the props have been passed to, I declare the parameter set new variable and get the last item of the array like this:
var lastItem = passedProp[passedProp - 1] || null
My question is how do I pass this property back to another component to use in a global service I am using to run inside a function. From what I am aware props can only be passed down in React, not up? Please correct me if I am wrong. The end result I want to achieve is to use this property's ID in function I am using in global service.
read about lifting state up ...
https://reactjs.org/tutorial/tutorial.html#lifting-state-up
You can pass a function to the child and the child can pass the information through this function.
I let you an example that you can copy & paste to see how it works :)
import React from 'react';
function ChildComponent(props) {
const { data, passElementFromChild } = props;
const lastElement = data[data.length - 1] || null;
setTimeout(() => {
passElementFromChild('this string is what the parent is gonna get');
}, 300);
return (
<div>Last element of the array is: {lastElement}</div>
);
}
function Question17() {
const data = ['firstElement', 'middleElement', 'lastElement']
const passElementFromChild = (infoFromChild) => {
console.log("infoFromChild: ", infoFromChild);
}
return (
<ChildComponent data={data} passElementFromChild={passElementFromChild} />
);
}
export default Question17;

React checkbox doesn't work as expected

I had and idea to ad some object style to React project. That is why I have written that class
export class Tagi {
constructor() {
this.members = [{
tag: "No matter",
color: "Aquamarine",
isMarked: true
}];
this.handleTagMarking = this.handleTagMarking.bind(this);
}
add(x) {
this.members.push(x);
}
static fromTable(table) {
let tags = new Tagi();
let shortened = unique(table);
for (let value of shortened) {
let record = {
tag: value,
color: colors[shortened.indexOf(value)],
isMarked: false
}
tags.add(record)
}
return tags;
}
get getAllTags() {
return this.members;
}
handleTagMarking(event){
let x = event.target.value;
const index = this.members.findIndex((element)=>element.tag==x);
const currentMarkStatus = this.members[index].isMarked;
if (currentMarkStatus) this.UnMarkTag(index); else this.MarkTag(index)
console.log(this.members)
}
The last part thereof is event handler, more about it later.
That class is implemented in Parent component like this
let Taggs =Tagi.fromTable(d);
console.log (Taggs.getMarkedTags);
Please note that this is implemented in its render function. It has nothing to do with its state.
Later in this Parent component I send it as props
render() {
const label = this.props.labels;
const SingleCheckbox =()=>{return(label.map((element)=>
<label key={element.tag} ><input type="checkbox" value={element.tag} checked={element.isMarked} onChange={this.props.fn} />{element.tag}</label>))}
return (
<div className="checkbox">
<SingleCheckbox />
</div>);
The problem is that checkbox doesn't react to checking items properly. What I mean by this is that data is changed ( I send to console the data within evenhandler so that is clear) but it does not check the fields what is expected behaviour. I suspect it happens because in Parent, Taggs are not state-linked (but only suspect) Is that correct or could there be other reason? If so, how could I easily reshape the code keeping its object oriented style?
I have just chcecked my initial idea of being-not-a-state.
That is not correct I guess. Now in constructor Taggs are initiated like this:
Taggs:Tagi.fromTable(props.disciplines.map((d) => d.tags)),
and later in render{return()} are called like this
<CheckBox labels ={this.state.Taggs.getAllTags} fn={this.state.Taggs.handleTagMarking}/>
Does not change anything at all

Setting custom props on a dynamically created React component

I'm refactoring some of my React code for ease of use in places where I can't use Babel directly (such as in short embedded JavaScript on pages). To assist with this I'm setting up a short function that builds the components and passes props to them. This code works just fine:
components.js:
import ResponsiveMenu from './components/responsive-menu';
window.setupMenu = (items, ele) => {
ReactDOM.render(<ResponsiveMenu items={items}/>, ele);
};
static-js.html:
<div id="menu"></div>
<script>
setupMenu({ items: [] }, document.getElementById('menu');
</script>
However, when I attempt to turn it into something more generic to handle more components like so:
components.js:
import ResponsiveMenu from './components/responsive-menu';
import AnotherComp from './components/another-comp';
window.setupComponent = (selector, name, props) => {
let eles;
if (typeof selector == 'string') {
eles = [];
let nl = document.querySelectorAll(selector), node;
for (let i = 0; node = nl[i]; i++) { eles.push(node); }
} else {
eles = $.toArray(selector); // A helper function that converts any value to an array.
}
return eles.map (
(ele) => {
let passProps = typeof props == 'function' ? props(ele) : props;
return ReactDOM.render(React.createElement(name, passProps), ele);
}
);
};
static-js.html:
<div id="menu"></div>
<script>
setupComponent('#menu', 'ResponsiveMenu', { items: [] });
</script>
I then get this error: Warning: Unknown prop "items" on <ResponsiveMenu> tag. Remove this prop from the element. For details, see (really unhelpful shortened link that SO doesn't want me posting)
Please help me understand why this works for the JSX version and not for the more manual version of creating the component.
When you pass string parameter to React.createElement, it will create native DOM element and there is no valid html DOM ResponsiveMenu.
You can store element into hash and store it into window variable.
Example:
// store component into window variable
window.components = {
ResponsiveMenu: ResponsiveMenu
}
//extract component from window variable by name
React.createElement(window.components[name], passProps)

Categories

Resources