
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
react-compound-components
Advanced tools
Create compound components with common managed state using React hooks.
Compound components is a pattern where components are used together such that they share an implicit state that lets them communicate with each other in the background. A compound component is composed of a subset of child components that all work in tandem to produce some functionality. - Alexi Taylor - dev.to
npm: npm i react-compound-components
Yarn: yarn add react-compound-components
Import the default exported function from library
import createCompoundComponent from "react-compound-components";
// OR (recommended)
import ccc from "react-compound-components";
cccccc function takes one react-hook function as parameter which is responsible for managing state of the compound component.
It returns a tuple of 3 values:
// Naming could/should be changed to suit the case.
const [Component, useCompoundState, register] = ccc(useManageStateHook);
useManageStateHookIn above example, useManageStateHook is a simple react-hook which can take some props and return state value and modifiers as an object.
const useManageStateHook = ({ initialValue }: { initialValue: number }) => {
const [value, setValue] = React.useState(initialValue);
const changeValue = (newValue: number) => setValue(value);
return { value, changeValue };
};
In addition to returning state values and modifiers, an optional Wrapper component can be returned as well. As name suggests, this Wrapper component wraps around the Compound Component. The Wrapper component can only receive children as prop and should return it somehow.
const useManageStateHook = ({ initialValue }: { initialValue: number }) => {
const [value, setValue] = React.useState(initialValue);
const changeValue = (newValue: number) => setValue(value);
const Wrapper: FC = ({ children }) => (
<div>
<h1>Compound component</h1>{" "}
<button onClick={() => changeValue(initialValue)}>Reset</button>
<hr />
{children}
</div>
);
return { Wrapper, value, changeValue };
};
ComponentThe first item in returned tuple is the actual Compound Component which is used to everywhere. This Component acts as a wrapper for all its sub-components. The sub-components can be accessed by using dot-notation of this Component. Eg.
<Component>
<Component.SubComponent1 />
<Component.SubComponent2 />
<Component.SubComponent3 />
</Component>
useCompoundStateThe resulting state can be accessed in any sub-component using the useCompoundState hook, which is returned in the tuple.
const SubComponent = () => {
const { value } = useCompoundState();
return <pre>{JSON.stringify(value, null, 2)}</pre>;
};
registerA callback provided to simplify attaching Sub-components to parent/main component. It takes 2 parameters
useCompoundState to access state properties. The component can take props as well.// 1. Register component with anonymous function and name as second parameter
register(() => <div />, "SubComponent1");
// 2. Register component as named component
register(function SubComponent2() {
return <div />;
});
// 3. Register component with declared function
const SubComponent3 = () => <div />;
register(SubComponent3);
Playground on CodeSandBox.
import { useState, FC } from "react";
import { render } from "react-dom";
import ccc from "react-compound-components";
interface Props {
initState?: number;
}
const [Counter, useCountState, register] = ccc((props: Props) => {
const { initCount = 0 } = props;
const [count, setCount] = useState(initCount);
const increment = () => setCount((count) => count + 1);
const decrement = () => setCount((count) => count - 1);
return { count, increment, decrement };
});
register(() => {
const { increment } = useCountState();
return <button onClick={increment}>Increase</button>;
}, "Increase");
register(function Decrease() {
const { decrement } = useCountState();
return <button onClick={decrement}>Decrease</button>;
});
const Count: FC = () => {
const { count } = useCountState();
return <span>{count}</span>;
};
register(Count);
const App = () => (
<Counter initCount={10}>
<Counter.Decrease />
<Counter.Count />
<Counter.Increase />
</Counter>
);
render(<App />, document.getElementById("root"));
Note: Not needed for JavaScript
The function assumes that the sub-components do not require/expect props. If any sub-component expects props, then some extra work is needed to be done for TypeScript.
In such case, it is recommended to split the component code to a new file.
// Tabs.tsx
import { useState, useCallback, FC } from "react";
import ccc from "react-compound-components";
interface TabsProps {
defaultActiveTabId?: string;
}
const [Tabs, useTabs, register] = ccc((props: TabsProps) => {
const { defaultActiveTabId = "" } = props;
const [activeTabId, setActiveTabId] = useState(defaultActiveTabId);
const changeActiveTabId = useCallback(
(tabId: string) => setActiveTabId(tabId),
[]
);
// Optional component to wrap Compound component
const Wrapper: FC = ({ children }) => (
<div className="tabs">
<h1>
Tabs
<button onClick={() => changeActiveTabId(defaultActiveTabId)}>
Reset active tab
</button>
</h1>
{children}
</div>
);
return { Wrapper, activeTabId, changeActiveTabId };
});
interface TabProps {
tabId: string;
disabled?: boolean;
}
const Tab: FC<ITabProps> = (props) => {
const { tabId, disabled, children } = props;
const { changeActiveTabId } = useTabs();
return (
<button
className="tab"
disabled={disabled}
onClick={() => changeActiveTabId(tabId)}
>
{children}
</button>
);
};
interface PanelProps {
tabId: string;
}
const Panel: FC<PanelProps> = (props) => {
const { tabId, children } = props;
const { activeTabId } = useTabs();
return activeTabId === tabId ? (
<div className="tabPanel">{children}</div>
) : null;
};
register(Tab);
register(Panel);
// The additional part is forcefully setting component types
// to recognize prop-types of sub-components.
export default (Tabs as unknown) as FC<TabsProps> & {
Tab: typeof Tab;
Panel: typeof Panel;
};
and import the component for usage:
// index.tsx
import { FC } from "react";
import { render } from "react-dom";
import Tabs from "./Tabs";
const App: FC = () => (
<Tabs defaultActiveTabId="tab2">
<Tabs.Tab tabId="tab1">Tab 1</Tabs.Tab>
<Tabs.Tab tabId="tab2">Tab 2</Tabs.Tab>
<Tabs.Tab tabId="tab3" disabled>
Tab 3
</Tabs.Tab>
<hr />
<Tabs.Panel tabId="tab1">Content of Tab 1</Tabs.Panel>
<Tabs.Panel tabId="tab2">Content of Tab 2</Tabs.Panel>
<Tabs.Panel tabId="tab3">Content of Tab 3</Tabs.Panel>
</Tabs>
);
render(<App />, document.getElementById("root"));
If you find a bug, please create an issue providing instructions to reproduce it. It's always very appreciable if you find the time to fix it. In this case, please submit a PR.
If you're a beginner, it'll be a pleasure to help you contribute. You can start by reading the beginner's guide to contributing to a GitHub project.
MIT © Siddhant Gupta
FAQs
Create react compound components
We found that react-compound-components demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.