Next.js - how to change the component that is displayed? - reactjs

I'm brand new to React/Nextjs.
I'm using this as a template:
https://ui.mantine.dev/component/navbar-simple
This is the example data:
const data = [
{ link: '/notifications', label: 'Notifications', icon: IconBellRinging },
{ link: '/billing', label: 'Billing', icon: IconReceipt2 },
{ link: '/security', label: 'Security', icon: IconFingerprint },
It is used to built a navbar:
export function NavbarSimple() {
const { classes, cx } = useStyles();
const [active, setActive] = useState('Billing');
const links = data.map((item) => (
<a
className={cx(classes.link, { [classes.linkActive]: item.label === active })}
href={item.link}
key={item.label}
onClick={(event) => {
event.preventDefault();
setActive(item.label);
}}
>
<item.icon className={classes.linkIcon} stroke={1.5} />
<span>{item.label}</span>
</a>
));
return (
<AppShell
<Navbar>
<Navbar.Section>
{links}
</Navbar.Section>
</Navbar>
>
{/*
I am trying to get the components to be swapped/updated here
*/}
</AppShell>
Goal: If someone clicks "Security" in the navbar, the Security component will load.
Let's say I have built the "Notifications", "Billing" and "Security" components.
To update DOM, I saw a guide for using react-router-dom to do this. But I am trying to stick with only Nextjs.
Whatever string is stored in "link" can be changed. But from the "link" in the data object, is there a way to update the component?
If someone can point me to a tutorial, example, or even what to search for, I'd greatly greatly appreciate it :) I've been researching this evening but have not found anything yet.
I also made a codesandbox: https://wytec9.csb.app/

You could have within this component a function that will bring the component you want depending on the value, a quick example for this:
const renderComponent = () => {
if(active === 'Billing'){
return <Billing/>
} else if (){
// you get the idea
}
}
Now call that function to bring up the right component:
return (
<AppShell
<Navbar>
<Navbar.Section>
{links}
</Navbar.Section>
</Navbar>
>
{renderComponent()}
</AppShell>

You could achieve this by modifying your current data array of objects, and adding a component corresponding to each link.
Here is what it would looks like
const data = [
{ link: '/notifications', label: 'Notifications', icon: IconBellRinging, component: <Notification /> },
{ link: '/billing', label: 'Billing', icon: IconReceipt2, component: <Billing /> },
{ link: '/security', label: 'Security', icon: IconFingerprint, component: <Security /> }
]
Create a state that will store the component (you could modify your active state to be an object containing both the label and object):
const [activeComponent, setActiveComponent] = useState(null);
Then, update it in your onClick.
<a
className={cx(classes.link, { [classes.linkActive]: item.label === active })}
href={item.link}
key={item.label}
onClick={(event) => {
event.preventDefault();
setActive(item.label);
setActiveComponent(item.component)
}}
>
<item.icon className={classes.linkIcon} stroke={1.5} />
<span>{item.label}</span>
</a>
Good, you then can render the active component where you need it:
<AppShell
<Navbar>
<Navbar.Section>
{links}
</Navbar.Section>
</Navbar>
>
{activeComponent}
</AppShell>

Related

How to use "react-icons" in loop (Map method) in ReactJS 😅

I'm using React-icons in my ReactJS project and I just wanted to loop (by Map method) the specific icons in each JSX field when data is render.
In other word, I want this{`<${e.contact.icons}/>`}in JSX code.
Here is my code section:-
Here is, I import some icons for React icons.
import { FaBeer, Fa500Px, FeAccusoft } from "react-icons/fa";
Here is a data array which I want to render in JSX.
const data = [
{
contact: [
{
title: 'contact',
icons: 'FaBeer',
},
{
title: 'contact',
icons: 'Fa500Px',
},
{
title: 'contact',
icons: 'FaAccusoft',
},
],
},
]
And this is my component in down below. Which I'm using icons. You get little idea what I want to do.
const contact = () => {
return (
<>
{data.map((e, i) => {
return (
<>
<div className="text-area">
<span> {`<${e.contact.icons}/>`} </span>
</div>
</>
);
})}
</>
);
};
export default contact;
I'm trying to use like this{`<${e.contact.icons}/>`}, but is not working. When I see in browser. It's look like this.
<FaBeer/>
<Fa500Px/>
<FaAccusoft/>
It's just return like a text, but I want to get icons.
Any suggestion ?
You cannot use strings to represent React Component Types, instead you can use the imported ComponentType itself.
import { FaBeer, Fa500Px, FaAccusoft } from "react-icons/fa";
// here is data for I want to show
const data = [
{
contact: [
{
title: "contact",
subtitle: "get in touch",
icons: FaBeer,
},
{
title: "contact",
subtitle: "get in touch",
icons: Fa500Px,
},
{
title: "contact",
subtitle: "get in touch",
icons: FaAccusoft,
},
],
},
];
const Contact = () => {
return (
<>
{data.map((e, i) => {
const Icon = e.contact.icons;
return (
<>
<div className="text-area">
<h1 className="title">{e.contact.title}</h1>
<h2 className="subtitle">{e.contact.subtitle}</h2>
<span><Icon /></span>
</div>
</>
);
})}
</>
);
};
export default Contact;
Note how the rendering of the icon changes as well. I have assigned the icon component to a variable Icon instead of calling <e.contact.icons/> directly because React expects components to start with a capital letter.
The Icon variable will be a React component (either a function component or a class component) so you can call that component by using standard JSX <Icon /> syntax. You can also pass any of the react-icons props, for example: <Icon color="#FF0000" size={24}/>.
https://codesandbox.io/s/fervent-goldwasser-y83cn?file=/src/App.js
import { FaBeer, Fa500Px, FaAccusoft } from "react-icons/fa";
// here is data for I want to show
const data = [
{
contact: [
{
title: "contact",
subtitle: "get in touch",
icons: FaBeer
},
{
title: "contact",
subtitle: "get in touch",
icons: Fa500Px
},
{
title: "contact",
subtitle: "get in touch",
icons: FaAccusoft
}
]
}
];
const contact = () => {
return (
<>
{data.map((e, i) => {
return (
<>
{e.contact.map((e, i) => {
return (
<div className="text-area" key={i}>
<h1 className="title">{e.title}</h1>
<h2 className="subtitle">{e.subtitle}</h2>
<span>
<e.icons />
</span>
</div>
);
})}
</>
);
})}
</>
);
};
export default contact;
Well, the option of importing FaIcon-s and putting them into "data" array looks pretty nice:
import { FaBeer, Fa500Px, FaAccusoft } from "react-icons/fa";
const data = [
{
contact: [
{
title: "contact",
subtitle: "get in touch",
icons: FaBeer,
},
...
On the other hand possibility of generating components "dynamically" by their string name could be still implemented.
Firstly, I find usefull following article: React / JSX Dynamic Component Name
Next, I've created a new FaIconDynamic component:
import {
AiFillAccountBook,
AiFillAlert,
AiFillAlipayCircle,
AiFillAndroid,
} from 'react-icons/ai';
export const FaIconDynamic = ({ type }) => {
const FaIcon = components[type];
return <FaIcon></FaIcon>;
};
const components = {
AiFillAccountBook,
AiFillAlert,
AiFillAlipayCircle,
AiFillAndroid,
};
And then that's pretty easy to generate any registered above fa-icon, f.e.:
function App() {
return (
<>
<FaIconDynamic type={'AiFillAccountBook'} />
<FaIconDynamic type={'AiFillAlert'} />
<FaIconDynamic type={'AiFillAlipayCircle'} />
<FaIconDynamic type={'AiFillAndroid'} />
</>
);
}
Of course, both approaches have their pros and cons and could be more benefitial in some situations over each other
I have got the answer. I know the answer is not an ideal one, but it's work for me just now. The problem with the answer is that. We imported all the fonts from react-icons. So, I guess, as we will grow the project larger. It will decrease the performances and the major factor of could be react icons.
And also as Mr.Ali Shefaee describe in the comment section.
import React from "react";
import { render } from "react-dom";
import * as FontAwesome from "react-icons/lib/fa";
Now that section we could use two type of method.
First one :-
Here we import the all icons and use the function to get specific icon which we want
const Icon = props => {
const { iconName, size, color } = props;
const icon = React.createElement(FontAwesome[iconName]);
return <div style={{ fontSize: size, color: color }}>{icon}</div>;
};
const App = () => {
const iconString = "FaBeer";
const beer = React.createElement(FontAwesome[iconString]);
return (
<div>
<Icon iconName={"FaBeer"} size={12} color="orange" />
</div>
);
};
render(<App />, document.getElementById("root"));
And Second :-
const App = () => {
const iconString = "FaBeer";
const beer = React.createElement(FontAwesome[iconString]);
return (
<div>
<FontAwesome.FaBeer />
<div style={{ fontSize: 24, color: "orange" }}>{beer}</div>
</div>
);
};
render(<App />, document.getElementById("root"));
Here is the Demo:- Codesandbox.
Thank to〈Evie.Codes〉.
It seems that the current answers already addresses the problem, so this only attempts to add small improvement for the solution. Here is an approach I tried in a similar situation.
Simplified demo on: stackblitz
This will keep data the same as posted in the question as it might need to be fetched:
const data = [
{
contact: [
{
title: 'contact',
icons: 'FaBeer',
},
{
title: 'contact',
icons: 'Fa500Px',
},
{
title: 'contact',
icons: 'FaChrome',
},
],
},
];
Define an object with the imported icons as static data:
import { FaBeer, Fa500Px, FaChrome } from 'react-icons/fa';
const icons = { FaBeer, Fa500Px, FaChrome };
In the output, the icon can taken out from static data, and rendered on condition:
const Contact = () => {
return (
<>
{data.map((e, i) => (
<React.Fragment key={i}>
{e.contact.map((item, index) => {
const Icon = icons?.[item.icons];
return (
<div key={index} className="text-area">
<span>{Icon && <Icon size="3em" color="hotpink" />}</span>
</div>
);
})}
</React.Fragment>
))}
</>
);
};
export default contact;
import { FaBeer, Fa500Px, FeAccusoft } from "react-icons/fa";
note: there is a typo in the name of the icon you imported .. it should be FaAccusoft
my suggestion for your question is to store the Icon components themselves in the object property .. so instead of storing it as string: "FaBeer" ... store it as a component: <FaBeer /> directly inside the object property .. like this:
const data = [
{
contact: [
{
title: "contact-1",
icons: <FaBeer />
},
{
title: "contact-2",
icons: <Fa500Px />
},
{
title: "contact-3",
icons: <FaAccusoft />
}
]
}
];
and then you can simply loop over them
const Contact = () => {
return (
<>
{data.map((e, i) => {
return (
<>
{e.contact.map((c) => {
return (
<div className="text-area">
{c.title}
<span>
{c.icons} // you simply call it like this and the icon will be rendered
</span>
</div>
);
})}
</>
);
})}
</>
);
};
You can also use Parser() from html-react-parser. https://github.com/remarkablemark/html-react-parser
const parse = require('html-react-parser');
{parse(`<${e.contact.icons}/>`)};

State is undefined when used in react router link inside useMemo hook

I am using react router to navigate to another page and pass state to that page using this inside useMemo
const columns = React.useMemo(
() => [
{
Header: "Actions",
id: "actions",
Cell: (tableProps) => {
return (
<>
<Link
to={{
pathname: "list-table/list-table-edit",
state: { selectedRow },
}}
>
<span
style={{
cursor: "pointer",
color: "grey",
textDecoration: "underline",
}}
onClick={(event) => {
setSelectedID(tableProps.row.original.asset_ID);
}}
>
<Tooltip title="Edit Row">
<EditButton aria-label="edit">
<CreateIcon fontSize="small" />
</EditButton>
</Tooltip>
</span>
</Link>
</>
);
},
},
...MakeTableColumns,
],
[selectedRow]
);
My state are declared like this
const [selectedID, setSelectedID] = useState();
const selectedRow = oriData.find((row) => row.asset_ID === selectedID);
when user click Edit, it will set row ID to selectedID and selectedRow finds row values and pass to list-table-edit page.
however, when I console.log props.location.state from list-table-edit , it's showing undefined. When I take out <Link> .. outside of useMemo and give a status id, it is working fine, I think it's something wrong with useMemo.
If anyone could help, I would be very much appreciated, thanks
Updated
Now I change from state: { selectedRow } to state: { tableProps.row.original } and pass to another page and it works without passing the state. I could have already had the selected row value by calling tableProps.row.original.
<Link to={{ pathname: '/list-table/list-table-edit', state: { record: selectedRow} }}>My route</Link>

Is it good idea to pass multiple components as a prop?

I have a two menu components: profile-menu and main-menu.
Each menu has links with icons and component-button to open it.
Is it good idea to pass multiple components as links array and than render it with map like in this example? if not, what is alternative?
const Icon = () => <div>1</div>
const Icon2 = () => <div>2</div>
const links = [{ href: '/', name: 'MainPage', icon: Icon }, { href: '/test', name: 'Test', icon: Icon2 }]
const MainMenu = () => {
return (
<Navigation side='left' links={links}>
<img src='smt..' />
</Navigation>
)
}
const Profile = () => {
return (
<Navigation side='right' links={links}>
<img src='smt..' />
</Navigation>
)
}
const Navigation = ({ side, links, children }) => {
const menuRelayRef = useRef(null) // to close menu on page click
// some logic
return (
<Fragment>
{cloneElement(children, { ref: menuRelayRef })}
<div show={show} side={side}>
{links.map((l) => {
const Icon = l.icon // is it ok?
return (
<div>
<button>{l.name}</button>
<Icon />
</div>
)
})}
</div >
</Fragment>
)
}
i think it depends on your Icon component :
if Icon component is img tag so you should pass only src,alt.
if Icon component is SVG icon so you should pass SVG icon component.
generally if your component has same parts, so try to DRY (Dont Repeat Yourself),
and pass only dynamic parts of component.but sometimes you have no other way and passing component is best solution.

ReactJS Call a function in render after an action

I'm using ReactJS, and shopify's polaris in order to create a website. I'm very new to react so this might be a newbie question but I looked over the internet and couldn't manage to put the pieces together.
I have a dropdown list and basically whenever the user clicks on an item from a list I want to add a button next to the dropdown. Here is my code:
import React from "react";
import { ActionList, Button, List, Popover } from "#shopify/polaris";
export default class ActionListExample extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false,
title: "Set Period",
};
}
renderButton() {
console.log("Button clicked")
return (
<div>
<Button fullWidth={true}>Add product</Button>;
</div>
);
}
togglePopover = () => {
this.setState(({ active }) => {
return { active: !active };
});
};
render() {
const activator = (
<Button onClick={this.togglePopover}>{this.state.title}</Button>
);
return (
<div style={{ height: "250px" }}>
<Popover
active={this.state.active}
activator={activator}
onClose={this.togglePopover}
>
<ActionList
items={[
{
content: "One",
onAction: () => {
this.setState({ title: "One" }, function() {
this.togglePopover();
this.renderButton() //THIS IS WHERE I CALL THE METHOD
});
}
}
]}
/>
</Popover>
</div>
);
}
}
I've placed a comment in the code to show where I call the renderButton() method. Whenever I click the "One" element in the dropdown, it prints out "Button clicked" but nothing gets rendered to the screen. Any help is greatly appreciated. Thanks in advance!
You need to add another variable to check if an item is clicked, and as #azium commented, you need to add the output to your JSX, not inside the onAction function.
As of right now, you close the Popper when an item is clicked, setting this.state.active to false, so you can't rely on that to render your button. You need to add something like this.state.isButton or something and in onAction include:
onAction: () => {
this.setState({ title: "One", isButton: true }, () => {
this.togglePopover();
});
}
and then in your JSX:
{this.state.isButton && this.renderButton()}
This is a perfect use case for conditional rendering.
You basically want to render a component based on a condition (Boolean from your state in this case).
Conditional rendering can be written is several ways as you can see in the docs.
In your case i would go for something like this:
return (
<div style={{ height: "250px" }}>
<Popover
active={this.state.active}
activator={activator}
onClose={this.togglePopover}
>
<ActionList
items={[
{
content: "One",
onAction: () => {
this.setState({ title: "One" }, function() {
this.togglePopover();
});
}
}
]}
/>
{this.state.active && this.renderButton()}
</Popover>
</div>
);
}
}
Note that i just placed it at a random place, feel free to move it wherever you need it in the markup.
Thanks to everyone's help I finally was able to do this. I placed an extra attribute in the state called isButton and I initially set it equal to false. Here is my render function:
render() {
const activator = (
<Button onClick={this.togglePopover}>{this.state.title}</Button>
);
return (
<div style={{ height: "250px" }}>
<Popover
active={this.state.active}
activator={activator}
onClose={this.togglePopover}
>
<ActionList
items={[
{
content: "One",
onAction: () => {
this.setState({ title: "One", isButton: true }, function() { //Set isButton to true
this.togglePopover();
});
}
}
]}
/>
{this.state.isButton && this.renderButton()} //ADDED HERE
</Popover>
</div>
);
}
Please look at the comments to see where code was changed. Thanks!

How to pass a prop back from child to parent in onclick handler and then to another component in React?

I am passing a function to a grandchild (SingleProject) from my parent (App) that opens a model. The grandchild just renders an li in the child (Gallery) that show a list of images(coming from the grandchild). I am trying to get the object from props on the grandchild, pass it back to App, and then through to the modal (Modal) to display the information about the image. The objects that hold the info about the images are stored in a service (module.exports). The openModal function is what is being passed along to the grandchild. The closeModal will be passed to the modal. I am having problems with this. Any help would be appreciated.
//APP (Parent)
class App extends Component {
constructor(props) {
super(props);
this.closeModal = this.closeModal.bind(this);
this.openModal = this.openModal.bind(this);
this.state = {
open: false,
projects: Service,
selectedProject: Service[0]
}
console.log('selectedProject: ', this.state.selectedProject)
}
closeModal(event) {
this.setState({open: false});
console.log('app: ', this.state.open);
}
openModal(event) {
this.setState({open: true});
console.log('app: ', this.state.open);
}
render() {
const show = {
display: 'block'
};
const hide = {
display: 'none'
};
return (
<div>
<div style={this.state.open === false ? hide : show}>
<Modal
value={this.state.open}
closeModal={this.closeModal}
project={this.state.selectedProject}
/>
</div>
<Header />
<Intro />
<WhatIDo />
<WhoIAm />
<Gallery
value={this.state.open}
projects={this.state.projects}
openModal={this.openModal}
/>
<Contact />
<Footer />
</div>
);
}
}
//GALLERY (child)
const Gallery = (props) => {
console.log('props: ', props)
const projectItems = props.projects.map(project => {
return (
<SingleProject
key={project.name}
project={project}
openModal={props.openModal}
/>
);
});
return (
<div className="gallery">
<h3>My Work</h3>
<ul>{projectItems}</ul>
</div>
);
}
//SINGLEPROJECT (grandchild)
const SingleProject = (props) => {
return (
<li className="gallery-images">
<img
src={props.project.img}
onClick={props.openModal}
/>
</li>
);
}
//MODAL (Modal)
const Modal = (props) => {
return(
<div className="modal">
<div
className="modal-close"
onClick={props.closeModal}
/>
<ModalDesc project={props.project} />
</div>
);
}
//SERVICE
module.exports = [
{
name: 'xxx',
img: '.././images/gallery/xxx',
link: 'xxx',
tech: 'AngularJS, jQuery, HTML, CSS, PostgreSQL, Node.js, Gulp, Express.js',
desc: 'A clone of the Texas restaurant Dixie Chicken\'s website featuring a
backend and some parallax effects. This site is not fully responsive.'
},
{
name: 'xxx',
img: '.././images/gallery/xx',
link: 'xxx',
tech: 'AngularJS, jQuery, HTML, CSS, PostgreSQL, Node.js, Gulp, Express.js',
desc: 'A fully responsive clone of the E-commerce website Stance. This was a
group project. I did the set up of the backend, the endpoints, the frontend
state management, the directives, most of the SQL files, the single products
view, the login view, the account view, and the register view.'
},
{
name: 'xxx',
img: '.././images/gallery/xxx',
link: 'xxx',
tech: 'AngularJS, jQuery, HTML, CSS',
desc: 'A fully responsive site for The Battlefield Pilot Club.'
}
];
If I understand what you need correctly, you want to pass the project information to the openModal method
You just have to make a small edit to the SingleProject component
const SingleProject = (props) => {
return (
<li className="gallery-images">
<img
src={props.project.img}
onClick={() => props.openModal(props.project)}
/>
</li>
);
}
and then in your openModal method you can set the selectedProject;
openModal(project) {
this.setState({
open: true,
selectedProject: project,
}, () => {
console.log('app: ', this.state.open); // this will print "true"
});
console.log('app: ', this.state.open); // this will print "false"
}
Explanation:
Instead of passing props.onClick to the onClick prop of <img/> we wrap it with an arrow function that will invoke props.onClick with the project object
When you're logging the state in the openModal method, if you want it to print true you'll need to print it in the callback to the setState method since the setState call does not immediately set the state of the class but rather, react will batch the updates. official documentation for the same here

Resources