How to get or filter a bunch of childNodes by their style class name in ReactJs - reactjs

I am having trouble figuring out how to get or filter a bunch of childNodes by their style class name inside my useEffect. Using ReactJs v18.
Straight after the line with: const circleElements = launcherCircle!.childNodes; I would like to get/filter the div's with the class name 'launcherPos' so I can position them in a circle formation.
const LauncherComponent = () => {
const launcherCircleRef = useRef<HTMLDivElement>(null);
let modules: Module[] | null = GetModules();
const enableLauncher = (module: Module) => {
return !module.IsEnabled ? styles['not-active'] : null;
};
useEffect(() => {
const launcherCircle = launcherCircleRef.current;
const circleElements = launcherCircle!.childNodes;
let angle = 360 - 190;
let dangle = 360 / circleElements.length;
for (let i = 0; i < circleElements.length; i++) {
let circle = circleElements[i] as HTMLElement;
angle += dangle;
circle.style.transform = `rotate(${angle}deg) translate(${launcherCircle!.clientWidth / 2}px) rotate(-${angle}deg)`;
}
}, []);
if (modules == null){
return <Navigate replace to={'/noaccess'} />
} else {
return (
<div data-testid="Launcher" className={styles['launcherContainer']} >
<div className={styles['launcherCircle']} ref={launcherCircleRef}>
{modules.map(function (module: Module, idx) {
return (
<div key={idx} className={styles['launcherPos']} ><div className={`${styles['launcherButton']} ${enableLauncher(module)}`}><img src={module.ImagePath} alt={module.Prefix} /></div></div>
)
})}
<div className={styles['launcherTextDiv']}>
<span>TEST</span>
</div>
</div>
</div>
)
}
};
export default LauncherComponent;
From what I've read getElementsByClassName() is not advisable practise because of the nature of ReactJs and it's virtual DOM.
I tried the following filter but I think with React garburling the class name I didn't get anything back.
const launcherChildren = launcherCircle!.children;
const circleElements = [...launcherChildren].filter(element => element.classList.contains('launcherPos'));
Maybe there's a way to ref an array of the just the children with the launcherPos class???
There must be a couple of different ways, but, they are eluding me.

When you filter/map an array of HTMLElements, the results are in the form of objects, which contains properties like, props, ref etc.
Since className is a prop on the element, you should try looking for the class name by digging into the props key.
Simply put, all the props that you pass to the element, like onClick, onChange, value, className are stored under the props property.
You can filter the results by converting the class name into an array and further checking if it contains the target string (launcherPos in this case).
Your code should look something like this:
const circleElements = [...launcherChildren].filter(element=>element.props.className.split(' ').includes('launcherPos'))
The above method could be used when an array directly holds elements. E.g: [<div></div>,<div></div>...].
The approach that you've followed is correct, except for the way you are selecting the elements by their class names. I can see that you are using CSS modules in this component, meaning all the class names exist as properties on the imported object(styles in this case), so when you use contains('launcherPos') you are essentially checking for the presence of a string, but when using CSS modules, class names are available only as object properties, that's the reason you are getting an empty array. Simply update launcherPos to styles.launcherPos and that shall fix the issue.
All-in-all your useEffect function should look something like this:
useEffect(() => {
const launcherCircle = launcherCircleRef.current;
const launcherChildren = launcherCircle!.children;
const circleElements = [...launcherChildren].filter(element => element.classList.contains(styles.launcherPos)); //change launcherPos to styles.launcherPos
let angle = 360 - 190;
let dangle = 360 / circleElements.length;
for (let i = 0; i < circleElements.length; i++) {
let circle = circleElements[i] as HTMLElement;
angle += dangle;
circle.style.transform = `rotate(${angle}deg) translate(${launcherCircle!.clientWidth / 2}px) rotate(-${angle}deg)`;
}
}, []);

Related

call function in React functional component

