So I am a bit new to react and I am trying to set up a toggled theme for my website.
These are the instructions I followed -"https://css-tricks.com/a-dark-mode-toggle-with-react-and-themeprovider/"
Note: My app is a react + NetCore app.
Whenever I use the ThemeProvider this is the error I get:
"Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem."
Here is my App.js code:
import React, { Component } from 'react';
import { Route } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { ThemeProvider } from 'styled-components';
import { GlobalStyles } from './components/GlobalTheme'
import { LightTheme, DarkTheme } from './components/Themes';
export default class App extends Component {
displayName = App.name
render() {
return (
<ThemeProvider theme={DarkTheme}>
<div>
<GlobalStyles />
<Layout>
<Route exact path='/' component={Home} />
</Layout>
</div>
</ThemeProvider>
)}
}
Here is my package.json file:
{
"name": "NotePadApp",
"version": "0.1.0",
"private": true,
"dependencies": {
"bootstrap": "^3.4.1",
"react": "17.0.2",
"react-redux": "^6.0.0",
"react-bootstrap": "^0.31.5",
"react-dom": "17.0.2",
"react-router-bootstrap": "^0.24.4",
"react-router-dom": "5.2.0",
"react-scripts": "1.0.17",
"rimraf": "^2.6.2"
},
"scripts": {
"start": "rimraf ./build && react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
Here is GlobalStyle:
//Global Themes
import { createGlobalStyle } from 'styled-components';
export const GlobalStyles = createGlobalStyle`
*,
*:: after,
*:: before {
box - sizing: border - box;
}
body {
background: ${({ theme }) => theme.bodyBackGroundColor} !important;
color: ${({ theme }) => theme.textColor} !important;
}
`;
Here is themes.js:
//Themes
export const DarkTheme = {
bodyBackGroundColor: '#222222',
textColor: '#ffffff',
}
export const LightTheme = {
bodyBackGroundColor: '#ffffff',
textColor: '#222222',
}
These are the suggestions given to me and I have looked into them as best as I could with no luck.
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
The problem is that you are passing JSON in ThemeProvider's theme prop but you should pass createMuiTheme. So your code becomes:
import React, { Component } from 'react';
import { Route } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { ThemeProvider, createMuiTheme } from '#material-ui/core/styles'; //<-- import createMuiTheme
import { GlobalStyles } from './components/GlobalTheme'
import { LightTheme, DarkTheme } from './components/Themes';
export default class App extends Component {
displayName = App.name
const theme = createMuiTheme(DarkTheme); //<-- here create mui theme calling createMuiTheme with DarkTheme JSON
render() {
return (
<ThemeProvider theme={theme}> //<-- pass theme to ThemeProvider
<div>
<GlobalStyles />
<Layout>
<Route exact path='/' component={Home} />
</Layout>
</div>
</ThemeProvider>
)}
}
Related
When I test the first case when a component is routed, and test another case, the previous case still appears in the test DOM.
After the test case : "renders properly" rendered a route to the Header component,
and the test case: "navigates to / on header title click" fails because the Header component is appearing more than once because the test DOM didn't clear the first case. I hope this image helps.
import * as React from 'react'
import {vi, describe, it, expect} from 'vitest'
import { fireEvent, waitFor} from '#testing-library/react'
import { Header } from "./Header"
import { renderWithRouter } from '../testHelpers'
vi.mock("./../CartWidget", ()=> ({
CartWidget: ()=> <div>Hello Widget</div>
}))
describe('Header Shared Component', ()=> {
it('renders properly', ()=> {
const {container} = renderWithRouter(()=><Header/>)
expect(container.innerHTML).toMatch('Goblin Store')
expect(container.innerHTML).toMatch('Hello Widget')
})
it('navigates to / on header title click', async()=> {
const {getByText, history} = renderWithRouter(() => <Header />);
fireEvent.click(getByText("Goblin Store"));
await waitFor (()=>expect(history.location.pathname).toEqual("/"))
})
})
// renderWithRouter
import * as React from 'react'
import * as ReactDom from 'react-router-dom';
import {createMemoryHistory, MemoryHistory} from 'history'
import {render, RenderResult} from '#testing-library/react'
const {Router} = ReactDom;
export const renderWithRouter: RenderWithRouter = (renderComponent, route)=>{
const history = createMemoryHistory()
if(route){
history.push(route)
}
return {
...render(
<Router history={history}>
{renderComponent()}
</Router>),
history
}
}
type RenderWithRouter = (
renderComponent: ()=> React.ReactNode,
route?: string
)=> RenderResult & { history: MemoryHistory }
export const Header = () => (
<header>
<div className="container">
<div className="nav-brand">
<Link to="/">Goblin Store</Link>
<p>Everything for your Typescript adventure</p>
</div>
...
</div>
</header>
);
I have read the react router official DOC with testing for react-router-dom v5, but it appears to be same on my end.
Here are my dependencies
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^5.3.4",
"#testing-library/jest-dom": "^5.16.5",
"#testing-library/react": "^13.4.0",
"vite": "^4.0.0",
"vitest": "^0.26.3"
I'm trying to use react-helmet in a React Typescript component like so
import { Helmet } from 'react-helmet'
interface TestComponentProps {}
const TestComponent: React.FC<TestComponentProps> = () => {
return (
<>
<Helmet>
<title>Content</title>
</Helmet>
</>
)
}
export default TestComponent
But I'm getting a TS error 'Helmet' cannot be used as a JSX component. Shown below
I've added the following dependencies with yarn
"#types/react-helmet": "^6.1.5",
"react-helmet": "^6.1.0",
How can I fix this TS error?
I was trying to upgrate material v5(from v4). As per documentation , it is recommended to use
Styled or sx API but I wanted to use makeStyles approach as in v4. Even though it is deprecated in v5 but can be used(as per documentation). But I am unable to get theme variable inside makeStyles callback function causing cann't access property of undefined(eg. theme.breakpoints.between...)
import makeStyles from '#mui/styles/makeStyles';
const useStyles = makeStyles((theme) => ({
// here theme variable is undefined
appBar: {
backgroundColor: "white",
height: "60px",
padding: "0px 5em",
[theme.breakpoints.between("xs", 'lg')]: {
padding: "0px",
},
},
.......
Here is my index.js
import { ThemeProvider, StyledEngineProvider } from "#mui/material/styles";
import theme from "./theme/theme"
ReactDOM.render(
<React.StrictMode>
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
</StyledEngineProvider>
</React.StrictMode>,
document.getElementById("root")
);
theme.js
import { createTheme, responsiveFontSizes, adaptV4Theme } from "#mui/material/styles";
import baseTheme from "./baseTheme";
let theme = createTheme(adaptV4Theme(baseTheme));
theme = responsiveFontSizes(theme);
export default theme;
Also I noticed, StyledEngineProvider is not being property imported(followed this)
package.json
"#emotion/react": "^11.5.0",
"#emotion/styled": "^11.3.0",
"#fortawesome/fontawesome-svg-core": "^1.2.32",
"#fortawesome/free-solid-svg-icons": "^5.15.1",
"#fortawesome/react-fontawesome": "^0.1.13",
"#mui/icons-material": "^5.1.0",
"#mui/lab": "^5.0.0-alpha.54",
"#mui/material": "^5.1.0",
"#mui/styles": "^5.1.0",
Do I have to install additional package to make it working?
Okay, just had this problem. The makeStyles and styled uses a different context to get theme. In order for your makeStyles to access the theme you need import { ThemeProvider } from '#mui/styles'. But to use styled(), you need import { ThemeProvider } from '#mui/material'.
And if you use both styled() AND makeStyles for migration, you need to have BOTH of those theme providers....
I am testing out the react-redux-firebase module and I have followed the updated documentation:http://docs.react-redux-firebase.com/history/v3.0.0/docs/v3-migration-guide.html. I have checked that my imports are correct, and I'm still receiving this error
"Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports."
I have tried to make my App component a class component and a functional component and double checked my imports.
// package.json
{
"name": "default-react",
"version": "0.1.0",
"private": true,
"dependencies": {
"firebase": "^6.3.1",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-redux": "^7.1.0",
"react-redux-firebase": "^2.3.0",
"react-router-dom": "^4.4.0-beta.8",
"react-scripts": "3.0.1",
"recompose": "^0.30.0",
"redux": "^4.0.4",
"redux-firestore": "^0.8.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { ReactReduxFirebaseProvider } from 'react-redux-firebase'
import createReduxStore from './store'
import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/database'
import 'firebase/firestore'
const fbConfig = {
config options
}
const rrfConfig = { userProfile: 'users' } // react-redux-firebase config
// Initialize firebase instance
firebase.initializeApp(fbConfig)
const store = createReduxStore()
const rrfProps = {
firebase,
config: rrfConfig,
dispatch: store.dispatch,
// createFirestoreInstance // <- needed if using firestore
}
ReactDOM.render(
<Provider store={store}>
<ReactReduxFirebaseProvider {...rrfProps}>
<App />
</ReactReduxFirebaseProvider>
</Provider>,
document.getElementById('root'));erviceWorker.unregister();
//store/index.js
import { createStore } from 'redux'
import reducer from './reducers'
const initialState = {}
export default () => {
return createStore(
reducer,
initialState,
// applyMiddleware(...middleware) // to add other middleware
)
}
//reducers/index.js
import { combineReducers } from 'redux'
import { firebaseReducer } from 'react-redux-firebase'
// import { firestoreReducer } from 'redux-firestore' // <- needed if using firestore
// Add firebase to reducers
export default combineReducers({
firebase: firebaseReducer
// firestore: firestoreReducer // <- needed if using firestore
})
//App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { connect } from 'react-redux'
import { compose } from 'recompose';
import PropTypes from 'prop-types'
import { firestoreConnect } from 'react-redux-firebase'
class App extends Component {
constructor(){
super();
this.state = {
todos:[]
}
}
render(){
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
}
export default compose(
firestoreConnect(['orders']),
connect((state) => ({
todos: [1,2,3]
})))(App)
ReactReduxFirebaseProvider is only available in v3.0.0 versions of react-redux-firebase, but you are still on v2.3 as per your package json.
"react-redux-firebase": "^2.3.0",
Reference
I have a material-ui navbar with a button on the left hand side, that button brings up a left hand drawer the only trouble is the drawer refuses to close on press of esc or on press of overlay.
I am using material-ui#next so the beta build of v1 so it is possible this is a bug with the material-ui people but given that I have never used the Drawer component before I thought I would check here first.
When I then click on the button at top left i get a drawer pop up but sadly I cannot remove it by hitting esc nor can i click on the overlay to remove it which I assumed was handled by
onRequestChange={this.handleClose}
in components/LeftDrawer but instead i get a warning in chrome which reads:
Warning: Unknown prop `onRequestChange` on <div> tag. Remove this prop from the element.
React strips such props but in the documentation I am seeing this being onRequestChange being used for the Drawer so I really do not know what is going on here.
Hoping someone can point to the problem whatever it might be (hoping it is just the result of me being tired and at my wits end.
package.json
"dependencies": {
"babel-loader": "^7.1.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"file-loader": "^0.11.2",
"history": "^4.6.3",
"material-ui": "^1.0.0-beta.5",
"material-ui-icons": "^1.0.0-alpha.19",
"react": "^15.4.0",
"react-dom": "^15.4.0",
"react-router": "^4.1.2",
"react-router-dom": "^4.1.2",
"react-tap-event-plugin": "^2.0.1"
},
"devDependencies": {
"babel-core": "^6.25.0",
"html-webpack-plugin": "^2.30.1",
"webpack": "^3.5.4",
"webpack-dev-server": "^2.7.1"
}
index.js
import React from 'react'
import ReactDom from 'react-dom'
import App from './components/App'
import {BrowserRouter, Route} from 'react-router-dom'
var injectTapEventPlugin = require("react-tap-event-plugin");
injectTapEventPlugin();
ReactDom.render(
<div>
<BrowserRouter>
<Route path="/" component={App}/>
</BrowserRouter>
</div>,
document.getElementById('app')
);
components/App.js
import React from 'react'
import FinalAppBar from './AppBar'
export default class App extends React.Component {
render() {
return (
<div>
<FinalAppBar/>
</div>
);
}
}
components/FinalAppBar.js
// #flow weak
import React from 'react';
import AppBar from 'material-ui/AppBar';
import PropTypes from 'prop-types';
import LeftDrawer from './LeftDrawer'
import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import IconButton from 'material-ui/IconButton';
import MenuIcon from 'material-ui-icons/Menu';
import { withStyles } from 'material-ui/styles';
class FinalAppBar extends React.Component {
constructor() {
super();
this.state = {
open: false
}
}
//Toggle function (open/close Drawer)
toggleDrawer() {
this.setState({
open: !this.state.open
})
}
handleClose() {
this.setState({open: false})
}
render() {
const classes = this.props.classes;
return (
<div>
<AppBar position="static">
<Toolbar>
<IconButton color="contrast" aria-label="Menu" onClick={() => this.toggleDrawer()}>
<MenuIcon />
</IconButton>
<Typography type="title" color="inherit" className={classes.flex}>
Title
</Typography>
<Button color="contrast">Login</Button>
</Toolbar>
</AppBar>
<LeftDrawer open={this.state.open}
handleClose={this.handleClose.bind(this)}
onToggleDrawer={this.toggleDrawer.bind(this)}
/>
</div>
)
}
}
FinalAppBar.propTypes = {
classes: PropTypes.object.isRequired,
};
const styles = {
root: {
marginTop: 30,
width: '100%',
},
flex: {
flex: 1,
},
};
export default withStyles(styles)(FinalAppBar);
components/LeftDrawer
import React from 'react';
import Drawer from 'material-ui/Drawer';
export default class LeftDrawer extends React.Component {
constructor(props) {
super(props);
this.state = {open: false};
}
handleClose() {
this.props.handleClose();
}
render() {
return (
<div>
<Drawer
docked={false}
width={600}
open={this.props.open}
onRequestChange={this.handleClose}
>
<li>Hello cruel world</li>
</Drawer>
</div>
);
}
}
EDIT
Ok so this is what works perfectly in the latest stable version of react:
export default class FinalAppBar extends React.Component {
constructor(props) {
super(props);
this.state = {open: false};
this.handleToggle = () => this.setState({open: !this.state.open});
this.handleClose = () => this.setState({open: false});
}
render() {
return (
<div>
<AppBar
title="Title"
iconClassNameRight="muidocs-icon-navigation-expand-more"
onLeftIconButtonTouchTap={this.handleToggle}
/>
<Drawer
docked={false}
width={200}
open={this.state.open}
onRequestChange={(open) => this.setState({open})}
>
<MenuItem onClick={this.handleClose}>Menu Item</MenuItem>
<MenuItem onClick={this.handleClose}>Menu Item 2</MenuItem>
</Drawer>
</div>)
}
}
Going to work on proving the answer from logan below
Looks like in Material 1.0 onRequestChange may have been changed to be onRequestClose.
The Drawer view does not mention either one and passes all unknown props through to <Modal /> here and in Modal.js the callback is onRequestClose.
This also appears to match the docs in the file itself: https://github.com/callemall/material-ui/blob/9a6a3dde01d51de89c0b7ef3fd911cb3b8c798f4/src/Drawer/Drawer.js#L124