React Router dynamic routing based on JS Array - reactjs

I'm making a simple marketplace, that has a home page with product cards - the cards are generated from an internal JS file (Data.js). It goes like this because I need to generate random names, and I use a function for that. The array goes like this:
let Data= [{
id: i,
nomeCompleto: nomeCompleto,
valor: preco(),
isFavorite: false,]}
When I click on the card, I use a dynamic route:
<Route path ='produto/:id' element={productSpecs}/>
It is working fine, the xxx/produto/id always matches the product id. The problem is, it's returning all of the array from Data. I need it to return only Data[(id - 2]).
As it is now, productSpecs is defined on the App.js as below, replacing props on ProductInfo:
const productSpecs = Data.map(item => {
return (
<ProductInfo
key = {item.id}
id = {item.id}
img = {item.img}
nomeCompleto = {item.nomeCompleto}
valor = {item.valor}
favorite = {item.isFavorite}
/>
)
})
Should I define the props on the Product Info JSX? If so, how?

Following your question, you could retrieve the id param inside of the ProductInfo and do a find() on the Data to get the product you're trying to display.
function ProductInfo() {
const { id } = useParams()
const product = Data.find(product => product.id === id);
...
}
and on the Routing level you could have
<Route path ='product/:id' element={<ProductInfo />}/>

Related

How to get or filter a bunch of childNodes by their style class name in 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)`;
}
}, []);

Get data from URL using Inertia

I am using InertiaJs library to create links as in the following:
<InertiaLink href={`/item/`+ itemId}>Go to the page</InertiaLink>
Clicking on this, the link will look like this: /item/156654
Question: How to get the itemId(156654,54654654, ... ) from the link above using Inertia?
I know that in React there is a function let history = useHistory();, but this approach doesn't work for me. What alternative exists?
<InertiaLink /> don't provide any context to opened page, so you need to parse URL manually. In the example below you can find parsePageId() function which actually parse location path in an optimal way.
// solution from https://stackoverflow.com/questions/4758103/last-segment-of-url-in-jquery
const parsePageId = (path) => path.substring(path.lastIndexOf('/') + 1)
const Page = () => {
const pageId = parsePageId(window.location.pathname)
// will render "Page id: js", because snippet href is "https://stacksnippets.net/js"
return (
<p>Page id: <b>{pageId}</b></p>
)
}
ReactDOM.render(<Page />, document.querySelector("#root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
You can use the window.location object to get the actual URL and then apply a simple parsing to obtain the item id:
const { pathname } = window.location
const splitPathname = pathname.split("/")
const itemId = splitPathname[splitPathname.length - 1]
We are just getting all the parts of the URL separated by "/" and getting the last one, which will be the item id.

How can I use dataset with cloneelement?

How can I set a data attribute on an element when using cloneelement?
I've tried it this way.
const clonedItem = React.cloneElement(item, {
dataset: { "data-test": 123 },
dataTest: 123,
})
But that doesn't set any attribute in my html...
May be you are storing a custom component in item variable i.e
let item = <ItemComponent />.
Just store html element in item variable i.e
let item = <div><h1>Hello</h1></div>
And that will set attribute in div element.
If you use custom component, you need to use the props passed in the component.
i.e ItemComponent will be as
function ItemComponent({dataTest}){
return (
<div dataTest={dataTest}>
<h1>Hello</h1>
</div>
);
}
The one that worked for me is by settings the key as string
const clonedItem = React.cloneElement(item, {
'data-test': 123
})

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

How to throw an error in this.state.forEach statement in react

I read a bit about error throwing but I don't really understand how to throw an error for the specific code in this code below when react complains about that it is undefined.
Do I use the componentDidCheck and if yes how do you exactly do it? The code where it catches the error is below and it is the forEach where it blows up
class Subject extends React.Component {
state = {
subject: [],
}
render() {
var products = [];
var index = 0;
this.state.subject.forEach(product => {
subjects.push(<Product
key={index} subjectName = {this.state.subjects[index].name}
subjectPrice = {this.state.subject[index].salePrice}
subjecttId = {this.state.subject[index].itemId}
/>)
index++;
});
You are uneccessarily making complex logic. You have to utilize .map in your case. It’s just simple with .map
Don’t use index as key instead use itemId from each product as key. Index should be always second option if you don’t have unique id in the data then go for index otherwise don’t.
Since you are already doing .forEach or .map you no need to use index to get product details, because .map or .forEach will give you each object from products array. You can just access product.itemId and others like that
The reason you get undefined error may be because the products state is initially undefined or empty array and that’s why you get that error
So do check whether products is an array and contains list of objects by checking its length and then do .map
You no need to track index, you no need local array variable to push Product component into it
Below is the most efficient way of rendering Product component in simple and clean way
.map without return
render() {
const { products } = this.state;
return(
<div>
{Array.isArray(products) && products.length > 0 && products.map(product=> (
<Product key={product.itemId} productName = {product.name} productPrice = {product.salePrice} productImage = {product.thumbnailImage} productId = {product.itemId}
/>))}
</div>
)
}
.map with return
.map without return
render() {
const { products } = this.state;
return(
<div>
{Array.isArray(products) && products.length > 0 && products.map(product=> {
return <Product key={product.itemId} productName = {product.name} productPrice = {product.salePrice} productImage = {product.thumbnailImage} productId = {product.itemId}
/>})}
</div>
)
}
If you do like above you won’t get into errors. I posted this answer to correct you because you are making things complex
You can check error and info that componentDidCatch gives you and check these two return fallback UI accordingly

Resources