I'm implementing a Theme Switcher using the React Context API and this tutorial
https://www.youtube.com/watch?time_continue=1&v=oDgxUodLwGU
I've started by making the Context
export default React.createContext({
theme: dark
});
Then I have defined my themes (dark & light)
Then I created a Component (TodoList)
import React from 'react';
import styled from 'styled-components';
const List = styled.ul`
background: ${props => {
debugger;
props.theme.background;
}};
color: ${props => props.theme.color};
font-family: sans-serif;
font-size: 20px;
li {
line-height: 36px;
}
`;
const TodoList = () => {
debugger;
return (
<List>
<li>item1</li>
<li>item2</li>
<li>item3</li>
</List>
);
};
export default TodoList;
Then in my App Component I used the code below
import React, {
Component
} from 'react';
import './App.css';
import TodoList from './components/TodoList';
import ThemeSwitcher from './components/ThemeSwitcher';
import * as themes from './styles/themes';
import ThemeContext from './styles/themes/context';
import { ThemeProvider } from 'styled-components';
class App extends Component {
state = {
theme: themes.dark,
};
toggleTheme = () => {
this.setState({ theme: this.state.theme === themes.dark ? themes.light : themes.dark });
};
render() {
console.log(this.state.theme);
return (
<div id="root">
<ThemeContext.Provider value={this.state}>
<ThemeSwitcher toggleTheme={this.toggleTheme} />
<ThemeContext.Consumer>
{theme => {
<ThemeProvider theme={theme}>
<TodoList />
</ThemeProvider>
}}
</ThemeContext.Consumer>
</ThemeContext.Provider>
</div>
);
}
}
export default App;
When I use TodoList component outside ThemeContext.Consumer, it renders just fine but inside it never renders. The breakpoints in the TodoList component never get hits...
Also I've tried disregarding the ThemeContext.Consumer and using the snippet below:
<ThemeProvider theme={theme}>
<TodoList />
</ThemeProvider>
the component rendered.
What possibly could I have done wrong?
Thanks in advance.
I was able to identify the problem, It was with the snippet below:
<ThemeContext.Consumer>
{theme => {
<ThemeProvider theme={theme}>
<TodoList />
</ThemeProvider>
}}
</ThemeContext.Consumer>
It should be
<ThemeContext.Consumer>
{theme => {
return (
<ThemeProvider theme={theme}>
<TodoList />
</ThemeProvider>
);
}}
</ThemeContext.Consumer>
Related
I have this problem upgrading my react application. Basically, I followed the damn mui-v5 documentation, and I still have some erros in my app.tsx file.
import { useReactOidc, withOidcSecure } from '#axa-fr/react-oidc-context';
import { Container } from '#mui/material';
import CssBaseline from '#mui/material/CssBaseline';
import {
ThemeProvider,
StyledEngineProvider,
Theme,
} from '#mui/material/styles';
import createStyles from '#mui/styles/createStyles';
import makeStyles from '#mui/styles/makeStyles';
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { Redirect, Route, Switch } from 'react-router-dom';
import { RootState } from 'src/lib/configuration/store-configuration';
import { DarkTheme, LightTheme } from 'src/components/material-ui-theme';
import routes from 'src/components/routes/routes';
import BreadCrumbs from 'src/components/shared/breadcrumbs/breadcrumbs';
import ErrorBoundary from 'src/components/shared/error/Error';
import Footer from 'src/components/shared/footer/footer';
import Header from 'src/components/shared/header/header';
import Notifier from 'src/components/shared/notifications';
import ScrollTop from 'src/components/shared/scroll-top/scroll-top';
import Sider from 'src/components/shared/sider';
import { useTranslation } from 'react-i18next';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
display: 'flex',
},
toolbar: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
},
container: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(1),
},
}),
);
const App = () => {
const classes = useStyles();
const { oidcUser } = useReactOidc();
const appStore = useSelector((store: RootState) => store.app);
const [drawerOpen, setDrawerOpen] = useState(false);
const handleDrawerToggle = () => {
setDrawerOpen(!drawerOpen);
};
const { i18n } = useTranslation();
useEffect(() => {
document.documentElement.lang = i18n.language;
}, [i18n.language]);
return (
<StyledEngineProvider injectFirst>
<ThemeProvider
theme={
appStore.theme === 'dark' ? DarkTheme : LightTheme
}
>
<div className={classes.root}>
<CssBaseline />
<Header handleDrawerToggle={handleDrawerToggle} />
{oidcUser ? (
<Sider
drawerOpen={drawerOpen}
handleDrawerToggle={
handleDrawerToggle
}
/>
) : null}
<main className={classes.content}>
<div id="back-to-top-anchor" />
<div className={classes.toolbar} />
<Container
maxWidth="xl"
className={classes.container}
>
<Notifier />
<BreadCrumbs />
<ErrorBoundary>
<Switch>
{routes.map(
(route, i) => (
<Route
key={
'route_' +
i
}
exact={
route.exact
}
path={
route.path
}
component={
route.secure
? withOidcSecure(
route.component,
)
: route.component
}
/>
),
)}
<Redirect to="/" />
</Switch>
</ErrorBoundary>
</Container>
<ScrollTop />
<Footer />
</main>
</div>
</ThemeProvider>
</StyledEngineProvider>
);
};
export default App;
Here is the navigator console error :
I tried to modify some imports and used:
import { createTheme } from '#material-ui/core';
const theme = createTheme();
And passing it to makestyles((theme) => ... but nothing changed.
Please guys I need your support ! :)
link for the open-source github project: https://github.com/InseeFr/sugoi-ui
I want to build my project with create react app. But, I encounter a blank page, when I run "yarn start" in project's directory. As others have said, I set "homepage": "." . but that does not work.
Some said router should be set to "hashrouter". Unfortunately, I don't understand how to do that.
This is my code that has used of context for building "themeSwitcher".
index.jsx:
import React from 'react';
import ReactDOM from 'react-dom';
import './app.css';
import {themeContext} from './context.js';
function themeSwitcher(){
return (
<themeContext.consumer>
{({Theme,changeTheme}) => (
<input
type="checkbox"
checked={Theme === "dark"}
onChange={() => changeTheme(Theme === "dark" ? "light" : "dark")}
/>
)}
</themeContext.consumer>
);
}
class app extends React.Component {
constructor(props) {
super(props);
this.state = {
Theme: "light",
changeTheme: this.changeTheme
};
}
changeTheme = (Theme) => {
this.setState({
Theme
});
};
render() {
return (
<themeContext.provider value={this.state}>
<div>
<p>this is a switcher theme</p>
<span>Dark mode</span>
<themeSwitcher />
</div>
</themeContext.provider>
);
}
}
ReactDOM.render(<app />, document.getElementById("root"));
context.js:
import React from "react";
export const themeContext = React.createContext({
Theme: "light",
changeTheme: () => {}
});
Why are the components written with small letters? Component names must begin with a capital letter.
If possible then present code from './context.js'
import React from 'react'
import ReactDOM from 'react-dom'
import './app.css'
import { ThemeContext } from './context.js'
function ThemeSwitcher() {
return (
<ThemeContext.Consumer>
{({ theme, toggleTheme }) => (
<input
type="checkbox"
checked={theme === 'dark'}
onChange={toggleTheme}
/>
)}
</ThemeContext.Consumer>
)
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
theme: 'light'
}
}
toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === 'dark'
? 'light'
: 'dark',
}));
}
contextValue = {
theme: this.state.theme,
toggleTheme: this.toggleTheme
}
render() {
return (
<ThemeContext.Provider value={this.contextValue}>
<div>
<p>this is a switcher theme</p>
<span>Dark mode</span>
<ThemeSwitcher />
</div>
</ThemeContext.Provider>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
You can also use hooks and functional components. The code is cleaner.
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import './app.css'
import { ThemeContext } from './context.js'
const ThemeSwitcher = () => (
<ThemeContext.Consumer>
{({ theme, toggleTheme }) => (
<input
type="checkbox"
checked={theme === 'dark'}
onChange={toggleTheme}
/>
)}
</ThemeContext.Consumer>
)
const App = () => {
const [theme, setTheme] = useState('light')
const toggleTheme = () => setTheme(theme === 'dark' ? 'light' : 'dark')
const contextValue = {
toggleTheme,
theme,
}
return (
<ThemeContext.Provider value={contextValue}>
<div>
<p>this is a switcher theme</p>
<span>Dark mode</span>
<ThemeSwitcher />
</div>
</ThemeContext.Provider>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
context.js code
import React, { createContext } from "react";
export const ThemeContext = createContext({
theme: "light",
toggleTheme: () => {}
});
I am trying to test a component which use useTheme hook provided by emotion.js. The theme is set during the app initialization.
Now when I write my test cases, the useTheme hook is unable to fetch the styles data as only header component is getting mounted for testing. How to mock the data provided by hooks.
app.js
import { theme } from '../src/utils/styles/themes/default';
const AppRoot = ({ path, Router }) => {
const routeRenderFunction = (props) => <RouteHandler route={props} />;
return (
<ThemeProvider theme={theme}>
<Router location={path} context={{}}>
<Switch>
{routePatterns.map((routePattern) => (
<Route key={routePattern} path={routePattern} render={routeRenderFunction} />
))}
</Switch>
</Router>
</ThemeProvider>
);
};
header.js
import React from "react";
import { css } from "emotion";
import { useTheme } from "emotion-theming";
import * as styles from "./Header.style";
const Header = ({userName = 'Becky Parsons', clinicName = 'The University of Southampton'}) => {
const theme = useTheme();
return (
<div className={css(styles.accountDetails(theme))}>
<div className={css(styles.accountContainer)}>
<div className={css(styles.accountSpecifics)}>
<h5 className={css(styles.accountDetailsH5(theme))} data-testid="user-name">{userName}</h5>
<h6 className={css(styles.accountDetailsH6(theme))} data-testid="clinic-name">
{clinicName}
</h6>
</div>
<div className={css(styles.avatar)} />
</div>
</div>
);
};
export default Header;
header.test.js
import React from 'react'
import {render} from '#testing-library/react';
import Header from './Header';
test('Check if header component loads', () => {
const { getByTestId } = render(<Header userName='Becky Parsons' clinicName='The University of Southampton'/>);
expect(getByTestId('user-name').textContent).toBe('Becky Parsons');
expect(getByTestId('clinic-name').textContent).toBe('The University of Southampton');
})
header.style.js
export const accountDetails = theme => ({
height: '70px',
backgroundColor: theme.header.backgroundColor,
textAlign: 'right',
padding: '10px 40px'
});
export const accountContainer = {
display: 'flex',
justifyContent: 'flex-end'
};
Error: Uncaught [TypeError: Cannot read property 'backgroundColor' of undefined]
You should wrap your component with ThemeProvider, try this;
test('Check if header component loads', () => {
const { getByText } = render(
<ThemeProvider theme={your theme object...}>
<Header userName='Becky Parsons' clinicName='The University of Southampton'/>
</ThemeProvider >
);
...
});
and you can look here: https://github.com/emotion-js/emotion/blob/master/packages/emotion-theming/tests/use-theme.js
I am trying to use a navbar from the Material UI collection however the component was written as a function and was using hooks. I'm trying to convert the component to a HOC (class) and I am having issues with accessing the theme in the component. Theme in the component in undefined
const styles = theme => ({
root: {
display: "flex"
},
});
<IconButton onClick={this.handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
Try this:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core';
import Paper from './Paper';
const styles = () => ({
root: {
display: 'flex'
}
});
const Bubble = props => {
const { classes } = props;
return (
<IconButton className={classes.root} onClick={this.handleDrawerClose}></IconButton>
);
};
export default withStyles(styles)(Bubble);
I'm trying to run some simple unit tests on React with Material UI but I'm getting an error that I don't know the cause of, I've ran tests with MaterialUI on other projects with no problems.
This is the code of the component I'm trying to test:
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import ReactRouterPropTypes from 'react-router-prop-types';
import { Icon } from 'react-fa';
import { withStyles } from 'material-ui/styles';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import MaterialIcon from 'material-ui/Icon';
import IconButton from 'material-ui/IconButton';
import Modal from 'material-ui/Modal';
import Hidden from 'material-ui/Hidden';
import SimpleButton from '../../common/SimpleButton';
import OutlineButton from '../../common/OutlineButton';
import * as sessionActions from '../../../actions/sessionActions';
import { APP_TITLE, LOGOUT, LOGIN, REGISTER } from '../../../constants/strings';
import { CHECKOUT } from '../../../constants/routes';
import styles from './styles';
import CartIcon from '../../common/Icons/Cart.svg';
import logo from '../../../images/logo.png';
import logoXs from '../../../images/logoXS.png';
import Login from '../Login';
import Register from '../Register';
import Wrapper from '../../common/Wrapper';
class Header extends Component {
constructor(props) {
super(props);
this.state = {};
}
handleLogoutClick = () => {
this.props.actions.logout();
};
handleOpenModal = (s) => {
this.setState({ openModal: s });
};
handleCloseModal = () => {
this.setState({ openModal: null });
};
handleCartIcon = () => {
this.props.history.push(CHECKOUT);
};
renderAuthenticated = () => (
<SimpleButton onClick={this.handleLogoutClick} className={this.props.classes.button}>
{LOGOUT}
</SimpleButton>
);
renderNotAuthenticated = () => {
const { classes, history } = this.props;
return (
<Fragment>
<SimpleButton onClick={() => this.handleOpenModal(LOGIN)} className={classes.button} id="btn-login-modal">
<Hidden smUp>
<Icon className={classes.icons} name="user-circle" alt="login" size="2x" />
</Hidden>
<Hidden xsDown>{LOGIN}</Hidden>
</SimpleButton>
<Modal open={this.state.openModal === LOGIN} onClose={this.handleCloseModal}>
<Login
history={history}
handleCloseModal={this.handleCloseModal}
handleChangeModal={this.handleOpenModal}
/>
</Modal>
<Hidden xsDown>
<OutlineButton
onClick={() => this.handleOpenModal(REGISTER)}
color="primary"
className={classes.button}
>
{REGISTER}
</OutlineButton>
<Modal open={this.state.openModal === REGISTER} onClose={this.handleCloseModal}>
<Register
history={history}
handleCloseModal={this.handleCloseModal}
handleChangeModal={this.handleOpenModal}
/>
</Modal>
</Hidden>
</Fragment>
);
};
render() {
const { authenticated, classes } = this.props;
return (
<AppBar position="static" color="default" className={classes.appBar}>
<Wrapper>
<Toolbar className={classes.toolBar}>
<div className={classes.flex}>
<Hidden smUp>
<img src={logoXs} alt={APP_TITLE} />
</Hidden>
<Hidden xsDown>
<img src={logo} alt={APP_TITLE} />
</Hidden>
</div>
{authenticated ? this.renderAuthenticated() : this.renderNotAuthenticated()}
<IconButton className={classes.button} onClick={() => this.handleCartIcon()}>
<MaterialIcon>
<img src={CartIcon} alt="cart" />
</MaterialIcon>
</IconButton>
</Toolbar>
</Wrapper>
</AppBar>
);
}
}
const { objectOf, any, bool } = PropTypes;
Header.propTypes = {
/* eslint-disable react/no-typos */
classes: objectOf(any).isRequired,
actions: objectOf(any).isRequired,
authenticated: bool.isRequired,
history: ReactRouterPropTypes.history.isRequired,
};
const mapDispatch = dispatch => ({
actions: bindActionCreators(sessionActions, dispatch),
});
const mapStateToProps = ({ session }) => ({
authenticated: session.authenticated,
checked: session.checked,
});
export default connect(mapStateToProps, mapDispatch)(withStyles(styles)(Header));
This is the test file:
import React from 'react';
import { shallow } from 'enzyme';
import Header from './';
import configureStore from '../../../store/configureStore';
const store = configureStore();
const header = shallow(<Header store={store} />);
describe('<Header />', () => {
it('should open modal when clicking `SimpleButton`', () => {
console.log(header.dive().debug());
});
});
And in when I run the tests I get this:
FAIL src/components/containers/Header/Header.test.js ● <Header /> › should open modal when clicking `SimpleButton`
TypeError: Cannot read property '200' of undefined
6 | padding: '10px 5px',
7 | boxShadow: `-webkit-box-shadow: 0px 4px 10px 0px ${
> 8 | theme.palette.gray[200]
9 | }; -moz-box-shadow: 0px 4px 10px 0px ${theme.palette.gray[200]}; box-shadow: 0px 4px 10px 0px ${
10 | theme.palette.gray[200]
11 | }`,
at styles (src/components/containers/Header/styles.js:8:7)
at Object.create (node_modules/material-ui/styles/getStylesCreator.js:31:35)
at WithStyles.attach (node_modules/material-ui/styles/withStyles.js:275:45)
at WithStyles.componentWillMount (node_modules/material-ui/styles/withStyles.js:205:16)
at ReactShallowRenderer._mountClassComponent (node_modules/enzyme-adapter-react-16/node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js:137:22)
at ReactShallowRenderer.render (node_modules/enzyme-adapter-react-16/node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js:102:14)
at node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:287:35
at withSetStateAllowed (node_modules/enzyme-adapter-utils/build/Utils.js:94:16)
at Object.render (node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:286:68)
at new ShallowWrapper (node_modules/enzyme/build/ShallowWrapper.js:119:22)
at ShallowWrapper.wrap (node_modules/enzyme/build/ShallowWrapper.js:1648:16)
at ShallowWrapper.<anonymous> (node_modules/enzyme/build/ShallowWrapper.js:1718:26)
at ShallowWrapper.single (node_modules/enzyme/build/ShallowWrapper.js:1620:25)
at ShallowWrapper.dive (node_modules/enzyme/build/ShallowWrapper.js:1710:21)
at Object.<anonymous> (src/components/containers/Header/Header.test.js:12:24)
I've tried using mount, wrapping the component in MuiThemeProvider, using the helper createShallow or createMount from MaterialUI.
Nothing works.
AppBar requires a muiTheme context, which you can provide to .dive() (see documentation)
Here is my test for a Header component rendering an AppBar material-ui component.
import React from "react";
import {shallow} from "enzyme";
import Header from "../";
describe("Header", () => {
it("should match the snapshot", () => {
const wrapper = shallow(<Header />);
// dive in AppBar
const wrappedComponents = wrapper.dive({
context: {
muiTheme: {
appBar: "",
zIndex: 0,
prepareStyles: () => {},
button: {
iconButtonSize: 0,
},
},
},
});
expect(wrappedComponents).toMatchSnapshot();
});
});