In Redux, I'm running the following in a redux-thunk action creator:
dispatch({type: "CASCADING_PROMPT_STATUS", promptName, list: 'model', status: 'disabled'});
dispatch({type: "CASCADING_PROMPT_STATUS", promptName, list: 'model', status: 'enabled'});
This triggers the reducer twice, and I can see in the console that Redux state changes from disabled -> enabled.
In React, I have the following component which has props connected to state of which CASCADING_PROMPT_STATUS updates.
However, in my component, I'm running a check to see if state changed in componentDidUpdate(prevProps)
This doesn't trigger.
If I change the action creator to delay the second dispatch setTimeout(<dispatch...>, 1); even by one millisecond, prevProps !== this.props, which is what I expect.
My component is connected to redux like so:
const C_Component = connect(mapStateToProps, mapDispatchToProps, null, {pure: false})(Component);
Does React batch up prop changes? Why do I have to delay the second dispatch? I thought redux dispatch were synchronous.
Edit: The way I'm updating the redux state is as follows:
var newState = {};
if (!_.isUndefined(state)){
newState = _.cloneDeep(state);
}
...
case "CASCADING_PROMPT_STATUS":
newState[action.promptName][action.list].disable = action.status === 'disabled';
break;
Redux dispatches are synchronous (unless a middleware intercepts and delays the action). However, React updates are batched in most cases.
Second, your reducer is definitely mutating the state, because you're not copying every level of nesting that needs to be updated. The mutations will cause your connected component to think nothing has changed, and skip updating. See the Structuring Reducers - Immutable Update Patterns section of the docs for more details on how to properly do immutable updates.
Actually... re-reading your reducer, you are doing a deep clone, so in theory that's not mutating. However, per the Redux FAQ, deep cloning is a bad idea. Instead, you should do nested shallow updates.
Third, your shouldComponentUpdate shouldn't compare this.props vs nextProps themselves. The actual props objects themselves will definitely be different. Instead, it should compare the contents of those objects - ie, props.a !== nextProps.a && props.b !== nextProps.b, etc. This is known as a "shallow equality comparison".
Related
I'm facing an issue with Redux and React
I use a redux action to fetch data from an API. When the component mounts, this action is fired and populate the Redux state. I want a second action to be fired with parameters (article) from the redux state.
My issue is that when I fire the second action, the redux state is still empty, so article is null, which causes an error.
componentDidMount() {
const { targetArticle, userVisit, match, article } = this.props
targetArticle(match.params.slug);
userVisit(match.params.slug, article.title);
}
I've already checked other topics on the subject like this one, but none of them works for me. How can I achieve that?
Thanks
You'd probably have to use componentDidUpdate lifecycle method. So given that userVisit is dependent on the result of targetArticle and assuming you are looking to this.props. for the updated Redux state, something like this should get you there:
componentDidUpdate(prevProps) {
if(prevProps.article !== this.props.article) {
// Now you have access to targetArticle's result and updated Redux state
userVisit(match.params.slug, this.props.article.title)
}
}
More in the docs: https://reactjs.org/docs/react-component.html#componentdidupdate
I am new to Redux and i am finding some problems with understanding concept of reducers,
I can see many examples showing it takes current state and return updated state,
My question is how it updates the Store by returning the new state ( i am finding difficult to understand the mechanism ),
can some one please explain me .
The Redux store is nothing but just an object holding all states of the application. The reducer is the only way to update the store.
The reducer is a pure function with takes an old state and returns a new state. In reducer what we need to do is that we just provide the old state which is store currently having and then the new state which we are going to change state. You can refer this for detailed explanation for reduce function.
In simple words, reducer takes existing state object updates some property passed through reducer function and returns new object state.
Following link has better explanation. This is very nice blog how to create your own redux. You will get exactly what happens in the redux store.
https://www.jamasoftware.com/blog/lets-write-redux/
this is image that i found very helpfull when i was learning the same concept.
Dispatch
When you dispatch any function it goes to all reducers and if the type of dispatch matches it will change the state of that reducer.
functionName:()=>(dispatch)({type:'some-thing-to-match',payload})
Reducers
That handle state change.
Store
Combination of all the reducers (root reducer).
const store = combineReducers({
Reducer1:r1,
Reducer2:r2,
Reducer3:r3
})
For example take the dispatch function in TodoList that matches in r1 and changes its state.Then by connect from 'react-redux' we will connect that reducers state to TodoList.
var mapStateToProps = state=>{
return:{
r1:r1
}
}
then react will react to any change in state. If state of r1 is changed then it will update that component.
Your question how it update store by returning state. Your reducer will get store(state) and function as input and change the store according to function and return the state to store.
Then we can connect our component to that store to catch any change in it.
As we can see in image. Dispatch will change the store's state.Then
you can import(connect) that reducer to see the changes in your
component.(here TodoItem is that component)
Actually this is the part that i was missing about reducers,The part i didnt catch was reducers out put has to be assigned to store property
let Action={type:'SET_VISIBILITY_FILTER',text: 'test pay load'}
//Invoking Reducer
let store=todoApp({},Action)
//Reducer
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
message: action.text
})
default:
return state
}
}
I'm storing my form inputs in React component state. When I submit the form, I trigger a Redux action. And when this action succeeds, I want to update the state again - to clear the form. But how to do it?
I mean, I can easily store form state in Redux too and everything will be resolved, but I'd prefer to store component specific things in component state.
You should be using something like redux-thunk to delay the dispatching until the API call succeeds:
const postForm = data => dispatch => fetch(...).then((...) => dispatch(...))
Since fetch returns a Promise, you can then wait until it's resolved (api call succeeded) before performing the form clearing in your component:
props.postForm(...)
.then(() => this.setState(<clear the form state>))
.catch(<do something to warn the user api call failed?>)
What does that action update on the state exactly?
One way would be to add an extra case in your componentWillReceiveProps that handle that update of the form. If the action let say updates the list, you could have something like the following on your componentWillReceiveProps method inside you component:
componentWillReceiveProps(nextProps) {
if (nextProps.list !== this.props.list) {
this.setState({
formFields: this.getNewClearFormFields()
})
}
}
Where getNewClearFormFields is a function that returns your new form fields
If you want to update the state after redux action succeeds, then I would suggest go ahead and put it in componentWillReceiveProps by comparing prevState and nextState
use mapStateToProps() to map redux state to component
and then update the component state like below
componentWillReceiveProps(nextProps) {
this.setState({
...
});
}
Following code using simple Redux API:
// Dummy Reducer that just returns previous state
const counter = (state = 0, action) => {
return state;
}
// Store
const { createStore } = Redux;
const store = createStore(counter);
const listener = () => {
console.log('Listener called...with ' + store.getState());
};
// Listener
store.subscribe(listener);
// Manually dispatching actions
store.dispatch({ type: 'DUMMY' });
store.dispatch({ type: 'DUMMY' });
store.dispatch({ type: 'DUMMY' });
produces following output:
Listener called...with 0
Listener called...with 0
Listener called...with 0
My question:
If nothing changes in the store, why listener is being notified as if something changed. Isnt is unnecessary and counter productive? Lets say, the listeners are views like React Container Components. They will try rerendering unnecessarily right ?
Or am I missing something?
In case of Flux, I feel we have higher flexibility in terms of whether to publish the change from the store or not. Is this a con for Redux over Flux ? Or am I missing something?
According to the docs, store.subscribe():
Adds a change listener. It will be called any time an action is dispatched, and some part of the state tree may potentially have changed.
Many (most?) popular redux patterns do not require developers to use store.subscribe() at all. See react-redux and redux-saga.
However, it's a good question whether store.subscribe affects performance of such frameworks.
With react-redux, the most popular redux framework for react, container components don't do long-running tasks like http requests; those are typically handled by dispatched actions. So container components tend to be very high-performance, simply pulling data out of simple objects in the store. When their output doesn't change, then the associated view components won't re-render.
I'm probably missing something very obvious and would like to clear myself.
Here's my understanding.
In a naive react component, we have states & props. Updating state with setState re-renders the entire component. props are mostly read only and updating them doesn't make sense.
In a react component that subscribes to a redux store, via something like store.subscribe(render), it obviously re-renders for every time store is updated.
react-redux has a helper connect() that injects part of the state tree (that is of interest to the component) and actionCreators as props to the component, usually via something like
const TodoListComponent = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
But with the understanding that a setState is essential for the TodoListComponent to react to redux state tree change(re-render), I can't find any state or setState related code in the TodoList component file. It reads something like this:
const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
Can someone point me in the right direction as to what I am missing?
P.S I'm following the todo list example bundled with the redux package.
The connect function generates a wrapper component that subscribes to the store. When an action is dispatched, the wrapper component's callback is notified. It then runs your mapState function, and shallow-compares the result object from this time vs the result object from last time (so if you were to rewrite a redux store field with its same value, it would not trigger a re-render). If the results are different, then it passes the results to your "real" component" as props.
Dan Abramov wrote a great simplified version of connect at (connect.js) that illustrates the basic idea, although it doesn't show any of the optimization work. I also have links to a number of articles on Redux performance that discuss some related ideas.
update
React-Redux v6.0.0 made some major internal changes to how connected components receive their data from the store.
As part of that, I wrote a post that explains how the connect API and its internals work, and how they've changed over time:
Idiomatic Redux: The History and Implementation of React-Redux
My answer is a little out of left field. It sheds light on a problem that led me to this post. In my case it seemed the app was Not re-rendering, even though it received new props.
React devs had an answer to this often asked question something to the tune that if the (store) was mutated, 99% of the time that's the reason react won't re-render.
Yet nothing about the other 1%. Mutation was not the case here.
TLDR;
componentWillReceiveProps is how the state can be kept synced with the new props.
Edge Case: Once state updates, then the app does re-render !
It turn out that if your app is using only state to display its elements, props can update, but state won't, so no re-render.
I had state that was dependent on props received from redux store. The data I needed wasn't in the store yet, so I fetched it from componentDidMount, as is proper. I got the props back, when my reducer updated store, because my component is connected via mapStateToProps. But the page didn't render, and state was still full of empty strings.
An example of this is say a user loaded an "edit post" page from a saved url. You have access to the postId from the url, but the info isn't in store yet, so you fetch it. The items on your page are controlled components - so all the data you're displaying is in state.
Using redux, the data was fetched, store was updated, and the component is connected, but the app didn't reflect the changes. On closer look, props were received, but app didn't update. state didn't update.
Well, props will update and propagate, but state won't.
You need to specifically tell state to update.
You can't do this in render(), and componentDidMount already finished it's cycles.
componentWillReceiveProps is where you update state properties that depend on a changed prop value.
Example Usage:
componentWillReceiveProps(nextProps){
if (this.props.post.category !== nextProps.post.category){
this.setState({
title: nextProps.post.title,
body: nextProps.post.body,
category: nextProps.post.category,
})
}
}
I must give a shout out to this article that enlightened me on the solution that dozens of other posts, blogs, and repos failed to mention. Anyone else who has had trouble finding an answer to this evidently obscure problem, Here it is:
ReactJs component lifecycle methods — A deep dive
componentWillReceiveProps is where you'll update state to keep in sync with props updates.
Once state updates, then fields depending on state do re-render !
This answer is a summary of Brian Vaughn's article entitled You Probably Don't Need Derived State (June 07, 2018).
Deriving state from props is an anti-pattern in all its forms. Including using the older componentWillReceiveProps and the newer getDerivedStateFromProps.
Instead of deriving state from props, consider the following solutions.
Two best practice recommendations
Recommendation 1. Fully controlled component
function EmailInput(props) {
return <input onChange={props.onChange} value={props.email} />;
}
Recommendation 2. Fully uncontrolled component with a key
// parent class
class EmailInput extends Component {
state = { email: this.props.defaultEmail };
handleChange = event => {
this.setState({ email: event.target.value });
};
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
}
// child instance
<EmailInput
defaultEmail={this.props.user.email}
key={this.props.user.id}
/>
Two alternatives if, for whatever reason, the recommendations don't work for your situation.
Alternative 1: Reset uncontrolled component with an ID prop
class EmailInput extends Component {
state = {
email: this.props.defaultEmail,
prevPropsUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// Any time the current user changes,
// Reset any parts of state that are tied to that user.
// In this simple example, that's just the email.
if (props.userID !== state.prevPropsUserID) {
return {
prevPropsUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}
// ...
}
Alternative 2: Reset uncontrolled component with an instance method
class EmailInput extends Component {
state = {
email: this.props.defaultEmail
};
resetEmailForNewUser(newEmail) {
this.setState({ email: newEmail });
}
// ...
}
As I know only thing redux does, on change of store's state is calling componentWillRecieveProps if your component was dependent on mutated state and then you should force your component to update
it is like this
1-store State change-2-call(componentWillRecieveProps(()=>{3-component state change}))