Sharing state with React context

React context allows state to be shared between components. It works really well with compound components. We are going to use it in our Tabs and Tab components to share state between them:

  1. Our first task is to create an interface for the context we are going to use in Tabs.tsx at the top of the file just beneath the import statements:
interface ITabsContext {
activeName?: string;
handleTabClick?: (name: string) => void;
}

So, our context will contain the active tab name as well as a reference to a tab click handler. These are the two bits of state that need to be shared between the components.

  1. Next, let's create the context underneath the ITabsContext interface:
const TabsContext = React.createContext<ITabsContext>({});

We've used the createContext function in React to create our context, which is a generic function that creates a context of a generic type, which in our case in ITabsContext.

We are required to pass the default context value as the parameter value to createContext but that doesn't make sense in our case, so we just pass an empty {} object to keep the TypeScript compiler happy. This is why both the properties in ITabsContext are optional.

  1. It's time to use this context now in our compound components. The first thing we need to do is to define the context provider in the Tabs render method:
public render() {
return (
<TabsContext.Provider
value={{
activeName: this.state ? this.state.activeName : "",
handleTabClick: this.handleTabClick
}}
>
<ul className="tabs">{this.props.children}</ul>
</TabsContext.Provider>
);
}

There are a few things going on here, so let's break this down:

  • The constant for our context we declared earlier, TabsContext, is available in JSX as a <TabsContext /> component.
  • The context provider fills the context with values. Given that Tabs manages the state and event handling, it makes sense for the provider to be referenced there. 
  • We reference the provider using <TabsContext.Provider />.
  • The provider takes in a property called value for the context value. We set this to an object containing the active tab name and the tab click event handler.
  1. We need to adjust the tab click handler slightly because the click isn't going to be handled directly in Tabs anymore. So, we simply need to take in the active tab name as a parameter and then set the active tab name state within the method:
private handleTabClick = (name: string) => {
this.setState({ activeName: name });
};
  1. Now that we have fed the context some data, it's time to consume this in the Tab component:
 public static Tab: React.SFC<ITabProps> = props => (
<TabsContext.Consumer>
{(context: ITabsContext) => {
const activeName = context.activeName
? context.activeName
: props.initialActive
? props.name
: "";
const handleTabClick = (e: React.MouseEvent<HTMLLIElement>) =>
{

if (context.handleTabClick) {
context.handleTabClick(props.name);
}
};
return (
<li
onClick={handleTabClick}
className={props.name === activeName ? "active" : ""}
>
{props.children}
</li>
);
}}
</TabsContext.Consumer>
);

This again looks a little daunting, so let's break it down:

  • We can consume a context via a Consumer component within the context component. So, this is <TabsContext.Consumer /> in our case.
  • The child for Consumer needs to be a function that has a parameter for the context value and returns some JSX. Consumer will then render the JSX we return.
Don't worry if this is still a little confusing. We'll cover this pattern in a lot more detail later when we cover children props and render props.
  • This context function gives us everything we need to render the tab. We have access to the state from the context argument as well as access to the Tab component props object.
  • The first line of the function determines the active tab name by using what is in the context. If the active tab in the context is an empty string, we use the current tab name if it has been defined as the initial active tab.
  • The second line of the function creates a tab click handler that calls the context tab click handler if it has been specified.
  • The return statement is as it was before, but we've been able to add a reference to the tab click handler and the class name now.

So, that's it for our tabs compound component. The syntax for React context may seem a little strange at first, but when you get used to it, it is really simple and elegant.

Before we can give this a try, we need to consume our compound component in our Product component. Let's replace our previous consumption of the Tabs component with the following highlighted JSX:

 <React.Fragment>
<h1>{product.name}</h1>

<Tabs>
<Tabs.Tab name="Description" initialActive={true}>
<b>Description</b>
</Tabs.Tab>
<Tabs.Tab name="Reviews">Reviews</Tabs.Tab>
</Tabs>

<p>{product.description}</p>
...
</React.Fragment>

This is exactly the JSX we wanted to achieve when we started to build the compound tabs component. If we go to the running app and browse to the product page, our tabs component works perfectly, with the description tab in bold:

So, compound components are great for components that rely on each other. The <Tabs.Tab /> syntax really calls out the fact that Tab needs to be used with Tabs.

React context works really well with compound components allowing the components, in the compound to easily share state. The state can even include functions such as event handlers.

Allowing the consumer to specify the content to be rendered in sections of a component gives the consumer a great deal of flexibility. Specifying this custom content as a child of a component is intuitive and feels natural. We'll continue with this approach in the following section where we'll complete our tabs component.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.172.146