React exporting withRouter and withStyles error - reactjs

I am using react along with redux and material-ui to make a component. I am attempting to write an export statement export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))
However, this doesn't seem to work I get an error that says
TypeError: Cannot set property 'props' of undefined
this.props = props;
This error is referencing one of my node_modules.
Here is my full code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {withRouter} from 'react-router-dom'
import { withStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import Button from '#material-ui/core/Button';
const styles = theme =>({
root: {
maxWidth: 345,
},
})
class FirstPage extends Component {
state = {
feeling: ''
}
//This function will dispatch the users response to index.js
//The dispatch type here is 'SET_FEELING'
submitData=(event) => {
event.preventDefault();
this.props.dispatch({type: 'SET_FEELING', payload: this.state})
this.changeLocation();
}
//This function will update the local state with the users response
handleChange= (event) => {
this.setState({
feeling: event.target.value
})
}
//This function will change the current url when a button is clicked
changeLocation= ()=> {
this.props.history.push('/secondPage')
}
render(){
const { classes } = this.props;
return(
<div>
<Card >
<CardContent className={classes.root}>
<form>
<input onChange={this.handleChange} placeholder='How are you feeling' value={this.state.feeling} />
</form>
</CardContent>
<CardActions>
<Button onClick={this.submitData}>Submit</Button>
</CardActions>
</Card>
</div>
)
}
}
//this export connects the component to the reduxStore as well as allowing us to use the history props
export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))

I believe the following code should work:
export default withRouter(connect()(withStyles(styles)(FirstPage)))
Instead of
export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))
First of all, connect() returns a function that only accepts an argument. Second, connect() should be wrapped inside withRouter(). This problem is stated in the github docs of React Router.

without using react-redux :
export default (withStyles(styles), withRouter)(FirstPage);

Related

const { authenticated } = this.props is undefined though I can clearly see it in redux

I have just copied some code from one project to another , and console.log("this is authenticated: " + authenticated) is returning undefined although i can see authenticated as true - in redux state (when i am in local host click right click inspect then go to redux then state.)
?
is this perhaps an issue with my version of react or redux?
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import Proptypes from "prop-types";
import MyButton from "./util/MyButton";
import { logoutUser } from "./redux/actions/userActions";
import { getConversations } from "./redux/actions/dataActions";
//icons
import KeyboardReturn from "#material-ui/icons/KeyboardReturn";
///npm install #material-ui/core
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Button from "#material-ui/core/Button";
import { auth } from "./firebase";
export class Navbar extends Component {
render() {
const { authenticated } = this.props;
console.log("this is authenticated: " + authenticated);
const { user } = this.props;
return (
<AppBar>
<Toolbar className="nav-container">
{authenticated ? (
<Fragment>
<Link to="/login">
<MyButton
tip="Logout"
onClick={this.handleLogout}
>
<KeyboardReturn color="primary" />
</MyButton>
</Link>
</Fragment>
) : (
<Fragment>
<Button
color="inherit"
component={Link}
to="/login"
onClick={this.handleLogout}
>
{" "}
Login
</Button>
<Button
color="inherit"
component={Link}
to="/signup"
>
{" "}
SignUp
</Button>
</Fragment>
)}
</Toolbar>
</AppBar>
);
}
}
const mapStateToProps = state => ({
authenticated: state.user.authenticated,
user: state.user,
conversations: state.data
});
const mapActionsToProps = {
getConversations,
logoutUser
};
export default connect(mapStateToProps, mapActionsToProps)(Navbar);
Background
When I say named and default exports/imports, I'm referring to es-module exports and es-module imports.
To simplify what the above article states, think of the two as such:
named exports are objects which contain all the namespaces provided to it. For instance, export const a; export const b; will produce an object {a, b} which will be accessible to other modules by way of import {a,b} from './file' or other variants.
default exports are just named exports that contain the access modifier default in it. In other words, it's just syntatic sugar which allows you to import the default object directly without destructuring and also provides you the ability to rename the object. For example export default ReactInner will allow you to import React from "react" without any errors. However, note that ReactInner === React.
Answer
You're most likely importing the wrong component
import {NavBar} from "../file"
which imports the named component (unconnected component, in your case).
Instead, you should have used
import NavBar from "../file"
which imports the default component (the connected component, in your case)
The reason the functional component works is because redux uses context internally to connect the state of the component to the redux store. As a result, you don't require two exports and the exported components will always be connected.

How can I update state in two separate components using a custom hook?

