How to create a nested menu in JavaScript? - javascript

So I want to achieve the image below but with content sent from server.
We can set menu items
const items = [
{ key: 'editorials', active: true, name: 'Editorials' },
{ key: 'review', name: 'Reviews' },
{ key: 'events', name: 'Upcoming Events' },
]
//...
<Menu vertical color='black' items={items}>
</Menu>
However, I do not see how to nest them. Just setting item 'content' to some XML.
How do I create a menu with multiple nested sections in ReactJS\Semantic-UI in Javacript?

I would create the following components:
<MenuContainer /> -> our root component
<Menu></Menu> -> can render either itself (<Menu />) or an <Item /> component
<Item></Item> -> can render only a <li /> or smth
And suppose we have the following JSON coming from our server:
{
label: 'Some menu',
children: [{ label: 'Sub menu', children: [...] }, ...],
}
Let assume that when we find an array in our JSON, that means that we have to render a menu. If we have an object, we render a simple child. A rough algorithm would be:
const MenuContainer = ({items}) => ({
{items.map(item => item.items ? <Menu items={items} /> : <Item data={item} /> }
});
Is this something you are looking for?

Related

Getting content of currently active Text component wrapped inside popover of antd

I am using antd components for my react app. I have a Text component wrapped inside of Popover component. Now in my case this Popover is applied to one particular column of table, i.e. every row-element in that column has a Popover component rendered for it upon mouse hovering.
title: "Name",
dataIndex: "name",
key: "name-key",
sortType: "string",
sortDirections: ["descend", "ascend"],
sorter: (a, b) => a.name.length - b.name.length,
render: (text, record) => (
<Popover>
<Text onMouseOver={handleOnMouseOverCommitId}> {name} </Text>
</Popover>
)
I want to get hold of the row-element's value, the one contained by the above Text component whenever I hover over it. In this case the value denoted by {name} above.
I tried getting it with e.target.value via onMouseOver event, but it returned undefined.
I think I get the reason behind it, because the event.target returns an html node of type <span>.
With a normal div element e.target.value has worked in the past for me. But doing the same thing with a predefined component like antd's Text seems a bit trickier.
Just to elaborate, the Popover has two buttons and based on which button user clicks, I need to render some other components, something like an overlay component.
But in order to do that I would also need to get hold of the text value which originally triggered the Popover.
Below is the code(most of the things removed for preciseness).
record.name is what I ultimately need to capture.
<Popover
content={
<>
<Space>
<Button onClick={showSomeOverlayPaneForName}>
{"View Details for record.name"}
</Button>
<Button href={"https://abc.xyz.com/" + record.role}>
{"View Role Details"}
</Button>
</Space>
</>
}
trigger={"hover"}
>
<Text style={{"color": blue.primary}} copyable={true} onMouseOver={handleOnMouseOverName}>{record.name}</Text>
</Popover>
The handleOnMouseOverName function(which doesn't work anyway) :
const handleOnMouseOverName = (e) => {
//console.log("e.target.value :--- ", e.target.value);
setCurrentActiveName(e.target.value)
}
And once my currentActiveName variable is set(via useState), I use that value inside my function showSomeOverlayPaneForName
const showSomeOverlayPaneForName = (e) => {
axios
.get(
`some-url`,
{
params: {name: currentActiveName}
}
)
.then((response) => {
setData(response.data);
}).catch(reason => {
//xyz
});
}
You need to pass on the record of the enclosing render function to the handleOnMouseOverName function.
Check the following example
import React from 'react';
import 'antd/dist/antd.css';
import './index.css';
import { Space, Table, Button, Popover } from 'antd';
const App = () => {
const data = [
{
key: '1',
name: 'John Brown',
address: 'New York No. 1 Lake Park',
role: 'admin',
},
{
key: '2',
name: 'Jim Green',
address: 'London No. 1 Lake Park',
role: 'user',
},
{
key: '3',
name: 'Joe Black',
address: 'Sidney No. 1 Lake Park',
role: 'manager',
},
];
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: (name, record) => {
const content = (
<>
<Space>
<Button
onClick={() => {
viewDetail(record);
}}
>
{'View Details for ' + record.name}
</Button>
<Button href={'https://abc.xyz.com/' + record.role}>
{'View Role Details'}
</Button>
</Space>
</>
);
return (
<>
<Popover content={content} title="Details">
<div
onMouseOver={() => {
handleOnMouseOverName(record);
}}
>
{name}
</div>
</Popover>
</>
);
},
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
];
const handleOnMouseOverName = (record) => {
console.log(record);
};
const viewDetail = (record) => {
console.log(record);
};
return <Table columns={columns} dataSource={data} />;
};
export default App;
Output:
I hope this helps.
From antd docs: https://ant.design/components/popover/#header
Apparently you're supposed to render the <Popover/> with a content={content}-prop
For example
const content = <div>Content to render under title</div>
const App = () => {
const value = "Text to hover";
return (
<Popover content={content} title="Title">
<Text>{value}</Text>
</Popover>
)
}

Filter unique option values with Autocomplete component from Material UI

I want Autocomplete from Material UI to list in the drop-down only unique option values from a property of the objects in my list of objects.
In the example below, the drop-down renders a list with the values ['Color1', 'Color2', 'Color1', 'Color2'], however I want it to render only unique values, like ['Color1', 'Color2'].
Here is the list of objects:
options = [
{
id: 1,
name: 'Name1',
color: 'Color1'
},
{
id: 1,
name: 'Name2',
color: 'Color2'
},
{
id: 1,
name: 'Name3',
color: 'Color1'
},
{
id: 1,
name: 'Name4',
color: 'Color2'
},
]
And here is the Autocomplete complete component:
<Autocomplete
freeSolo
value={initialTasting}
options={options}
getOptionLabel={option => option.color}
filterOptions={(options, state) => options}
onChange={(e, value) => {setFieldValue('color', value.color)}}
renderInput={params => (
<TextField
{...params}
label={'Color'}
variant='outlined'
margin='dense'
fullWidth
/>
)}
/>
I left the rest of the code out, since it's too long. It uses Formik, Yup, has children components so on.
I guess I can achieve the result I'm looking for using the prop filterOptions, however, I can't make it. I'm too new to JavaScript..!
Thanks in advance.
What do you think about updating your options array like this?..
function getUniqueListBy(arr, key) {
return [...new Map(arr.map(item => [item[key], item])).values()]
}
const optionsUnique = getUniqueListBy(options, 'color');
...and then pass optionsUnique to the options parameter from <Autocomplete /> component

React Native Image Binding

Trying to navigate some React Native image bindings which I am unsure how to proceed.
Right now, the following code works.
I have a data file of items:
MyData.js
import { Images } from '#configuration';
const myData = [
{
name: 'One',
img: Images.one
},
{
name: 'Two',
img: Images.two
},
{
name: 'Three',
img: Images.three
}
]
And the images.js file
export const Images = {
one: require('#assets/images/one.png'),
two: require('#assets/images/two.png'),
three: require('#assets/images/three.png')
}
And in my React screen, I am trying to render that as:
import {MyData} from '#data';
export default function MyScreen({navigation}) {
const [myData] = useState(MyData);
return (
<View>
<FlatList data={myData} keyExtractor={(item, index) => item.name}
renderItem={({item, index}) => (
<Card image={item.img}>
</Card>
)}
</>
</View>
)
}
The above displays the image accordingly.
Now, I wanted to have the data binding of the FlatList bound to an API which returns the following JSON:
[
{
name: "One",
img: "Images.one"
},
{
name: "Two",
img: "Images.two"
},
{
name: "Three",
img: "Images.three"
}
]
Of course the binding to the Card image does not work as the img is interpreted as a string.
I tried to modify the image binding to:
<Card image={JSON.parse(item.img)}>
</Card>
But this simply throw an error and the image does not render.
Simply stumped on how images would be handled in this scenario.
Any insight would be appreciated. Thanks.
If you can do some adjustment in your json, it is possible
Just keep image name inside json data
{
name: "One",
img: "one"
},
{
name: "Two",
img: "two"
},
{
name: "Three",
img: "three"
}
]
and when you want to render it you can use
import { Images } from '#configuration';
<Card image={Images[item.img]}>
</Card>
If your name property is always same as the actual image source, then I would consider using name property to extract image url like below:
import { Images } from '#configuration';
<Card image={Images[item.name.toLowerCase()]}>
</Card>

