How to use redux state with react hooks - reactjs

So we recently decided to start using hooks in our current react app. We are using redux for state management and my question was how does this work with hooks?
I've read some articles where people use the context api with hooks to create a state manager but I would like to keep using redux for now.
I know the react api has a useReducer method, can this be used to dispatch redux actions? I've been looking for a tutorial/example but can't seem to find any resources online for this. I may be headed down the wrong path but would like to know if I am. Thanks.

Nothing changes with hooks when using Redux, Redux Higher Order Component has nothing to do with Hooks. useReducer isn't meant for dispatching Redux actions but for updating the component internal state the same way Redux does.
So when you use useReducer you will dispatch actions, update the state with a reducer etc. but not the Redux state! Instead, you're doing that with the component state.
A component that consumes useReducer is a standard component with an internal state (to manage input states or whatever you want) wrapped, as usual before the hooks born, in a Redux's connect HOC.
If it could be helpful you can clarify your ideas with this post

I'm not a heavy user of the Redux (I prefer MobX), so I might be overlooking certain subtle aspects. But from what I see it's pretty straightforward and React docs on hooks provide very nice example:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
Instead of reducer here, you can simply use one of the related reducers from your existing state management code base. Simply import it into the component file and pass it to the useReducer as the first argument. The state that you will return from there will become a new state for the component and it will auto-rerender itself with it. Obviously you will need to disconnect such component from Redux (if it is connected). Otherwise as it was mentioned in one of the comments you will end up with redundant state management logic.
However on your place I wouldn't rewrite it for hooks without any utter necessity.

Related

`useSelector` rerenders even when the object is the same

I am refactoring React app that was built using class syntax and connect to React hooks.
App is meant to be for debugging and one of the bugs that has to be fixed is that in the reducer file we are not returning the new state object so the component doesn't rerender.
export default function comments(state = InitialState, action) {
switch (action.type) {
case "CHANGE_SORT": {
state.sort = action.sort;
return state;
}
default:
return state;
}
}
This doesn't cause the app to rerender so the state doesn't update and that is fine. However, once I refactored the app to use React hooks and useSelector the component rerenders even with this code. Is there a way I could make it not rerender unless the new state object is returned?
This is how the store is setup:
const store = createStore(
combineReducers({
posts,
sort,
}),
{},
applyMiddleware(thunkMiddleware)
);
The first problem is that your reducer is mutating the existing state, and you can never mutate state in a Redux app.
Both useSelector and connect will cause your component to re-render if you return new references. You'd need to show your useSelector for me to give a more specific answer as to what's going on.
Also, you should be using our official Redux Toolkit package to write your Redux logic, including using the createSlice API to write your reducers. It will drastically simplify your Redux code.

Is the storage of data acquired, with an HTTP-Client such as Axios, in the context-api state a good practice?

i've been developing an app using react 16.8 and to avoid importing Axios in every component i started making use of the context API to hold data from all my requests on its internal state and reference this data in the component where i need it with the <Consumer/>, but the number of properties in the state grew considerably, i wonder if this is ok, and if it is should i clean the state after using the data where i needed, in a method like ComponentWillUnmount?
Class ContextProvider {
state = {
prop1: [],
prop2: [],
prop3: [],
{...n}
}
getProp1Data = () => {
Axios.get(url)
.then(res => this.setState({prop1: res.data})
}
{.... n}
}
You should consider using Redux since with Redux you can travel between the state history in development, and re-evaluate the current state from the action history when the code changes. In this way you be able to import Axios only once to your middle ware and to make your code more clear.
In case you consider to use ComponentWillUnmount you won't be 'cleaning' your state since setState() can't be called on this life cycle method. ComponentWillUnmount is related to the competent itself on the DOM and not to the state. There's not a problem with your props being 'grew considerably' and you may use as many as you like.

useReducer in customer hooks does not update the UI

I want to use a useReducer to implement a custom hooks like useLegacyState mentioned in the official documentation to flexibly update the state in the component.
https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables
Only use useState or useReducer can be implemented:
https://codesandbox.io/embed/usereducer-update-any-05v82
However, I am unable to update the UI using custom hooks:
https://codesandbox.io/embed/customer-usestates-xxn01
Why?
You need to update your reducer function:
function reducer(state, action) {
return Object.assign({}, state, action);
}
The reason is
when you do Object.assign(state, action), state also gets updated with attributes from action, which causes React not being able to disguise the original state from the updated state, hence shouldComponentUpdate returns false since state and the updated state are the same.

React componentDidUpdate not receiving latest props

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".

How does a redux connected component know when to 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}))

Resources