I am trying to create a custom hook to be able to open and close a pop-out menu with conditional rendering using style display: none and display:block. I think I understand how to share the state between the components (I can console log that and get that working) , but I can not figure out how to update the state using the hook.
I am certain that I have some fundamental misunderstanding here but if anyone can clarify what it is I am trying to achieve that would be awesome! I have tried to learn this for several nights and here is where I have got to.
This is the header of the pop out menu it only contains a close button at the moment
import React from 'react'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faWindowClose } from '#fortawesome/free-solid-svg-icons'
import useOpenCloseElementMenu from '../Hooks/openCloseElementMenu'
function ElementMenuHeader() {
const { elementMenuOpenClose, setElementMenuOpenClose } = useOpenCloseElementMenu();
return (
<div id="App-Close-Element-Menu-Container">
<button id="App-Close-Element-Menu"
onClick={() => setElementMenuOpenClose(false) }
>
<FontAwesomeIcon icon={faWindowClose} />
</button>
</div>
);
}
export default ElementMenuHeader
This is the pop out menu
import React from 'react';
import SizerGroup from '../Sizer/sizerGroup';
import './element-menu.css';
import ElementMenuHeader from './element-menu-header';
import TitleWithLine from './title-with-line';
import TypeSelector from './type-selector';
import TemplateSelector from './template-selector';
import useOpenCloseElementMenu from '../Hooks/openCloseElementMenu'
function Editor(props) {
const { elementMenuOpenClose, setElementMenuOpenClose } = useOpenCloseElementMenu();
console.log(elementMenuOpenClose);
return (
<div className="App-Element-Menu"
style={{display: elementMenuOpenClose ? 'block' : 'none' }}
>
<ElementMenuHeader />
<TitleWithLine title="Element size" />
<SizerGroup />
<TitleWithLine title="Elements" />
<TypeSelector />
<TitleWithLine title="Templates" />
<TemplateSelector />
</div>
);
}
export default Editor
This is the toolbar that has the open menu button
import React from 'react'
import Button from '../Button/button'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faBoxes } from '#fortawesome/free-solid-svg-icons'
import './toolbar.css'
import useOpenCloseElementMenu from '../Hooks/openCloseElementMenu'
function Toolbar(props) {
const { toolbar_show_or_hide } = props
const elementMenuIcon = <FontAwesomeIcon icon={ faBoxes } />
const { elementMenuOpenClose, setElementMenuOpenClose } = useOpenCloseElementMenu();
const openEditor = setElementMenuOpenClose[true]
return (
<div className="App-Toolbar" style={{ display: toolbar_show_or_hide ? "flex" : "none" }} >
<Button
id="App-Open-Element-Menu-Button"
icon={ elementMenuIcon }
useToolTip={ true }
toolTipText="Elements menu. Select elements to populate the theme."
buttonFunction={ openEditor }
/>
</div>
)
}
export default Toolbar
This is the hook
import React, { useState } from 'react';
const useOpenCloseElementMenu = () => {
const [elementMenuOpenClose, setElementMenuOpenClose] = useState(false);
return { elementMenuOpenClose, setElementMenuOpenClose };
};
export default useOpenCloseElementMenu;
I feel you donot have to pass the hook in a separate function, useOpenCloseElementMenu, like you did.
Instead of importing the ../Hooks/openCloseElementMenu function thing,
I'd rather just call the hooks directly instead as
const [elementMenuOpenClose, setElementMenuOpenClose] = useState(false);
in the editor and toolbar component in place of const { elementMenuOpenClose, setElementMenuOpenClose } = useOpenCloseElementMenu();.
Also which component did you use the toolbar component if I may ask? Because I don't seem to see any of that here...It confusing where it got the toolbar_show... props from.
{I hope this helps cause that seem like the most obvious reason}

Get ref from connected redux component withStyles

I have this export of a working component:
export default connect(
mapStateToProps, actions,
null, { withRef: true, forwardRef: true }
)(withTheme()(withStyles(styles)(MainMenu)));
And its call:
<MainMenu
ref={(connectedMenu) => this.menuRef = connectedMenu.getWrappedInstance()}
user={user}
/>
I've expected to get a MainMenu ref, but I keep getting WithTheme object instead.
I've also tried to get through innerRef, but got the following errors:
TypeError: connectedMenu.getWrappedInstance is not a function
TypeError: Cannot read property 'getWrappedInstance' of null
Before all of that I've tried that React.createRef() format, but it didn't worked.
How do I get this ref?
Assuming you are using v4 of Material-UI, your syntax for withTheme is incorrect. In v4 the first set of parentheses was removed.
Instead of
withTheme()(YourComponent)
you should have
withTheme(YourComponent)
Below is code from a modified version of the react-redux todo list tutorial that shows the correct syntax. I've included here the two files that I changed (TodoList.js and TodoApp.js), but the sandbox is a fully working example.
In TodoApp, I use the ref on TodoList to get and display its height. The displayed height will only get updated if TodoApp re-renders, so I've included a button to trigger a re-render. If you add a couple todos to the todo list, and then click the re-render button, you will see that the new height of the list is displayed (showing that the ref is fully working).
In TodoList, I'm using withStyles to add a blue border around the todo list to show that withStyles is working, and I'm displaying the primary color from the theme to show that withTheme is working.
TodoList.js
import React from "react";
import { connect } from "react-redux";
import Todo from "./Todo";
import { getTodosByVisibilityFilter } from "../redux/selectors";
import { withStyles, withTheme } from "#material-ui/core/styles";
import clsx from "clsx";
const styles = {
list: {
border: "1px solid blue"
}
};
const TodoList = React.forwardRef(({ todos, theme, classes }, ref) => (
<>
<div>theme.palette.primary.main: {theme.palette.primary.main}</div>
<ul ref={ref} className={clsx("todo-list", classes.list)}>
{todos && todos.length
? todos.map((todo, index) => {
return <Todo key={`todo-${todo.id}`} todo={todo} />;
})
: "No todos, yay!"}
</ul>
</>
));
const mapStateToProps = state => {
const { visibilityFilter } = state;
const todos = getTodosByVisibilityFilter(state, visibilityFilter);
return { todos };
};
export default connect(
mapStateToProps,
null,
null,
{ forwardRef: true }
)(withTheme(withStyles(styles)(TodoList)));
TodoApp.js
import React from "react";
import AddTodo from "./components/AddTodo";
import TodoList from "./components/TodoList";
import VisibilityFilters from "./components/VisibilityFilters";
import "./styles.css";
export default function TodoApp() {
const [renderIndex, incrementRenderIndex] = React.useReducer(
prevRenderIndex => prevRenderIndex + 1,
0
);
const todoListRef = React.useRef();
const heightDisplayRef = React.useRef();
React.useEffect(() => {
if (todoListRef.current && heightDisplayRef.current) {
heightDisplayRef.current.innerHTML = ` (height: ${
todoListRef.current.offsetHeight
})`;
}
});
return (
<div className="todo-app">
<h1>
Todo List
<span ref={heightDisplayRef} />
</h1>
<AddTodo />
<TodoList ref={todoListRef} />
<VisibilityFilters />
<button onClick={incrementRenderIndex}>
Trigger re-render of TodoApp
</button>
<div>Render Index: {renderIndex}</div>
</div>
);
}

