Sending event.target.value as parameter to dispatched action - reactjs

I'm trying to update my store's "searchField" value (it starts as a blank string) when a user inputs a value into the text box of the free-response component. When I type in the field, the "searchField" property becomes undefined and I fear it's a fundamental error I can't see (I'm still quite new to Redux.) I've included my reducer, component and relevant action code below. Any help is greatly appreciated!
free-response.component.jsx:
export const FreeResponse = ({searchField,changeSearchField,i}) =>{
let questionURL="/images/question";
return(
<div className="main">
<img src={`${questionURL}${i}.png`}/>
<form >
<input type="text" onChange={changeSearchField} value={changeSearchField} alt="text field"/>
</form>
</div>
)}
const mapStateToProps=state=>({
searchField: state.question.searchField,
i:state.question.i
})
const mapDispatchToProps=dispatch=>({
changeSearchField: (e)=>dispatch(changeSearchField(e.target.value))
})
export default connect(mapStateToProps,mapDispatchToProps)(FreeResponse);
question.reducer.js:
return{
...state,
searchField:changeSearchField(e)
};
question.utils.js (action creator):
export const changeSearchField=(e)=> e;
question.actions.js:
export const changeSearchField=()=>({
type:QuestionActionTypes.CHANGE_SEARCHFIELD,
})

It seems like you did not define the payload for your changeSearchField action. This is to ensure that the values from your form input will be passed on by the action creator.
This is one way you can do it:
export const changeSearchField = (searchField) => ({
type: QuestionActionTypes.CHANGE_SEARCHFIELD,
payload: searchField
});
And on your reducer, you just need to update the store with the values from the payload (the below may differ depending on the actual structure of your store):
return {
...state,
searchField: action.payload.searchField,
};

Related

Prefill an editable input field with data from API response in React

I have a function that I use to make the API call and fetch the response. There is also another react file that renders the UI page. I'm able to store the response from the API using props and state. The issue I'm facing is that I'm able to prefill the input field with the correct data, but I'm unable to edit the input field. Also, I have to pass the input to another component.
Below is my code:
export const SelectorForm = ({ selectorDetail, selectorId, otherProp }) => {
return (
<>
<Row>
<input type="text" value={selectorDetail} onChange={updateSelectorIdAction} />
</Row>
</>
);
};
export const mapStateToProps = state => {
return {
selectorDetail: state.selectorDetail,
selectorId: selectorIdSelector(state),
};
};
export const mapDispatchToProps = {
updateSelectorIdAction: updateSelectorId,
};
export default SelectorForm;
export const updateSelectorId = (value) => ({
type: UPDATE_SELECTOR_ID,
payload: value,
});
What I need is to display the selectorDetail in the input field, take user input in selectorId and pass selectorId to another component (this part is done).
How should I change my onChange to do this?

Getting redux-form library initialValue injected into <Field>

I am building an EDIT form where the end user can see the previous data submitted, change it and save it back to the database.
The issue is to display the previous data inside the input fields. The form has been built with redux-form (here the library) which uses <Field> components.
At the moment, the process is the following:
Get DATA & Dispatch
This code sends the data already saved in the database to a reducer
React.useEffect(() => {
axios.get(`${API.BASE_URL}${API.VIEW_STORES}/${params.storesid}`)
.then(({data}) => {
dispatch(editStoreFields(data))
})
}, [dispatch, params.storesid])
Reducer
export default function (state = [], action) {
switch (action.type) {
case EDIT_STORE_FIELDS:
return {
...state,
myStore: action.data,
}
default:
return state;
}
}
Now I take the code sent to the reducer, pass it via mapStateToProps
class UsersForm extends Component {
render() {
const {
handleSubmit, pristine, reset, submitting, t, myStore
} = this.props;
if(myStore === undefined) {
return false
}
return (
<form className="form" onSubmit={handleSubmit}>
<div className="form__form-group">
<span className="form__form-group-label">MY BUSINESS</span>
<div className="form__form-group-field">
<Field
name="businessname"
component={renderField}
type="text"
>>>>> ISSUE IS HERE <===== I need to inject the value of myStore.businessname in here, but I can't figure that out.
/>
</div>
</div>
<ButtonToolbar className="form__button-toolbar">
<Button color="primary" type="submit">SAVE</Button>
</ButtonToolbar>
</form>
);
}
}
const mapStateToProps = (state) => {
return {
myStore: state.storeReducer.myStore
}
}
export default reduxForm({
form: 'vertical_form_validation', // a unique identifier for this form
validate,
enableReinitialize : true
}, mapStateToProps)(transHook(UsersForm));
Redux store value:
storeReducer:
myStore:
_id(pin):"610d12a52afdd35d4f2456a1"
businessname(pin):"qeqe"
MyStore value returned via mapState...
{
_id:"610d12a52afdd35d4f2456a1"
businessname:"qeqe"
}
So, as you can see the routing for passing the data from the API GET call to the props is absolutely fine but I can't find a way to pass the props to the <Field> and display it as text which can be changed by the user and resubmitted.
Any help is much appreciated. Please note that you need to know how redux-form library works in order to suggest a solution.
Thanks Joe
UPDATE:
I've found the solution.
1st:
<Field
name="businessname"
component={renderField}
type="text"
/>
nothing is needed here. The function mapStateToProps with initialValues will automatically use the name of the fields and assign the correct value.
Here the main part:
const mapStateToProps = (state) => ({
initialValues: state.storeReducer.myStore
})
myStore is already an object with all the keys which matches the fields' names. With this syntax automatically I have all the fields in the HTML populated with the correct data which can be changed and re-submitted.
At the top, just to keep all inline, call the initialValues props for the IF statement:
const {
handleSubmit, pristine, reset, submitting, t, initialValues
} = this.props;
if(initialValues === undefined) {
return false
}
This is not necessary to fix the issue, but I want to make a note in case you guys are following this code.

Can not render/display the fetched data from an api using, ReactJs, Axios, Redux

this is where all the things are happening
const Home = () => {
//FETCHING THE INFORAMATION
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchHeroes());
}, [Home]);
//PULLING THE DATA OR EXTRACTING IT FROM THE STATE
const { heroesInfo, heroName, heroType, attackType, mostPicked } =
useSelector((state) => state.HeroesInfoAll);
console.log(heroesInfo);
return (
<div>
<HeroList>
{heroesInfo.map((heroes) => {
<Heroes />;
})}
</HeroList>
</div>
);
};
I am also learning about Redux. I have use the reducer which has these arrays in which I want to pass the values accordingly for now though I am only passing the value to the "heroesInfo" array
const initState = {
heroesInfo: [],
heroName: [],
heroType: [],
attackType: [],
mostPicked: [],
};
const HInfoReducer = (state = initState, action) => {
switch (action.type) {
case "FETCH_HEROES":
return { ...state, heroesInfo: action.payload.heroesInfo };
default:
return { ...state };
}
};
export default HInfoReducer;
This is the Heroes component Which I want to render out for each data value present in state which you can see in the first code snippet
const Heroes = () => {
return (
<>
<h1>Hero Name</h1>
<div className="hero-image">
<img src="" alt="Hero Image" />
</div>
<h2>Hero Type</h2>
<h2></h2>
</>
);
};
export default Heroes;
I also console logged out some results to confirm the data was present in the state or not. Installed Redux tools to check it out as well here is the result
[this image shows that the data was extracted for sure after the FETCH_HEROES action ran][1]
[Here I console logged out the heroesInfo array which has the data which I want in it however on the left side my screen is completely blank. I expect it to render out my component for each element present inside of the array][2]
[1]: https://i.stack.imgur.com/nRqZr.png
[2]: https://i.stack.imgur.com/CZbHn.png
I hope I don't get banned this time, I really don't know how to ask questions but all I want to know is why is the component not being rendered out even though the data is present in their?
Please check HeroList component, if the component is returning proper data.
I rewrote the code and can get the data now but I have a new problem which I will be asking soon.

