Enhancing the theme using material-ui@next and typescript

While developing my theme using material-ui, I decided to introduce two new palette options that would offer a wider range of light and dark shades. To achieve this, I extended the Theme type with the necessary modifications:

import {Theme} from "material-ui/styles";
import {Palette} from "material-ui/styles/createPalette";

export interface ExtendedTheme extends Theme {
    palette: ExtendedPalette
}

export interface ExtendedPalette extends Palette {
    light: Color,
    dark: Color,
}

The complication arises when attempting to apply these additional options within the WithStyles render helper:

const styles = (theme : ExtendedTheme) => ({ root: {color: theme.light['100'] }});

export interface MyProps {classes: {[index: string] : string}};
const MyComponent = (props : MyProps) => {...};

// Type ExtendedTheme is not assignable to Theme
export default withStyles(styles : StyleRulesCallback)(MyComponent);

Despite the functional success of my code in pure javascript, it encounters an error due to the mismatched types. The material-ui typings are set up to expect only the Theme type as the argument for the style callback function:

export type StyleRulesCallback<ClassKey extends string = string> = (theme: Theme) => StyleRules<ClassKey>;

I initially believed that extending an interface would allow for polymorphic implementation, where ExtendedTheme would seamlessly incorporate Theme.

Answer №1

To overcome the issue, one can utilize module augmentation:

declare module '@material-ui/core' {
  interface Theme {
    colors: {
      success: {
        dark: string,
        light: string,
      }
    }
  }
}


Additionally, it is advisable to define the module within the App component while encapsulating the children in a ThemeProvider:

import { createMuiTheme, ThemeProvider, colors, ThemeOptions } from '@material-ui/core';

declare module '@material-ui/core' {
  interface Theme {
    colors: {
      success: {
        dark: string,
        light: string,
      }
    }
  }
}