How to render react component from a json object?

I made a menu component that accepts a json object with all menu itens.
For icons i use react-icons/io.
The json object is something like this:
const menus = {
Item1: { buttonText: 'Item 1 text', icon: { IoLogoAndroid }, path: 'item 1 path' },
Item2: { buttonText: 'Item 2 text', icon: { IoLogoAndroid }, path: 'item 2 path'},
};
This is the Menu function that will render the menu items as buttons:
const buttons = Object.keys(this.props.menus).map((menu) => {
return (
<a key={menu} href={this.props.menus[menu].path}
onClick={this.changeMenu.bind(this, menu)}>
{...this.props.menus[menu].icon} <- fail here
{this.props.menus[menu].buttonText}
</a>
);
})
I tried many ways to render the icon, but i am clueless on how this could work. Not sure if this is even possible. Any pointers?
If you are importing the icon from where you are defining the object then just tag it <IoLogoAndroid/>;, so react knows it should treat it as an element to render.
const menus = {
Item1: { buttonText: 'Item 1 text', icon: <IoLogoAndroid/> , path: 'item 1 path' },
Item2: { buttonText: 'Item 2 text', icon: <IoLogoAndroid/>, path: 'item 2 path'},
};
And then just call it directly (remove the ...)
<a key={menu} href={this.props.menus[menu].path}
onClick={this.changeMenu.bind(this, menu)}>
{this.props.menus[menu].icon}
{this.props.menus[menu].buttonText}
</a>
Alternatively you could just call React.createElement if you don't want to tag it in your object definition.
<a key={menu} href={this.props.menus[menu].path}
onClick={this.changeMenu.bind(this, menu)}>
{React.createElement(this.props.menus[menu].icon)}
{this.props.menus[menu].buttonText}
</a>
Here is a sample showing the 2 implementations https://codesandbox.io/s/pmvyyo33o0
I was just working with a similar project, and I managed to make it work, with a syntax like this
I here have an array of objects (like yours)
links: [
{
name: 'Frontend',
link: 'https://github.com/Thomas-Rosenkrans-Vestergaard/ca-3-web',
icon: <FaCode size={40} />,
id: 1
},
{
name: 'Backend',
link: 'https://github.com/Thomas-Rosenkrans-Vestergaard/ca-3-backend',
icon: <FaCogs size={40}/>,
id: 2
},
{
name: 'Mobile',
link: 'https://github.com/Thomas-Rosenkrans-Vestergaard/ca-3-app',
icon: <FaMobile size={40} />,
id: 3
}]
I then render my component by mapping, where I pass in the entire object as a prop
const projects = this.state.projects.map((project, i) => {
return(
<Project key={`Project key: ${i}`} project={project} />
)
})
I then use objectdestructing to get the prop
const { logo } = this.props.project
then it can just be displayed
//in my case I use antd framework, so I pass the FAIcon component in as a prop
<Meta
avatar={logo}
title={title}
description={description}
/>
I suppose you could do the same thing, by just passing the entire menu object in as a prop, and then accessing the icon?
You need to change the following:
icon: <IoLogoAndroid />
And in the code (remove the spread operator):
this.props.menus[menu].icon
Also, a few refactoring suggestions.
Do you really need to have an object of objects? Why not an array of objects, where every item has a "name" prop? It will be easier to iterate through, as you can access props directly from map, unlike with object keys.
You are creating a button list, so you should have ul and li tags aswell.
Consider passing only a reference to onClick such as:
onClick={this.changeMenu}
If you need to pass data around, you should use dataset for that. Pass a name/path then find it inside the change handler to avoid rebinding inside every re-render.
Refactored suggestion with an array of objects
changeMenu = e => {
const { menus } = this.props;
const { menuName } = e.target.dataset;
const menu = menus.find(menu => menu.name === menuName);
// Do something with menu
return;
};
renderMenu() {
return (
<ul>
{this.props.menus.map(menu => (
<li key={menu.name} style={{ listStyle: "none" }}>
<a
data-menu-name={menu.name}
href={menu.path}
onClick={this.changeMenu}
>
{menu.icon}
{menu.buttonText}
</a>
</li>
))}
</ul>
);
}

