I am trying to change the route based on the dropdown option in the breadcrumb.Any suggestions would be appreciated.
This is the component that is getting the first option but I am not sure how to gran the other options after the dropdown is generated.
const MenuItemViews = ({ params: { category, subCategory, item }, children }) => {
const menuItem = sideNavData.lookupItem(category, subCategory, item);
console.log(menuItem);
console.info(children);
return (
<div>
{
menuItem.name === 'Bill'
? <div>
<h2>Labels</h2>
{
!children
? <Link to={`/${category}/${subCategory}/${item}/${menuItem.childItems[0].name}`} >
<Image src={menuItem.content} />
</Link>
: children
}
</div>
: <ContentContainer>
<h1>{menuItem.name}</h1>
<Image src={menuItem.content} alt='item' />
</ContentContainer>
}
</div>
);
};
this is the component that is displaying the breadcrumbs.
const labelDropdownOptions = [
{ key: 'OptionOne', value: 'OptionOne', text: 'OptionOne' },
{ key: 'OptionTwo', value: 'OptionTwo', text: 'OptionTwo' },
{ key: 'OptionThree', value: 'OptionThree', text: 'OptionThree' },
];
class TopBar extends Component {
resolver = (key) => {
if (key === 'Home') {
return key;
}
return this.props.params[key];
}
dropdownLink = (link, key, text, index, routes) => {
console.log(routes);
if (text === 'OptionOne') {
return (
<Dropdown defaultValue={'OptionOne'} key={key} options={labelDropdownOptions} />
);
}
return <Link key={key} to={link}>{text}</Link>;
}
render() {
const { routes, params } = this.props;
return (
<TopBarHeader>
<IndexLink to='/'>
<HomeIcon><Icon name='home' /></HomeIcon>
</IndexLink>
<BreadcrumbWrapper>
<Breadcrumbs
createLink={this.dropdownLink}
params={params}
resolver={this.resolver}
routes={routes}
/>
</BreadcrumbWrapper>
</TopBarHeader>
);
}
}
I was able to do this by passing this.props.router.push into the onClick prop and specifying the value.
class TopBar extends Component {
resolver = (key) => {
if (key === 'Home') {
return key;
}
return this.props.params[key];
}
dropdownLink = (link, key, text, index, routes) => {
const category = sideNavData.lookupCategory(this.props.category);
if (link === '/TabTwo/Names/Bill/OptionOne' || link === '/TabTwo/Names/Bill/OptionTwo' || link === '/TabTwo/Names/Bill/OptionThree') {
return (
<span key={index}>
{
Object.keys(category).map((subCategory, i) => {
return (
<span key={i}>
{
Object.keys(category[subCategory]).map((item, itemIndex) => (
<span key={itemIndex}>
{
category[subCategory][item].name === 'Bill'
? <Dropdown
defaultValue={'OptionOne'}
options={category[subCategory][item].childItems}
onChange={(event, data) => { this.props.router.push(`/${this.props.category}/${subCategory}/${category[subCategory][item].name}/${data.value}`); }}
/>
: null
}
</span>
))
}
</span>
);
})
}
</span>
);
}
return <Link key={key} to={link}>{text}</Link>;
}
render() {
const { routes, params } = this.props;
return (
<TopBarHeader>
<IndexLink to='/'>
<HomeIcon><Icon name='home' /></HomeIcon>
</IndexLink>
<BreadcrumbWrapper>
<Breadcrumbs
createLink={this.dropdownLink}
params={params}
resolver={this.resolver}
routes={routes}
/>
</BreadcrumbWrapper>
</TopBarHeader>
);
}
}
Related
`
<Manager><div role="presentation"
id="popover"
className={popoverClasses}
onClick={onDismiss}
onKeyUp={onDismiss}
>{backdrop && <div className={styles.popover__backdrop} />}
<div ref={popoverRef}> --how to implement
<Reference>
{({ ref }) => {
// how can we implement ref
ref = ref; -- how to implement
const targetProps = {
ref: setReferenceRef,
// Accessibility
//event handler
onKeyUp: (keyUpEvent) => {
if (
['Enter', 'Space Bar', 'Spacebar', ' '].includes(
// key of event handler
keyUpEvent.key,
)
) {
if (!open) {
onEvent();
} else {
onReferenceClickClose();
}
}
},
[on${event[0].toUpperCase()}${event.substr(1)}]: !open
? onEvent()
: onReferenceClickClose(),
};
return (
<span
role="button"
className={styles.popover__reference}
tabIndex={tabIndex}
aria-haspopup="dialog"
{...targetProps}
>
{reference}
{refIcon}
);
}}
<Popper
placement={position}
modifiers={{
...modifiers,
preventOverflow: {
boundariesElement: boundaries,
},
}}
>
{({ ref, placement, arrowProps }) => (
{title && {title}}
{children(closePopover)}
)}
`
I am working where I want to automatically collapse the menu when other one opens.Now I already tried making separate useState and passing values but it doesn't work.I am sharing the before and after code with you.
In Dashboard.jsx I am maping through the menus and passing it to MenuCards.jsx as props, now if A menu is clicked hence is Expandable, it is again passed to getExpandableMenu and the menu is exapanded.
The end goal I want is one menu to open and if the other menu is clicked the first one to close first.
Before code -
// Dashboard.tsx
setMenuList([
{
title: "Thermal Comfort",
icon: thermal,
decorator: new ManekinDecorator(IModelApp.viewManager.selectedView!),
tooltip: "Thermal Comfort",
},
{
title: "Surface Plots",
icon: surfacePlot,
decorator: null,
tooltip: "Surface Plots",
},
{
title: "Contour Plots",
icon: contour,
decorator: null,
tooltip: "Contour Plots",
},
{
title: "Comfort Cloud",
icon: comfortCloud,
decorator: new ComfortDecorator(),
tooltip: "Comfort Cloud",
},
{
title: "Flowlines",
icon: flowline,
//decorator: getFlowLineDecorator(),
decorator: null,
tooltip: "FlowLines",
},
]);
} else {
setMenuList([]);
setIsDropDownVisibal(false);
}
}, [viewPort]);
<div className="menu">
<div style={{ overflowY: "scroll", width: "inherit" }}>
{menuList.map((menu) => (
<MenuCard
menu={menu.title}
icon={menu.icon}
decorator={menu.decorator}
tooltip={menu.tooltip}
/>
))}
</div>
</div>
// MenuCard.tsx
const MenuCard = (props: any) => {
const [toggle, setToggle] = React.useState(true);
const [expandOption, setExpandOption] = React.useState(false);
const onClick = () => {
if (props.decorator !== null) {
if (toggle) {
IModelApp.viewManager.decorators.forEach((decorator) => {
IModelApp.viewManager.dropDecorator(decorator);
});
IModelApp.viewManager.addDecorator(props.decorator);
} else IModelApp.viewManager.dropDecorator(props.decorator);
}
setToggle(!toggle);
setExpandOption(!expandOption);
};
const { menu, icon, tooltip } = props;
return (
<div>
<div className="mainMenu" onClick={onClick}>
<div className="icon">
<img src={icon} alt="icon" className="menuIcon" />
</div>
<div className="menuTitle">
<p className="title">{menu}</p>
</div>
<InfoTooltip tooltipText={tooltip} />
</div>
{expandOption && (
<GetExpandedMenu menuName={menu} decorator={props.decorator} />
)}
</div>
);
};
export default MenuCard;
// GetExpandedMenu.tsx
export default function GetExpandedMenu(props:any){
const {menuName} = props
const surfacePlotManager= new SurfacePlotManager()
const contourPlotManager=new ContourPlotManager();
switch(menuName) {
case 'Surface Plots':
return <SurfacePlot plotManager={surfacePlotManager}/>
case 'Thermal Comfort':
return <ThermalComfortMenu decorator={props.decorator}/>
case 'Flowlines':
return <FlowLines />
case "Contour Plots":
return <ContourPlot plotManager={contourPlotManager}/>;
case 'Comfort Cloud':
return <ComfortCloud/>
default:
return <p>expanded options</p>;
}
}
Here is what I tried doing which failed.
// MenuCard.tsx
// tried making separate useState for every menu
/* eslint-disable eqeqeq */
import React from "react";
import "./MenuCard.scss";
const MenuCard = (props: any) => {
const [toggle, setToggle] = React.useState(true);
const [expandOption, setExpandOption] = React.useState(false);
const [ThermalExpandOption, setThermalExpandOption] = React.useState(false);
const [SurfaceExpandOption, setSurfaceExpandOption] = React.useState(false);
const [ContourExpandOption, setContourExpandOption] = React.useState(false);
const [CloudExpandOption, setCloudExpandOption] = React.useState(false);
const [FlowlinesExpandOption, setFlowlinesExpandOption] = React.useState(false);
const onClick = () => {
console.log("Menu clicked is", props.id);
if (props.id == 0) {
setThermalExpandOption(!ThermalExpandOption);
setSurfaceExpandOption(false);
setContourExpandOption(false);
setCloudExpandOption(false);
setFlowlinesExpandOption(false);
} else if (props.id == 1) {
setThermalExpandOption(false);
setSurfaceExpandOption(!SurfaceExpandOption);
setContourExpandOption(false);
setCloudExpandOption(false);
setFlowlinesExpandOption(false);
} else if (props.id == 2) {
setThermalExpandOption(false);
setSurfaceExpandOption(false);
setContourExpandOption(!CloudExpandOption);
setCloudExpandOption(false);
setFlowlinesExpandOption(false);
} else if (props.id == 3) {
setThermalExpandOption(false);
setSurfaceExpandOption(false);
setContourExpandOption(false);
setCloudExpandOption(!CloudExpandOption);
setFlowlinesExpandOption(false);
} if (props.id == 4) {
setThermalExpandOption(false);
setSurfaceExpandOption(false);
setContourExpandOption(false);
setCloudExpandOption(false);
setFlowlinesExpandOption(!FlowlinesExpandOption);
}
if (props.decorator !== null) {
if (toggle) {
IModelApp.viewManager.decorators.forEach((decorator) => {
IModelApp.viewManager.dropDecorator(decorator);
});
IModelApp.viewManager.addDecorator(props.decorator);
} else IModelApp.viewManager.dropDecorator(props.decorator);
}
setToggle(!toggle);
setExpandOption(!expandOption);
};
const { menu, icon, tooltip } = props;
return (
<div>
<div className="mainMenu" onClick={onClick}>
<div className="icon">
<img src={icon} alt="icon" className="menuIcon" />
</div>
<div className="menuTitle">
<p className="title">{menu}</p>
</div>
<InfoTooltip tooltipText={tooltip} />
</div>
{ ThermalExpandOption && (
<GetExpandedMenu menuName={menu} decorator={props.decorator} />
)}
{ SurfaceExpandOption && (
<GetExpandedMenu menuName={menu} decorator={props.decorator} />
)}
{ ContourExpandOption && (
<GetExpandedMenu menuName={menu} decorator={props.decorator} />
)}
{ CloudExpandOption && (
<GetExpandedMenu menuName={menu} decorator={props.decorator} />
)}
{ FlowlinesExpandOption && (
<GetExpandedMenu menuName={menu} decorator={props.decorator} />
)}
</div>
);
};
export default MenuCard;
Lift state up.
If state of your item depends on state of menu item next to it, you can refactor both to depend on the state from "menu" container. Something like "openedItem" state.
You can find detailed instructions in React docs here:
https://reactjs.org/docs/lifting-state-up.html ,
with examples.
i trying to make a history.push on button click
i have this search bar that will show the names of doctors when serched {suggestion.firstname}
i am trying to pass {suggestion.id } as url when cliked on the li corresponding
when clicked on li no call going to finddoctor
function finddoctor(e) {
console.log(e);
history.push(`/detiled/${e} `);
}
const onChange = (event) => {
const value = event.target.value;
setInputValue(value);
setShowResults(false);
const filteredSuggestions = suggestions.filter(
(suggestion) =>
suggestion.firstname
.toString()
.toLowerCase()
.includes(value.toLowerCase()) ||
suggestion.id.toString().toLowerCase().includes(value.toLowerCase())
);
setFilteredSuggestions(filteredSuggestions);
setDisplaySuggestions(true);
};
const onSelectSuggestion = (index) => {
setSelectedSuggestion(index);
setInputValue(filteredSuggestions[index]);
setFilteredSuggestions([]);
setDisplaySuggestions(false);
};
const SuggestionsList = (props) => {
const {
suggestions,
inputValue,
onSelectSuggestion,
displaySuggestions,
selectedSuggestion,
} = props;
if (inputValue && displaySuggestions) {
if (suggestions.length > 0) {
return (
<ul className="suggestions-list" style={styles.ulstyle}>
{suggestions.map((suggestion, index) => {
const isSelected = selectedSuggestion === index;
const classname = `suggestion ${isSelected ? "selected" : ""}`;
return (
<li
style={styles.listyle}
onClick={()=> finddoctor(suggestion.id)}
key={index}
className={classname}
>
{suggestion.firstname}
</li>
);
})}
</ul>
);
} else {
return <div>No suggestions available...</div>;
}
}
return <></>;
};
useEffect(() => {
axios
.get("admin-panel/all-doctors-list/")
.then((res) => {
const data = res.data;
setShowSerch(data);
});
}, []);
return (
<>
<div className="note-container" style={styles.card}>
<div style={styles.inner}>
<p style={{ textAlign: "left" }}>Search Doctors</p>
<form className="search-form" style={{}}>
{showResults ? (
<FontAwesomeIcon
style={{ marginRight: "-23px" }}
icon={faSearch}
/>
) : null}
<input
onChange={onChange}
value={inputValue}
style={styles.input}
type="Search"
/>
<SuggestionsList
inputValue={inputValue}
selectedSuggestion={selectedSuggestion}
onSelectSuggestion={onSelectSuggestion}
displaySuggestions={displaySuggestions}
suggestions={filteredSuggestions}
/>
</form>
</div>
</div>
</>
);
};
Are you using "react-router-dom" in your project?
In this case, you should use the history object in a specific way. For example, you can get it with the useHistory hook.
import { useHistory } from "react-router-dom";
const SuggestionsList = ({
suggestions,
inputValue,
displaySuggestions,
selectedSuggestion,
}) => {
let history = useHistory();
const finddoctor = (e) => {
console.log(e);
history.push(`/detiled/${e} `)
};
if (inputValue && displaySuggestions) {
if (suggestions.length > 0) {
return (
<ul className="suggestions-list">
{suggestions.map((suggestion, index) => {
return (
<li
onClick={() => finddoctor(suggestion.id)}
key={index}
>
{suggestion.firstname}
</li>
)
})}
</ul>
)
} else {
return <div>No suggestions available...</div>
}
}
return <></>
};
Can Anyone hazard a guess as to why the following optimisticResponse/update code (question continues after code):
AddToCart.js
const ADD_TO_CART_MUTATION = gql`
mutation addToCart($id: ID!) {
addToCart(id: $id) {
id
quantity
}
}
`;
class AddToCart extends React.Component {
outOfStock(){
alert('This item is out of stock.');
};
update = (cache, { data: { addToCart } }) => {
const data = cache.readQuery({ query: CURRENT_USER_QUERY });
data.me.cart.push(addToCart);
cache.writeQuery({ query: CURRENT_USER_QUERY, data });
};
render() {
const { id, quantity, title, image, price } = this.props;
return (
<Mutation
mutation={ADD_TO_CART_MUTATION}
variables={{
id,
}}
optimisticResponse={{
__typename: 'Mutation',
addToCart: {
__typename: 'CartItem',
id: '-1',
quantity: 1,
item: {
__typename: 'Item',
id,
price,
image,
title,
description: 'test',
mainDescription: 'test',
quantity,
}
}
}}
update={this.update}
refetchQueries={[{ query: CURRENT_USER_QUERY }, { query: ALL_ITEMS_QUERY }]}
>
{(addToCart, { loading }) => {
if (quantity >= 1) {
return (<button disabled={loading} onClick={() => {
addToCart().catch(err => alert(err.message));
}}>
Add{loading && 'ing'} To Cart 🛒
</button>)
} else {
return (<button>
Out of Stock 🛒
</button>)
}
}}
</Mutation>
);
}
}
export default AddToCart;
AddToCart parent component (Item.js)
export default class Item extends Component {
static propTypes = {
item: PropTypes.object.isRequired,
};
render() {
const { item } = this.props;
return (
<User>
{({ data: { me } }) => {
let hasPerms;
hasPerms = (me && me === null) ? false : (me && me.permissions.some(permission => ['ADMIN'].includes(permission)));
return (
<ItemStyles>
{item.image && <img src={item.image} alt={item.title} />}
<Title>
<Link
href={{
pathname: '/item',
query: { id: item.id },
}}
>
<a>{item.title}</a>
</Link>
</Title>
<PriceTag>{formatMoney(item.price)}</PriceTag>
<p>{item.description} { (item.quantity <= 10 && item.quantity !== 0) && `- (${item.quantity} in stock)` }</p>
<div className="buttonList">
<AddToCart id={item.id} quantity={item.quantity} image={item.image} title={item.title} price={item.price} />
{hasPerms && (
<>
<Link
href={{
pathname: 'update',
query: { id: item.id },
}}
>
<a>Edit ✏️</a>
</Link>
<DeleteItem id={item.id}>Delete This Item</DeleteItem>
</>
)}
</div>
<div className="buttonList">
</div>
</ItemStyles>
);
}}
</User>
);
}
}
Item parent (Items.js)
class Items extends Component {
render() {
return (
<Center>
<Pagination page={this.props.page} />
<Query
query={ALL_ITEMS_QUERY}
variables={{
skip: this.props.page * perPage - perPage,
}}
>
{({ data, error, loading }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ItemsList>{data.items.map((item, i) => <Item item={item} key={item.id}></Item>)}</ItemsList>
);
}}
</Query>
<Pagination page={this.props.page} />
</Center>
);
}
}
export default Items;
causes my UI to rerender (component unmounts) after adding an item to an empty cart (See instructions to replicate)?
Go to https://flamingo-next-production.herokuapp.com.
login using, Username: testing123#123.com, Password:testing123.
click 'My cart' link and delete any items in there. Leave cart window open.
click Shop link and add an item. Observe the open cart window/UI after you have clicked add (You may have to wait a few moments before observing change).
Why does this happen and what's the best way to rectify it??
I am trying to build a lazy loading tabs component that fetches the code/bundle for the tab only when it is clicked. I am trying to use lazy+suspense for it. I would like to animate/color the tab the user clicked on when it is loading to indicate that a tab is being loaded. How can I do that best?
Here is some example code I have whipped up. The bug here is that the tab-header can sometimes get rendered twice when the code is being loaded. How can I avoid the issue and display a loading state on the new tab.
import React, {lazy, Suspense, useState, useReducer} from "react";
import "./App.css";
import classNames from "classnames";
import Spinner from "./Spinner";
const Tokyo = lazy(() => {
return import("./Tokyo");
});
const Mexico = lazy(() => {
return import("./Mexico");
});
const London = lazy(() => {
return import("./London");
});
const App = () => {
const [_, setTab] = useState("Tokyo");
return (
<div className="App">
<header className="App-header">
<Tabs initialTab="Tokyo" onTabChange={setTab}>
<Tab id="Tokyo" name="Tokyo">
<Tokyo />
</Tab>
<Tab id="Mexico" name="Mexico">
<Mexico />
</Tab>
<Tab id="London" name="London">
<London />
</Tab>
</Tabs>
</header>
</div>
);
};
const Tab = () => {
return null;
};
function genClickLog(log, current) {
const set = new Set([current]);
const newLog = [current];
log.forEach(l => {
if (!set.has(l)) {
log.push(l);
newLog.push(l);
}
});
return newLog;
}
function createSuspenseTree(targetTab, log, child, tabs, handleTabChange) {
const head = log.shift();
if (head !== targetTab) {
console.warn(`expect ${head} to be ${targetTab}`);
}
let current = child;
log.forEach(l => {
current = (
<Suspense
fallback={
<Fallback
tabs={tabs}
prevTab={l}
activeTab={targetTab}
onTabChange={handleTabChange}
/>
}
>
{current}
</Suspense>
);
});
return <Suspense fallback={<Spinner />}>{current}</Suspense>;
}
function reducer(state, action) {
switch (action.type) {
case "change":
if (state.current === action.id) {
return state;
}
return {
current: action.id,
prev: state.current,
clickLog: genClickLog(state.clickLog, action.id),
};
case "initial":
return {
current: action.id,
prev: null,
clickLog: [action.id],
};
default:
throw new Error("bad reducer action");
}
}
const Tabs = props => {
const {children, onTabChange, initialTab} = props;
const [state, dispatch] = useReducer(
reducer,
{
clickLog: [],
prev: null,
current: null,
},
{type: "initial", id: initialTab}
);
const handleTabChange = tab => {
dispatch({type: "change", id: tab});
onTabChange(tab);
};
const tabs = React.Children.map(children, x => ({
id: x.props.id,
name: x.props.name,
render: x.props.children,
}));
const child = (
<>
<TabHeader
tabs={tabs}
activeTab={state.current}
onTabChange={handleTabChange}
/>
{tabs.map(x => (
<div key={x.id}>
<TabFrag
id={x.id}
key={x.id}
activeTab={state.current}
render={x.render}
/>
</div>
))}
</>
);
return (
<div className="TabContainer">
{createSuspenseTree(
state.current,
[...state.clickLog],
child,
tabs,
handleTabChange
)}
</div>
);
};
const Fallback = props => {
const {prevTab, activeTab, onTabChange, tabs} = props;
if (prevTab && prevTab !== activeTab) {
return (
<>
<TabHeader
tabs={tabs}
activeTab={prevTab}
loadingTab={activeTab}
onTabChange={onTabChange}
/>
{tabs.map(x => (
<div key={x.id}>
<TabFrag
id={x.id}
key={x.id}
activeTab={prevTab}
render={x.render}
/>
</div>
))}
</>
);
}
return <Spinner />;
};
const TabFrag = props => {
if (props.id === props.activeTab) {
return props.render;
}
return null;
};
const TabHeader = props => {
const {tabs, activeTab, loadingTab, onTabChange} = props;
return (
<div className="TabHeader">
{tabs.map(x => (
<TabItem
id={x.id}
key={x.id}
name={x.name}
active={x.id === activeTab}
loading={x.id === loadingTab}
onTabChange={onTabChange}
/>
))}
</div>
);
};
const TabItem = props => {
const {id, name, loading, active, onTabChange} = props;
const handleTabChange = () => {
onTabChange(id);
};
return (
<div
className={classNames("TabItem", {
ActiveTab: active,
LoadingTab: loading,
})}
onClick={handleTabChange}
>
{name}
</div>
);
};