I am playing around with redux-orm and after adding an entity my selector is not updating with the new objects in mapStateToProps, hence resulting in render() not being called again. I've been through the tutorials but cannot see what I am doing wrong:
Another component is calling the BookReducer, so I know 100% the reducer is being called.
Here is my reducer:
export default function BookReducer(state = initialState, action) {
const session = orm.session(state);
switch (action.type) {
case 'newBook':
session.Book.create({ id: 0, type: 'novel' });
//I can see verify that the object has been added to the ORM
console.log(session.Book.all().toRefArray())
}
return session.state;
}
Here is my selector:
import orm from './orm';
const BookSelector = createSelector(orm, state => state.orm, session => {
return session.Book.all().toRefArray()
});
Here is my component:
class Main extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log("debug")
console.log(this.props.books)
return (
<div>
{this.props.books}
</div>
)
function mapStateToProps(state) {
return {
isOpen: true,
books: BookSelector(state)
};
}
}
You don't seem to have connected the Main compnent with mapStateToProps . USe connect like export default connect(mapStateToProps)(Main); and define it outside the Main component
import connect from 'react-redux';
class Main extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log("debug")
console.log(this.props.books)
return (
<div>
{this.props.books}
</div>
)
}
function mapStateToProps(state) {
return {
isOpen: true,
books: BookSelector(state)
};
}
export default connect(mapStateToProps)(Main);
Also you need to export and import the BookSelector
import orm from './orm';
const BookSelector = createSelector(orm, state => state.orm, session => {
return session.Book.all().toRefArray()
});
export default BookSelector;
Related
I'm trying to learn to React - Redux. I created an Exam Component like the following:
class ExamHome extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<TabPane>
<Exam />
</TabPane>
)
}
}
export default ExamHome;
I created a Exam React component like the following:
class Exam extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
"hello"
)
}
}
const mapStateToProps = state => {
return {
examUrl: state.examApp.examUrl
}
};
export default connect(mapStateToProps, {
showExam
})(Exam);
Here's my reducer:
let initialState = {
examUrl: null
}
const exam = (state = initialState, action) => {
switch (action.type) {
case "SHOW_EXAM":
return { ...state, examUrl: action.examToken }
default:
return state
}
};
export default exam
And here's my action:
export const showExam = t => {
return dispatch => dispatch({
type : "SHOW_EXAM", examToken : t
})
}
It works fine if i remove connect If I add connect i get the following error:
Error: Element type is invalid: expected a string (for built-in
components) or a class/function (for composite components) but got:
object.
Check the render method of ExamHome.
I tried all the possible solutions I found in Stackoverflow but none of them working.
Thanks for your help.
Edit:
Here's rootReducer.js
import { combineReducers } from "redux"
import { connectRouter } from 'connected-react-router'
import customizer from "./customizer/"
import auth from "./auth/"
import navbar from "./navbar/Index"
import exam from "./exam/exam"
const rootReducer = (history) => combineReducers({
customizer: customizer,
auth: auth,
navbar: navbar,
exam: exam,
router: connectRouter(history)
})
export default rootReducer
You just need to define your dispatch and return to connect function and run it using componentDidMount:
import * as Actions from "path of showExam, Actions file only";
class Exam extends React.Component {
componentDidMount() {
props.onShowExam("any value you want in default");
}
constructor(props) {
super(props)
....
}
const mapStateToProps = state => {
return {
examUrl: state.examApp.examUrl
}
};
const mapDispatchToProps = dispatch => {
return {
onShowExam: (val) => dispatch(Actions.showExam(val))
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Exam);
I am creating a web app where a user chooses a restaurant on one screen and the name of the restaurant is stored and then the menu screen checks the state for the restaurant name and gets the menu items from the server for that restaurant. When I press the button in userscreen.js the first time, the state doesn't change but it does change the next time. However, when I check the state from menu.js it is still the initialized state, which is empty. How do I get the state to not change back to its original value?
Here are the files im working on:
userscreen.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setRestaurant } from '../actions/restaurant';
class Userscreen extends Component {
constructor(props){
super(props);
this.state={
r2:'',
};
this.handleClick1 = this.handleClick1.bind(this);
}
componentDidMount(){
fetch('api/rest-ratings', {
method: 'GET'
})
.then(res => res.json())
.then(body =>{
this.setState({r2: body.C})
})
}
handleClick1(event){
event.preventDefault()
this.props.setRestaurant("ChopChop");
console.log(this.props.rest)
}
render() {
return (
<div>
<img src={user_background} alt="" style= {{width: "100%", height: "auto", margin:"0auto"}}/>
<div id="btn2" onClick={this.handleClick1}>
Chop Chop
<div>
<StarRatingComponent name="ChopChop" editing={false} starCount={5} value={parseInt(this.state.r2)}/>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => ({
rest: state.rest,
})
export default connect(mapStateToProps, { setRestaurant })(Userscreen)
menu.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setRestaurant } from '../actions/restaurant';
class Menu extends Component {
constructor(props){
super(props);
this.state={
rest1: 'abc',
};
}
componentDidMount(){
console.log("mine",this.state.rest1)
console.log("store",this.props.rest)
}
render() {
return (
<div>
{this.state.rest}
</div>
);
}
}
const mapStateToProps = (state) => ({
rest: state.rest
})
export default connect(mapStateToProps, {setRestaurant})(Menu);
restaurant.js
export const setRestaurant = (restaurant) => dispatch => {
dispatch({
type: 'SET_RESTAURANT',
payload: restaurant
})
}
restReducer.js
const initialState = {
restaurant : ''
}
export default function(state = initialState, action ) {
switch(action.type) {
case 'SET_RESTAURANT':
return {
...state,
restaurant: action.payload,
}
default:
return state;
}
}
index.js
import { combineReducers } from 'redux';
import errorReducer from './errorReducer';
import authReducer from './authReducer';
import restReducer from './restReducer';
export default combineReducers({
errors: errorReducer,
auth: authReducer,
rest: restReducer
});
Your dispatch binder function should be defined as a simple action creator if it is going to be used like it is in export default connect(mapStateToProps, {setRestaurant})(Menu);:
export const setRestaurant = restaurant => ({
type: 'SET_RESTAURANT',
payload: restaurant
})
See defining mapDispatchToProps as an object
Also note that the mapStateToProps function passes the returned object via the props (as the name suggests) so using those passed in props should be done through this.props (instead of this.state).
return (
<div>{this.props.rest.restaurant}</div>
)
I am trying to map an action to props however however I'm getting an error:
TypeError: _this2.props.updateUsername is not a function
How does one successfully map redux actions to props and call the function successfully? I havnt seen this error pop up in any other stackoverflow question/answers is it a simple mistake? Could it be a wrong setup of redux in .index or .app?
I have tried:
- importing without using default export
- having different formats of mapDispatchToProps (eg without using bindactioncreators)
- fixing typos
Component:
import { updateUsername } from "../../actions/user-actions";
import React, { Component } from "react";
import { InputText } from "primereact/inputtext";
import { Button } from "primereact/button";
import { Password } from "primereact/password";
import "./UserLogin.css";
import { connect } from "react-redux";
import { bindActionCreators } from 'redux'
export class UserLoginPage extends Component {
constructor(props) {
super(props);
this.state = { //used to be using states so ill leave these here for now
username: "",
password: "",
renderTryAgain: false
};
this.checkLoginDetails.bind(this.checkLoginDetails);
}
async checkLoginDetails() {
...
}
render() {
const usernameBox = (
<InputText
...
value={this.props.username}
onChange={e => this.props.updateUsername(e.target.value)}
/>
);
const passwordBox = (
<Password
...
/>
);
const loginButton = (
<Button
...
/>
);
return (
<header className="User-login">
<p>Dashboard User Login</p>
<div className="p-grid">
<div className="p-col">{usernameBox}</div>
<div className="p-col">{passwordBox}</div>
<div className="p-col">{loginButton}</div>
</div>
</header>
);
}
}
const mapStateToProps = state => ({
username: state.username
});
const mapDispatchToProps = dispatch => bindActionCreators(
{
updateUsername,
},
dispatch,
)
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserLoginPage);
Reducers:
import { UPDATE_USERNAME} from '../actions/user-actions'
export function passReducer(state = "", {type, payload}) {
switch (type) {
case true:
return payload
default:
return state
}
}
export function usernameReducer(state = '', {type, payload}) {
switch (type) {
case UPDATE_USERNAME:
return payload.username
default:
return state
}
}
export default { passReducer, usernameReducer };
Action:
export const UPDATE_USERNAME = 'username:updateUsername'
export function updateUsername(newUsername){
return {
type: UPDATE_USERNAME,
payload: {
username: newUsername
}
}
}
export default {UPDATE_USERNAME, updateUsername}
Many Thanks
Can you check once after updating your constructor as below?
constructor(props) {
super(props);
//...
}
Don't use mapDispatchToProps. Instead just wrap all the actions you want to map inside an object and pass them as the second argument to the connect helper method.
Like this connect(mapStateToProps, { updateUsername })(UserLoginPage)
Hope this helps!
EDIT: I solve my issue and it is working for me now - also edited my code to reflect new changes.
I am getting this error and I am not sure what is the cause of this error.
I cannot show code as it is company's material, so I will try my best to describe it:
App.js:
`class App extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<div>
<Header />
<RouteList />
<Footer />
</div>
)
}
}`
My <RouteList /> is a a stateless function that returns all Routes for the web-application.
Header.js:
class Header extends React.Component {
constructor (props) {
super(props);
this.changeHeader = this.changeHeader.bind(this);
}
changeHeader(headerType) {
this.props.actions.changeHeader(headerType)
}
GetHeader() {
// if-else statement which will return a different sub header class
const HeaderType = this.props.renderHeader.headerType
if (headerType == 'abc') {
<aHeader changeHeader={this.changeHeader} />
} [...] {
// Final else block return something
}
}
render () {
return (
<div>{this.GetHeader()}</div>
)
}
}
function mapStateToProps(state, ownProps) {
return { renderHeader: state.renderHeader};
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(headerActions, dispatch) };
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));
this.props.action.changeHeader(headerType) is an if-else statement which depending on what the value of headerType is, will fire a different action.
state.renderHeader is declared in my rootReducer.
I pass changerHeader() into individual header.js which are stateless (i.e. aHeader.js, bHeader.js...). When a navlink is clicked it will invoke the method and also route the page to another UI. This is how i embed the method into the navlink: onClick={changeHeader(input')}.
rootReducer.js
const rootReducer = combineReducers({renderHeader});
export default rootReducer;
The renderHeader is the renderHeaderReducer.
headerAction.js
export function changeHeader(headerType) {
if (headerType == "abc") {
return {type: type, headerType: "abc"}
} [...] {
// something default
}
}
renderHeaderReducer.js
export default function renderHeaderReducer(state = initialState, action) {
switch(action.type) {
case "abc":
return (Object.assign({}, ...state, {headerType: action.headerType}));
[...];
default:
return state;
}
}
At this point when the link is clicked, the web browser should refresh, leaving the Header in place but modifying the part. However my website goes into an infinite loop, and the error is:
Error: Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
When I did a console.log to see what is going on, it seems to be looping over all the various options that i defined which will render Header.js
It turns out that the main problem was when i called my onClick method.The infinite loop that bugged my code was a result of the onClick function firing even without being clicked.
Original: onClick={this.changeHeader('abc')}
New: onClick={() => changeHeader('abc')}
Refer to this post for an explanation.
Thank you.
time for some pseudo code :)
From what I understand, you have a Header Component which is connected to a rootReducer component which contains the header for which Router Link you are on.
I have some similar code in my application where we use individual components dispatch action to update the rootReducer header. The header just listens for updates using redux and re-renders itself.
class Header extends React.Component {
// render the header
render() {...}
}
const mapStateToProps = (state) => {
return {
header: state.rootReducer.header
}
}
export default withRouter(connect(mapStateToProps, {})(Header));
the component
class MySpecialRouteComponent extends React.Component {
componentDidMount() {
this.props.changeHeader("Special Component")
}
render() {...}
}
const mapStateToProps = (state) => {
return {
...whatever
}
}
export default withRouter(connect(mapStateToProps, {changeHeader})(MySpecialRouteComponent));
you shouldn't make render do the setState in React ever!
I'll just show how I would set everything up to handle this situation.
redux/header-actions.js (call these action creators from your components):
export const changeHeader = (headerType) => {
return {
type: 'CHANGE_HEADER',
payload: {
headerType: headerType
}
}
}
redux/header-reducers.js (note: this will be handled when you call the action):
const INITIAL_STATE = {
headerType: 'header-a'
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case 'CHANGE_HEADER':
return changeHeader(state, action.payload);
default:
return state;
}
}
const changeHeader = (state, payload) => {
// this is where your app will update the state, to indicate
// which header should be displayed:
return {
...state,
headerType: payload.headerType
}
}
redux/index.js:
import headerReducers from './reducers/header-reducers';
import { combineReducers } from 'redux';
const allReducers = combineReducers({
header: headerReducers
});
export default allReducers;
Now you can set up your header.js component like this:
import { changeHeader } from '../redux/header-actions';
class Header extends Component {
render() {
return (
<div>{this.renderHeader()}</div>
);
}
renderHeader() {
if (this.props.headerType === 'header-a')
return <aHeader changeHeader={this.props.changeHeader} />
else
return <bHeader changeHeader={this.props.changeHeader} />;
}
}
function mapStateToProps(store, ownProps) {
return {
headerType: store.header.headerType
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
changeHeader: changeHeader
},
dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));
Then in, for example, aHeader.js:
class aHeader {
constructor() {
super();
this.changeHeader = this.changeHeader.bind(this);
}
render() {
return <div onClick={this.changeHeader}>header a</div>;
}
changeHeader() {
this.props.changeHeader('header-b'); // call the action creator here
}
}
I would like to use localstorage to persist the auth state to avoid slow page content on refreshes. I've read about this elsewhere but unsure how to implement in my case. Could anyone help me work out how to edit the below to make it work please?
This example is similar but I'm not sure how to apply it to my case.
import React from 'react';
import { firebase } from '../firebase';
import PropTypes from 'prop-types';
const withAuthentication = (Component) => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: null,
};
}
getChildContext() {
return {
authUser: this.state.authUser,
};
}
componentDidMount() {
firebase.auth.onAuthStateChanged(authUser => {
authUser
? this.setState(() => ({ authUser }))
: this.setState(() => ({ authUser: null }));
});
}
render() {
return (
<Component />
);
}
}
WithAuthentication.childContextTypes = {
authUser: PropTypes.object,
};
return WithAuthentication;
}
export default withAuthentication;
Easy, just replace:
this.setState(() => ({ authUser }))
with
localStorage.setItem('authUser', JSON.stringify(authUser))
or
localStorage.removeItem('authUser')
to remove it
then you can read it:
JSON.parse(localStorage.getItem('authUser'))
instead of this.state.authUser
and in componentDidMount check if localStorage.getItem('authUser') exists before making the call again.