What do values only in JS map mean and how to write it the long way

I understand that only passing values of an object to JS map function results in having the objects values as variables (works just fine)
{links.map(({ key, href, label }) => (
<li key={key}>
<Link href={href} />
</li>
))}
but how does it actually look like when I want to write it without syntactic sugar?
My attempt so far
{links.map(link => ({ key = link.key, href = link.href }) => (
<li key={key}>
<Link href={href} />
</li>
))}
but with the latter I'm getting:
Functions are not valid as a React child
I need to know how to deconstruct the syntactic sugar example because I have a nested object and I need to iterate through each of the nesting.
Your second example returns an anonymous function on each iteration.
I think you wanted to do this:
{links.map(link => (
<li key={link.key}>
<Link href={link.href} />
</li>
))}
Edit
Not sure what you realy want to do with nested objects.
But as per your example, here is a running code:
class App extends React.Component {
state = {
links: [
{
key: 'one',
href: '/some-url',
sublinks: [{
href: "/hello",
label: "hello"
}]
},
{
key: 'two',
href: '/some-url2',
sublinks: []
}
]
}
render() {
const { links } = this.state;
return (
<div>
{links.map(link => (
<div>
<a href={link.href}>{link.key}</a>
{link.sublinks &&
link.sublinks.map(subLink => (
<div>
<a href={subLink.href}>{subLink.label}</a>
</div>
))}
</div>
))}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root" />
I see you accepted an answer for the original question. In response to your question about handling sub links, take a look at this way. It supports multiple levels of sub links without having to repeat any code. This is useful if you don't know how many levels there are, for example in a menu or navigation.
Fiddle demo
The TreeList component:
const TreeList = (props) => {
return (
<ul>
{props.links.map(link => (
<li key={link.key}>
<Link href={link.href} label={link.label} />
{link.sublinks ? <TreeList links={link.sublinks}></TreeList> : null}
</li>
))}
</ul>
)
}
Usage:
class App extends React.Component {
render() {
const links = [{ key:4, label: 'top', href: '/some-url'}, { key:1, label: 'one', href: '/some-url', sublinks: [{ key:2, href: "/hello", label: "hello", sublinks: [{ key:3, href: "/hello", label: "grandchild" }] }]} ];
return <TreeList links={links}></TreeList>;
}
}

Categories

Resources