How can I add sub navigation to menu? - reactjs

I have config file where I have configured the menuItems array as follows. Which is menu.json
{
"menuItems": [
{ "name": "Services", "link": "/services" },
{ "name": "References", "link": "/references" },
{ "name": "Careers", "link": "/careers" },
{ "name": "Our Story", "link": "/story" },
{ "name": "Contact Us", "link": "/contact" },
{ "name": "Guides", "link": "/guides"},
{ "name": "Blog", "link": "/blog" }
]
}
I have tried adding subMenuItems inside the menuItems array but so far it results in error in the query in the useMenu.js
{
"menuItems": [
{ "name": "Services", "link": "/services", "subMenuItems": [ {"name": "Page", "link": "/page"}] },
{ "name": "References", "link": "/references" },
{ "name": "Careers", "link": "/careers" },
{ "name": "Our Story", "link": "/story" },
{ "name": "Contact Us", "link": "/contact" },
{ "name": "Guides", "link": "/guides"},
{ "name": "Blog", "link": "/blog" }
]
}
Then in the querying the items inside the array here in the useMenu.js
import React from 'react'
import { useStaticQuery, graphql } from 'gatsby'
import { useLocale } from '../hooks/locale'
function useMenu() {
// Grab the locale (passed through context) from the Locale Provider
// through useLocale() hook
const { locale } = useLocale()
// Query the JSON files in <rootDir>/i18n/translations
const { rawData } = useStaticQuery(query)
// Simplify the response from GraphQL
const simplified = rawData.edges.map(item => {
return {
name: item.node.name,
menuItems: item.node.translations.menuItems,
}
})
// Only return menu for the current locale
const { menuItems } = simplified.filter(lang => lang.name === locale)[0]
return menuItems
}
export default useMenu
const query = graphql`
query useMenu {
rawData: allFile(filter: { sourceInstanceName: { eq: "menu" } }) {
edges {
node {
name
translations: childMenuJson {
menuItems {
link
name
subMenuItems {
link
name
}
}
}
}
}
}
}
`
And then adding the subMenuItems to the query. Is this wrong way to do it? How would I be able to add the sub menu there? The menu itself is set here in the navigation.js file and is following:
import React from 'react'
import useMenu from '../useMenu'
import * as S from './styled'
const Navigation = ({ isActive, handleToggleMenu }) => {
const [menuItems, subMenuItems] = useMenu()
return (
<>
<S.Navigation>
{menuItems.map((menu, index) => (
<S.NavigationLink
to={menu.link}
aria-label={menu.name}
activeClassName="active"
key={`${menu.link}${index}`}
>
{menu.name}
</S.NavigationLink>
))}
{subMenuItems.map((subMenu, index) => (
<S.NavigationLink
to={menu.link}
aria-label={subMenu.name}
activeClassName="active"
key={`${subMenu.link}${index}`}
>
{subMenu.name}
</S.NavigationLink>
))}
</S.Navigation>
</>
)
}
export default Navigation
The error I am getting is following: TypeError: Cannot read property '0' of undefined

