Reusable react-redux container components - reactjs

In my React/Redux app I am often facing with the problem of implementing components with state which should be used throughout the app.
Let's take simple popup component as an example with open/close state which can be reused in any page.
Here is two possible approaches I found:
Use setState and "local" reducer (I use recompose.withReducer which is just syntax sugar for React's native setState) to manage its state. It looks easy and reusable until you need change the component's state in the other part of your page (close popup in out case). And you cannot just call some redux action to change the state.
Keep the component's state in the Redux store. With such approach we can call closePopupAction({ id }) in any place of the components tree to change it's state.` But we need somehow put its reducer (which i want to keep in the popup's folder) to the root reducer when the component is mounted and delete it when the component is unmounted. Plus we can have multiples popups in the page and each of them have its own state.
Did anybody face with a similar problem ?

I think you should keep state of component in redux. You can create reducer for this component and use combineReducers function in this way:
rootReducer = combineReducers({
moduleA: combineReducers({
popupA: popupReducer("id1"),
popupB: popupReducer("id2")
}),
moduleB: combineReducers({
popupA: popupReducer("id3")
})
})
});
then when you call closePopupAction("id1") reducer can check id and change proper part of state.
PS: Id should be provided in better way :)

You could mount each component's state to its own slice of the store.
So your closePopupAction actions would be called with that mount path:
closePopupAction({ mountPath: 'popups.popup1' })
and you would need only one reducer to handle all closePopupAction actions, which could be registered once at startup:
(state, { type, mountPath }) => {
if (type === 'CLOSE_POPUP_ACTION') {
// manipulate with the slice at `mountPath`, e.g.
return _.set(_.cloneDeep(state), `${mountPath}.isOpen`, false)
}
// ...
}

Related

Do selectors cause re-rendering on unrelated components in Redux toolkit?

I am diving into Redux toolkit and made a simple project to understand its concepts better.
function App() {
//..
return(
<UserList /> // uses submittedUserSelector
<UserInformation /> // uses selectedUserSelector
);
}
The basic idea is that you can either add a user to UserList or select one from the list which will be shown in UserInformation. Both the submitted and selected user are managed by different reducers in the same store.
export const rootReducer = combineReducers({
form: formReducer, // have submitted user selector
user: userReducer // have selected user selector
});
// formReducer.tsx
export const submittedUserSelector = (state:RootState)=>state.form.submittedUser; //object
// selectedUserReducer.tsx
export const selectedUserSelector = (state:RootState)=>state.user.selectedUser; //object
According to the official documentation and I quote:
When an action is dispatched, useSelector() will do a reference
comparison of the previous selector result value and the current
result value. If they are different, the component will be forced to
re-render. If they are the same, the component will not re-render.
So I was expecting that when I selected a user which dispatches an action in userReducer
would result in re-rendering UserList as well (as submittedUserSelector returns an object). That didn't happen, however.
Why? Does redux-toolkit figure out which components in the tree are using a particular selector and only evaluate those? Is that somewhere in the documentation?
Selectors will be called on every state change and only cause a component rerender if the selector result changes from the last render - so usually, unrelated components will not be rerendered.
Keep in mind that general React component rendering still applies - if a parent component rerenders, all child components will be rerendered.
This has nothing to do with redux-toolkit though - it is the normal behaviour of the react-redux hooks. See https://react-redux.js.org/api/hooks

Using Redux, can any component get and set (by action) any data in the store?

Using Redux, is it true that any component and sub-component on the page get all data of the one and only store, and be able to send out any action at all, even if it is not meant for that component to send out?
Can all the components use
const store = createStore(mainReducer);
let state = store.getState();
and be able to see all states of the whole app? Can any component dispatch any action all all? So for example, if there is Counter component and a Comment component, can the Comment component accidentally send out a "INCREASE_COUNT" action?
Using Redux any component "can" access any data in the store, but for the access you have to 'connect' it with the store. When you 'connect' you also specify a map to which part you want this component to access. That's how you are in control, it only gets access to what you want only.
The same goes for actions. You have to map the actions also - which component can dispatch which action, when your 'connect' to the store.
Check this out for more info - https://redux.js.org/basics/usage-with-react
To most part of your question, it seems the answer is Yes.
Yes, the components can access the whole store ( one it has subscribed to ) and can dispatch actions when needed. I do not think there is any way you can put action/store behind some restrictions.
can the Comment component accidentally send out an "INCREASE_COUNT" action? Yes if you try to dispatch it again from the child component.
If you could add any specific example you have to ask, I can add more to my answer.
I hope it helps you !
" every component has access to the store" is wrong, it is like this " every component has access to the state and actions in the store that you "the developer" specify.
for a component to able to access the store, you need to wrap it in the connection function like so
import { connect } from "react-redux";
// Your component
export default connect(mapStateToProps, dispatchActionToProps);
// the component will only have access to the store props and actions that you specify
// in mapStateToProps and dispatchActionToProps
const mapStateToProps = state => {
return {
// state is the result of combineReducers
// whatevery key the component needs you can specify here
};
}
const dispatchActionToProps = dispatch => {
return {
// your store actions
};
}

React-Redux narrowing state for child component

Is there a way to pass some part of state (not the whole state) to the child component of some React component? So that mapStateToProps and getState method in redux middleware would use such a substate?
This is important for developing react-redux logic that is unaware of state structure it is used in. It could be then used in several app places or different applications. Such logic would behave as an independent module and satisfy the principle of encapsulation.
Such concept exist in Knockout.js ("with" operator) and in desktop WPF.NET (passing DataContext to child element).
Thank you!
One possible approach is to use selectors and/or namespaces.
With selector, you can encapsulate the logic of querying the state object.
However, you cannot totally hide redux state's shape from your react-redux logic, since it's subscribed to the whole store, by design. Still, we can use a namespace here.
Example:
// counter.js
const namespace = 'counter'
const actualReducer = (state, action) => action.type === 'INC' ? state + 1 : state;
export default { [namespace]: actualReducer }
export const selector = (state, ownProps) => state[namespace]
// configureStore.js
import reducer from './counter'
const store = createStore(reducer)
export default store
// Component.js
import { selector } from './counter'
const Component = props => <some markup />
export default connect(selector)(Component)
So what do we have? In brief:
Component.js is not aware of state shape and even the namespace;
configureStore.js is not aware of the counter namespace;
counter.js is the only place, that is aware of its namespace in the store.
One can also extract selector to separate file since it's the only place where we have to deal with original state object.
Note that namespace should be unique, or, to be re-usable from app to app, it can be imported or injected.
IMO, the idea of re-using react-redux stuff (i.e. connect(...)) is not very solid. Since usually, the state shape is not going to be the same from app to app
I'd encourage you to keep it explicit, so the underlying component is the real one who should have an API that is as unopinionated as possible.

Is it possible or a good idea to hold react components inside reducer and based on action pass the component to the container?

I want to build a generic dialog, all its responsibility is to show the model dialog and the content inside the dialog. However i want to pass the content via reducer based on the action. So if the dispatched action is to display a list of radio buttons in the modal dialog the reducer based on switch will return a react component which gets rendered in the dialog or may the dispatched action is to display a table with rows and columns and the reducer will return another component which then gets rendered in the modal dialog.
Is this possible or a good idea? if not how can I build a generic dialog where the content to display is passed in dynamically?
my reducer will be something like this
import {LOAD_RADIOBUTTONS} from "../actions/radiobutton_action";
import {LOAD_TABLE} from "../actions/table_action";
import LoadRadioButtons from "../components/LoadRadioButtons";
import LoadTableData from "../components/LoadTableData";
const INITIAL_STATE = { data: null, openDLG: false, loadData: null };
export default function(state = INITIAL_STATE, action) {
switch(action.type){
case LOAD_RADIOBUTTONS :
return { data: action.payload, openDLG: true, LoadData: LoadRadioButtons };
case LOAD_TABLE :
return { data: action.payload, openDLG: true, LoadData: LoadTableData };
default: return state;
}
}
Now the container will get data from the reducer and the component it needs to render and will render it.
It is definitely possible to hold a component in your Redux state, as components are just functions, whether they are class-based components or functional components. However, it might be better to pass the component you want to render down as a prop, as holding the component to render in your store will probably make your state merging logic more complicated.
For instance, the parent component of the component which will do the dynamic rendering might read from the Redux store, and based on the state of the Redux store, choose one of many components to pass down as a prop to its child. This seems like a better approach to me, as it stays within the Redux good practice of making components pure functions of their props and state, and it keeps your Redux store data model cleaner.

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