I'm stating to learn react and redux today, yet I cannot figure out how to force component to rerender after state change.
Here is my code:
const store = createStore(loginReducer);
export function logout() { return {type: 'USER_LOGIN'} }
export function logout() { return {type: 'USER_LOGOUT'} }
export function loginReducer(state={isLogged:false}, action) {
switch(action.type) {
case 'USER_LOGIN':
return {isLogged:true};
case 'USER_LOGOUT':
return {isLogged:false};
default:
return state;
}
}
class App extends Component {
lout(){
console.log(store.getState()); //IT SHOW INITIAL STATE
store.dispatch(login());
console.log(store.getState()); //IT SHOWS THAT STATE DID CHANGE
}
////THIS IS THE PROBLEM,
render(){
console.log('rendering')
if(store.getState().isLogged){
return (<MainComponent store={store} />);
}else{
return (
<View style={style.container}>
<Text onPress={this.lout}>
THE FOLLOWING NEVER UPDATE :( !!{store.getState().isLogged?'True':'False'}</Text>
</View>
);
}
}
The only way i could trigger update is by using
store.subscribe(()=>{this.setState({reload:false})});
inside constructor, so that i manually trigger an update state of component to force rerender.
but how can i link both store and component states ?
Your component is only going to re-render if its state or props are changed. You are not relying on this.state or this.props, but rather fetching the state of the store directly within your render function.
Instead, you should use connect to map the application state to component props. Component example:
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
export class App extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
return (
<div>
{this.props.isLoggedIn ? 'Logged In' : 'Not Logged In'}
</div>
);
}
}
App.propTypes = {
isLoggedIn: PropTypes.bool.isRequired
};
function mapStateToProps(state, ownProps) {
return {
isLoggedIn: state.isLoggedIn
};
}
export default connect(mapStateToProps)(App);
In this very simplified example, if the store's isLoggedIn value changes, it will automatically update the corresponding prop on your component, which will cause it to render.
I suggest reading the react-redux docs to help you get started:
https://redux.js.org/basics/usage-with-react
I ended up here because I had written a bad reducer. I had:
const reducer = (state=initialState, action) => {
switch (action.type) {
case 'SET_Q':
return Object.assign(state, { // <- NB no {}!
q: action.data,
})
default:
return state;
}
}
I needed:
const reducer = (state=initialState, action) => {
switch (action.type) {
case 'SET_Q':
return Object.assign({}, state, { // <- NB the {}!
q: action.data,
})
default:
return state;
}
}
Related
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'm using Redux in an application for the first time and having trouble understanding how to pass a component's internal state to the global state object.
export default class ComponentOne extends Component {
constructor() {
this.state = {
number: 0
}
handleNumber = (e) => {
this.setState({
number: e.target.value
})
}
render() {
console.log(this.state.number)
return (
<div>
<input onChange={this.handleNumber} type="number">
</div>
)
}
}
}
function mapStateToProps(state) {
return {
number: state
}
}
export default connect(mapStateToProps, { HANDLE_NUMBER_CHANGE })(ComponentOne);
My Actions & Reducers:
const HANDLE_NUMBER_CHANGE = state => {
return {
type: 'HANDLE_NUMBER_CHANGE'
}
}
export default (state = 0, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
//Im lost here - trying to save internal state
default:
return state;
}
}
My store is set up properly, using redux-thunk for middleware.
When I log store.getState() - it is logging 0 regardless of my components internal state.
Can anybody explain how this works?
When you have global state you dont need to save it to the local state. It is accessible to the component as this.props.value.
The way to set global state is by passing the value to the action creator, which returns it in the action. The reducer gets it in the action object and saves it.
There are many simple examples available. Here is one.
Here is your code after changes:
(I didn't run it - there might be errors, but I believe that you will be able to fix them by yourself; I have divided the code between several files - this is how usually how this is done. Look in the example in the above link if you have problems)
// file: src/components/ComponentOne.js
import React from 'react';
import { connect } from 'react-redux';
import { handleNumber } from '../actions';
class ComponentOne extends Component {
constructor(props) {
super(props);
this.handleNumber = this.handleNumber.bind(this);
}
render() {
console.log(this.state.number)
return (
<div>
<input onChange={(e) => this.props.handleNumber(e.target.value)} type="number" />
</div>
);
}
}
function mapStateToProps(state) {
return {
number: state
}
}
export default connect(mapStateToProps, { handleNumber })(ComponentOne);
// end of file
/// separate file: src/reducers/index.js ////
import { combineReducers } from 'redux';
import dataReducer from './dataReducer';
export default combineReducers({
number: dataReducer
});
// end of file
// separate file: src/reducers/dataReducer.js
const DataReducer = (state = 0, action) => {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return action.payload;
default:
return state;
}
};
export default DataReducer;
// end of file
// separate file: src/actions/index.js
export function handleNumber(value) {
return ({
type: 'HANDLE_NUMBER_CHANGE',
payload: value
});
}
I don't see the logic in making your internal state equal your store. I'm not saying you're wrong, but it doesn't seem to fit within the redux paradigm. However...
Action should be...
export function HANDLE_NUMBER_CHANGE = number => {
return {
type: 'HANDLE_NUMBER_CHANGE'
payload: number
}
}
Reducer should look like...
export default (state = {number: 0}, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return (state = {
...state,
number: action.payload,
});
Lastly, you'll need to call a dispatch from your onChange function.
dispatch(HANDLE_NUMBER_CHANGE(e.target.value).
If you do not pass the value to the action, there is no way for the reducer to add it to the store.
If you are managing your ComponentOne state using redux then you dont need
react state.
ComponentOne
export default class ComponentOne extends Component {
constructor() {
handleNumber = (e) => {
this.props.updateNumber(e.target.value);//call dispatch method
}
render() {
return (
<div>
<input onChange={this.handleNumber} type="number">
</div>
)
}
}
}
function mapStateToProps(state) {
return {
number: state.number //map updated number here
}
}
function mapDispatchToProps(state) {
return {
updateNumber(number){
dispatch({type: 'HANDLE_NUMBER_CHANGE',number});//dispatch action
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ComponentOne);
reducers:
export default (state = 0, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return {
...state,number : actio.number//update number here
}
default:
return state;
}
}
After combining two reducers together (EditButton and TodoApp), my app everytime start crash. Before it, when I just use only one reducer TodoApp I did not have any problem with reducers. But now I cannot figure out what is wrong, because every time I get the error in map function of component below . Error "TypeError: Cannot read property 'map' of undefined".
So, what is I forgot? Also I cannot get the state in nested components or containers of App. It's strange too, but in App I can do that by console.log() for example.
/* REDUCERS */
import { combineReducers } from 'redux'
import { ADD_TODO, EDIT_TODO, DELETE_TODO, FILTER_TODO_UP, FILTER_TODO_DOWN } from '../Variables/Variables'
const initialState = {
todos: []
}
function EditButton(state, action) {
if (typeof state === 'undefined') {
return 'Edit';
}
switch (action.type) {
case EDIT_TODO:
return state = "Edit" ? "Done" : "Edit"
default:
return state
}
}
function TodoApp(state, action) {
if (typeof state === 'undefined') {
return initialState;
}
switch (action.type) {
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
id: action.id,
text: action.text,
done: action.done
}
]
});
case EDIT_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
id: action.id,
text: action.text,
done: action.done
}
]
});
case DELETE_TODO:
return Object.assign({}, {
todos: state.todos.filter(todos => todos.id !== parseInt(action.id))
});
case FILTER_TODO_UP:
return Object.assign({}, {
todos: [
...state.todos.sort((a, b) => b.id - a.id)
]
});
case FILTER_TODO_DOWN:
return Object.assign({}, {
todos: [
...state.todos.sort((a, b) => a.id - b.id)
]
});
default:
return state;
}
}
export default combineReducers({TodoApp, EditButton})
/* APP */
import React, { Fragment } from 'react';
import TodoFormAdd from '../Containers/TodoFormAdd';
import TodoListAdd from '../Containers/TodoListAdd';
import TodoFormFilterAdd from '../Containers/TodoFormFilterAdd';
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<Fragment>
// console.log(this.props.state.getState()) - work!
<TodoFormAdd />
<TodoListAdd store={this.props.store} />
<TodoFormFilterAdd />
</Fragment>
);
}
}
export default App;
/* CONTAINER */
import { connect } from 'react-redux';
import TodoList from '../Components/TodoList/TodoList';
import { DeleteTodo } from '../Actions/AddTodo'
// console.log(this.props.state.getState()) - does not work!
const mapStateToProps = state => ({
todos: state.todos
});
const mapDispatchToProps = dispatch => ({
todoFormDelete: todo => dispatch(DeleteTodo(todo))
});
export default connect(
mapStateToProps,
mapDispatchToProps)(TodoList)
/* COMPONENT */
import React from 'react';
import TodoIteam from '../TodoIteam/TodoIteam'
class TodoList extends React.Component {
handleDelete = (e) => {
let target = e.target;
let closestDelete = target.closest('span');
let closestEdit = target.closest('button');
if (closestDelete) {
let index = closestDelete.parentNode.getAttribute('index');
this.props.todoFormDelete(index);
} else {
return
}
}
render(props) {
// console.log(this.props.state.getState()) - does not work!
return (
<ul onClick={this.handleDelete}>{this.props.todos.map((iteam, index) =>
// this where I get an error
<TodoIteam key={index} index={iteam.id} {...iteam} />
)}
</ul>
);
}
}
export default TodoList;
As you are using ES6 property shorthand notation in combineReducers :
combineReducers({TodoApp, EditButton})
This is equivalent to writing combineReducers({ TodoApp: TodoApp, EditButton: EditButton })
But inside your CONTAINER you are accessing state.todos there is nothing called todos coming from state instead its TodoApp and Hence you get error in your .map():
this.props.todos.map((iteam, index) {}
EDIT :
As you are returning an object containing an array from your reducers called todos so to access correct state you need to use reducer Name followed by an array name you are returning which would be TodoApp.todos
So inside your Container you need to access correct reducer
const mapStateToProps = state => ({
todos: state.TodoApp.todos // Notice TodoApp is used instead of todos
});
You can read more about combineReducers on Redux Documentation
I am trying to implement auth (sign up/out) using React + Redux (SSR and Thunks). I have no idea why components are not updatating when Redux state updates...
This is the component that should get rerendered:
class Navbar extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedIn: props.authentication.loggedIn
};
}
render() {
let links = null;
if (this.state.loggedIn) {
links = ...
} else {
links = ...
}
return (<Toolbar>{links}</Toolbar>)
}
}
const mapStateToProps = state => {
return {
authentication: state.authentication
}
}
const mapDispatchToProps = dispatch => {
return {
signOut: () => {dispatch(userActions.logout())}
}
}
const AuthNavbar = connect(mapStateToProps, mapDispatchToProps)(Navbar)
export default AuthNavbar;
And that is my reducer:
const reducers = {
authentication,
registration,
alert
}
const todoApp = combineReducers(reducers)
export default todoApp
Authentication reducer:
const authentication = (state = initialState, action) => {
switch (action.type) {
...
case userConstants.LOGIN_SUCCESS:
return Object.assign({}, state, {
loggedIn: true,
loggingIn: false,
user: action.user
});
...
default:
return state;
}
}
And The Action - Login:
function login(email, password) {
return dispatch => {
dispatch({type: userConstants.LOGIN_REQUEST, email});
userService.login(email, password).then(
user => {
dispatch({type: userConstants.LOGIN_SUCCESS, user});
},
error => {
dispatch({ type: userConstants.LOGIN_FAILURE });
dispatch({type: alertActions.error(error)});
}
);
}
}
UserService.login is a function that calls and api witch fetch.
Looks like Action gets fired as it should, Redux state gets updated, but the component does not update:
Double checked Redux Dev Tools - state does get updated, so there must be a problem with the way I am using connect utilities?
You are storing the logedin props in the state inside the constructor, which will run only once in the life time of the component.
When a new prop is coming back you are not updating the state.
Either use the props directly:
if (this.props.authentication.loggedIn) {
links = ...
Or update the state in componentWillReceiveProps
componentWillReceiveProps(nextProps){
// update the state with the new props
this.setState({
loggedIn: nextProps.authentication.loggedIn
});
}
Your render function is dependent on state.loggedIn, but state.loggedIn is not changing; only this.props.authentication.loggedIn is changing in response to the action. Your component, in its current form, does not need state. You can remove it to make this work:
class Navbar extends React.Component {
render() {
let links = null;
if (this.props.authentication.loggedIn) {
links = ...
} else {
links = ...
}
return (<Toolbar>{links}</Toolbar>)
}
}
I am trying a simple example of rendering 2 components via dispatching an action on componentWillMount() which updates the store.
Initial State:
export default {
dashboards: [],
dashboardContent: []
};
2 Reducers:
export default function dashboardContentReducer(state = initialState.dashboardContent, action) {
switch(action.type) {
case types.LOAD_DASHBOARD_CONTENT_SUCCESS:
return action.dashboardContent;
default:
return state;
}
}
and
export default function dashboardReducer(state = initialState.dashboards, action) {
switch(action.type) {
case types.LOAD_DASHBOARDS_SUCCESS:
return action.dashboards;
default:
return state;
}
}
Here's where things get a little weird.
I am able to dispatch the action to call these reducers, but only 1 of them will function to update the redux store. I do so as follows:
class NavigationBar extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.dispatch(dashboardActions.loadDashboards());
}
render() {
return (
<div className="rc-navigationBar">
<h1>Navigation!</h1>
{this.props.dashboards.map((dashboard, index) => {
return <h1 key={index}>{dashboard.title}</h1>
})}
</div>
);
}
}
and for the other:
class ContentPage extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.dispatch(dashboardContentActions.loadDashboardContent(extractIdFromRoute()));
}
render() {
return (
<div>
<h1>Content!</h1>
{this.props.dashboardContent.map((content, index) => {
return <h1 key={index}>{content.application}</h1>;
})}
</div>
);
}
}
When I simultaneously try to modify the store, I get this error:
Uncaught (in promise) Error: A state mutation was detected between dispatches, in the path 'dashboards.1.filter.Pivot.ancestorOrigins'. This may cause incorrect behavior.
What am I doing wrong here?
You are returning it in a wrong way. It should be like this -
export default function dashboardContentReducer(state = default, action) {
switch(action.type) {
case types.LOAD_DASHBOARD_CONTENT_SUCCESS:
return Object.assign({}, state, { dashboardContent:action.dashboardContent });
default:
return state;
}
}