const [menuItems, subMenuItems] = useMenu() here, you are importing your custom hook from ../useMenu. This hook, since it's a custom-coded one, only accepts the exported values from your useMenu.js file so, it doesn't allow that notation because you are only exporting menuItems:
function useMenu() {
// Grab the locale (passed through context) from the Locale Provider
// through useLocale() hook
const { locale } = useLocale()
// Query the JSON files in <rootDir>/i18n/translations
const { rawData } = useStaticQuery(query)
// Simplify the response from GraphQL
const simplified = rawData.edges.map(item => {
return {
name: item.node.name,
menuItems: item.node.translations.menuItems,
}
})
// Only return menu for the current locale
const { menuItems } = simplified.filter(lang => lang.name === locale)[0]
return menuItems // here, the only export
}
In addition, the way you use the state is not correct. When you set a state using useState hook, like:
const [value, setValue]= useState("hi");
Your state is held by value, so if you want to get the current state's value, you need to access to value, initially set to "hi". setValue is just a helper function that is used to set the new state, so if you want to set a new value, overriding "hi", you need to use the setter function setValue("new value").
Said that I can't know if your useMenu.js logic is correct, assuming that, I guess that you don't want two separate loops to get the subMenuItems data. You should do something like:
import * as S from './styled'
const Navigation = ({ isActive, handleToggleMenu }) => {
const menuItems = useMenu()
return (
<>
<S.Navigation>
{menuItems.map((menu, index) => {
if(menu.subMenuItems){
menu.subMenuItems.map(submenuItem=>{
return console.log(submenuItem);
})
}
return <S.NavigationLink
to={menu.link}
aria-label={menu.name}
activeClassName="active"
key={`${menu.link}${index}`}
>
{menu.name}
</S.NavigationLink>
})}
</S.Navigation>
</>
)
}
export default Navigation
Step by step, your state has changed to:
const menuItems = useMenu()
Since you are exporting menuItems, your data is perfectly valid there. You have your subMenuItems inside the menuItems.
In the same loop where you are iterating through menuItems, you have the nested property of subMenuItems so, if your menu (the iterable variable) has the subMenuItems property, you want to iterate again trough it to get them. In this case, I'm returning a console.log() but adapt it to your navigation menu:
if(menu.subMenuItems.length){
menu.subMenuItems.map(submenuItem=>{
return console.log(submenuItem);
})
}

Related

Why am I not getting a re-render, when using map function to list components?

I wanted to make a notification component but ran into some issues.
First of all, I didn't notice the bug, because I set the array, and didn't push to it. I can confirm that it's pushing it to the array, but for some reason, it's not updating in React.
I already set keys as a timestamp, but still not working.
import './App.css';
import { v4 as uuid } from 'uuid';
import { React, useState } from 'react';
import { animated, Spring} from 'react-spring';
function App() {
var notifications = [
{
"identifier": new Date().getTime(),
"title": "lath",
"content": "react is good",
"type": true,
}
]
// window.addEventListener("message", (event) => {
// let data = event.data ?? undefined
// switch (data.type) {
// case "sendNotify":
// console.log(data)
// break
// }
// });
// addToNotify()
function addToNotify() {
notifications.push({
// "identifier": uuid().slice(0,8),
"identifier": new Date().getTime(),
"title": "lath",
"content": "this is a test",
"type": true,
})
console.log(notifications)
}
return [
<button key="clickBtn" onClick={() => addToNotify()}></button>,
<div key="notify-wrap" className="notify-wrapper">
{notifications.map(notif => (
// console.log(notif),
<Notification key={notif.identifier + "-key"} title={notif.title} content={notif.content} type={notif.type} identifier={notif.identifier}/>
))}
</div>,
]
}
function Notification(props) {
// var key = uuid().slice(0,8)
// console.log(props)
return (
<Spring config={{duration: 500}} from={{ opacity: 0 }} to={{opacity : 1}}>
{styles =>
<animated.div key={props.identifier} className="notify-container" style={styles}>
<div key={props.identifier + "-header-container"} className="notify-header-container">
<h1 key={props.identifier + "-header"} className="notify-header">{props.title}</h1>
<img key={props.identifier + "-header-img"} className="notify-icon" src={`img/${props.type ? "check" : "cross"}.png`}></img>
</div>
<p key={props.identifier + "-content"} className="notify-text">{props.content}</p>
</animated.div>
}
</Spring>
);
}
export default App;
You should use a useState hook to handle the notifications state, so that every time a new notification is added to the array, the useState triggers a re-render with the new state.
Change your code to:
import { v4 as uuid } from "uuid";
import { React, useState } from "react";
import { animated, Spring } from "react-spring";
const sampleNotification = {
// "identifier": uuid().slice(0,8),
identifier: new Date().getTime(),
title: "lath",
content: "this is a test",
type: true
};
function App() {
// use useState() and initialize it with your notifications array
var [notifications, setNotifications] = useState([
{
identifier: new Date().getTime(),
title: "lath",
content: "react is good",
type: true
}
]);
function addToNotify(notification) {
// change the state by appending the new notification to the previous state
setNotifications((prevNotifications) =>
prevNotifications.concat(notification)
);
console.log(notifications);
}
return [
<button
key="clickBtn"
onClick={() => addToNotify(sampleNotification)}
></button>,
<div key="notify-wrap" className="notify-wrapper">
{notifications.map((notif) => (
// console.log(notif),
<Notification
key={notif.identifier + "-key"}
title={notif.title}
content={notif.content}
type={notif.type}
identifier={notif.identifier}
/>
))}
</div>
];
}
[...]
export default App;
You should rather create a state to store your notifications in. Each time the state is updated, it'll trigger an re-render.
const [notifications , setNotifications] = useState([
{
"identifier": new Date().getTime(),
"title": "lath",
"content": "react is good",
"type": true,
}
])
const addToNotify = () => {
const notisObj = {
"identifier": new Date().getTime(),
"title": "lath",
"content": "this is a test",
"type": true,
}
setNotifications((curVal) => [...curVal, notisObj]);
}

