You can do something like this:
First create a context file named for example ItemList.context.tsx :
import React, {
createContext,
Dispatch,
FunctionComponent,
useState
} from "react";
import { Item } from "./data";
type ItemListContextType = {
itemList: Item[]; // type of your items that I declare in data.ts
setItemList: Dispatch<React.SetStateAction<Item[]>>; //React setState type
};
export const ItemListContext = createContext<ItemListContextType>(
{} as ItemListContextType
);
export const ItemListContextProvider: FunctionComponent = ({ children }) => {
const [itemList, setItemList] = useState<Item[]>([]);
return (
<ItemListContext.Provider
value={{ itemList: itemList, setItemList: setItemList }}
>
{children}
</ItemListContext.Provider>
);
};
You can then add the context provider in the parent component ( App.tsx in your example):
import "./styles.css";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { ItemListContextProvider } from "./ItemList.context";
export default function App() {
return (
<div className="App">
<ItemListContextProvider>
<ItemList />
<ItemContainer />
</ItemListContextProvider>
</div>
);
}
and you can finally access your Item List by using the hook useContext
in your two components:
for ItemList.tsx where you need to set the list (and optionally get the list to avoid putting twice an item):
import { useContext } from "react";
import { data, Item } from "./data";
import { ItemListContext } from "./ItemList.context";
const items: Item[] = data;
export default function ItemList() {
const { itemList, setItemList } = useContext(ItemListContext); // here you get your list and the method to set the list
const addItemToItemList = (item: Item) => {
//you are using the itemList to see if item is already in the itemList
if (!itemList.includes(item)) setItemList((prev) => [...prev, item]);
};
return (
<div className="itemlist">
{items.map((item, index) => (
<div style={{ marginBottom: 15 }} key={index}>
<div style={{ fontWeight: 800 }}>{item.name}</div>
<div>{item.description}</div>
<button onClick={() => addItemToItemList(item)}>
Add to sidebar
</button>
</div>
))}
</div>
);
}
And in your ItemContainer.tsx you only need the list so you can import only the setItemList
from the context with useContext
:
import { useContext } from "react";
import { ItemListContext } from "./ItemList.context";
export default function ItemContainer() {
const { itemList } = useContext(ItemListContext);
return (
<div style={{ flexGrow: 4 }}>
<h1 style={{ textAlign: "center" }}>List of items</h1>
<p>Number of items: {itemList.length}</p>
{itemList.length > 0 && (
<ul>
{itemList.map((item, i) => (
<li key={i}>{item.name}</li>
))}
</ul>
)}
</div>
);
}
UPDATE with a Router
It's quite the same thing you only need to wrap your browser router in the context provider if you want it to be at the highest place in your app (for a them provider or a dark mode provider for example):
export default function App() {
return (
<div className="App">
<ItemListContextProvider>
<BrowserRouter>
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/itemList" component={ItemListPage} />
</Switch>
</BrowserRouter>
</ItemListContextProvider>
</div>
);
}
but I suggest you to put your provider the nearest place where your subscribers components are.
You can for example create a page component that will be use in the browser router and put in it the provider, like this:
ItemListPage.tsx
import React, { FunctionComponent } from "react";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { Link } from "react-router-dom";
import { ItemListContextProvider } from "./ItemList.context";
const ItemListPage: FunctionComponent = () => {
return (
<>
<ItemListContextProvider>
<h1 style={{ alignSelf: "flex-start" }}>ITEM LIST</h1>
<Link to="/">homePage</Link>
<div className="itemListPage">
<ItemList />
<ItemContainer />
</div>
</ItemListContextProvider>
</>
);
};
export default ItemListPage;
and of course you remove the context provider in your App.tsx and it should look like :
App.tsx
import React, { FunctionComponent } from "react";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { Link } from "react-router-dom";
import { ItemListContextProvider } from "./ItemList.context";
const ItemListPage: FunctionComponent = () => {
return (
<>
<ItemListContextProvider>
<h1 style={{ alignSelf: "flex-start" }}>ITEM LIST</h1>
<Link to="/">homePage</Link>
<div className="itemListPage">
<ItemList />
<ItemContainer />
</div>
</ItemListContextProvider>
</>
);
};
export default ItemListPage;
