How can I pass stateProps through JSON? (Gatsby) - javascript

I use JSON and useStaticQuery to pass data about links on my website. For example—
simBarItems.json
[
{
"content": "Provia",
"url": "/foto/provia",
"id": 1
},
{
"content": "Velvia",
"url": "/foto/velvia",
"id": 2
},
{
"content": "Astia",
"url": "/foto/astia",
"id": 3
},
{
"content": "Classic Chrome",
"url": "/foto/classic-chrome",
"id": 4
},
{
"content": "Acros",
"url": "/foto/acros",
"id": 5
},
{
"content": "PRO Neg.",
"url": "/foto/pro-neg",
"id": 6
}
]
SimBar.jsx
import React from "react"
import { Link, useStaticQuery, graphql } from "gatsby"
import { container } from "./SimBar.module.scss"
function isActive({ isCurrent }) {
return isCurrent ? { className: "active" } : {}
}
function isPartiallyActive({ isPartiallyCurrent }) {
return isPartiallyCurrent ? { className: "active" } : {}
}
export default function SimBar() {
const data = useStaticQuery(graphql`
query {
allSimBarItemsJson {
nodes {
id
content
url
}
}
}
`)
let items = data.allSimBarItemsJson.nodes
return (
<nav className={container}>
<ul>
<li>
<Link to="/foto" getProps={isActive}>
Alle foto
</Link>
</li>
{items.map(item => (
<li key={item.id}>
<Link to={item.url} getProps={isPartiallyActive}>
{item.content}
</Link>
</li>
))}
</ul>
</nav>
)
}
As you can see, most items should have the same stateProp, so they can go in the same map. However, the first item should have a different stateProp.
My question is: How can I pass the stateProp through JSON so that I can include the item with the isActive link in the map? Is it possible?
Thanks!

If you want to modify your JSON data, you have a few options (at least):
Modify directly the JSON to add the isActive attribute only in the first element
Tweak your data directly in the component
Before the loop:
let items = data.allSimBarItemsJson.nodes
items[0].isActive=true
So now your first item has the attribute active so when you loop you can access to it:
{items.map(item => {
console.log("has isActive attribute: ", item.isActive)
return <li key={item.id}>
<Link to={item.url} getProps={isPartiallyActive}>
{item.content}
</Link>
</li>
})}
Using the index to determine which one is the first, hence isActive, on the fly:
{items.map((item, index) => {
console.log("has active attribute: ", index===0)
return <li key={item.id}>
<Link to={item.url} getProps={isPartiallyActive}>
{item.content}
</Link>
</li>
})}
As you can see in the docs, the map loop as a second argument takes the index so you can access to the first position at any time (index===0).
Each implementation will be more optimal depending on what you want to achieve with the isActive (i.e: if it needs to change between items or not).

Related

Is using react hooks the proper way to dynamically add menu items from parsed json?