How to passe value from dropdown made by semantic ui in react

I have problem with this select by semantic ui. I made object with values I mapping it, state is changing but value in Select is not updating - if I put props as default value, it is like hard coded, and not changing at all. So what i can do (e target value doesnt work) to pass the selected icon to change props state? Should i use hook?
import React from 'react';
import { Dropdown } from 'semantic-ui-react';
const Dropdown = () => {
let icons = [
{
"v1": "home",
"ico": "home"
},
{
"v1": "cart",
"ico": "cart"
},
{
"v1": "folder",
"ico": "folder"
},
{
"v1": "group",
"ico": "group"
},
{
"v1": "dollar",
"ico": "dollar"
},
{
"v1": "users",
"ico": "users"
},
{
"v1": "briefcase",
"ico": "briefcase"
},
{
"v1": "globe",
"ico": "globe"
},
{
"v1": "calendar",
"ico": "calendar"
},
{
"v1": "favorite",
"ico": "favorite"
},
];
const options = icons.map(({ v1, ico }) => ({ value: v1, icon: ico }))
return(
<div>
<Dropdown
text='Add icon'
icon='add circle'
floating
labeled
button
className='icon'
value={prop.icon}
options={options}
name='icon'
></Dropdown>
</div>
)
}
You can pass an onChange function to the component that receives two arguments: event and result. The result argument is an object that has a key value with the selected value of the dropdown. You then need to use the useState hook to save the selected value in the component state.
Hence, you could do something like this:
import {useState} from "react"
const [value, setValue] = useState("")
const handleChange = (e, result) => {
// result.value is the selected value from the Dropdown
setValue(result.value)
}
<Dropdown
onChange={handleChange}
/>

How to access the data passed through "this.props.history.push(...)"

Constructor code which contains the data
constructor(props){
super(props);
this.state = {
Data: [
{
"name": 'red'
},
{
"name": 'green'
},
{
"name": 'red'
},
{
"name": 'brown'
},
{
"name": 'yellow'
},
{
"name": 'brown'
}
]
}
}
The code for my button where I map the data
{this.state.Data.map((color) => (
<>
<div className="ViewDetailsBtn"><Button onClick={this.clickMe.bind(this, color.name)} className="ViewDetailsBtnLink">View Details</Button></div></>
))}
onClick function Code
clickMe = (details) => {
console.log(details);
this.props.history.push({
pathname: "/ViewDetails",
state: {detail: details}
});
}
Here it displays the color name on my console properly and it redirects me to ViewDetails but how do I display the color name on the ViewDetails page?
ViewDetails page code
import React from 'react'
const App = (props) => {
console.log(props);
//const data = props.location.state.detail;
return (
<div>
<h1>Details:-</h1>
{/* <h1>{data}</h1> */}
</div>
)
}
export default App
your ViewDetails component should accept a parameter:
const ViewDetails = (props) => {}
then you should be able to access that data through that parameter

How do I set initial state in React with Apollo graphql and graphene-django

