Pass props from 'Tab' to 'Pane' (Semantic UI with React JS) - reactjs

I am using Semantic UI 'Tab' module with React and passing some props from 'Tab' to each 'Pane'.
How to access the props in each 'Pane'?
const VideoGallery = () => {
const [video, setVideo] = useState([]);
return (
<div>
<Tab
panes={panes}
video={video} //passing 'video' as a prop
/>
</div>
)
}
const panes = [
{
menuItem: { key: 'allVideos'},
render: () =>
<Tab.Pane>
// Need to access 'video' prop here
</Tab.Pane>,
},
{
menuItem: { key: 'oneToOne'},
render: () =>
<Tab.Pane>
// Need to access 'video' prop here
</Tab.Pane>,
},
]

If you want use video in panes. you can update panes to a function:
const panes = (video) => {
return [
{
menuItem: { key: "allVideos" },
render: () => <Tab.Pane >// Using video in here</Tab.Pane>,
},
{
menuItem: { key: "oneToOne" },
render: () => <Tab.Pane>// Using video in here</Tab.Pane>,
},
];
};
<Tab
panes={panes(video)}
/>

Related

How to get the name of the speedDial rendered element in MuiV4?

I have the following code where I want to get the name of the clicked element but for some reason it is not compatible with any type of action in typescript ?
const _setModal = () => {
const element = e.currentTarget;
const name = element.name;
// the the modal acording to the name at hand
};
const actions = [
{ icon: <HelpIcon name='tutorial' />, name: 'Tutorial', onClick: _setModal },
{ icon: <AddCircleIcon />, name: 'Add Day', onClick: _setModal },
];
return (
<SpeedDial
>
{actions.map((el) => {
return <SpeedDialAction id={el.name} key={el.name} icon={el.icon} onClick={el.onClick} />;
})}
</SpeedDial>
);

How to render loader on button clicked?

I pass an array of actions in child component. Then in child component I render three buttons. Each button get its own handler. Also I pass loader to child component.
I expect to get the following: on "Button 1" click the first button should become "Button 1 click" and no changes to other buttons.
What I actually get: on "Button 1" click. All buttons get "click" text.
How can I fix that? Codesandbox https://codesandbox.io/s/wispy-river-m2zgb?file=/src/App.tsx
interface IButtonBlockProps {
actions: {
tool: string;
onClick: () => void;
}[];
loader: any;
}
enum Loader {
Button1 = "button 1"
}
const ButtonBlock: React.FC<IButtonBlockProps> = ({ actions, loader }) => {
return (
<div>
{actions.map((item, idx) => (
<button key={idx} onClick={item.onClick}>
{item.tool}
{loader === Loader.Button1 && "Clicked"}
</button>
))}
</div>
);
};
const App: React.FC = () => {
const [loader, setLoader] = useState<Loader | null>(null);
const handleClick = () => {
console.log("on Button 1 click");
setLoader(Loader.Button1);
};
const actions = [
{
tool: "Button 1",
onClick: () => handleClick()
},
{
tool: "Button 2",
onClick: () => console.log("Button 2")
},
{
tool: "Button 3",
onClick: () => console.log("Button 3")
}
];
return (
<div className="App">
<ButtonBlock actions={actions} loader={loader} />
</div>
);
};
You can add one more check to your button if its Button1 or not something like below:
<button key={idx} onClick={item.onClick}>
{item.tool}
{loader === Loader.Button1 && item.tool === "Button 1" && "Clicked"}
</button>
What you could do instead is have an array of loading indexes, and whenever mapping the actions, if the loading array contains the index of the action, you can show clicked or loading or whatever.
This is assuming there would be something that after the button is no longer loading (like in the case of an API call completing) you would set the button to not loading by removing that index from the array.
Here is a modified version of your code sandbox that achieves this:
interface IButtonBlockProps {
actions: {
index: number;
tool: string;
onClick: () => void;
}[];
loader: any;
}
const ButtonBlock: React.FC<IButtonBlockProps> = ({ actions, loader }) => {
return (
<div>
{actions.map((item, idx) => (
<button key={idx} onClick={item.onClick}>
{item.tool}
{loader.includes(item.index) && `Clicked`}
</button>
))}
</div>
);
};
const App: React.FC = () => {
const [loadingButtons, setLoadingButtons] = useState<number[]>([]);
console.log(loadingButtons);
const handleClickButton1 = useCallback(() => {
console.log("on Button 3 click");
setLoadingButtons([...loadingButtons, 0]);
setTimeout(() => {
setLoadingButtons([...loadingButtons.filter((x) => x !== 0)]);
}, 5000);
}, [loadingButtons]);
const handleClickButton2 = useCallback(() => {
console.log("on Button 2 click");
setLoadingButtons([...loadingButtons, 1]);
setTimeout(() => {
setLoadingButtons([...loadingButtons.filter((x) => x !== 1)]);
}, 5000);
}, [loadingButtons]);
const handleClickButton3 = useCallback(() => {
console.log("on Button 3 click");
setLoadingButtons([...loadingButtons, 2]);
setTimeout(() => {
setLoadingButtons([...loadingButtons.filter((x) => x !== 2)]);
}, 5000);
}, [loadingButtons]);
const actions = [
{
index: 0,
tool: "Button 1",
onClick: () => handleClickButton1()
},
{
index: 1,
tool: "Button 2",
onClick: () => handleClickButton2()
},
{
index: 2,
tool: "Button 3",
onClick: () => handleClickButton3()
}
];
return (
<div className="App">
<ButtonBlock actions={actions} loader={loadingButtons} />
</div>
);
};