How to Pass in Store as a prop

I am currently having a problem getting store to be passed in as a prop and am wondering what to label a few things.
The current error is within create store, I'm unsure what to do with it.
I have tried other methods and only want to use the store method where I pass it in as a prop
import React from 'react';
import { MockGit } from './Constants';
import ExpansionPanelSummary from '#material-ui/core/ExpansionPanelSummary';
import ExpansionPanelDetails from '#material-ui/core/ExpansionPanelDetails';
import Typography from '#material-ui/core/Typography';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import ExpansionPanel from '#material-ui/core/ExpansionPanel';
import Button from '#material-ui/core/Button';
import TestAPI from './TestAPI';
import { displayGitData, userInfoURL, getDataSaga } from '../sagas/sagas';
import { createStore } from 'redux';
class GitData extends React.Component {
constructor(props) {
super(props);
}
render() {
const store = createStore(...); //this is what im unsure of.
const { store } = this.props;
return (
<ExpansionPanel>
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
<Typography> {MockGit} </Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
{displayGitData()}
{userInfoURL()}
{getDataSaga()}
<TestAPI />
</ExpansionPanelDetails>
</ExpansionPanel>
);
}
}
export default GitData;
The goal is to get store passed in as a prop with no errors.
Any help would be great, Thanks!
You're doing it wrong, here's the recommended way to use React with Redux:
store.js
import { createStore } from 'redux';
export default createStore(...)
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store.js'
const App = () => (<h1>Hello from App</h1>);
ReactDOM.render(
<Provider store={store}><App/></Provider>
document.querySelector('#react-root')
);
You now have an app that is bound with the store.
The react-redux npm package allows also to bind component props to store dispatches and store state, example:
my-component.js
import React from 'react';
import { connect } from 'react-redux';
class MyComponent extends React.Component {
render() {
return (
<p>{this.props.hello}</p>
)
}
}
export default connect(state => ({hello: state.helloReducer.value}))(MyComponent)
For further tutorials, check the official docs of react-redux, or this good youtube playlist.

How to use refs with react-redux's connect HOC on two components?

I have two components which both use the connect HOC.
import {connect} from "react-redux";
import ComponentB from "./ComponentB";
class ComponentA extends Component {
render(){
return {
<div>
<button
onClick={this.refs.ComponentB.showAlert()}
>
Button
</button>
<ComponentB
ref={instance => {
this.ComponentB = instance.getWrappedInstance();
}}
/>
</div>
}
}
}
export default connect(mapStateToProps, {}, null, {withRef: true})(ComponentA)
Having ComponantA with the connect HOC gives me the error "TypeError: Cannot read property 'getWrappedInstance' of null"
export default ComponantA;
Not using the HOC would not give me this error.
import {connect} from "react-redux";
class ComponentB extends Component {
showAlert = () => {
alert("Please Work");
}
render(){
return {
<div>ComponentB</div>
}
}
}
export default connect(mapStateToProps, {}, null, {withRef: true})(ComponentB)
React.createRef was introduced in React 16.3 and is supposed to be used like:
this.componentBRef = React.createRef();
...
<button
onClick={() => this.componentBRef.current.getWrappedInstance().showAlert()}
>
Button
</button>
<ComponentB
ref={this.componentBRef};
}}
/>
As explained in this answer, the pattern used in createRef allows to lazily access a ref through current property because this.componentBRef.current is initially null.
Since Redux is in use, there's a chance that the interaction between components should be performed via Redux instead.

Resources