I can’t seem to find an answer to this that I can understand
I have tabs and a tab block, which is a clone of this basic tab block plugin. I’m trying to add the ability to select an "icon" for each tab block.
I’m close. Using the MediaUpload component, I’m able to see the file I’ve selected under the activeTab
object, but it doesn’t update the parent block attribute, so I can’t reference the icon_url
attribute.
tab/edit.js
const Edit = ({ attributes, setAttributes, clientId }) => {
const { uid, activeTab } = attributes;
useEffect(() => {
if (!uid) {
setAttributes({ uid: clientId });
}
}, []);
const display = activeTab === uid ? "block" : "none";
const ALLOWED_MEDIA_TYPES = ["image", "svg"];
const setTabIcon = (icon_url) => {
const parentBlock = select("core/block-editor").getBlock(clientId);
dispatch("core/block-editor").updateBlockAttributes(
parentBlock.clientId,
{
...attributes,
icon_url,
}
);
};
return (
<div {...useBlockProps()}>
<InspectorControls>
<div>
<MediaUpload
allowedTypes={ALLOWED_MEDIA_TYPES}
onSelect={(media) => setTabIcon(media.url)}
render={({ open }) => (
<button onClick={open}>Open Media Library</button>
)}
/>
</div>
</InspectorControls>
<div className={"guten-tab-panel"} style={{ display }}>
<InnerBlocks
allowedBlocks={["core/heading", "core/paragraph"]}
renderAppender={() => <InnerBlocks.ButtonBlockAppender />}
/>
</div>
</div>
);
};
export default Edit;
I would first think that using setAttributes here would also update the parent, but this only updates setActive
in the child block. It doesn’t keep the change.
In tabs.js, I’m trying to reference tab.icon_url. icon_url doesn’t exist, only uid
and title
tabs/tabs.js
const Edit = ({ attributes, setAttributes, clientId }) => {
const { tabs, activeTab } = attributes;
const blockProps = useBlockProps({
className: `${useBlockProps().className} guten-tab-wrapper`,
});
const setActiveTab = (uid) => {
setAttributes({ activeTab: uid });
const parentBlock = select("core/block-editor").getBlock(clientId);
parentBlock.innerBlocks.forEach((innerBlock) => {
dispatch("core/block-editor").updateBlockAttributes(
innerBlock.clientId,
{
activeTab: uid,
}
);
});
};
const addNewTab = () => {
const tab = createBlock("ahsan03/tab");
const position = tabs.length;
dispatch("core/block-editor").insertBlock(tab, position, clientId);
setAttributes({
tabs: [
...tabs,
{
uid: tab.clientId,
title: `Tab ${tabs.length + 1}`,
icon_url: "",
},
],
});
setActiveTab(tab.clientId);
};
const tabTitleChange = (newValue) => {
setAttributes({
tabs: [
...tabs.map((tab) => {
return tab.uid === activeTab
? {
...tab,
title: newValue,
}
: tab;
}),
],
});
};
useEffect(() => {
if (tabs.length && !activeTab) {
setActiveTab(tabs[0].uid);
}
}, [tabs]);
return (
<>
<div {...blockProps}>
<div className={"guten-tabs-nav"}>
{tabs.map((tab) => {
return (
<div
key={tab.uid}
className={"guten-tab-item"}
role="tab"
tabIndex="0"
onClick={() => setActiveTab(tab.uid)}
>
<div
className={`guten-tab-link${
tab.uid === activeTab
? " is-active"
: ""
}`}
>
<img src={tab.icon_url} alt="" />
{console.log("tabs tab", {
tab,
})}
<RichText
tagName="div"
value={tab.title}
onChange={tabTitleChange}
/>
</div>
</div>
);
})}
<Button
variant={"primary"}
icon={"plus"}
onClick={addNewTab}
>
{__("", "gtt")}
</Button>
</div>
<div className={"guten-tab-content"}>
<InnerBlocks
allowedBlocks={["ahsan03/tab"]}
renderAppender={false}
/>
</div>
</div>
</>
);
};
export default Edit;
How can I fix this so uploading an image is in the parent block attributes?
Here’s an updated setTabIcon function that I think is closer to what I need, I’m just not sure what to do after fetching the parentBlock.
const setTabIcon = (icon_url) => {
const parentBlockIds =
select("core/block-editor").getBlockParents(clientId);
parentBlockIds.forEach((parentBlockId) => {
const parentBlock = select("core/block-editor").getBlock(parentBlockId);
console.log({ parentBlock });
});
};
2
Answers
I was able to fix this by moving setTabIcon and the MediaFile component to the parent block.
Would love suggestions on how to improve this code.
Make a state variable in the parent component, and then pass that state and setState to the child as props, and then from the child component, you can update the state in the parent component.