I am trying to call a function from a div like the following
<div id='div_abstract'>
{content.abstract && content.abstract.length ? (
<article id="abstract" onMouseUp={spanSelect}>{content.abstract </article>) : ''}
</div>
My functional component is structured like this
export default function ExternalInfos(props) {
...
function spanSelect() { ... }
return(
...
);
}
And the function I'm trying to call is
let table = [];
function spanSelect() {
var span = document.createElement("span");
span.setAttribute("id","span");
if (window.getSelection()) {
var text = window.getSelection();
if (text.rangeCount) {
var range = text.getRangeAt(0).cloneRange();
range.surroundContents(span);
text.removeAllRanges();
text.addRange(range);
};
};
let object = window.getSelection().toString();
table.push(object);
const annotation = document.getElementById("annotationArea");
annotation.updateObjectAnnotation(table);
}
But nothing happens when I select text from my div and it doesn't return an error.
How do I solve this?
You need to capitalize the event handler prop: onMouseUp.
From the React docs (https://reactjs.org/docs/handling-events.html):
"React events are named using camelCase, rather than lowercase."

How do I update value of an array inside ref in vue 3

I am building an application with vuejs
I have a ref which contains an array and I want to update a single value inside it
export default defineComponent({
setup() {
const allQuestions = ref([{"like" => 1},{"like" => 0}]);
allQuestions.value[1].like = 1;
}
}
I want to update the second like in the ref array.
Javascript uses a different syntax than php for objects.
const allQuestions = ref([{like:1},{like:0}]);
allQuestions.value[1].like = 1

How to resolve "serializes to the same string" message with Jest?

In my React app, I've built a function that accepts a string full of regular text and any number of URLs. It then converts these into a <span> in React with every URL inside of an <a href tag. The code works really well but I can't seem to write a Jest test for it.
Here's what I've tried so far:
expect(convertHyperlinks('http://stackoverflow.com'))
.toStrictEqual(<span><a href='http://stackoverflow.com' target='_blank'>stackoverflow.com</a></span>);
And:
expect(convertHyperlinks('http://stackoverflow.com'))
.toMatchInlineSnapshot(<span><a href='http://stackoverflow.com' target='_blank'>stackoverflow.com</a></span>);
In the former case I'm getting the "serializes to the same string" message.
In the latter case, it's showing me this:
Expected properties: <span>stackoverflow.com</span>
Received value: <span>stackoverflow.com</span>
Might anyone know how to build a passing test for this?
Robert
Update: Here's the code for the function in question:
export const convertHyperlinks = (text: string): React.Node => {
// Find all http instances
const regex = /http\S*/g;
const hyperlinkInstances = text.match(regex);
if (!hyperlinkInstances) {
return <span>{text}</span>;
}
// Break up `text` into its logical chunks of strings and hyperlinks
let items = [];
let idx1 = 0;
let idx2 = -1;
hyperlinkInstances.forEach((hyperlink) => {
idx2 = text.indexOf(hyperlink, idx1);
if (idx2 === idx1) {
items.push(hyperlink);
idx1 += hyperlink.length;
} else {
items.push(text.substring(idx1, idx2));
items.push(hyperlink);
idx1 = idx2 + hyperlink.length;
}
});
if (idx1 < text.length) {
items.push(text.substring(idx1, text.length));
}
return (
<span>
{items.map((item) => {
if (item.includes('http://')) {
const plainLink = item.replace('http://', '');
return (
<a href={item.toLowerCase()} target='_blank' key={plainLink}>
{plainLink}
</a>
);
} else {
return item;
}
})}
</span>
);
};
You are returning a ReactNode from the method, which is an object. But you are trying to assert as just a string. It would'nt work.
This is what you may be getting back from the method,
And so, you must assert against the object you got, and not the way you are doing it right now,
const result = convertHyperlinks('http://stackoverflow.com')
expect(result.props[0].key).equals('stackoverflow.com');
// similar kind of assertions.
Additionally, I would suggest you go the component route and just render the component in the test method and assert for presence of elements as opposed to diving into react objects.
A representation of the same is as follows,
Here is your component,
const ConvertToHyperlinks = ({text}: {text: string}) => {
// your logic and then returning DOM elements.
return <></>;
}
Then you use it anywhere as,
<div>
<ConvertToHyperlinks text={'https://www.test.com/'} />
</div>
In your unit test you can then,
const renderedComponent = render(<ConvertToHyperlinks text={''https://www.anytyhing.com}/>);
expect(renderdComponent.getByText('anytyhing.com')).ToBeInTheDocument();
Here I am using some Rect Testing Library method but the idea is same even if you use enzyme etc.

display empty form while loading data in react

Ok, newbie alert, but I have a react component like below. I want to load user object and then show user.name and possible other nested properties, like user.address.street.
const ProfileDisplay =()=>
{
const [user, setUser] = useState(null);
useEffect(()=>{
async function populate(){
const user = await fetch('server/getUserProfile/'...);
setUser(user)
}
populate();
},[whatevs])
return (<FancyLookingForm>...<span>Your name is {user.name}</span>...</FancyLookingForm>);
}
...
During the time the user-object is loaded I want the form to be displayed, but empty. The alternatives I see is either create a dummy object that looks like the one that will come from the server and use that as init object to useState(). The other alternative is to use an if null-check on every place I use the user-object.
I will do quite alot of forms, so I want to check if there is a better way to do this?
I have written a utility function that will help you.
/**
* object is the forms data container in your example user
* property is field name e.g. name, age
*/
function getPropertryValue(object, property) {
if(!object) return "";
const keys = Object.keys(object);
const keysLength = keys.length;
for(let i= 0; i < keysLength; i++) {
const key = keys[i];
if(key == property) {
return object[key];
}
if(typeof object[key] === 'object'){
return utilFindHas(object[key], property);
}
}
return "";
};
So instead of using {user.name} use getPropertyValue(user, "name")
It will return empty string if it doesn't exist yet.

React JS / Material UI - Breadcrumbs with dynamic Ids in between path

I am new to react and trying to create breadcrumbs with dynamic Ids between path.
My props to component is:
const breadcrumbsData = {
path: "facilities/65743/facilitycontact",
breadcrumbNameMap: {
"/facilities": "Facility",
"/facilities/:facilityId": ":facilityId",
"/facilities/:facilityId/facilitycontact": "Facility Contact"
}
};
code which renders breadcrumbs is
<Breadcrumbs arial-label="Breadcrumb">
{paths.map((path, index) => {
const url = "/" + paths.slice(0, index + 1).join("/");
const last = index === paths.length - 1;
return last ? (
<Typography color="inherit"> {breadcrumbNameMap[url]} </Typography>
) : (
<Link to={url}>{breadcrumbNameMap[url]}</Link>
);
})}
</Breadcrumbs>
I tried different approaches but could not figure out how to do this. It works perfect without Ids in between path. for example
const breadcrumbsData = {
path: "facilities/facilityprofile/facilitycontact/workcontact",
breadcrumbNameMap: {
"/facilities": "Facility",
"/facilities/facilityprofile": "Facility Profile",
"/facilities/facilityprofile/facilitycontact": "Facility Contact"
}
};
P:S - Tried 'match' but did not go anywhere with that, and my use case is I am manually passing path from props.
Here is complete code
Code Sandbox - BreadCrumbs using react and material UI
I am not really sure why you need to this that way, but here is an ad hoc solution. Don't use your breadcrumbsData.breadcrumbNameMap directly to find the name. Pre-process it and generate your map with simple string replace.
For example, define a utility to pre-process the map:
generateNameMap = (id, idKey, map) => {
// takes the actual id i.e. '65743',
// the template key that need to be replaced with i.e. ':facilityId'
// and the initial map i.e. breadcrumbsData.breadcrumbNameMap
const clone = {};
Object.keys(map).forEach(k => {
clone[k.replace(idKey, id)] = map[k].replace(idKey, id) // replace the template in both key and value with actual id
})
return clone;
}
Now define your breadcrumbNameMap to use that utility rather than using breadcrumbsData.breadcrumbNameMap directly:
const breadcrumbNameMap = this.generateNameMap(paths[1], ':facilityId', breadcrumbsData.breadcrumbNameMap);
Here is a demo:
const generateNameMap = (id, idKey, map) => {
const clone = {};
Object.keys(map).forEach(k => {
clone[k.replace(idKey, id)] = map[k].replace(idKey, id)
})
return clone;
}
const breadcrumbNameMap = generateNameMap('65743', ':facilityId', {
"/facilities": "Facility",
"/facilities/:facilityId": ":facilityId",
"/facilities/:facilityId/workcontact": "Facility Contact",
"/facilities/facilityprofile/facilitycontact/": "Work Contact"
});
console.log(breadcrumbNameMap);

Resources