- The React Reference App is a resource designed to provide you with practical examples of various coding patterns and practices, mainly in
-
- ReactJS
-
- .
-It serves as a reference point for you to understand, learn, and implement these patterns in your own projects.
-
-
-
-
- Patterns are a way of organizing code in a way that makes it easier to understand, maintain, and extend. They are a set of best practices that have been proven to work well in a variety of situations.
-In the end, patterns are like blueprints that can solve recurring problems and challenges.
-
-
-
-
- The main purpose of this app is to provide an overview of the most common patterns and practices in ReactJS using simple examples.
-Specific issues or challenges, such as managing state, handling forms will be addressed. These are also shown in conjunction with the
-
- Marigold Design System
-
- in appropriate places.
-
-
-
-
-
-
-
- How to use the React Reference App?
-
-
-
-
- Using the React Reference App is straightforward. Each pattern or practice is presented with a detailed explanation and code examples.
-You can copy these code examples directly into your own projects. To do this, simply click the copy button (
-
- ) in the code snippet.
-Clicking this button will copy the code to your clipboard, ready to be pasted into your own code editor.
-
-
-
-
-
-
-
- Which patterns will be addressed?
-
-
-
-
- The patterns are selected from our survey we made. So in future we will try to add all of them.
-
-
-
-
-
-
-
- If you have suggestions for patterns that should be added to the React Reference App, or if you think some of your own code could
-be transformed into a useful pattern here, please let us
-
- know
-
- .
-
-
-
-
-
-
-
- Feedback
-
-
-
-
- Constructive feedback, ideas and suggestions for improvement are welcome.
-
-
-
-
- Please use one of our
-
- channels
-
- to contact us.
-
- This document provides an explanation and examples of using compound components in React. Compound components are a design pattern
-where related child components are encapsulated within a parent component to create a reusable and flexible component suite.
-
-
-
-
- This pattern enhances customization and simplifies the internal composition of components.
-
-
-
-
- Importance of the Pattern
-
-
-
-
- Compound components are important for several reasons:
-
-
-
-
-
-
-
-
-
-
-
- Encapsulation and Reusability:
-
- They allow encapsulating related components, making the parent component more reusable and modular.
-
-
-
-
-
-
-
-
-
-
-
- Customizability:
-
- Each child component can be individually customized without exposing numerous customization props through the parent component.
-
-
-
-
-
-
-
-
-
-
-
- Simplicity in State Management:
-
- A well-designed compound component should require minimal state management, making the component easier to understand and maintain.
-
-
-
-
-
-
-
-
-
-
- Use-Cases and Examples
-
-
-
-
- Example 1:
-
- <select>
-
- and
-
- <option>
-
- in HTML
-
-
-
-
- A similiar example in HTML are the
-
- <select>
-
- and
-
- <option>
-
- tags:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- select.html
-
-
-
-
-
-
-
-
-
-
-
- _
- 10
-
-
-
- <select>
-
-
-
-
-
- _
- 10
-
-
-
- <option value="pizza">Pizza</option>
-
-
-
-
-
- _
- 10
-
-
-
- <option value="sushi">Sushi</option>
-
-
-
-
-
- _
- 10
-
-
-
- <option value="soup">Soup</option>
-
-
-
-
-
- _
- 10
-
-
-
- </select>
-
-
-
-
-
-
-
-
-
-
-
-
- The select tag works together with the option tag which is used for a drop-down menu to select items in HTML. Here the
-
- <select>
-
- manages the state of the UI, then the
-
- <option>
-
- elements are configured on how the
-
- <select>
-
- should work.
-
- E-commerce websites often have “breadcrumbs” to help the user navigate to parent/grand-parent pages. For example:
-
-
-
-
-
- Electronics / Cell Phones / Cases
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Let's imagine we have the following code for these breadcrumbs (see BreadcrumbsExample.tsx).
-
-
- How we can built now a compound component?
-
-
- Looking at the markup, it seems like we'll need two components:
-
-
-
-
-
-
-
-
- A
-
- Breadcrumbs
-
- parent wrapper to contain the group
-
-
-
-
-
-
-
-
-
-
- A
-
- Crumb
-
- component for each link in the group
-
-
-
-
-
-
-
-
-
-
- Copy the chunk for the parent wrapper (
-
- Breadcrumbs
-
- ) and made it generic.
-
-
-
-
- Copy the chunk for each link in the group (
-
- Crumb
-
- ) and made it generic.
-
-
-
-
- As final step we need to connect these components.
-
-
- We can call it weird or awesome, but JS functions can have properties.
-Functions are like objects in this way. We can "hang" data on them, like clothes hung on a clothesline.
-
-
-
-
- Finally we can use this compound component in our BreadcrumbsExample.tsx.
-
-
-
-
- Note that we only need to import the
-
- Breadcrumbs
-
- component.
-
-
-
-
- Because we connect
-
- Crumb
-
- to
-
- Breadcrumbs
-
- we can access it as a property.
-
- By leveraging React context, we can provide the necessary states and functions to child components without exposing them through props, resulting in a cleaner and more maintainable codebase.
-
-
-
-
-
-
-
-
-
-
-
- If we want to pass props to different components and avoid prop drilling, we will need to set up a
-
- React context
-
- object for providing states managed at the parent level to all children components.
-
-
-
-
- This component is essentially a wrapper that exposes the active tab state and the function that allow us to change the active tab. The context provider acts as the wrapper. So now all the children inside this container can access the activeTab and the setter function using the useContext hook.
-
-
-
-
- Then let’s add the tab component. This is the button that’ll help us to switch between sections.
-On clicking this button, it triggers the
-
- changeTab
-
- from the context changing the \`activeTab state. Any other component that’s accessing this state will be re-rendered.
-
-
-
-
- And finally, the actual tab section. Based on the activeTab from the context, we display the specific tab section.
-
-
-
-
- Now we can default export the tabs component and attach the other sub-components to this component.
-
-
-
-
- Inside the TabsExample file, let’s import all our tab components and use them as we please.
-
-
- Note: We can add any other elements we want like the
-
- div
-
- . We can also use a completely different arrangement of the elements.
-
-
- "State" is any data that describes the current behavior of an application.
-
- This could include values like "a list of objects fetched from the server", "which item is currently selected", "name of the currently logged-in user", and "is this modal open?".
-
-
-
-
-
- State
-
- allows you to create interactive and dynamic user interfaces. By managing the state of component, you can update the UI in response to user interactions, API calls, or other events.
-
-
-
-
- Here are different types for state management that will be covered in our example:
-
-
-
-
-
-
-
-
-
-
-
- Local State:
-
- Data we manage in one or another component e.g handling inputs data in from.
-
-
-
-
-
-
-
-
-
-
-
- Server State:
-
- Data are fetched from the server via an API and cached on the client, there are a couple of great libraries that make data fetching in React a breeze such as:
-
-
- React Query
-
-
- .
-
-
-
-
-
-
-
-
-
-
- Let's break down the state management code example:
-
- Initializing local state for filters using useState hook
-
-
- Here, we use the
-
- useState
-
- hook to manage the local state of our filters. This state will store the user's input for the movie title and category.
-
-
-
-
- Handle User Input: SearchField & Select
-
-
- We use the
-
- SearchField
-
- component to allow the user to input a search query. The
-
- onChange
-
- handler updates the local state with the new search term, which triggers a re-fetch of the data.
-
-
- The
-
- Select
-
- component allows the user to choose a category. The
-
- onChange
-
- handler updates the local state with the selected category, which also triggers a re-fetch of the data.
-
-
-
-
- Fetch date from server
-
-
- The
-
- fetchData
-
- function builds the API URL with query parameters and fetches data from the server.
-
-
-
-
- Use React Query to Manage Server State
-
-
- The
-
- useQuery
-
- hook from React Query fetches data based on the current filters and manages the server state, including loading and error states.
-
-
-
-
- Error Handling
-
-
- If an error occurs during data fetching, we display an error message.
-
-
-
-
- Loading State
-
-
- While data is being fetched, we display a loading indicator.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ServerStateExample.tsx
-
-
-
-
-
-
-
-
-
-
-
- _
- 116
-
-
-
- import {
-
-
-
-
-
- _
- 116
-
-
-
- Card,
-
-
-
-
-
- _
- 116
-
-
-
- Image,
-
-
-
-
-
- _
- 116
-
-
-
- Inline,
-
-
-
-
-
- _
- 116
-
-
-
- SearchField,
-
-
-
-
-
- _
- 116
-
-
-
- Select,
-
-
-
-
-
- _
- 116
-
-
-
- Stack,
-
-
-
-
-
- _
- 116
-
-
-
- Text,
-
-
-
-
-
- _
- 116
-
-
-
- } from '@marigold/components';
-
-
-
-
-
- _
- 116
-
-
-
- import { useQuery } from '@tanstack/react-query';
-
-
>
),
diff --git a/src/components/compoundComponents/Breadcrumbs/Breadcrumbs.tsx b/src/routes/compound-component/_components/Breadcrumbs.tsx
similarity index 94%
rename from src/components/compoundComponents/Breadcrumbs/Breadcrumbs.tsx
rename to src/routes/compound-component/_components/Breadcrumbs.tsx
index d37da73..d212687 100644
--- a/src/components/compoundComponents/Breadcrumbs/Breadcrumbs.tsx
+++ b/src/routes/compound-component/_components/Breadcrumbs.tsx
@@ -1,4 +1,4 @@
-import { ReactNode } from 'react';
+import type { ReactNode } from 'react';
function Breadcrumbs({ children }: { children: ReactNode }) {
return (
diff --git a/src/components/compoundComponents/Breadcrumbs/BreadcrumbsExample.tsx b/src/routes/compound-component/_components/BreadcrumbsExample.tsx
similarity index 100%
rename from src/components/compoundComponents/Breadcrumbs/BreadcrumbsExample.tsx
rename to src/routes/compound-component/_components/BreadcrumbsExample.tsx
diff --git a/src/components/compoundComponents/Tabs/Tab.tsx b/src/routes/compound-component/_components/Tab.tsx
similarity index 100%
rename from src/components/compoundComponents/Tabs/Tab.tsx
rename to src/routes/compound-component/_components/Tab.tsx
diff --git a/src/components/compoundComponents/Tabs/TabPanel.tsx b/src/routes/compound-component/_components/TabPanel.tsx
similarity index 100%
rename from src/components/compoundComponents/Tabs/TabPanel.tsx
rename to src/routes/compound-component/_components/TabPanel.tsx
diff --git a/src/components/compoundComponents/Tabs/Tabs.tsx b/src/routes/compound-component/_components/Tabs.tsx
similarity index 100%
rename from src/components/compoundComponents/Tabs/Tabs.tsx
rename to src/routes/compound-component/_components/Tabs.tsx
diff --git a/src/components/compoundComponents/Tabs/TabsExample.tsx b/src/routes/compound-component/_components/TabsExample.tsx
similarity index 100%
rename from src/components/compoundComponents/Tabs/TabsExample.tsx
rename to src/routes/compound-component/_components/TabsExample.tsx
diff --git a/src/components/compoundComponents/Breadcrumbs/breadcrumbs.mdx b/src/routes/compound-component/breadcrumbs.mdx
similarity index 88%
rename from src/components/compoundComponents/Breadcrumbs/breadcrumbs.mdx
rename to src/routes/compound-component/breadcrumbs.mdx
index f96a39f..996b090 100644
--- a/src/components/compoundComponents/Breadcrumbs/breadcrumbs.mdx
+++ b/src/routes/compound-component/breadcrumbs.mdx
@@ -45,7 +45,7 @@
Copy the chunk for the parent wrapper (`Breadcrumbs`) and made it generic.
```tsx Breadcrumbs.tsx focus=3:9
- // from ./Breadcrumbs.tsx
+ // from ./_components/Breadcrumbs.tsx
```
---
@@ -53,7 +53,7 @@
Copy the chunk for each link in the group (`Crumb`) and made it generic.
```tsx Breadcrumbs.tsx focus=11:31
- // from ./Breadcrumbs.tsx
+ // from ./_components/Breadcrumbs.tsx
```
---
@@ -61,7 +61,7 @@
As final step we need to connect these components.
```tsx Breadcrumbs.tsx focus=34
- // from ./Breadcrumbs.tsx
+ // from ./_components/Breadcrumbs.tsx
```
We can call it weird or awesome, but JS functions can have properties.
@@ -72,7 +72,7 @@
Finally we can use this compound component in our BreadcrumbsExample.tsx.
```tsx BreadcrumbsExample.tsx
- // from ./BreadcrumbsExample.tsx
+ // from ./_components/BreadcrumbsExample.tsx
```
---
@@ -80,7 +80,7 @@
Note that we only need to import the `Breadcrumbs` component.
```tsx BreadcrumbsExample.tsx focus=1,6,12
- // from ./BreadcrumbsExample.tsx
+ // from ./_components/BreadcrumbsExample.tsx
```
---
@@ -88,7 +88,7 @@
Because we connect `Crumb` to `Breadcrumbs` we can access it as a property.
```tsx BreadcrumbsExample.tsx focus=6:12
- // from ./BreadcrumbsExample.tsx
+ // from ./_components/BreadcrumbsExample.tsx
```
diff --git a/src/routes/compound-component/index.lazy.tsx b/src/routes/compound-component/index.lazy.tsx
new file mode 100644
index 0000000..f9e4b23
--- /dev/null
+++ b/src/routes/compound-component/index.lazy.tsx
@@ -0,0 +1,6 @@
+import { createLazyFileRoute } from '@tanstack/react-router';
+import Content from './index.mdx';
+
+export const Route = createLazyFileRoute('/compound-component/')({
+ component: Content,
+});
diff --git a/src/components/compoundComponents/index.mdx b/src/routes/compound-component/index.mdx
similarity index 81%
rename from src/components/compoundComponents/index.mdx
rename to src/routes/compound-component/index.mdx
index 0ae5747..032b5fa 100644
--- a/src/components/compoundComponents/index.mdx
+++ b/src/routes/compound-component/index.mdx
@@ -1,6 +1,7 @@
-import BreadcrumbDoc from './Breadcrumbs/breadcrumbs.mdx';
-import TabsDoc from './Tabs/tabs.mdx';
-import DemoLink from '../DemoLink';
+import DemoLink from '@/components/DemoLink';
+
+import BreadcrumbDoc from './breadcrumbs.mdx';
+import TabsDoc from './tabs.mdx';
# Compound components in React
@@ -37,13 +38,7 @@ The select tag works together with the option tag which is used for a drop-down
### Example 2: Building a compound component
-
- View Demo
-
-
-
- View Demo
-
+View Demo
E-commerce websites often have “breadcrumbs” to help the user navigate to parent/grand-parent pages. For example:
@@ -56,13 +51,7 @@ E-commerce websites often have “breadcrumbs” to help the user navigate to pa
### Example 3: Compound component with Context
-
- View Demo
-
-
-
- View Demo
-
+View Demo
By leveraging React context, we can provide the necessary states and functions to child components without exposing them through props, resulting in a cleaner and more maintainable codebase.
diff --git a/src/routes/compound-component/preview.breadcrumbs.lazy.tsx b/src/routes/compound-component/preview.breadcrumbs.lazy.tsx
new file mode 100644
index 0000000..a648a97
--- /dev/null
+++ b/src/routes/compound-component/preview.breadcrumbs.lazy.tsx
@@ -0,0 +1,8 @@
+import { createLazyFileRoute } from '@tanstack/react-router';
+import BreadcrumbsExample from './_components/BreadcrumbsExample';
+
+export const Route = createLazyFileRoute(
+ '/compound-component/preview/breadcrumbs'
+)({
+ component: BreadcrumbsExample,
+});
diff --git a/src/routes/compound-component/preview.tabs.lazy.tsx b/src/routes/compound-component/preview.tabs.lazy.tsx
new file mode 100644
index 0000000..74a6ddc
--- /dev/null
+++ b/src/routes/compound-component/preview.tabs.lazy.tsx
@@ -0,0 +1,6 @@
+import { createLazyFileRoute } from '@tanstack/react-router';
+import TabsExample from './_components/TabsExample';
+
+export const Route = createLazyFileRoute('/compound-component/preview/tabs')({
+ component: TabsExample,
+});
diff --git a/src/components/compoundComponents/Tabs/tabs.mdx b/src/routes/compound-component/tabs.mdx
similarity index 87%
rename from src/components/compoundComponents/Tabs/tabs.mdx
rename to src/routes/compound-component/tabs.mdx
index 84f7130..bb91a18 100644
--- a/src/components/compoundComponents/Tabs/tabs.mdx
+++ b/src/routes/compound-component/tabs.mdx
@@ -1,7 +1,7 @@
```tsx Tabs.tsx focus=5
- // from ./Tabs.tsx
+ // from ./_components/Tabs.tsx
````
If we want to pass props to different components and avoid prop drilling, we will need to set up a [React context](https://react.dev/reference/react/hooks#context-hooks) object for providing states managed at the parent level to all children components.
@@ -9,7 +9,7 @@
---
```tsx Tabs.tsx focus=7:15
- // from ./Tabs.tsx
+ // from ./_components/Tabs.tsx
````
This component is essentially a wrapper that exposes the active tab state and the function that allow us to change the active tab. The context provider acts as the wrapper. So now all the children inside this container can access the activeTab and the setter function using the useContext hook.
@@ -17,7 +17,7 @@
---
```tsx Tab.tsx
- // from ./Tab.tsx
+ // from ./_components/Tab.tsx
````
Then let’s add the tab component. This is the button that’ll help us to switch between sections.
@@ -26,7 +26,7 @@
---
```tsx TabPanel.tsx
- // from ./TabPanel.tsx
+ // from ./_components/TabPanel.tsx
````
And finally, the actual tab section. Based on the activeTab from the context, we display the specific tab section.
@@ -34,7 +34,7 @@
---
```tsx Tabs.tsx focus=2:3,17:19
- // from ./Tabs.tsx
+ // from ./_components/Tabs.tsx
````
Now we can default export the tabs component and attach the other sub-components to this component.
@@ -42,7 +42,7 @@
---
```tsx TabsExample.tsx
- // from ./TabsExample.tsx
+ // from ./_components/TabsExample.tsx
````
Inside the TabsExample file, let’s import all our tab components and use them as we please.
diff --git a/src/routes/compoundComponent.index.lazy.tsx b/src/routes/compoundComponent.index.lazy.tsx
deleted file mode 100644
index 248bccb..0000000
--- a/src/routes/compoundComponent.index.lazy.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createLazyFileRoute } from '@tanstack/react-router';
-import CompoundApp from '../components/compoundComponents/CompoundApp';
-
-export const Route = createLazyFileRoute('/compoundComponent/')({
- component: CompoundApp,
-});
diff --git a/src/routes/index.lazy.tsx b/src/routes/index.lazy.tsx
index b597799..4392986 100644
--- a/src/routes/index.lazy.tsx
+++ b/src/routes/index.lazy.tsx
@@ -1,6 +1,6 @@
import { createLazyFileRoute } from '@tanstack/react-router';
-import App from '../components/App';
+import Content from './index.mdx';
export const Route = createLazyFileRoute('/')({
- component: App,
+ component: Content,
});
diff --git a/src/components/index.mdx b/src/routes/index.mdx
similarity index 100%
rename from src/components/index.mdx
rename to src/routes/index.mdx
diff --git a/src/components/state-management/ServerState/ServerStateExample.tsx b/src/routes/state-management/_components/ServerStateExample.tsx
similarity index 100%
rename from src/components/state-management/ServerState/ServerStateExample.tsx
rename to src/routes/state-management/_components/ServerStateExample.tsx
diff --git a/src/routes/state-management/example.lazy.tsx b/src/routes/state-management/example.lazy.tsx
deleted file mode 100644
index 0531449..0000000
--- a/src/routes/state-management/example.lazy.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createLazyFileRoute } from '@tanstack/react-router';
-import ServerStateExample from '../../components/state-management/ServerState/ServerStateExample';
-
-export const Route = createLazyFileRoute('/state-management/example')({
- component: ServerStateExample,
-});
diff --git a/src/routes/state-management/index.lazy.tsx b/src/routes/state-management/index.lazy.tsx
index b5f1619..e1f0df0 100644
--- a/src/routes/state-management/index.lazy.tsx
+++ b/src/routes/state-management/index.lazy.tsx
@@ -1,6 +1,6 @@
import { createLazyFileRoute } from '@tanstack/react-router';
-import StateManagementApp from '../../components/state-management/StateManagementApp';
+import Content from './index.mdx';
export const Route = createLazyFileRoute('/state-management/')({
- component: StateManagementApp,
+ component: Content,
});
diff --git a/src/components/state-management/index.mdx b/src/routes/state-management/index.mdx
similarity index 84%
rename from src/components/state-management/index.mdx
rename to src/routes/state-management/index.mdx
index f9f2ede..fa4903c 100644
--- a/src/components/state-management/index.mdx
+++ b/src/routes/state-management/index.mdx
@@ -1,5 +1,5 @@
-import ServerStateDoc from './ServerState/server-state.mdx';
-import DemoLink from '../DemoLink';
+import DemoLink from '@/components/DemoLink';
+import ServerStateDoc from './server-state.mdx';
# State Management in React
@@ -17,9 +17,7 @@ Here are different types for state management that will be covered in our exampl
### Let's break down the state management code example:
-
- View Demo
-
+View Demo
diff --git a/src/routes/state-management/preview.lazy.tsx b/src/routes/state-management/preview.lazy.tsx
new file mode 100644
index 0000000..d2398f1
--- /dev/null
+++ b/src/routes/state-management/preview.lazy.tsx
@@ -0,0 +1,6 @@
+import { createLazyFileRoute } from '@tanstack/react-router';
+import ServerStateExample from './_components/ServerStateExample';
+
+export const Route = createLazyFileRoute('/state-management/preview')({
+ component: ServerStateExample,
+});
diff --git a/src/components/state-management/ServerState/server-state.mdx b/src/routes/state-management/server-state.mdx
similarity index 81%
rename from src/components/state-management/ServerState/server-state.mdx
rename to src/routes/state-management/server-state.mdx
index fdcb0b0..2adcb3f 100644
--- a/src/components/state-management/ServerState/server-state.mdx
+++ b/src/routes/state-management/server-state.mdx
@@ -1,9 +1,9 @@
-import ServerStateExample from './ServerStateExample';
+import ServerStateExample from './_components/ServerStateExample';
```tsx ServerStateExample.tsx focus=24:27
- // from ./ServerStateExample.tsx
+ // from ./_components/ServerStateExample.tsx
````
#### Initializing local state for filters using useState hook
@@ -12,7 +12,7 @@ import ServerStateExample from './ServerStateExample';
---
```tsx ServerStateExample.tsx focus=63,71:73
- // from ./ServerStateExample.tsx
+ // from ./_components/ServerStateExample.tsx
````
#### Handle User Input: SearchField & Select
@@ -23,7 +23,7 @@ import ServerStateExample from './ServerStateExample';
---
```tsx ServerStateExample.tsx focus=29:37
- // from ./ServerStateExample.tsx
+ // from ./_components/ServerStateExample.tsx
````
#### Fetch date from server
@@ -32,7 +32,7 @@ import ServerStateExample from './ServerStateExample';
---
```tsx ServerStateExample.tsx focus=39:51
- // from ./ServerStateExample.tsx
+ // from ./_components/ServerStateExample.tsx
````
#### Use React Query to Manage Server State
@@ -43,7 +43,7 @@ import ServerStateExample from './ServerStateExample';
```tsx ServerStateExample.tsx focus=53:55
- // from ./ServerStateExample.tsx
+ // from ./_components/ServerStateExample.tsx
```
#### Error Handling
@@ -55,7 +55,7 @@ import ServerStateExample from './ServerStateExample';
```tsx ServerStateExample.tsx focus=86
- // from ./ServerStateExample.tsx
+ // from ./_components/ServerStateExample.tsx
```
#### Loading State
diff --git a/tests/smoke.test.tsx b/tests/smoke.test.tsx
new file mode 100644
index 0000000..ffbd670
--- /dev/null
+++ b/tests/smoke.test.tsx
@@ -0,0 +1,11 @@
+import { render, screen } from '@testing-library/react';
+import { expect } from 'vitest';
+
+test('smoketest', async () => {
+ const Component = () =>