I need to wrap a navigational component in React in a div that acts as a link/a (I have an <a></a> nested in the nav item already and can nest another <a></a> within. I need re-route the user to a new route on click of this outer most div that is acting as a wrapper. I have multiple navigational sections and want to use something like an onClick={this.handleNavClick(newRoute)} but am not having any success. I am console logging the correct linkPath but nothing happens on click.
Here is my function:
handleNavClick(linkPath) {
console.log(linkPath)
let currentPath = window.location.pathname;
currentPath = window.location.linkPath;
},
Here is an example of me trying to re-route from a nav section:
getNavItem() {
const currentPath = window.location.pathname;
const reportPath = "/reporting";
return (
<div onClick={this.handleNavClick(dashboardsPath)}>
<li id="nav-item-view" className={ classNames({"active": currentPath === reportPath}, "sidebar-item")}>
<div className="active-carat"></div>
</li>
</div>
);
},
You should try something like this:
onClick={() => {this.handleNavClick(dashboardsPath)}}
This is because onClick accepts a function, but here you're passing the result of a function.
You can do something like this
getNavItem() {
const currentPath = window.location.pathname;
const reportPath = "/reporting";
const handleNavClick = () => {
let currentPath = window.location.pathname;
currentPath = dashboardPath;
};
return (
<div onClick={handleNavClick}>
<li id="nav-item-view" className={ classNames({"active": currentPath === reportPath}, "sidebar-item")}>
<div className="active-carat"></div>
</li>
</div>
);
},
However I'd avoid creating a handler on every render for performance reasons. You should instead create a NavItem component with it's own handleNavClick method and dashboardPath prop.
Related
I built tabbing functionality in my web app using Links and an Outlet with subroutes
const { currentTab } = useLoaderData();
function tabClassName(tab: string) {
if (currentTab == tab) { return "is-active"; }
return "";
}
return ([
<div className="tabs is-centered m-5 pr-5 pl-5">
<ul>
<li className={tabClassName("tab1")}><Link to="tab1">one</Link></li>
<li className={tabClassName("tab2")}><Link to="tab2">two</Link></li>
<li className={tabClassName("tab3")}><Link to="tab3">three</Link></li>
</ul>
</div>,
<Outlet></Outlet>
]);
The loader function supplies the component with the current subroute like this:
const parts = new URL(request.url).pathname.split("/");
const tab = parts.pop() || parts.pop() || ""; // get final part of path /settings/tab1 -> tab1
if (tab === "settings") {
return redirect("/settings/tab1");
}
return json<LoaderData>({ currentTab: tab });
When I click the links the correct route is loaded instantly. However, the css class is only applied after clicking the same link a second time. I found out that the loader function is only executed after the second click. How can i force remix to make a server request again? or should i use client side js for this? I cant use the NavLink component because of the way my css framework is structured.
Thanks!
I have a button named yes in the child component where I delete a list item from array and local storage using the props.id passed from the parent component.
The problem is the item is deleted from array and local storage but is still visible on the screen until I press delete button on another item in the parent component.
when I press delete button in the parent component it opens an overlay. When I press yes button on overlay I want list item to be removed from the screen immediately.
here is the code in the child component.
import React, { useCallback, useState } from "react";
import styles from "./Delete.module.css";
function Delete(props) {
// console.log();
const store = props.store;
const [no, setNo] = useState(false);
let [deleted, setDelted] = useState(store);
console.log(deleted);
console.log("Length :" + store.length);
const noBtnHandler = () => {
console.log("clicked");
setNo(!no);
props.setDel(false);
};
const yesBtnHandler = () => {
console.log("Dlete.js :" + props.id);
const filteredStore = deleted.filter((task) => task.id !== props.id);
setDelted([...filteredStore]);
localStorage.removeItem(props.id);
// console.log(deleted);
setNo(!no);
};
return (
<div className={`${no === false ? styles.del : styles.delOn}`}>
<section>
<div>
<h3>Are you Sure ?</h3>
</div>
<div>
<button type="button" onClick={yesBtnHandler}>
{" "}
Yes{" "}
</button>
<button type="button" onClick={noBtnHandler}>
{" "}
No{" "}
</button>
</div>
</section>
</div>
);
}
export default Delete;
You are passing store from the parent component to the Delete Component and setting a new state here 'deleted'. so you are only calling the setDeleted on the Delete component which wont affect the parent component.
The correct implementation is to have the store state in the parent component if you don't already have it. It is will still be same like deleted state but possibly with a better name. Say const [store, setStore] = useState([])
Define a function to filter out a particular record just like you have in the yesBtnHandler handler. but this function will be defined in the parent component. Say as an example
const removeRecord = (id) => {
const filteredStore = store.filter((task) => task.id !== id);
localStorage.removeItem(id);
setStore(filteredStore);
}
You now need to pass the a function to the Delete Component from the parent rather than passing the whole store. Like
<Delete removeFunc= {() => { removeRecord(id) }} />
After passing this function, you need to call it in your yesBtnHandler function. Like
function Delete({removeFunc}) {
...
const yesBtnHandler = () => {
removeFunc();
setNo(!no);
};
}
Try remove the trailing ...
const yesBtnHandler = () => {
console.log("Dlete.js :" + props.id);
const filteredStore = deleted.filter((task) => task.id !== props.id);
setDelted([filteredStore]);
//or setDelted(filteredStore);
localStorage.removeItem(props.id);
// console.log(deleted);
setNo(!no);
};
my understanding of this is that you're trying to change the state of a parent component from a child component. If that's what you're intending to do then you can do the following:
Define the function Delete(id) {...} inside the parent component rather than the child component.
Next, you'll have to pass both the function and the array to your child component, something like this: <ChildComponent array={array} onDelete={Delete}, where array is the array in your parent component and Delete is the function to delete an item from the array.
Finally, in your child component, with the props passed in correctly, i.e, function ChildComponent({array, Delete}) {...}you can now have access to the array in your parent component, and actually modify it like you'd like. To fire the event listener on the yes button in the child component, do this: <button type="button" onClick={() => Delete(array.id)}> {" "} Yes{" "} </button>
I hope this will be helpful
I'm trying to create a filter button by ReactJs, spent a lot of times but still do not know why it's doesn't work
Here are my codePen: https://codepen.io/tinproht123/pen/gOxeWpy?editors=0110
const [menuItems, setMenuItems] = React.useState(menu);
const [categories, setCategories] = React.useState(allCategories);
const filterItems = (category) =>{
if(category === 'all'){
setMenuItems(menu);
return;
}
const newItems = menu.filter((item)=> item.category === category)
setMenuItems(newItems);
}
return(
<section className='container'>
<div className='title'>
<h1>Our menu</h1>
</div>
<Categories categories={categories} filterItems={filterItems}/>
<Menu menu={menuItems}/>
</section>
)
}~~~
I checked your code and the problem isn't in the part that you showed to us.
Instead please check your codes 103th line, on codepen. Your code seems like that:
const Menu = () =>{
return(
<div className='menu-container'>
{menu.map((menuItem) => {
....
Be careful to the first line, since your all menu items stays in menu variable, even though you made correct filtering, you're printing the result for all menus.
I saw that you're sending a prop to a <Menu menu={menuItems}>....
but you're not using it. To use this prop you should add a parameter to your Menu function;
const Menu = ({menu}) =>{
return(
<div className='menu-container'>
{menu.map((menuItem) => {
Just like above.
In the Menu component, you're not passing any props but just rendering the const value declared on top of the file.
You're filtering exactly by categories but rendering is not showing the updated one. :)
In React JSX I want to convert a part of the text into an anchor tag dynamically. Also on click of the anchor tag, I want to do some API call before redirecting it to the requested page. But I am failing to achieve this. Can anyone have a look and let me know where am I going wrong?
I have recreated the issue on code sandbox: here is the URL: Code Sandbox
Relevant code from sandbox:
import React from "react";
import "./styles.css";
export default function App() {
let bodyTextProp =
"This text will have a LINK which will be clicked and it will perform API call before redirect";
let start = 22;
let end = 26;
let anchorText = bodyTextProp.substring(start, end);
let anchor = `<a
href="www.test.com"
onClick={${(e) => handleClick(e)}}
>
${anchorText}
</a>`;
bodyTextProp = bodyTextProp.replace(anchorText, anchor);
const handleClick = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
const handleClick2 = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
return (
<div className="App">
<h3 dangerouslySetInnerHTML={{ __html: bodyTextProp }} />
<a href="www.google.com" onClick={(e) => handleClick2(e)}>
Test Link
</a>
</div>
);
}
The problem is variable scope. While it is entirely possible to use the dangerouslySetInnerHTML as you are doing, the onClick event isn't going to work the same way. It's going to expect handleClick to be a GLOBAL function, not a function scoped to the React component. That's because React doesn't know anything about the "dangerous" html.
Normally React is using things like document.createElement and addEventListener to construct the DOM and add events. And since it's using addEventListener, it can use the local function. But dangerouslySetInnerHTML bypasses all of that and just gives React a string to insert directly into the DOM. It doesn't know or care if there's an event listener, and doesn't try to parse it out or anything. Not really a good scenario at all.
The best solution would be to refactor your code so you don't need to use dangerouslySetInnerHTML.
*Edit: since you say that you need to do multiple replacements and simply splitting the string won't suffice, I've modified the code to use a split.
When used with a RegExp with a capturing group, you can keep the delimiter in the array, and can then look for those delimiters later in your map statement. If there is a match, you add an a
import React from "react";
import "./styles.css";
export default function App() {
let bodyTextProp =
"This text will have a LINK which will be clicked and it will perform API call before redirect";
let rx = /(\bLINK\b)/;
let array = bodyTextProp.split(rx);
const handleClick = (e) => {
console.log("The link was clicked.");
e.preventDefault();
};
const handleClick2 = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
return (
<div className="App">
<h3>
{array.map((x) => {
if (rx.test(x))
return (
<a href="www.test.com" onClick={handleClick}>
{x}
</a>
);
else return x;
})}
</h3>
<a href="www.google.com" onClick={(e) => handleClick2(e)}>
Test Link
</a>
</div>
);
}
I am trying to copy this fiddle:
http://jsfiddle.net/jhudson8/135oo6f8/
(I also tried this example
http://codepen.io/adamaoc/pen/wBGGQv
and the same onClick handler problem exists)
and make the fiddle work for server side rendering, using ReactDOMServer.renderToString
I have this call:
res.send(ReactDOMServer.renderToString((
<html>
<head>
<link href={'/styles/style-accordion.css'} rel={'stylesheet'} type={'text/css'}></link>
</head>
<body>
<Accordion selected='2'>
<AccordionSection title='Section 1' id='1'>
Section 1 content
</AccordionSection>
<AccordionSection title='Section 2' id='2'>
Section 2 content
</AccordionSection>
<AccordionSection title='Section 3' id='3'>
Section 3 content
</AccordionSection>
</Accordion>
</body>
</html>
)));
the Accordion element looks like so:
const React = require('react');
const fs = require('fs');
const path = require('path');
const Accordion = React.createClass({
getInitialState: function () {
// we should also listen for property changes and reset the state
// but we aren't for this demo
return {
// initialize state with the selected section if provided
selected: this.props.selected
};
},
render: function () {
// enhance the section contents so we can track clicks and show sections
const children = React.Children.map(this.props.children, this.enhanceSection);
return (
<div className='accordion'>
{children}
</div>
);
},
// return a cloned Section object with click tracking and 'active' awareness
enhanceSection: function (child) {
const selectedId = this.state.selected;
const id = child.props.id;
return React.cloneElement(child, {
key: id,
// private attributes/methods that the Section component works with
_selected: id === selectedId,
_onSelect: this.onSelect
});
},
// when this section is selected, inform the parent Accordion component
onSelect: function (id) {
this.setState({selected: id});
}
});
module.exports = Accordion;
and the AccordionSection component looks like so:
const React = require('react');
const AccordionSection = React.createClass({
render: function () {
const className = 'accordion-section' + (this.props._selected ? ' selected' : '');
return (
<div className={className}>
<h3 onClick={this.onSelect}>
{this.props.title}
</h3>
<div className='body'>
{this.props.children}
</div>
</div>
);
},
onSelect: function (e) {
console.log('event:',e);
// tell the parent Accordion component that this section was selected
this.props._onSelect(this.props.id);
}
});
module.exports = AccordionSection;
everything works, and the CSS is working, but the problem is that the onClick doesn't get registered. So clicking on the accordion elements does nothing. Does anyone know why the onClick handler might not get registered in this situation?
React DOM render to string only sends the initial HTML as a string without any JS.
You need a client side react router as well which will attach the required JS handlers to the HTML based on their react-id's. The JS needs to run on both sides.
Universal rendering boilerplate for quick start. https://github.com/erikras/react-redux-universal-hot-example
Another question which is similar to yours. React.js Serverside rendering and Event Handlers
None of the hooks will register with ReactDOMServer.RenderToString. If you want to accomplish server side rendering + hooks on your react component, you could bundle it on the client (webpack, gulp, whatever), and then also use ReactDOMServer.RenderToString on the server.
Here's a blog post that helped me accomplish this:
https://www.terlici.com/2015/03/18/fast-react-loading-server-rendering.html