const App = () => {
  const theme = createMuiTheme({
    colors: {
      success: {
        dark: colors.green[600],
        light: colors.green[300],
      },
    } as ThemeOptions,
  });

  return (
    <ThemeProvider theme={theme}>
     <a href="https://material-ui.com/customization/theming/">Theming</a>
    </ThemeProvider>
  )

Answer №2

After pondering the issue, I have found a solution to address my custom options in a more flexible manner.

export interface ExtendedPalette extends Palette {
    light?: Color,
    dark?: Color,
}

In order to handle these additional options within my styles callback, I must verify their existence, which can be somewhat tedious. Yet, I believe this is the most optimal approach available.

const styles = (theme : ExtendedTheme) => { 
    let light = theme.palette.light[100];
    if(light === undefined) light = theme.common.white;
    { root: {color: light }}
};

The challenge arises from the fact that while the Theme object is provided to the callback through the use of withStyles, the typings for this callback specify the Theme type since they are unaware of my ExtendedTheme type. The conflict occurs when ExtendedTheme includes options that are unknown to Theme. By making these extra options optional, it allows Theme to remain compliant with ExtendedTheme. Essentially, an extended interface can be substituted where its parent is anticipated, but not vice versa unless the extension ensures compatibility with the parent.

An example may help illustrate this concept further.

export interface Foo {foo: string};
export interface Bar extends Foo {bar: string}

function getFoo(f : Foo) {console.log(f.foo)}
function getBar(b : Bar) {console.log(b.bar)} 
function getFooBar(fb: Bar) {console.log(fb.foo, fb.bar)}

const f : Foo = {foo: 'foo'}
const b : Bar = {foo: 'foo', bar: 'bar'}

getFoo(f) // foo
getFoo(b) // foo
getBar(f) // Error Incompatible Type
getBar(b) // bar
getFooBar(f) // Error Incompatible Type
getFooBar(b) // foo bar

getFoo(b) functions correctly because Bar necessarily includes all properties of

Foo</code. On the other hand, both <code>getBar(f)
and getFooBar(f) fail due to the absence of the key bar in the Foo type.

If we redefine Bar as follows:

export interface Bar extends Foo {bar? : string}

The compiler acknowledges that Foo meets the minimum requirements for the Bar type. However, it is important to account for potential null values. As a result, the following adjustment enables successful execution:

getBar(f)

Nevertheless, the presence of implicit nulls prompts the compiler to issue warnings. This necessitates modifying the function accordingly:

function getBar(b : Bar) {
    let bar = b.bar
    if(bar === undefined) bar = b.foo;
    console.log(bar);
}

getBar(b) // bar
getBar(f) // foo

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible for Angular.js to interact with JSTL expression language?

Is it possible for angular.js to interact with JSTL expression language? I am trying to generate ng-options using an array. Here is an example of what I am attempting to accomplish: <select ng-model="detail" ng-init="Categories = '${Categories}& ...

Is it considered good practice to make a POST request within a GET request?

Is it considered good practice to make a POST request while also making a GET request in my app? Or is this frowned upon? In my application, the functionality works like this: when the page loads, it needs to retrieve user data. If the user data is not fo ...

What is the best way to construct an AJAX call that includes two sets of POST data?

I'm currently working on a project that involves sending WebGL frames/screenshots to a server for saving to the hard drive and later merging them into a video file. I came across this helpful resource: Exporting video from WebGL Without delving too m ...

Steps to display API data as HTML in a React app instead of a string when passed as a prop

I am currently faced with a challenge of displaying text from a recipe API. The text contains HTML elements like bold tags and links, but it is wrapped in a string which means that it's being shown as literal text current text My goal is to have the ...

Deactivate a setInterval function within a Vue.js component

I am facing an issue in my VUE SPA where I have a component running a recursive countdown using setInterval functions. The problem is that the countdown continues running in the background even when I switch to another view, but I want to destroy the setIn ...

Tips for inserting a value into a specific location within an array using JavaScript

I am working with an array of objects that looks like this: const array = [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 } ]; My task is to add a new entry to this array, but it needs to be inserted at a specific position. For instance, when adding { ...

Is it possible to achieve a fade effect in material-ui that truly hides the component instead of just disabling its visibility? If so, how can this be accomplished?

Currently, I am utilizing the component provided by material-ui, which can be found at material-ui. <Fade in={!randomizeFlag}> <Grid> <FormControlLabel control={<Switch onChange={this.handleStartValueFlag} & ...

Performing a Search Operation using AJAX with Elasticsearch

I've been struggling to find the correct method for requesting data from elasticsearch using a jQuery AJAX call. I keep encountering parsing errors or getting all documents in the index instead of my intended results. $(document).ready(function() ...

displaying HTML on the web page only, without appearing in the text input field

update1: I have updated the image to provide better clarity https://i.sstatic.net/hYLsI.png I am currently working on implementing chip filters similar to Google Flights. When selecting "sports," it currently displays "sports1" and replaces "sports" with ...

When the condition fails to activate

I am encountering an issue with my code that involves an if statement checking the value of a variable and binding a mousewheel event based on its true or false value. The problem is, the if condition only triggers on load and not when the value changes to ...

When I click the back button on my mouse in React, it returns JSON data instead of HTML and CSS

I am working on a web application with two pages: a menu and orders. When I navigate from the orders page to the menu page and click the back button, I receive JSON data instead of the HTML page. The data loads correctly, but the page does not display the ...

Error: The initial parameter must be a string, Buffer, ArrayBuffer, Array, or Array-like Object. An object type was passed in instead in CryptoJS

In my quest to encrypt and decrypt data in react-native, I embarked on using the crypto node module by incorporating it into my react native project through browserify. While attempting encryption with the code snippet provided below, an error surfaces sta ...

Unexpected TypeError thrown by a simple react-cube-navigation demonstration

Looking to utilize the react-cube-navigation component, available here. Encountering a TypeError when attempting to run the provided example, React throws an error: TypeError: props.rotateY.to(function (x) { return "scale is not a function. ( ...

Ways to expedite the process of retrieving data for a JavaScript array

Looking to acquire the creation date of 20000 files and save it in an array. The total time taken is 35 minutes, which seems quite lengthy. (Image Processing Time) Is there a method to generate the array with a quicker processing time? Are there any i ...

Is it possible to synchronize Vue data values with global store state values?

I need help updating my data values with values from store.js. Whenever I try the code below, I keep getting a blank page error. Here's how I have it set up in App.vue: data() { return { storeState: store.state, Counter: this.storeState.C ...

Delay the loading of HTML elements to improve website performance

While working on my portfolio, I noticed that the page load time is quite long due to all the images needing to load before the page is fully loaded. I started thinking if there is a way to delay or prevent the loading of images and then have them load la ...

A More Straightforward Approach to Unsubscribing from Observables in Angular 7

Is there a way to simplify the process of automatically unsubscribing from Observables when a component is destroyed using takeUntil? It becomes tedious having to repeat the same code in multiple components. I am looking for a solution that allows me to a ...

Issue with Angular routing not functioning properly post form submission

Recently, I created a contact form using Angular for the frontend and NodeJs with Nodemailer for the backend. However, I encountered an issue where after submitting the form, instead of redirecting to the default page set in the app, the page remains on th ...

Having a single quote within a double quote can cause issues with JavaScript code

I am currently developing a Django web app and facing an issue with sending a JSON object to my JavaScript code using a Django template variable. # views.py import json def get_autocomplete_cache(): autocomplete_list = ["test1", "test2", "test3", "te ...

Retrieving Array keys from PHP using jQuery

As a beginner in jQuery, I am eager to learn how to retrieve Array keys from a php file using jQuery. Currently, I have jQuery code set up to send input to file.php and execute a query on every keyup event. When file.php echoes the result, it looks somet ...