Looking for a clever way to render multiple children component dynamically

I have a Modal component composed of a <Header> and a <Content>.
I want both children <Header> and <Content> to be rendered dynamically based on a given value ('modal type').
My first intuition was to create an object storing modal components like:
export const useModalDetails = (onClose = () => {}) => {
const [ modalDetails, setModalDetails ] = useMergeState({
Header: () => <div/>,
Content: () => <div/>,
onClose,
});
return [modalDetails, setModalDetails];
};
Then a function to set modal details base on the value type:
const onChangeModalType = (type) => {
if (type === 'type1') {
setModalDetails({
Header: () => <div>Modal Header Type 1</dov>,
Content: () => <div>Modal Content Type 1</div>,
open: true
})
} else if (type === 'type2') {
setModalDetails({
Header: () => <div>Modal Header Type 2</dov>,
Content: () => <div>Modal Content Type 2</div>,
open: true
})
}
}
Then I can use the react-hook in the <Modal> component:
import SemanticModal from 'semantic-ui-react'
const Modal = () => {
const [ modalDetails, setModalDetails ] = useMergeState({
Header: () => <></>,
Content: () => <></>,
open: false,
});
return [modalDetails, setModalDetails];
onChangeModalType('type2')
return (
<>
<SemanticModal
Header={modalDetails.Header}
Content={modalDetails.Content}
open={modalDetails.open}
/>
<button onClick={() => onChangeModalType('type1')}>load type 1</button>
<button onClick={() => onChangeModalType('type2')}>load type 2</button>
</>
)
}
Bear in mind I have 12 different types of content, and header for a <Modal>, that means 12 if, else statements for each.
Do you have any idea how I could develop that in a smarter, clever, more readable, maintainable... WAY?
If you're decided on having an individual modal component that switches behaviour based on state/props you could approach it like this:
const modals = {
type1: {
Header: <div>Modal1 Header</div>,
Content: <div>Modal1 Content</div>,
open: true,
},
type2: {
Header: <div>Modal2 Header</div>,
Content: <div>Modal2 Content</div>,
open: true,
},
};
const Modal = ({type}) => {
const [modalType, setModalType] = useState(type);
const { Header, Content, open } = modals[modalType];
return (
<>
<SemanticModal
Header={Header}
Content={Content}
open={open}
/>
</>
);
};
An alternative to consider is creating a reusable BaseModal component which you extend into a seperate component for each use case:
const BaseModal = ({ Header, Content, open }) => {
return (
<>
<SemanticModal Header={Header} Content={Content} open={open} />
</>
);
};
const Modal1 = () => {
return (
<BaseModal
Header={<div>Modal1 Header</div>}
Content={<div>Modal1 Content</div>}
open={true}
/>
);
};
The nice thing about this pattern is that you still get to share the common elements of all modals whilst selectively injecting the parts that need to differ