I am new to React/JavaScript but feel like I am learning decently, so bear with me.
I have a React web app site that loads a JSON file and displays the data into groupings like so.
If it matches a specific file name, it labels it "Full Configuration", else it just displays the file name on the menu item.
After the JSON file is parsed, a div with unique ID is displayed for each grouping. I keep an array of all the div IDs.
What I would like to do, is append a menu item in the menu to view a single div of the "Full Configuration" file because this view has around 30ish groups so if a user wants to see a group that's located at the very bottom, they have to scroll all the way down.
Use case: Lets say the user only wants to see the GPS Lever Arms, there would be a menu item for "GPS Lever Arms" they could click. The menu item would change the display property of all divs to "none" except for that one. So the user would then see something like this.
I have done some looking the past few days and it seems like the useEffect is the correct way to go about this but I have not seen it implemented in my desired way.
This is my current code that receives a json object data and creates the div per group and then displays each item inside the group.
import React, {useState} from 'react'
import ConfigParam from './ConfigParam'
export default function Grouping({data}){
const [jsonData, setJsonData] = useState(data)
// Array of arrays To hold the grouping results.
var groupResults = {};
var divIDs = [];
for(var obj in data)
{
if(data[obj].hasOwnProperty('meta') === false)
{
console.log(`no meta field in ${data[obj]}`);
return <></>
}
else if(data[obj].meta === "")
{
console.log(`${data[obj]} meta field is empty`);
return <></>
}
else
{
// check if the group property is missing - create it as empty
if(data[obj].meta.hasOwnProperty('group') == false)
{
data[obj].meta.group = ""
}
if(!groupResults.hasOwnProperty(data[obj].meta.group))
{
groupResults[data[obj].meta.group] = [];
}
// take the name of the key and add it as a property inside the object
data[obj].name = obj;
// push the object onto the json
groupResults[data[obj].meta.group].push(data[obj]);
}
}
const editValue = ({name,data}) =>
{
var temp = jsonData;
temp[name].value = data;
//console.log(data)
setJsonData(temp)
}
// Get the keys of the groupResults (Group names)
const test = Object.keys(groupResults);
return(
test.map((member,index) =>{
// get the keys within the current group (items)
const subItems = Object.keys(groupResults[member]);
// create a div id per group
let div_id = "group-" + test[index];
divIDs.push(div_id);
console.log("new divID:", div_id);
// todo - append button for each div to view each individual div.
return(
<div id={div_id} style={{display:"flex", border: "1px black solid", marginBottom: "10px"}}>
<div style={{flex:4, margin: "10px"}}>
<div style={{margin: "10px"}}>
<h4><b>{test[index]}</b></h4>
</div>
<div style={{margin: "30px"}}>
{subItems.map((sub, index2)=>{
return(
<ConfigParam
editValue={editValue}
key={index2}
name={groupResults[member][sub].name}
type={groupResults[member][sub].meta.type}
value={groupResults[member][sub].value}
description={groupResults[member][sub].meta.description}
meta={groupResults[member][sub].meta} />
)
})}
</div>
</div>
</div>
)
})
)
}
for context, the JSON looks like this:
{
"GPS Lever Arm X (ft)": {
"value": 1.78,
"meta": {
"group": "GPS Lever Arms",
"description": "GPS <--> IMU",
"type": "number",
"readOnly": false,
"min": -1.7976931348623157e+308,
"max": 1.7976931348623157e+308
}
},
"GPS Lever Arm Y (ft)": {
"value": 0,
"meta": {
"group": "GPS Lever Arms",
"description": "GPS <--> IMU",
"type": "number",
"readOnly": false,
"min": -1.7976931348623157e+308,
"max": 1.7976931348623157e+308
}
},
"GPS Lever Arm Z (ft)": {
"value": 0,
"meta": {
"group": "GPS Lever Arms",
"description": "GPS <--> IMU",
"type": "number",
"readOnly": false,
"min": -1.7976931348623157e+308,
"max": 1.7976931348623157e+308
}
}
}
In the event someone would like to see the menu code, here -
Side note: the "grouping" file is called from the ConfigFile. - I can provide that code if needed.
import React from 'react'
import { Tab, Row, Col, Nav} from 'react-bootstrap'
import FunctionPanel from './FunctionPanel'
import VariablePanel from './VariablePanel'
import ConfigFile from './ConfigFile'
export default function MainViewColumns({groups}) {
const member = groups[1];
const files = member.files;
const funcs = member.functions;
const vars = member.variables;
var defaultKey="controls"
var menuItems = [];
var viewItems = [];
if(files)
{
for(var x = 0; x < files.length; x++)
{
// Get the file name info
const fullFileName = files[x].split('/').pop();
const fileInfo = fullFileName.split(".");
const fileExtension = fullFileName.split(".").pop();
const filename = fileInfo[0];
// create the menu item based on filename
if(fullFileName === "atacnavMicroConfig.json")
{
defaultKey=filename;
var menuItem =
<Nav.Item key={filename}>
<Nav.Link eventKey={filename}>Full Configuration</Nav.Link>
</Nav.Item>
}
else
{
menuItem =
<Nav.Item key={filename}>
<Nav.Link eventKey={filename}>{filename}</Nav.Link>
</Nav.Item>
}
// create the view item
var viewItem =
<Tab.Pane key={filename} eventKey={filename} >
<ConfigFile file={files[x]}/>
</Tab.Pane>
// append menu item and view item to their respective lists.
menuItems.push(menuItem);
viewItems.push(viewItem);
}
}
return (
<Tab.Container defaultActiveKey={member.name}>
<Tab.Container defaultActiveKey={defaultKey}>
<Row>
<Col sm={2}>
<div id="navColumn">
<Nav variant="pills" className="flex-column">
<Nav.Item id="mainMenuItem">Main Menu</Nav.Item>
<Nav.Item key="controls">
<Nav.Link eventKey="controls">Controls</Nav.Link>
</Nav.Item>
{menuItems.map((item, index) => {
return(menuItems[index]);
})}
</Nav>
</div>
</Col>
<Col sm={9}>
<Tab.Content>
<Tab.Pane key="controls" eventKey="controls" >
<div style={{display: "flex", justifyContent:"space-between"}}>
<div style={{flex: 8, marginTop: "10px"}} className="d-flex flex-col justify-content-center">
<FunctionPanel functions={funcs}/>
<VariablePanel variables={vars}/>
</div>
</div>
</Tab.Pane>
{viewItems.map((item, index) => {
return(viewItems[index]);
})}
</Tab.Content>
</Col>
</Row>
</Tab.Container>
</Tab.Container>
)
}
Is useEffect the proper way to accomplish this ? if so, how would I go about implementing it?
Thanks in advance.

