I'm using React and Redux in my web app.
In the login page, I have multiple fields (inputs).
The login page in composed from multiple components to pass the props to.
I was wondering how should I pass the props and update actions.
For example, lets assume I have 5 inputs in my login page.
LoginPage (container) -> AuthenticationForm (Component) -> SignupForm (Component)
In the LoginPage I map the state and dispatch to props,
and I see 2 options here:
mapStateToProps = (state) => ({
input1: state.input1,
...
input5: state.input5
})
mapDispatchToProps = (dispatch) => ({
changeInput1: (ev) => dispatch(updateInput1(ev.target.value))
...
changeInput5: (ev) => dispatch(updateInput5(ev.target.value))
})
In this solution, I need to pass a lot of props down the path (the dispatch actions and the state data).
Another way to do it is like this:
mapStateToProps = (state) => ({
values: {input1: state.input1, ..., input5: state.input5}
})
mapDispatchToProps = (dispatch) => ({
update: (name) => (ev) => dispatch(update(name, ev.target.value))
})
In this solution, I have to keep track and send the input name I want to update.
How should I engage this problem?
It seems like fundamental question, since a lot of forms have to handle it,
but I couldn't decide yet what would suit me now and for the long run.
What are the best practices?
I think best practice would be to handle all of this logic in the React component itself. You can use component's state to store input's data and use class methods to handle it. There is good explanation in React docs https://reactjs.org/docs/forms.html
You probably should pass data in Redux on submit. Ether storing whole state of the form as an object, or not store at all and just dispatching action with api call.
TL;DR. it's a more 'general' coding practice. But let's put it under a react-redux context.
Say if you go with your first approach, then you will probably have 5 actionCreators as:
function updateInput1({value}) { return {type: 'UPDATE_INPUT1', payload: {value}} }
...
function updateInput5({value}) { return {type: 'UPDATE_INPUT5', payload: {value}} }
Also if you have actionTypes, then:
const UPDATE_INPUT1 = 'UPDATE_INPUT1'
...
const UPDATE_INPUT5 = 'UPDATE_INPUT5'
The reducer will probably look like:
function handleInputUpdate(state = {}, {type, payload: {value}}) {
switch (type) {
case UPDATE_INPUT1: return {..., input1: value}
...
case UPDATE_INPUT5: return {..., input5: value}
default: return state
}
}
What's the problem? I don't think you're spreading too many props in mapStateToProps/mapDispatchToProps, Don't repeat yourself!
So naturally, you want a more generic function to avoid that:
const UPDATE_INPUT = 'UPDATE_INPUT'
function updateInput({name, value}) { return {type: UPDATE_INPUT, payload: {name, value}} }
function handleInputUpdate(state = {inputs: null}, {type, payload: {name, value}}) {
switch (type) {
case UPDATE_INPUT: return {inputs: {...state.inputs, [name]: value}}
default: return state
}
}
Finally, the "selector" part, based upon how the state was designed, get component's props from it would be fairly trivial:
function mapStateToProps(state) { return {inputs: state.inputs} }
function mapDispatchToProps(dispatch) { return {update(name, value) { dispatch(updateInput(name, value)) } }
In summary, it's not necessarily a redux/react problem, it's more how you design app state, redux just offers you utilities and poses some constraints to enable "time traveling" (state transitions are made explicit within a mutation handler based on a separate action).
Best practice to handle this problem is having a local state on your Form Component and managing it locally because I believe it's not a shared state. onSubmit you could dispatch your action passing down the state to the action which is required in making an API call or posting it to your server.
If you try to keep updating your store as the user types, it will keep dispatching the action which might cause problems in future. You read more here Handling multiple form inputs in react
Related
Can I use a selector from a reducer?
I have a code similar to that:
case Action Types.ACTION: {
const loading = getIsLoading(state)
return {
atribute1: false,
attribute2: action.payload,
}
}
default: {
return state;
}
}
}
export const getState = createFeatureSelector<State>('feature');
export const getIsLoading = createSelector(
getState,
state => state.loading
);
When I dispatch a action at first I get a error like that: "can't read 'loading' of undefined.
Where is my mistake? Something wrong with my approach?
Like I've stated in the comment: It's a best practice not to duplicate information in the state. If you need to call a selector in a reducer it looks like you'll do just this.
You can avoid doing this by creating a third, higher order, reducer that will return whatever is needed from the existing ones.
So you have the getIsBusy reducer and you create another one that will just return the new data from your state, the one where you wanted to read isBusy:
export const getAttribute = createSelector(
getState,
state => state.attribute
);
Now you have getIsBusy and getAttribute as selectors and you can create a third one where you can place whatever logic you need:
export const getHigherOrderSelector = createSelector(
getIsBusy,
getAttribute,
(isBusy, data) => {
return { isBusy, data };
// Or whatever other processing you might want to do here
}
);
So I just switched to using stateless functional components in React with Redux and I was curious about component lifecycle. Initially I had this :
// actions.js
export function fetchUser() {
return {
type: 'FETCH_USER_FULFILLED',
payload: {
name: 'username',
career: 'Programmer'
}
}
}
Then in the component I used a componentDidMount to fetch the data like so :
// component.js
...
componentDidMount() {
this.props.fetchUser()
}
...
After switching to stateless functional components I now have a container with :
// statelessComponentContainer.js
...
const mapStateToProps = state => {
return {
user: fetchUser().payload
}
}
...
As you can see, currently I am not fetching any data asynchronously. So my question is will this approach cause problems when I start fetching data asynchronously? And also is there a better approach?
I checked out this blog, where they say If your components need lifecycle methods, use ES6 classes.
Any assistance will be appreciated.
Firstly, don't do what you are trying to to do in mapStateToProps. Redux follows a unidirectional data flow pattern, where by component dispatch action, which update state, which changes component. You should not expect your action to return the data, but rather expect the store to update with new data.
Following this approach, especially once you are fetching the data asynchronously, means you will have to cater for a state where your data has not loaded yet. There are plenty of questions and tutorials out there for that (even in another answer in this question), so I won't worry to put an example in here for you.
Secondly, wanting to fetch data asynchronously when a component mounts is a common use case. Wanting to write nice functional component is a common desire. Luckily, I have a library that allows you to do both: react-redux-lifecycle.
Now you can write:
import { onComponentDidMount } from 'react-redux-lifecycle'
import { fetchUser } from './actions'
const User = ({ user }) => {
return // ...
}
cont mapStateToProps = (state) => ({
user = state.user
})
export default connect(mapStateToProps)(onComponentDidMount(fetchUser)(User))
I have made a few assumptions about your component names and store structure, but I hope it is enough to get the idea across. I'm happy to clarify anything for you.
Disclaimer: I am the author of react-redux-lifecycle library.
Don't render any view if there is no data yet. Here is how you do this.
Approach of solving your problem is to return a promise from this.props.fetchUser(). You need to dispatch your action using react-thunk (See examples and information how to setup. It is easy!).
Your fetchUser action should look like this:
export function fetchUser() {
return (dispatch, getState) => {
return new Promise(resolve => {
resolve(dispatch({
type: 'FETCH_USER_FULFILLED',
payload: {
name: 'username',
career: 'Programmer'
}
}))
});
};
}
Then in your Component add to lifecycle method componentWillMount() following code:
componentDidMount() {
this.props.fetchUser()
.then(() => {
this.setState({ isLoading: false });
})
}
Of course your class constructor should have initial state isLoading set to true.
constructor(props) {
super(props);
// ...
this.state({
isLoading: true
})
}
Finally in your render() method add a condition. If your request is not yet completed and we don't have data, print 'data is still loading...' otherwise show <UserProfile /> Component.
render() {
const { isLoading } = this.state;
return (
<div>{ !isLoading ? <UserProfile /> : 'data is still loading...' }</div>
)
}
I have a component which builds onto the Select component from Ant Design https://ant.design/components/select/
<SomeComponent
onSelect = { this.props.handleSelect }
onDeselect = { this.props.handleDeselect }
selectionList = { valuesList }
value = { values }/>
onSelect triggeres the action this.props.handleSelect
export function handleSelect(value) {
return dispatch => {
dispatch(actionCreator(HANDLE_SELECT, value));
}
}
That actions goes into the reducer
case HANDLE_SELECT: {
const newValues = value_select(state, action);
return {
...state,
find: {
...state.a,
values: newValues
}
}
}
Finally, value_select is called to do all the magic
export const value_select = function(state, action) {
...
const newData = {
XYZ: action.payload
}
return newData
}
This brings me to my question.
Is it possible to send further metadata with the action? Imagine I use the component <SomeComponent.../> several times. I would not know which of the rendered components triggered the action when the onSelect is fired.
If I want to process the information in value_select = function(state, action) {... later, I want to know which component caused the action to process my data properly. I need to set XYZ in value_select() dynamically, depending on which <SomeComponent.../> caused the action. action.payload only gives me what is saved in value in <SomeComponent.../>, nothing more.
Is there a way to send some more information with the onSelect or is that bad practice and I would need an action for each component <SomeComponent.../> anyway?
Absolutely. It's your action and your reducer, you can attach any information you want to it.
The most common approach for structuring an action is the Flux Standard Action approach, which expects your actions to look like {type, payload, meta, error} but it's really up to you what you put into your actions.
For some more ideas, you might want to read through the Structuring Reducers - Reusing Reducer Logic section of the Redux docs.
I was reading the documentation for the Redux library and it has this example:
In addition to reading the state, container components can dispatch actions. In a similar fashion, you can define a function called mapDispatchToProps() that receives the dispatch() method and returns callback props that you want to inject into the presentational component.
This actually makes no sense. Why do you need mapDispatchToProps when you already have mapStateToProps?
They also provide this handy code sample:
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
What is this function and why it is useful?
I feel like none of the answers have crystallized why mapDispatchToProps is useful.
This can really only be answered in the context of the container-component pattern, which I found best understood by first reading:Container Components then Usage with React.
In a nutshell, your components are supposed to be concerned only with displaying stuff. The only place they are supposed to get information from is their props.
Separated from "displaying stuff" (components) is:
how you get the stuff to display,
and how you handle events.
That is what containers are for.
Therefore, a "well designed" component in the pattern look like this:
class FancyAlerter extends Component {
sendAlert = () => {
this.props.sendTheAlert()
}
render() {
<div>
<h1>Today's Fancy Alert is {this.props.fancyInfo}</h1>
<Button onClick={sendAlert}/>
</div>
}
}
See how this component gets the info it displays from props (which came from the redux store via mapStateToProps) and it also gets its action function from its props: sendTheAlert().
That's where mapDispatchToProps comes in: in the corresponding container
// FancyButtonContainer.js
function mapDispatchToProps(dispatch) {
return({
sendTheAlert: () => {dispatch(ALERT_ACTION)}
})
}
function mapStateToProps(state) {
return({fancyInfo: "Fancy this:" + state.currentFunnyString})
}
export const FancyButtonContainer = connect(
mapStateToProps, mapDispatchToProps)(
FancyAlerter
)
I wonder if you can see, now that it's the container 1 that knows about redux and dispatch and store and state and ... stuff.
The component in the pattern, FancyAlerter, which does the rendering doesn't need to know about any of that stuff: it gets its method to call at onClick of the button, via its props.
And ... mapDispatchToProps was the useful means that redux provides to let the container easily pass that function into the wrapped component on its props.
All this looks very like the todo example in docs, and another answer here, but I have tried to cast it in the light of the pattern to emphasize why.
(Note: you can't use mapStateToProps for the same purpose as mapDispatchToProps for the basic reason that you don't have access to dispatch inside mapStateToProp. So you couldn't use mapStateToProps to give the wrapped component a method that uses dispatch.
I don't know why they chose to break it into two mapping functions - it might have been tidier to have mapToProps(state, dispatch, props) IE one function to do both!
1 Note that I deliberately explicitly named the container FancyButtonContainer, to highlight that it is a "thing" - the identity (and hence existence!) of the container as "a thing" is sometimes lost in the shorthand
export default connect(...)
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
syntax that is shown in most examples
It's basically a shorthand. So instead of having to write:
this.props.dispatch(toggleTodo(id));
You would use mapDispatchToProps as shown in your example code, and then elsewhere write:
this.props.onTodoClick(id);
or more likely in this case, you'd have that as the event handler:
<MyComponent onClick={this.props.onTodoClick} />
There's a helpful video by Dan Abramov on this here:
Redux: Generating Containers with connect() from React Redux (VisibleTodoList)
mapStateToProps() is a utility which helps your component get updated state(which is updated by some other components),
mapDispatchToProps() is a utility which will help your component to fire an action event (dispatching action which may cause change of application state)
mapStateToProps, mapDispatchToProps and connect from react-redux library provides a convenient way to access your state and dispatch function of your store. So basically connect is a higher order component, you can also think as a wrapper if this make sense for you. So every time your state is changed mapStateToProps will be called with your new state and subsequently as you props update component will run render function to render your component in browser. mapDispatchToProps also stores key-values on the props of your component, usually they take a form of a function. In such way you can trigger state change from your component onClick, onChange events.
From docs:
const TodoListComponent = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
const TodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
Also make sure that you are familiar with React stateless functions and Higher-Order Components
Now suppose there is an action for redux as:
export function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
When you do import it,
import {addTodo} from './actions';
class Greeting extends React.Component {
handleOnClick = () => {
this.props.onTodoClick(); // This prop acts as key to callback prop for mapDispatchToProps
}
render() {
return <button onClick={this.handleOnClick}>Hello Redux</button>;
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: () => { // handles onTodoClick prop's call here
dispatch(addTodo())
}
}
}
export default connect(
null,
mapDispatchToProps
)(Greeting);
As function name says mapDispatchToProps(), map dispatch action to props(our component's props)
So prop onTodoClick is a key to mapDispatchToProps function which delegates furthere to dispatch action addTodo.
Also if you want to trim the code and bypass manual implementation, then you can do this,
import {addTodo} from './actions';
class Greeting extends React.Component {
handleOnClick = () => {
this.props.addTodo();
}
render() {
return <button onClick={this.handleOnClick}>Hello Redux</button>;
}
}
export default connect(
null,
{addTodo}
)(Greeting);
Which exactly means
const mapDispatchToProps = dispatch => {
return {
addTodo: () => {
dispatch(addTodo())
}
}
}
mapStateToProps receives the state and props and allows you to extract props from the state to pass to the component.
mapDispatchToProps receives dispatch and props and is meant for you to bind action creators to dispatch so when you execute the resulting function the action gets dispatched.
I find this only saves you from having to do dispatch(actionCreator()) within your component thus making it a bit easier to read.
React redux: connect: Arguments
Let's say I've got an app with two reducers - tables and footer combined using combineReducers().
When I click on some button two actions are being dispatched - one after another: "REFRESH_TABLES" and "REFRESH_FOOTER".
tables reducer is listening for the first action and it modifies the state of tables. The second action triggers footer reducer. The thing is it needs current state of tables in order to do it's thing.
My implementation looks something like below.
Button component:
import React from 'react';
const refreshButton = React.createClass({
refresh () {
this.props.refreshTables();
this.props.refreshFooter(this.props.tables);
},
render() {
return (
<button onClick={this.refresh}>Refresh</button>
)
}
});
export default refreshButton;
ActionCreators:
export function refreshTables() {
return {
type: REFRESH_TABLES
}
}
export function refreshFooter(tables) {
return {
type: REFRESH_FOOTER,
tables
}
}
The problem is that the props didn't update at this point so the state of tables that footer reducer gets is also not updated yet and it contains the data form before the tables reducer run.
So how do I get a fresh state to the reducer when multiple actions are dispatched one after another from the view?
Seems you need to handle the actions async so you can use a custom middleware like redux-thuk to do something like this:
actions.js
function refreshTables() {
return {
type: REFRESH_TABLES
}
}
function refreshFooter(tables) {
return {
type: REFRESH_FOOTER,
tables
}
}
export function refresh() {
return function (dispatch, getState) {
dispatch(refreshTables())
.then(() => dispatch(refreshFooter(getState().tables)))
}
}
component
const refreshButton = React.createClass({
refresh () {
this.props.refresh();
},
{/* ... */}
});
Although splitting it asynchronous may help, the issue may be in the fact that you are using combineReducers. You should not have to rely on the tables from props, you want to use the source of truth which is state.
You need to look at rewriting the root reducer so you have access to all of state. I have done so by writing it like this.
const rootReducer = (state, action) => ({
tables: tableReducer(state.tables, action, state),
footer: footerReducer(state.footer, action, state)
});
With that you now have access to full state in both reducers so you shouldn't have to pass it around from props.
Your reducer could then looks like this.
const footerReducer = (state, action, { tables }) => {
...
};
That way you are not actually pulling in all parts of state as it starts to grow and only access what you need.