MobX ReactJS AntD updates won't re-render

So I'm trying change some table data within my AntD Table using ReactJS and MobX. The data in my MobX observable changes, but the table doesn't re-render an update until I say.... resize the page and the table re-renders. I've recreated my issue on CodeSandbox - it's not the exact data type, but this is the EXACT issue I'm running into, any thoughts???
https://codesandbox.io/s/remove-items-from-mobx-array-forked-3nybr?file=/index.js
#action
change = (key) => {
this.data
.filter(item => key === item.key)
.forEach(piece => {
piece.key = 10;
});
console.log(this.data);
};
const FooTable = () => {
const columns = [
{
title: "ID",
dataIndex: "key"
},
{
title: "Name",
dataIndex: "name"
},
{
title: "Last Name",
dataIndex: "lastName"
},
{
title: "Actions",
render: (text, record) => {
return (
<Button
type="link"
icon="delete"
onClick={() => tableStore.change(record.key)}
>
Delete
</Button>
);
}
}
];
return useObserver(() => {
return <Table columns={columns} dataSource={tableStore.data} />;
});
};
Because AntD Table is not observer by itself you need to use toJS on your data before you pass it to AntD.
import { toJS } from "mobx";
// ...
const FooTable = () => {
const columns = [ ... ];
return useObserver(() => {
return <Table columns={columns} dataSource={toJS(tableStore.data)} />;
});
};

React.js: How to call hook first before first render?

I'm trying to create a dynamic menu using React
I have a JSON response which contains how my menu should look like:
[
{ id: 0,
label: "Dashboard",
link: "/app/dashboard",
icon: <HomeIcon /> },
{
id: 1,
label: "Inward",
link: "/app/inward",
icon: <InboxIcon />,
children: [
{ label: "PESONet", link: "/app/inward/pesonet" },
{ label: "PESONet Inquiry", link: "/app/inward/pesonetinquiry" },
{ label: "PDDTS", link: "/app/inward/pddts" },
{ label: "SWIFT", link: "/app/inward/swift" },
{ label: "Philpass", link: "/app/inward/philpass" },
],
}
]
I'm able to put this JSON response in the state with this:
Sidebar.js
const [sideBar, setSideBar] = useState([])
useEffect(() => {
const sidebar = customizeSidebar()
setSideBar(sidebar)
}, [])
The function customizeSidebar() can be found in my context:
UserContext.js
function customizeSidebar(dispatch, profileId, history){
ProfileMaintenanceService.retrieveSideMenu()
.then((response) => {
return response.data
}).catch((err) => {
// check first if api is down
})
}
As you can see, whenever I get a response, I return it as well.
Therefore, I can get it in the Sidebar.js.
However, problem arises when render happens first before the useEffect function.
const [sideBar, setSideBar] = useState([])
useEffect(() => {
const sidebar = customizeSidebar()
setSideBar(sidebar)
}, [])
return (
<List className={classes.sidebarList}>
{sideBar.map(link => (
<SidebarLink
key={link.id}
location={location}
isSidebarOpened={isSidebarOpened}
{...link}
/>
))}
</List>
)
Already tried using useLayoutEffect but render still happens first before my API call and assigning to state.
Is there any way I can do first my API call and assign to state before the first render?
Thank you for those who would help.
Either you return a placeholder is there is no sidebar data:
if (!sideBar.length) {
return (
<div className="sidebar-placeholder">
<Spinner/> || Loading text...
</div>
);
}
return (
<List>...
);
Or you wrap your Sidebar inside a provider component, and pass your sidebar configuration as a prop.
the best imo is to do something like this
return (
{sideBar.length&&<List className={classes.sidebarList}>
{sideBar.map(link => (
<SidebarLink
key={link.id}
location={location}
isSidebarOpened={isSidebarOpened}
{...link}
/>
))}
</List>}
)
So your sidebar will be rendered only when the sidebar data array is full.

Resources