`useSelector` rerenders even when the object is the same - reactjs

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.

Related

Redux - get initial state from the backend - which is the proper way to do it?

I would like to get some variables from the backend (ie showTutorial) for the initial state in a reducer.
I was thinking about using just a call in axios from the reducer, but I am unsure if that is the best way of doing it.
This is how it is done right now:
import { UNSET_TUTORIAL, SET_FILTER_BLOCKS } from "../actions/types";
const initialState = {
showTutorial: true, // <-- instead of initializying to true, do the axios call
filterBlocks: "ALL"
};
export default (state = initialState, action) => {
switch (action.type) {
case UNSET_TUTORIAL:
return { ...state, showTutorial: false };
case SET_FILTER_BLOCKS:
return { ...state, filterBlocks: action.payload };
default:
return state;
}
};
I do not want to use redux-persist as I would like to understand first the proper way of doing it.
There is no difference between how you do this and how you update state from a backend for any other component.
You should have an action triggered on a top level app component (or any component created at application launch) that loads data from the backend and dispatches the action with the results from the backend. Your reducer then updates state based on this action.
The triggering of the action on your top level app component can be done using a lifecycle event when the component mounts, typically getDerivedStateFromProps()
The best way to do it is to call the relevant action from App.js, and render 'loading' until the data is properly fetched. Obviously, in case of error you will not continue to render components but display an error message.
Reducers should never call actions, this is breaking the redux flow.

How to use redux state with react hooks

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.

How reducers update the store

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
}
}

How can I use a React 16.3 Context provider with redux store?

I'm working in a codebase that has a bunch of redux already working for managing and persisting state, as well as dispatching actions. My goal with the new Context API is to get rid of the prop-drilling that I have to do to deliver all these pieces of state to various components but keep the existing code for managing state in redux.
Now I've removed the excessive prop drilling code and replaced them with context Providers and Consumers, hooking them up in my components in all the right places. I'm still dispatching actions to redux and getting API responses populating my redux store, and I want to somehow notify the contexts to update when specific parts of the redux store update.
How do I get updates from the redux store delivered into my different Providers?
Since you are using the new Context Api, you can easily connect the component that uses the Context Provider with redux store and update the context values like
const Context = React.createContext();
class App extends React.Component {
state = {
user: ''
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.user !== prevState.user) {
return {user: nextProps.user}
}
return null;
}
render() {
return <Context.Provider value={this.state.user}>
<MyComponent />
</Context.Provider>
}
}
const mapStateToProps(state) {
return {
user: state.user
}
}
export default connect(mapStateToProps)(App);

Selector being called even when I don't mutate what its mapPropsToState

I have a React app that does some simple recording. I have a Component Recorder which connects to my redux store like this:
export default connect(
state => ({
recordings: state.recordings,
recordingSelector: selectRecordingBufferWithID(this.recordingID)
}),
dispatch =>
bindActionCreators({
startNewRecordingAction,
stopNewRecordingAction
},
dispatch
)
)(SampleRecorder);
The problem I'm having is that selectRecordingBufferWithID in my redux code is firing too often. Part of my reducer code looks like this:
function samplesReducer(state = [], action) {
switch (action.type) {
case MORE_SAMPLES:
return [...action.samples];
default:
return state
}
}
function recordingsReducer(state = [], action) {
switch (action.type) {
case NEW_RECORDING:
return newRecording(state, action.recordingID);
case STOP_RECORDING:
return stopRecording(state, action.recordingID);
default:
return state
}
}
const rootReducer = combineReducers({
samplesReducer,
recordingsReducer
})
const store = createStore(rootReducer);
export { store };
So, while I want selectRecordingBufferWithID to be utilized only when a START/STOP_RECORDING action occurs, it is called for each time MORE_SAMPLES is called.
My understanding of react-redux is that the selector is part of the mapStateToProps function that the connect function accepts. And somehow, connect cause my component to render and for its props to be updated with the mapped state from the redux store. the selectRecordingBufferWithID selector will also be called each time this happens so I can do a refined getter into the store.
So to summarize, my recordingSelector is firing more often than I expect. My only theory is that my reducers are somehow mutating the state of state.recordings each time it tries to reduce state.samples which makes react-redux render my component with it mapped to state.recording.
But otherwise, I'm stuck.
connect does not work the way you think it does. What it really does is:
Subscribe to the store. This subscription will be triggered after every dispatched action.
Execute your mapStateToProps to inject the initial set of props to your Sample Recorder component.
When any action dispatches, the subscription kicks in, and connect applies again your mapStateToProps to new global state.
If your selector returns the same props as before, it won't render your SampleRecorder again.
So the misunderstanding is that your selector shouldn't be called. But the fact is that connect needs to call your selector to decide when to re-render and when not.
The summary of this is that your selector should be either simple, or memoizable using reselect to avoid expensive calculations. You didn't show you selector code so we can't tell from here. :)

Resources