I'm currently learning how to use apollo client as for a graphql API exposed via graphene-django's DjangoObjectType node. Here's an example
I define a node
class CompanyNode(DjangoObjectType):
class Meta:
model = Company
filter_fields = {
'domain': ['exact'],
'name': ['exact', 'icontains', 'istartswith']
}
interfaces = (graphene.relay.Node, )
class Query(graphene.ObjectType):
companies = DjangoFilterConnectionField(CompanyNode)
A typical query looks like this
query queryCompanies {
companies {
edges {
node {
id
name
__typename
}
}
}
}
And the response is
{
"data": {
"companies": {
"edges": [
{
"node": {
"id": "Q29tcGFueU5vZGU6MQ==",
"name": "great",
"__typename": "CompanyNode"
}
},
{
"node": {
"id": "Q29tcGFueU5vZGU6MTI=",
"name": "awesome",
"__typename": "CompanyNode"
}
},
]
}
}
}
I'm using apollo client with react for the frontend and my queries are returning okay. A typical component looks like this.
To initialize some state in index.js I'm writing the companies data to the cache.
const cache = new InMemoryCache();
cache.writeData({
data: {
companies: {
edges: [],
typename: 'CompanyNodeConnection',
__typename: 'CompanyNodeConnection',
},
},
});
Then in my component, I'm reading from my cache like below.
import React from 'react';
import { useQuery, useApolloClient } from '#apollo/react-hooks';
const Companies = () => {
const root = useApolloClient().cache.data.data;
const QUERY_ALL_COMPANIES = gql`
query getCompanies {
companies {
edges {
node {
id
name
}
}
}
}
`;
const { loading, error } = useQuery(QUERY_ALL_COMPANIES);
if (error) {
return <p>{JSON.stringify(error?.graphQLErrors[0]?.message)}</p>;
}
return (
<div>
<h2>Companies</h2>
{loading ? (
<p>loading</p>
) : (
<div>
{root['$ROOT_QUERY.companies'].edges.map((com) => {
const { id: edgeKey } = com;
const {
node: { id: itemNodeId },
} = root[edgeKey];
const { id, name } = root[itemNodeId];
return (
<div key={id}>
{name}
</div>
);
})}
</div>
)}
</div>
);
};
export default Companies;
Now my question is, is this the proper way to initialise this state? Secondly, is this the proper way of reading this particular query from the cache?
I'd like to see other patterns and opinions on what works. I'm thinking this pattern of reading state is too brittle.

how to create React search with multiple fields name

i am working on autocomplete with reactjs and react material-ui. Now its working on only one field name symbol but i want its work on multiple fields name like "symbol and name" Here is my working code and API response. API response filed name return row.symbol;
React search code
import React, { Component } from "react";
import Autocomplete from "./Autocomplete";
import { render } from "react-dom";
import ApiService from "../../service/ApiService";
const style = {
flexGrow: 1,
};
export class SearchScripComponent extends Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
searchArray: [],
message: null,
};
this.searchScripData = this.searchScripData.bind(this);
}
componentDidMount() {
this.searchScripData(this.requesDATA2());
}
requesDATA2() {
let data1 = { symbolOrName: "TATA" };
return data1;
}
searchScripData(searchScrip: any) {
ApiService.searchScripDataList(searchScrip).then((res) => {
this.setState({ searchArray: res.data.data });
});
}
render() {
const suggestions = this.state.searchArray.map((row: any) => {
return row.symbol;
});
return <Autocomplete suggestions={suggestions} />;
}
}
export default SearchScripComponent;
API Data
{
"statusCode": 200,
"message": "SUCCESS",
"data": [
{
"scripId": 299,
"symbol": "TATAGLOBAL",
"name": "abc"
},
{
"scripId": 520,
"symbol": "TATAYODOGA",
"name": "ttp"
},
{
"scripId": 1195,
"symbol": "TATASPONGE",
"name": "eer"
},
{
"scripId": 30,
"symbol": "TATASTLBSL",
"name": "qwer"
}
]
}

Resources