I have a question about what is the best practice using setState with redux state in component
For example: I have a component with an onClick event.
In the component A's onClick event, I have dispatch some redux action which will change redux state from reducer:
someOnClickFunc = () => {
this.props.someReduxAction()
}
and I have a component b:
import React, { Component } from 'react'
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
class ComponentB extends Component {
constructor(props) {
super(props);
this.state = {
someValue: false,
};
}
render() {
return (
<div>
{someValue}
</div>
)
}
}
const mapStateToProps = state => ({
someReduxState: state.someReduxState
});
export default connect(
mapStateToProps,
)(ComponentB);
My component B received redux state and I want to change component self state with this redux state.
I can do it like that after render.
if (this.props.someReduxState.someVal == true) {
this.state.someValue = true
}
But i don't want to use this.state... I prefer to use this.setState like that:
if (this.props.someReduxState.someVal == true) {
this.setState({
someValue: true
})
}
where is the best place to do that.
When I do that after render() , or componentDidUpdate I'm getting this error:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I believe the best way here is to use the componentDidUpdate method. But there's a catch, you need to check if the new props from the redux state are equal to the already existing props. Only then, you should proceed to mutate your state. Here's an example:
componentDidUpdate(prevProps, prevState) {
if(prevProps.somedata.data !== this.props.somedata.data) {
this.setState({ //update the state after checking
someProperty: value
});
}
}
Related
As in the title, I have a React component, the relevant part of which looks a bit like this as of now:
class myComponent {
fetchSomething = async() => {
this.setState({data: fetch(props.id)})
}
componentDidMount(){
this.fetchSomething()
}
render() {
{data}
}
}
const mapStateToProps = state => {
...
return { id }
}
export default connect(mapStateToProps)(myComponent);
I need to basically rerun the fetchSomething function whenever the id property in redux store changes, since I want to see the changes as soon as it happens, and I don't want to have to mount the component again.
you can try using componentDidUpdate lifecycle method.
componentDidUpdate(prevProps) {
if(prevProps.id !== this.props.id) {
this.fetchSomething();
}
}
More on componentDidUpdate or other useful lifecycle methods here
I'm currently learning react and redux and stumbled into a problem i can't really get my head around. Trying to implement the same functionality
as in this article: https://medium.com/#yaoxiao1222/implementing-search-filter-a-list-on-redux-react-bb5de8d0a3ad but even though the data request from the rest api i'm working with is successfull i can't assign the local state in my component to my redux-state, in order to be able to filter my results. Heres my component:
import React from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as fetchActions from '../../actions/fetchActions'
import Stafflist from './Stafflist'
class AboutPage extends React.Component {
constructor(props) {
super(props)
this.state = {
search: '',
currentlyDisplayed: this.props.store.posts
}
this.updateSearch = this.updateSearch.bind(this)
}
updateSearch(event) {
let newlyDisplayed = this.state.currentlyDisplayed.filter(
(post) => {Â
return (
post.name.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
|| post.role.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
)}
)
console.log(newlyDisplayed)
this.setState({
search: event.target.value.substr(0, 20),
currentlyDisplayed: newlyDisplayed
})
}
render() {
return (
<div className="about-page">
<h1>About</h1>
<input type="text"
value={this.state.search}
onChange={this.updateSearch}
/>
//component for rendering my list of posts.
<Stafflist posts={this.state.currentlyDisplayed} />
</div>
)
}
}
// this is here i assign my api data to this.props.store.posts
function mapStateToProps(state, ownProps) {
return {
store: state
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(fetchActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AboutPage)
Comparing how i assign my stores state to my local component with how it works in the article, it seems to be done in the same way. Mine:
this.state = {
search: '',
currentlyDisplayed: this.props.store.posts
}
article:
this.state = {
searchTerm: '',
currentlyDisplayed: this.props.people
}
within react devtools i can see my data in as it should be in the store, but it won't work to assign it to my local state within the component in order to perform the filtering, and i don't really know how to debug this. My state in the local component just says
State
currentlyDisplayed: Array[0]
Empty array
also if i change
<Stafflist posts={this.state.currentlyDisplayed} />
to
<Stafflist posts={this.props.store.posts} />
the list renders as it should :)
Reducer:
import * as types from '../actions/actionTypes'
import initialState from './initialState'
export default function postReducer(state = initialState.posts, action) {
switch(action.type) {
case types.FETCH_POSTS_SUCCESS:
return action.posts.data.map(post => {
return {
id: post.id,
name: post.acf.name,
role: post.acf.role
}
})
default:
return state
}
}
Any ideas?
The problem with your code is that you do not handle how to get newly received props to your state. This means that when you receive the data from your api call only the props are updated while component state is not.
If you look carefully at the referenced article, in the onInputChange method they recalculate the state from the props whereas your updateState method only filters from the local state.
Setting the state in the constructor only ensures that the props are copied on the initial mount of the component. At that point in time the store only contains the initial state (initialState.posts in your reducer code). This is the price of keeping both component state and store; you must think of how to keep the two in sync after the initial load.
One solution is to update the state in componentWillReceiveProps:
componentWillReceiveProps(nextProps){
const nextFiltered = nextProps.store.posts.filter(your filtering code here);
this.setState({currentlyDisplayed: nextFiltered});
}
This will update your state whenever the component receives new props. Note react has phased out componentWillReceiveProps and you should use getDerivedStateToProps as of react 16.3. Refer here for more details.
I am currently grabbing a prop from state and using it on an event listener. i.e.,
import * as React from 'react';
import { getDetails } from './actions';
interface Props {
selecting: boolean;
getDetails(): Action<void>;
}
#connect((state) => ({
selecting: state.items.selecting,
}), {
getDetails,
})
export default class Grid extends React.PureComponent<Props> {
onMouseEnter = () => {
if (!this.props.selecting) {
this.props.getDetails();
}
}
render() {
return (
<div onMouseEnter={this.onMouseEnter} />
);
}
}
However, whenever the selecting property changes, it causes a re-render to my component.
Is there a way to pass a variable from state through connect and NOT have it trigger this update to my component? I want it almost as if it were an instance-bound variable rather than a state variable.
Try overriding the shouldComponentUpdate() lifecycle function. This gives you much more granular control over when your component should or shouldn't re-render (at the cost of added code complexity).
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.someLogic !== this.props.someLogic)
return false; // Don't re-render
return true;
}
Documentation: Here
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.
I want to call a promise based function before dispatching an action to the store.
The problem is that I only want to call the function when the component is going to be displayed. I use a toggle action that turns the component on and off.
Here is a sample of my code:
if ( /*component is going to be displayed*/) {
init().then(function() {
store.dispatch(toggleSomething());
});
}
else {
store.dispatch(toggleSomething());
}
Action:
export const SomethingActions = {
TOGGLE_SOMETHING: 'TOGGLE_SOMETHING'
};
export function toggleSomething() {
return {
type: SomethingActions.TOGGLE_SOMETHING
};
}
Reducer:
export default function somethingState(state = defaultState, action) {
switch (action.type) {
case somethingActions.TOGGLE_SOMETHING
return Object.assign({}, state, { open: !state.open});
default:
return state;
}
}
part of the React component:
Something.propTypes = {
display: React.PropTypes.bool.isRequired
};
function mapStateToProps(state, ownProps) {
return {
display: state.something.open
};
}
I basically want to know the value of open/display of the component above or another way to know whether the component is being displayed or not.
I don't want to pollute the render function or store a bool that changes every time I call dispatch.
Is there a way to do that?
By the sounds of it, you'd want to take advantage of React's lifecycle methods. Particularly the componentWillMount and componentWillReceiveProps.
componentWillReceiveProps does not get triggered for the initial render, so you may want to extract out the logic into a separate function so that it can be reused for both hooks:
function trigger(isDisplayed) {
if (isDisplayed) {
init().then(function() {
store.dispatch(toggleSomething());
});
}
else {
store.dispatch(toggleSomething());
}
}
componentWillMount() {
trigger(this.props.display);
}
componentWillReceiveProps(nextProps) {
trigger(nextProps.display);
}
Q1: "The problem is that I only want to call the function when the component is going to be displayed"
A1: This is definitely a problem for react lifecycle methods, in particular, componentWillMount() & componentDidMount()
Q2: "I basically want to know the value of open/display of the component above or another way to know whether the component is being displayed or not."
A2: The componentDidMount() method will be called when the component is rendered. To prevent an infinite loop where the component calls your promise on render just to call the promise again when the state changes, avoid including the toggled state in your component. Dispatch actions on component mounting that toggle the state in the store, but don't use this state in this component. This way you know whether the component is rendered without having the UI update. I hope that helps!
import React from 'react';
class StackOverFlow extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
toggleSomethingOn();
}
componentWillUnmount() {
toggleSomethingOff();
}
render() {
return (
<div>
The component has been rendered!
<br />
</div>
);
}
}
function toggleSomethingOn() {
//dispatches action to toggle state "open"
}
function toggleSomethingOff() {
//dispatches action to toggle state "closed"
}
export default StackOverFlow;
A2: If you are just looking to find out if a component has been rendered (outside of your code) you could go to your browser's developer tools and search the elements/DOM for your component html.
I am using react and redux, i want to update my counter value in react view, i am able to console latest state of my redux store but it is not reflect in my react view.
const counter = (state = 0, action) => {
console.log(action);
if(action.type == "INCREMENT")
return state + 1;
if(action.type == "DECREMENT")
return state - 1;
else
return state; // return same state if action not identified
}
const {createStore} = Redux;
const store = createStore(counter);
class Counter extends React.Component {
constructor() {
super();
}
render() {
return (
<div>
<div>{this.props.state.getState()}</div>
<button onClick={this.props.onIncrement} >INC</button>
<button onClick={this.props.onDecrement} >DEC</button>
</div>
);
}
}
const render = () => {
ReactDOM.render(
<Counter
state={store}
onIncrement={
() => store.dispatch({ type : "INCREMENT" })
}
onDecrement={
() => store.dispatch({ type : "DECREMENT" })
}
/>,
document.querySelector('#counter'));
}
store.subscribe(function() {
console.log(store.getState())
});
render();
Demo
React doesn't automatically re-render the view every time some Javascript data changes, even if your view is bound to that data.
A React component only re-renders in a few cases:
You call this.setState({ ... }) inside the component
A parent React component is re-rendered
There are a few other ways to force a re-render, but they are not recommended because they are much slower and will make your app sluggish.
To correct your sample, do your data binding for actual data on the state object, rather than props. This way React knows to re-render just your component when the counter changes. This might not be very important in a small sample, but this is very important when you want to reuse your component or embed it in a larger page.
Then subscribe to your store, and in the callback invoke setState with any changes. That way React can decide when your re-render should actually happen.
class Counter extends React.Component {
constructor(props) {
super();
this.state = {counter: 0}; // Setup initial state
this.storeUpdated = this.storeUpdated.bind(this);
props.store.subscribe(this.storeUpdated); // Subscribe to changes in the store
}
storeUpdated() {
this.setState( // This triggers a re-render
{counter: this.props.store.getState()});
}
render() {
return (
<div>
<div>{this.state.counter}</div>
<button onClick={this.props.onIncrement} >INC</button>
<button onClick={this.props.onDecrement} >DEC</button>
</div>
);
}
}
After you've played with this for a while and gotten familiar with how Redux and React work, I suggest you check out this library:
Get the library here: https://github.com/reactjs/react-redux
See http://redux.js.org/docs/basics/UsageWithReact.html for a tutorial on this library
This handles the bridge between React and Redux in a much more clean way than you can achieve by manually doing all the bindings yourself.