How to Render Nested Map Items inside react component

I have a react navigation component that is rendered based on some JSON data.
I have sucessfully rendered the top level items, but I'm having trouble rendering the second level sub pages.
This is how I think the code should look I also only want the tag to be output can someone help me with the syntax to achive this?
{headerData.TopLevelPages.map(toplevelPage => (
<li key={toplevelPage.Id}>{toplevelPage.NavLinkText}</li>
<ul>//Only out put UL If SubNavMenuItems as items
{toplevelPage.SubNavMenuItems.map(sublevelPage => (
<li key={sublevelPage.Id}>{sublevelPage.NavLinkText}</li>
))}
<ul>
))}
A simple recursive function can render a nested menu with any depth.
Try like below.
const headerData = { TopLevelPages: [ { NavLinkText: "Text 1", Id: "1", SubNavMenuItems: [ { NavLinkText: "sub Text 1-1", Id: "1-1" }, { NavLinkText: "sub Text 1-2", Id: "1-2", SubNavMenuItems: [ { NavLinkText: "sub-sub Text 1-2-1", Id: "1-2-1" } ] } ] }, { NavLinkText: "Text 2", Id: "2" } ] };
function App() {
const renderNavMenu = (menus) => {
return menus.map(({ NavLinkText, Id, SubNavMenuItems }) => (
<ul>
{/* render current menu item */}
<li key={Id}>{NavLinkText}</li>
{/* render the sub menu items */}
{SubNavMenuItems && <ul>{renderNavMenu(SubNavMenuItems)}</ul>}
</ul>
));
};
return renderNavMenu(headerData.TopLevelPages);
}
ReactDOM.render(<App/>, document.getElementById("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"></div>
{headerData.TopLevelPages.map(toplevelPage => (
<li key={toplevelPage.Id}>{toplevelPage.NavLinkText}</li>
{ toplevelPage.SubNavMenuItems &&
( <ul>//Only out put UL If SubNavMenuItems as items
{toplevelPage.SubNavMenuItems.map(sublevelPage => (
<li key={sublevelPage.Id}>{sublevelPage.NavLinkText}</li>
))}
<ul>)
}
))}
do a conditional rendering once more

React is duplicating my object value giving me a Warning: Each child in a list should have a unique "key" prop

Im trying to make a navigation bar for a website and it's giving me the "Warning: Each child in a list should have a unique "key" prop." inside my props.dropList.map
I have two files:
NavigationItems.js -> where I render my navigation bar
const NavigationItems = () => {
const projectDropdown = [
{ id: 0, value: "architecture" },
{ id: 1, value: "land" },
{ id: 2, value: "design" },
{ id: 3, value: "list" },
];
const officeDropdown = [
{ id: 4, value: "contact" },
{ id: 5, value: "team" },
];
return (
<div>
<ul className={styles.NavigationItems}>
<NavigationItem
link={`/projects`}
name="projects"
dropList={projectDropdown}
/>
<NavigationItem link={`/news`} name="news" exact />
<NavigationItem
link={`/office`}
name="office"
dropList={officeDropdown}
/>
</ul>
</div>
);
};
export default NavigationItems;
NavigationItem.js -> where I use the map function
const NavigationItem = (props) => {
let i = 0;
return (
<li className={styles.NavigationItem}>
<NavLink to={props.link} activeClassName={styles.active}>
{props.name}
</NavLink>
{props.dropList && (
<div className={styles.DropdownItems}>
<ul className={styles.DropdownItem}>
{props.dropList.map((drop) => {
console.log("i " + i);
console.log("id " + drop.id);
console.log("value " + drop.value);
i++;
return (
<li key={drop.id}>
<NavLink
exact
to={`${props.link}/${drop.value}`}
activeClassName={styles.active}
>
{drop.value}
</NavLink>
</li>
);
})}
</ul>
</div>
)}
</li>
);
};
export default NavigationItem;
So what happens is that the code loops twice duplicating the key values. It should be looping only once. I don't know why it loops twice, I'm only mapping my values once. For reference
this is what my console shows when I click my links
So your problem doesn't occure in either of the components you provided, but in your "Land" component. (Check the render method of Land)

React js Apollo Destructuring Result

This is my second simple query from the Apollo client. My first attempt queries a list of users and displays them, works just fine. I don't understand what the differences between my two query functions.
So I'm trying to make a super simple query from an Apollo client. The query is two fields the id and client name. The query is executed and results are returned, verified by console.log(data). So I believe the server is work properly and the query is working. I ran the Apollo codegen to download and create the ts files that contain my interfaces.
I also have copied the output from Apollo's Playground and created little plain java script destructure. I was able to destructure the result quiet easily, but I can't apply that code to the React project. Best I can seem to do is display "pt_Clients" nothing else. I have been stuck on this for embarrassing amount time.
clients.tsx
export const GET_CLIENTS = gql`
query pt_Clients {
pt_Clients {
id
clientname
}
}`;
interface ClientsProps extends RouteComponentProps { }
const PTClients: React.FC<ClientsProps> = () => {
const {
data,
loading,
error
} = useQuery<
pt_ClientsTypes.pt_Clients_pt_Client
>(GET_CLIENTS);
if (loading) return <Loading />;
if (error) return <p>ERROR</p>;
if (!data) return <p>Not found</p>;
return (
<Fragment>
<nav>
<ul>
<h3>Client List</h3>
{console.log(data)}
{
data &&
Object.keys(data).map((client: any) => (
<li>
<Link to={'/admin/pt/clients/:' + client.id} >{client.clientname}</Link>
</li>
))
}
</ul>
</nav>
<Switch>
<Route
path='/admin/pt/clients/:id'
render={({match}) => {
const { id } = match.params;
return <AdminClient clientid={id} />
}}
/>
</Switch>
</Fragment>
);
}
export default PTClients;
my codegen file types/pt_Clients.ts
/* tslint:disable */
/* eslint-disable */
// #generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: pt_Clients
// ====================================================
export interface pt_Clients_pt_Client {
__typename: "pt_Client";
id: number,
clientid: number;
status: number | null;
clientname: string | null;
}
export interface pt_Clients {
pt_Client: pt_Clients_pt_Client | null;
}
Playground Results
{
"data": {
"pt_Clients": [
{
"id": 1,
"clientname": "Client A",
"__typename": "pt_Client"
},
{
"id": 2,
"clientname": "Client B",
"__typename": "pt_Client"
},
{
"id": 3,
"clientname": "Client C",
"__typename": "pt_Client"
},
{
"id": 4,
"clientname": "Client D",
"__typename": "pt_Client"
}
]
},
"loading": false,
"networkStatus": 7,
"stale": false
}
Thanks for taking the time to read and any help that offered.
Okay. So first things first.
The clients array is in data.pt_Clients not in data itself. So to render the list of clients, you need to do data.pt_Clients.map((client : any))
Second, the to prop for Link should not contain the : character. It should be there, as you've correctly written inRoute component's path prop only.
Check the following code
<ul>
<h3>Client List</h3>
{console.log(data)}
{
data &&
data.pt_Clients.map((client: any) => (
<li>
<Link to={`/admin/pt/clients/${client.id}`}>
{client.clientname}
</Link>
</li>
))
}
</ul>
On a sidenote, the li, and Link elements should contain akey prop.

Mapping child categories to parent categories

I am returning data of categories and sub categories the data will come through looking like so:
[
{
"Cid": 1284805663,
"Name": "PARENT CATEGORY",
"ParentCid": 0,
"PicUrl": "",
"SortOrder": 1,
"Type": "manual_type"
},
{
"Cid": 1284805664,
"Name": "CHILD CATEGORY",
"ParentCid": 1284805663,
"PicUrl": "",
"SortOrder": 1,
"Type": "manual_type"
}
]
In the above example the only way I know that these two are connected is because the childs ParentCid matches the parents Cid.
What I am currently doing is mapping out all of the data but instead I would want to add a class to the sub categories and I am not exactly sure how to go about differentiating the two.
Here is my code below that is mapping all of the items.
import React from 'react';
import {Link} from 'react-router';
import {animateScroll as scroll} from 'react-scroll';
class SellerStoreCategories extends React.Component {
componentDidUpdate() {
// scroll.scrollTo(document.documentElement.scrollTop, {
scroll.scrollTo(400, {
duration: 0,
delay: 0,
smooth: false
})
}
render() {
return (
<div className="store__body__menu__accordian">
<ul className="list-unstyled">
{this.props.sellerCats.map((cat) => (
<li>
<Link
to={`/seller/${this.props.storeName}/${cat.Cid}`}
className={`black-link ${this.props.activeCategory == cat.Cid ? 'active' : ''}`}
>
{cat.name_en}
</Link>
</li>)}
</ul>
</div>
);
}
}
export default SellerStoreCategories;
Just to clarify further
I get hundreds of categories and the way to know which one is attached is by ParentCid so if a ParentCid matches a Cid and that Cid's ParentCid = 0 then that is the parent
So basically all of the categories that have "ParentCid": 0, are Parent categories
You need to build a tree from your array. There are several ways to achieve this in javascript. for instance (code not tested)
let childMap = { 0: [] }
let root = { Cid: 0, nodes: childMap[0] }
for (let i = 0; i < sellerCats.length; i++) {
let category = sellerCats[i]
childMap[category.Cid] = childMap[category.Cid] || []
childMap[category.ParentCid] = childMap[category.ParentCid] || []
category.nodes = childMap[category.Cid]
childMap[category.ParentCid].push(category)
}
After this code, the 'root' object should contain the tree representation of your data, then you can build your jsx recusivly with something like that:
buildCategory(cat) {
return (
<li>
<Link
to={`/seller/${this.props.storeName}/${cat.Cid}`}
className={`black-link ${this.props.activeCategory == cat.Cid ? 'active' : ''}`}
>
{cat.name_en}
</Link>
<ul>
{cat.nodes && cat.nodes.length ?
cat.nodes.map(this.buildCategory)
: '' }
</ul>
</li>
)
}
render() {
return (
<div className="store__body__menu__accordian">
<ul className="list-unstyled">
{root.nodes.map(this.buildCategory)}
</ul>
</div>
);
}

Categories