UPDATE: It's not possible to solve from information I gave you, the problem is that code is updated in ComponentReceiveProps hook and original values overwrite changed values.
I work on project developed by external agency that didn't do the best job.
It is front-end application using React, Redux and Redux-Forms
There is one form that is used in two different pages. It works well in one page, but doesn't react in the other. I cannot change value of inputs by UI, neither by changeFieldValue function. Redux state is not changed.
In both cases, the same component is used. It contains reduxForm function.
const mapDispatchToProps = dispatch => ({
changeFieldValue: (field, value) => dispatch(change('ContractConsumption', field, value)),
});
class ContractConsumption extends React.Component {
// ...
}
ContractConsumption = reduxForm({
form: 'ContractConsumption',
enableReinitialize: true
})(ContractConsumption);
Any help would be appreciated. It's confusing to me, why the same component with redux form would work in one case and not in the other.
The whole code is overly complicated and heavily coupled, so it's not possible to share it easily or test it part by part.
It looks like you/agency didn't connect the form to the store:
import React, { Component } from 'react';
import { reduxForm } from 'redux-form';
import { connect } from 'react-redux';
class Example extends Component {
// ...
};
const mapStateToProps = (state) => ({
// ...
});
const mapDispatchToProps = (dispatch) => ({
// ...
});
Example = connect(
mapStateToProps,
mapDispatchToProps
)(Example);
export default reduxForm({
form: 'example' // a unique name for this form
})(Example);
See How do I mapStateToProps or mapDispatchToProps?
Related
I'm implementing Redux on React and it's currently working, but I'm not sure if it's correct. I have a redux folder with 3 files: actions, reducers and store.
Actions has this code:
export const SETTER_USER = "SETTER_USER"
export const setterUserAction = ({ email, username, role, lastVideo }) => ({
type: SETTER_USER,
payload: { email, username, role, lastVideo }
})
I'm calling this action from the component, this is the code on the component:
import React, { useState, useEffect } from "react";
import { connect } from 'react-redux'
import { setterUserAction } from '../../redux/actions'
...
const Login = ({ navigation, user, setUser }) => {
...
}
const mapStateToProps = state => ({
user: state.user
})
const mapDispatchToProps = ({
setUser: setterUserAction
})
export default connect(mapStateToProps, mapDispatchToProps)(Login)
I used to have the action inside the component, which I can imagine is not ideal. So I moved it to an actions.js file. But now I'm not using a dispatch function, which feels weird as dispatch is part of the whole pattern. So, what do you think? Is this correctly implemented? Or it's just working by luck?
You are defining mapDispatchToProps as an object. It was recommended by react-redux team: You can read more about it in here: https://react-redux.js.org/using-react-redux/connect-mapdispatch#defining-mapdispatchtoprops-as-an-object
We recommend always using the “object shorthand” form of mapDispatchToProps, unless you have a specific reason to customize the dispatching behavior.
Update: If you are using Function component with Hook, you should use the React-Redux hooks API: https://react-redux.js.org/api/hooks
We recommend using the React-Redux hooks API as the default approach in your React components.
The existing connect API still works and will continue to be supported, but the hooks API is simpler and works better with TypeScript.
But if you are using Class Component. You should use connect with mapDispatchToProps as an object instead of as a function.
some questions about React.js and Redux:
Can functional components also take advantage of the store and the states saved therein? e.g maybe in combination with React hooks like useEffect()?
In general, I can combine multiple reducers to one rootReducer and createStore(rootReducer) with it, and then pass it to a Provider Component that wraps my Component with it, this way, the store should be globally available in my whole app, correct?
For every component that want to use the store / states, do I always have to import the 2 methods mapStateToProps() and mapDispatchToProps() from react-redux for every Component and then connect them? Or can I also do this on some top-level component and make the usage of redux available in all my components globally, like in question 2) with the store provider?
last question: Can I still use the this.state property in my Components or use them in parallel as an addition (e.g for this Component isolated states) and then get the props from this state as usual with this.state.someState or is this not possible anymore when I already use Redux? And in the same way, can I still use / pass props to my components and read them from my Components as well, or is everything managed by state now only? (Or has the passing of props to my children nothing to do with Redux)?
1) Yes functional components can take advantage of the store. Its arguably much cleaner to read since props can be destructured right away.
const MyComponent = ({ auth }) => {
const [display, setDisplay] = useState(false)
useEffect(() => {
if(auth.user){
setDisplay(true)
}
}, [auth.user])
return(
<div>
{ display ? "Content": "Please sign in" }
</div>
)
}
const mapStateToProps = (state) => {
return{
auth: state.auth
}
}
export default connect(mapStateToProps)(MyComponent)
2) That is correct. You can also use combineReducers() which in some ways is cleaner to read.
import { createStore, combineReducers } from "redux"
import authReducer from "./reducers/authReducer"
import postReducer from "./reducers/postReducer"
const store = createStore(combineReducers({
auth: authReducer,
post: postReducer
}))
export default store
Then import store, wrap your App.js in a Provider and give it a prop of that store.
3) Generally, if you want your component to have direct access to the store it is a recognized pattern to use connect() in each one. Whether you decide to use mapStateToProps() or mapDispatchToProps() is entirely dependent on what that component needs to do. It does not required that you use both, you can just define one or the other in the connect().
import React, { useState } from "react"
import { addPost } from "/actions/postActions"
import { connect } from "react-redux"
const Form = ({ addPost }) => {
const [text, setText] = useState("")
const handleSubmit = (e) => {
e.preventDefault()
addPost(text)
}
return(
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)}/>
</form>
)
}
const mapDispatchToProps = (dispatch) => {
return {
addPost: (text) => dispatch(addPost(text))
}
}
export default connect(null, mapDispatchToProps)(Form)
4) You might have noticed by now that in the context of components, redux-state is stored as props. They are entirely different and isolated streams of data. So state remains untouched and controlled by the component itself. You can still freely use methods like this.state.dog even when your component is connected to the store. This is the isolation between component-state and redux-state.
import React, { useState } from "react"
import { connect } from "react-redux"
class MyDogs extends React.Component{
state = {
dog: "Tucker"
}
render(){
return(
<div>
Component State Value: {this.state.dog} //Tucker
Redux State Value: {this.props.dog} //Buddy
</div>
)
}
const mapStateToProps = (state) => {
return {
dog: state.dog
}
}
export default connect(mapStateToProps)(MyDogs)
I am building an app using redux and react-native.
I am curious about a pattern which I use. I have encountered no downsides however I haven't seen it in any tutorials which makes me wonder why nobody does it.
Instead of passing action creators as props in the connect function like
connect(mapStateToProps,{ func1, func2 })(Component);
I imported the app store inside of the module where I declare the functions in the first place:
import { AppStore } from '../App';
const actionCreator = () => {
doSomethng();
appStore.dispatch({ type: 'Action' });
};
This to me makes it easier to do async actions because I need no middleware:
import { AppStore } from '../App';
const actionCreator = async () => {
await doSomethng();
appStore.dispatch({ type: 'Action' });
};
I did this because of the js-lint error 'no-shadow'. It made me realise that in order to use it I had to import the action creators in the component file, and then pass it as a prop to the connect function in order for the action creator to have access to dispatch.
import { actionCreator1, actionCreator2 } from './actionCreators';
const myComponent = (props) => {
const { actionCreator1, actionCreator2 } = props; //shadowed names
return (
<Button onPress={actionCreator1} />
);
};
export default connect({}, { actionCreator1, actionCreator2 })(myComponent)
In my version I just import it once but do not pass it to connect. This eliminates the need to shadow names.
import { actionCreator1, actionCreator2 } from './actionCreators';
const myComponent = (props) => {
return (
<Button onPress={actionCreator1} />
);
};
export default connect({})(myComponent)
I like that you try to find your own solutions to your specific problems. It's the sign of an engineer, just in this case this isn't the solution.
I think the idea of how Redux teaches you to do things is not intended to be canonized. You have the ability to put a dispatcher on your props because it allows things to be transparent, meaning that things are bound outside of your class and injected in. You have hidden your store dependency by directly referencing it in some other files. It's no longer as obvious how your application works with regards to the workflow. Another react developer would be confused, I suppose that's the major downside.
If you're ok with those aspects what you're doing is fine. Fine as in, it gets the job done, but not "fine" in that it embraces concepts like Single Responsibility Principle
This is a tagalong of this question here. In contrast to that question, I don't need my presentational component to be a class. Is there a way to retrieve Redux's store without using a class and the corresponding super() method?
container.js
function mapStateToProps(state) {
return {
a: state.a
};
}
function mapDispatchToProps(dispatch) {
return {
setA: a => dispatch(setA(a)),
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(container);
presentational.js
function b({ a }) {
return {
console.log(a) // returns undefined
}
}
Does the dispatch work the same way?
Yes you can access the store from anywhere you like. You just need to export it from the file where you create it.
import { configureStore } from '...';
export const store = configureStore(...);
Import the store in your presentational.js file
import {store} from '...'
// And now you can access the current state or perform a dispatch:
store.getState() // current state
store.dispatch()
EDIT
My previous answer was wrong apologies for that, actually functional components (the one without class) can also access redux state and they can do that using connect from react-redux the very same way class components do
Reason for previous wrong answer
I once long ago tried to use connect with functional components and it didn't work for some weird reasons but when I converted the functional component to class component it worked without making changes to any other logic so I concluded that only class components can access redux state.
But I was wrong as I tested my case in this sandbox link https://codesandbox.io/s/38yw3l6nom (please look out for sample component in containers folder)
Previous wrong answer (please don't read if you are looking only for the correct solution)
No, connect from 'react-redux' modules only works on class components. Also, super is a method called in a constructor and hence they can only be called in class. You can refer this link here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes.
If you want any data stored in redux in your presentational component you'll have to pass it through a container component which will have access to the redux store. Please read more about here https://redux.js.org/recipes/writing-tests#components.
If you want to use connect on a presentational component then you'll have to use composition. recompose to achieve that.
import {compose} from 'recompose';
const presentationalComponent = props => {
return (
<div>{//Your content here}</div>
);
}
function mapStateToProps(state) {
return {
a: state.a
};
}
function mapDispatchToProps(dispatch) {
return {
setA: a => dispatch(setA(a)),
};
}
export default compose(
connect(
mapStateToProps,
mapDispatchToProps
)
)(presentationalComponent);
You'll not have to make container component this way.
I have a container component within my React and Redux application:
import { connect } from 'react-redux'
import MyComponent from '../components/mycomponent'
const mapStateToProps = state => ({
myData: state.myData[state.activeDataId]
})
export default connect(mapStateToProps)(MyComponent)
If state.myData[state.activeDataId] does not exist then I want to dispatch an action to fetchMyData or fetchMyDataIfNeeded.
Note that, at the moment, my container does not contain any JSX, it just forwards props to a presentational component. I have seen this being called a 'Pure Container' though I'm not sure if that's a common term.
Is there a common pattern to dispatch actions from a Pure Container? I am thinking without:
expecting the presentational component to worry about this logic by passing an onLoad event to it
making the container a React.Component and triggering via componentDidMount
Is it a bad idea to dispatch actions from mapStateToProps, mapDispatchToProps or mergeProps?
As noted elsewhere, doing this in the container is a bad idea.
Instead of worrying about this in the container, it makes sense to fetch the data conditionally in your component. I know you mentioned not wanting to extend react.component, but you should definitely consider making this component a class in order to fetch data in a component lifecycle hook.
As detailed in another answer, connect takes a second argument of mapDispatchToProps. Pass in the fetchData dispatcher there (example of how to do this here.)
Then, in your component you can check myData. If it is not there, then you dispatch via
this.props.whatYouCalledDispatch()
Yes, it is a bad idea to dispatch any action in container.
In your case, the best approach is:
Map your state, action creator to component props
Check the props in componentDidMount (or componentDidUpdate) and fetchDataYouNeed, then component will be updated
Your container should be:
import { connect } from 'react-redux';
import {fetchDataYouNeed} from './actions
import MyComponent from '../components/mycomponent';
const mapStateToProps = state => ({
myData: state.myData[state.activeDataId]
});
const mapDispatchToProps = (dispatch) => {
return {
fetchDataYouNeed: ()=>{
dispatch(fetchDataYouNeed());
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
Your component
class YourComponent extends Component{
componentDidMount(){
let {myData, activeDataId} = this.props;
if(myData && !myData[activeDataId]){
this.props.fetchDataYouNeed();
}
}
render(){
....
}
}
Learn more here https://facebook.github.io/react/docs/react-component.html#componentdidmount
This seems to work, though I'm not sure if it has any unintended effects:
import { connect } from 'react-redux'
import MyComponent from '../components/mycomponent'
import { fetchMyData } from '../actions/mydata'
const mapStateToProps = state => ({
dataId: state.activeDataId,
myData: state.myData[state.activeDataId]
})
const mapDispatchToProps = { fetchMyData }
const mergeProps = (stateProps, dispatchProps) => {
if (!stateProps.myData) {
dispatchProps.fetchMyData(stateProps.dataId)
}
return stateProps
}
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)
Alternatively, brianzinn suggested that by using Redux Saga to manage side effects, this issue becomes redundant.