React & Redux: State of component is not updating even with mapStateToProps

I posted an answer below, but if someone can explain why this is necessary you'll get the bounty, I went through a redux tutorial and feel like I didn't learn about mapDispatchToProps, only mapStateToProps. If you can explain at a deeper level what exactly mapStateToProps and mapDispatchToProps are doing and how they are different I'll give you the bounty.
Minimum Reproducible Example
I have a mapStateToProps function that looks like
const mapStateToProps = state => {
return {
firstName: state.firstName,
middleName: state.middleName,
lastName: state.lastName,
}
}
const ReduxTabForm = connect(mapStateToProps)(MyTab)
In my MyTab component I have a button that is supposed to be inactive if these 2 field do not have anything entered in, but the state of whether or not the button is disabled does not change
function App() {
const {firstName, lastName} = store.getState().formData
const isDisabled = () => {
const {firstName, lastName} = store.getState().form
const requiredFields = [firstName, lastName]
alert(requiredFields)
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields[i]){
return true
}
}
return false
}
return (
<div className="App">
<div className='bg-light rounded'>
<div className='px-sm-5 pt-0 px-4 flexCenterCol mx-5'>
<div>
<input
type='text'
className="form-control"
value={store.getState().formData['firstName']}
placeholder="First Name"
onChange={(e) => {
store.dispatch(setFormData({'firstName': e.target.value}))
}}
></input>
<input
type='text'
className="form-control"
value={store.getState().formData['lastName']}
placeholder="Last Name"
onChange={(e) => {
store.dispatch(setFormData({'lastName': e.target.value}))
}}
></input>
</div>
<button
type="submit"
disabled={isDisabled()}
>
Button
</button>
</div>
</div>
</div>
)
}
That alert statement executes on page refresh, but does not execute any time after that when I enter data in. I have checked that the redux state updating and it is. The button will not update though, and the isDisabled function will not run more than once
I looked at your reducer code and it looks like this:
...
const reducer = combineReducers({
formData: formReducer,
})
export default reducer
Which means your redux state structure is like this:
state = {
formData: {
firstName: <value>,
middleName: <value>,
lastName: <value>,
}
}
Solution
So, to make your component re-render when the redux state is changed, you need to subscribe to the correct state variables in your mapStateToProps function. Change it to this and will work:
const mapStateToProps = state => {
return {
firstName: state.formData.firstName, // Note that I added "formData"
middleName: state.formData.middleName,
lastName: state.formData.lastName
}
}
Couple of side notes:
It's a better to use the props instead of directly accessing the redux store.
For debugging, console.log is preferred over alert. React DevTools and Redux DevTools are even better.
I don`t know if this will solve your problems, but maybe it is one of the things below:
1 - As Mohammad Faisal said the correct form of calling props should be
const { firstName, lastName } = props;
2 - Instead of reduxTabForm, maybe you could use this instead:
export default connect(mapStateToProps)(MyTab);
3 - And finally, maybe it is an error in the "isDisabled":
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields){
return false
}
}
If you look carefully, you can see that you are not checking if there is an error inside requeiredFields, your are looking if that doesnt exist if (!requiredFields), maybe changing the condition to if(!requiredFields[i]) so it check each variable and not if the array doesn`t exists.
Edit: the return condition is correct? Returning False when something doesn`t exists?
const ReduxTabForm = connect(mapStateToProps,null)(MyTab)
Try this code snippet, as you are not passing dispatch function to your component. It is better to pass null value.
mapdispatchtoProps is the same basic theory of mapStateToProps.You are storing the function inside the store(which usually in actions) and when rendering the component you attach those function in store to your props. After rendering the component you will be able to run the functions which are in store.
your state values are passed to your component as props. so if you want to access them inside component you should do something like this
const {firstName, lastName} = props
What you could do is something like:
<button
type="submit"
disabled={!fistname && !lastname}
/>
this way if any of your fields be falsy, button is disabled. (empty string is falsy)
The React redux documentation also explains mapDispatchToProps
If you call your action called setFormData from your redux/actions.js then you will be mapping the action setFormData (which is an object) to your component. The action setFromData replace mapDispatchToProps. This is what the documentation says about mapping actions to your components
If it’s an object full of action creators, each action creator will be
turned into a prop function that automatically dispatches its action
when called.
To fix your problem, change your connect function call to this.
const ReduxApp = connect(
setFormData,
mapStateToProps
)(App)
Your issues is with this code isDisabled()
const isDisabled = () => {
const {firstName, lastName} = store.getState().form
const requiredFields = [firstName, lastName]
alert(requiredFields)
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields){
return false
}
}
return true
}
You are trying to test in loop !requiredFields, an array you created which always return false, even if it doesn't have values. It's a reference type. What you can do now is
const isDisabled = () => {
const {firstName, lastName} = store.getState().form
const requiredFields = [firstName, lastName]
alert(requiredFields)
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields[i]){
return false
}
}
return true
}
Your loop will check firstNAme and LastName values if they are undefined or not and test should respond with this.
The problem is in the mapStateToProps method. Your code is setting firstNAme and lastName as formData within state object and you are trying to access it from state object directly.
This codesandbox has your project updated with the fix. I didn't fix any other thing in your code so everything is as it is only mapStateToProps should be:
const mapStateToProps = (state) => {
return {
firstName: state.formData["firstName"],
middleName: state.middleName,
lastName: state.formData["lastName"]
};
};
and this will fix your issue. mapStateToPropsis kind of a projection function which will project entire store to some object which will only have properties based on requirements of your component.
Redux re-render components only when store state changes. Check if you are only updating store's state property not whole state because redux compare state by comparing their references, so if you are doing something like this inside your reducer:
State.firstName = action.payload.firstName;
return State;
Then change this to this:
return {...State, firstName: action.payload.firstName}
Note: If you unable to grasp that then kindly provide your reducer code too so that I can see how you are updating your store state.
Looking at your MRE on GitHub, the first thing I want to say is that your button will only have one state and it is the one that the isDisabled() method returns when you refresh the page. This is because the App component it's not getting refresh every time you write on the input fields, so you will never be able to make it change.
you need to subscribe to the correct state variables in your mapStateToProps function. Like this:
const mapStateToProps = state => {
return {
firstName: state.formData.firstName,
middleName: state.formData.middleName,
lastName: state.formData.lastName
}
}
So now that you have this right, you have to introduce this props into your component, like this:
function App({firstName, lastName}) { ...
Another thing to add, is that your are not initializing the states when you create your reducer in reducer.js. If you don't do this, your initial states for firstName and lastName will be null. Here is how you should do it:
import {combineReducers} from 'redux'
import {SET_FORM_DATA} from './actions'
const initialState = {
firstName: "",
lastName: ""
}
const formReducer = (state = initialState, action) => {
if (action.type === SET_FORM_DATA){
return {
...state,
...action.payload
}
}else{
return state
}
}
const reducer = combineReducers({
formData: formReducer,
})
export default reducer
Finally you have to update App:
function App({firstName, lastName}) {
return (
<div className="App">
<div className='bg-light rounded'>
<div className='px-sm-5 pt-0 px-4 flexCenterCol mx-5'>
<div>
<input
type='text'
className="form-control"
placeholder="First Name"
onChange={(e) => {
store.dispatch(setFormData({'firstName': e.target.value}));
console.log(firstName === "h");
}}
></input>
<input
type='text'
className="form-control"
placeholder="Last Name"
onChange={(e) => {
store.dispatch(setFormData({'lastName': e.target.value}))
alert(JSON.stringify(store.getState()))
}}
></input>
</div>
<button
type="submit"
disabled={firstName == "" || lastName == ""}
>
Button
</button>
</div>
</div>
</div>
);
}
So in this way you will be able to update the states from your store and have the dynamic behavior that you were looking for.
In isDisabled function you read data from form: const {firstName, lastName} = store.getState().form but I guess they are saved to formData.
Changing const {firstName, lastName} = store.getState().form to const {firstName, lastName} = store.getState().formData should help.

reduxForm: how to best dispatch an action?

I am trying to submit an address form using redux form. It seems like a good way of handling the input data and validation.
I am just wondering if I can make the syntax a bit cleaner, because, frankly, trying to use connect at the same time makes the code a mess at the bottom. In my case, I want to send the address data to a Node endpoint, so I need to call an action generator which sends an AJAX request. I'm wondering if I'm missing something obvious to make dispatching an action inside the submit function easier.
class AddressForm extends Component {
renderContent() {
return formFields.map(({ name, label }) => (
<Field
key={name}
name={name}
label={label}
type='text'
component={FormField}
/>
)
);
};
render() {
return (
<form onSubmit={this.props.handleSubmit}>
{this.renderContent()}
<button type="submit">Next</button>
</form>
);
};
};
const validate = (values) => {
const errors = {};
errors.email = validateEmail(values.email || '');
formFields.forEach(({ name }) => {
if (!values[name]) errors[name] = 'Please provide a value';
});
return errors;
};
const myReduxForm = reduxForm({
validate,
form: 'addressForm'
})(AddressForm);
const mapDispatchToProps = (dispatch, ownProps) => ({
onSubmit: data => dispatch(submitForm(data, ownProps.history))
});
export default connect(null, mapDispatchToProps)
(withRouter(myReduxForm));
Sure, instead of connecting, you can use the handleSubmit prop inside your component. It allows you to supply a callback with three arguments: values, dispatch and props. So you can do something like:
<form onSubmit={this.handleSubmit((values,dispatch,{submitForm})=> dispatch(submitForm(values)))} />

Resources