Redux-form get event in submit handler function - javascript

Next to my redux forms I've a small sidebar with form names to switch between different forms. To keep the app state updated anytime when a user switch between these forms via the sidebar I do a submission of the current form state via the redux-form handleSubmit function. The submission is fired with a dispatch which updates the app state. This works fine but to make the clicked form visible I need to change the 'active' value in my state to the name of the form, this name is set on the list item in the sidebar items loop as a classname.
I tried to receive the clicked element classname via event.target.className but it seems to have no results as event seems not to be the event I expected. How can I access the event in a handleSubmit mission to change the active form with the clicked className of list item of the sidebar?
p.s. I will create a separated function, if / else statement or something else for this saveData function as I know the event.target.className won't be correct if the form would be submitted by the form button itself.
Example code
... some form field code
class ComponentName extends React.Component {
constructor(props) {
super(props);
this.state = {
someName : this.props.someName,
}
this.saveData = this.saveData.bind(this);
}
saveData(values, dispatch) {
let newState = {
SomeName : update(this.state.someName, {
active : {
$set : this.state.active <======= event.target.className ?
},
rows : {
data : {
$set : values.rows
}
}
})
}
this.setState(newState);
dispatch(someActionNAme(newState.someName));
}
render() {
const { handleSubmit, submitting } = this.props;
var items = {}
Object.values(this.state.someName).map(function(item, i) {
if (typeof item == 'object' && item !== 'undefined' && item !== null) {
items[i] = item;
}
})
return (
<div className="someName__form">
<form onSubmit={handleSubmit(this.saveData)}>
<ul className="someName__sidebar">
{ Object.keys(items).map((item, i) => (
<li
key={i}
data-id={i}
onClick={handleSubmit(this.saveData)}
id={items[item].meta.name}
className={items[item].meta.name}
data-sort={items[item].meta.sort}
>
{items[item].meta.label}
{this.state.someName.active == items[item].meta.name &&
<b>
[Active]
</b>
}
</li>
)
)}
</ul>
<FieldArray name="rows" component={renderRows} />
<div>
<button id="button" type="submit" disabled={submitting} onClick={handleSubmit(this.saveData)}>
Save
</button>
</div>
</form>
</div>
)
}
}
const mapStateToProps = (state) => ({
initialValues : state.someName.rows,
someName : state.someName,
});
const mapDispatchToProps = (dispatch) => ({
dispatch,
});
ComponentName = reduxForm({
form : 'formName',
destroyOnUnmount: false
})(ComponentName);
export default connect(mapStateToProps, mapDispatchToProps)(ComponentName);

To help others out, I found myself the solution by diving deep into the github issue articles of redux-form.
It's easy and possible to add some additional data to the form values via the handleSubmit function. By adding data as below, the additional data will be merged into the object with the values as follow.
...onClick={handleSubmit(values => this.saveData({...values, active: 'componentName' }))}
Values object will now contain an extra key value pair:
{
"languages": [
{
"name": "Spanish",
"level": "3"
}
],
"active": "componentName"
}

Related

Issues rendering the right dashboard UI user based on roles

I am trying to render UI in my project based on selected roles (brands, agency, influencer) on click. However, the logic that I am putting together is not loading the right UI and I don't quiet understand why not.
I have tried moving the role and setRole to the top component and passed the props down to the child components that read role and updated it via setRole so that I can have the state to be available in two places.
I also set a logic that should display components based on if the role equals the value of the buttons.
What happened was the components weren't loading upon clicking the function that handles click. However, logging out to the console if the role equals the value of the clicked button returns true, the right string that the logic was based on.
What I am expecting to happen is to load the component e.g: "Brands" when users click and select "brands" which is the value of the clicked button. Vise versa for the other components.
My code is as follows:
import { useState } from 'react';
import { useSession } from 'next-auth/react';
import Brands from './Brands';
import Agency from './Agency';
import CreatorsDash from './CreatorsDashboard';
export default function FirstPageModal({ role: userRole }) {
const [role, setRole] = useState(userRole);
const { data: session } = useSession();
const handleClick = (e) => {
e.preventDefault();
let buttonValue = e.target.value;
const clickedRole = role?.map((user) => {
let { role } = user;
if (buttonValue) {
userRole = { role: buttonValue };
}
return { userRole };
});
console.log(clickedRole); //Returns an array
clickedRole.map((item) => {
const { role } = item.userRole;
console.log(role); //Returns string ("agency" / "brands" / "Influencer")
if (session && role === 'brands') {
console.log(role); //This logs "brands" as expected but doesn't return the component
// return <Brands session={session} role={role} />;
} else if (session && role === 'agency') {
return <Agency session={session} role={role} />;
} else if (session && role === 'Influencer') {
return <CreatorsDash session={session} role={role} />;
} else {
console.log('Select a role');
}
});
};
return (
<>
<div className="">
<button type="button" className="" onClick={handleClick} value="agency">
As an Agency
</button>
<button type="button" className="" onClick={handleClick} value="brands">
As a Brand
</button>
<button
type="button"
className=""
onClick={handleClick}
value="Influencer"
>
As an Influencer
</button>
</div>
</>
);
}
Returning a component from an onClick handler doesn't automatically render the component. One thing you could do is to keep track of the role in the state and then put the <Brands /> <Agency/> and <CreatorsDash /> components in the render function and dynamically show/hide them like {role === "brands" && <Brands />. This can also be done with css, although the benefits of this are not so clear,.
Side note, it is very helpful to post a codepen with your code, especially as your code gets more complicated

ReactJS onClick not working on first element in menu

I'm making a custom dropdown list in reactjs. When I click on any element in the dropdown list I get it's value and put it inside an input and it works just fine. The problem is that the first element returns nothing and I can't get it's value. Also I developed the dropdown list to disappear when I choose any element inside of it. But like I said it works just fine on all elements except the first one.
I solved the problem by setTimeOut and hide the dropdown list in 50 milliseconds. But I don't think this's a right solution.
//Select Component
class Select extends Component {
showList = (e) => {
e.target.closest('.select').classList.add('active');
}
hideList = (e) => {
setTimeout(() => {
e.target.closest('.select').classList.remove('active');
}, 100);
}
selectValue = (e) => {
let value = e.target.getElementsByTagName('span')[0].innerHTML;
this.setState({ selectValue: value })
}
render() {
return (
<input
{...this.props}
type="text"
placeholder={this.props['placeholder']}
onFocus={this.showList}
onBlur={this.hideList}
value={this.state.selectValue}
onChange={this.changeSelectValue}
required
/>
<div className="select">
<div className="select-options menu full-width">
{
this.props.list.map(element => {
return (
<MenuItem text={element} onClick={(e) => this.selectValue(e)} />
)
})
}
</div>
</div>
);
}
}
==================
class MenuItem extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
<p className={"menu-item " + this.props['type'] } {...this.props}>
{this.props['icon'] ? <i className={this.props['icon']} ></i> : null}
<span>{this.props['text']}</span>
</p>
);
}
}
1.use key prop for every MenuItem Component when you are using the list to create a list of MenuItems.
2.Instead of getting value from target in selectValue function directly pass the value from onClick handler.
selectValue = (e , element) => {
this.setState({ selectValue: element })
}
<MenuItem text={element} key={element} onClick={(e) => this.selectValue(e , element)} />
Editted:-
Remove the onBlur handler and put the functionality of hideList inside selectValue function after setState,you can use setState with callback if normal setState doesn't work
selectValue = (e) => {
let value = e.target.getElementsByTagName('span')[0].innerHTML;
this.setState({ selectValue: value })
e.target.closest('.select').classList.remove('active');
}

Error dynamically updating an array list in ReactJs

I am learning reactJS and so I am trying my hands on an example. This example has a form textfield that can add an item to an existing array on click of a button. I am having errors here as when I enter a text and click on the button, the array list is not updated except I try to make changes to the text entered in the textfield. This is what I am doing:
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
currentName : '',
arrays : ['john', 'james', 'timothy']
}
}
render() {
const showNames = this.state.arrays.map((thisName) => {
const values = <li>{thisName}</li>;
return values;
});
const getText = (e) => {
let value = e.target.value;
this.setState({
currentName : value
})
}
const addToUsers = () => {
this.state.arrays.push(this.state.currentName)
}
return (
<div>
<p>Add new name to List</p><br/>
<form>
<input type="text" onChange={getText}/>
<button type="button" onClick={addToUsers}>Add User</button>
</form>
<ul>
{showNames}
</ul>
</div>
);
}
}
export default App;
There are a host of things wrong with this, but your issue is likely that you need to use setState to modify state.
import React, { Component } from 'react';
class App extends Component {
constructor(){
super();
this.state = {
names: ['john', 'james', 'timothy']
}
}
addToUsers = () => {
this.setState(
prevState => ({
names: [...prevState.names, this.input.value]
})
)
}
render() {
const names = this.state.names.map(
(name, index) => <li key={index}>{name}</li>
)
return (
<div>
<p>Add new name to List</p><br/>
<form>
<input type="text" ref={e => this.input = e} />
<button type="button" onClick={this.addToUsers}>Add User</button>
</form>
<ul>
{names}
</ul>
</div>
)
}
}
export default App;
This quick edit changes a few things:
Uses setState for the addToUsers method
Eliminate onChange tracking and pull the name directly from the input when the button is clicked
Move the addToUsers method out to the component class rather than defining it on render
Rename this.state.arrays to this.state.names
Simplify conversion of this.state.names into list items
Set key on array elements (name list items)
Use prevState in setState to avoid race conditions
You need to make sure you update state using the setState method.
When you update arrays you are reaching into the state object and manipulating the data directly instead of using the method.
Instead try something like:
const addToUsers = () => {
const newArray = this.state.arrays.concat([this.state.currentName]);
this.setState({
arrays: newArray
});
}
You probably must add
onChange={getText}.bind(this)
to your functions.
Also change this
const addToUsers = () => {
this.state.arrays.push(this.state.currentName)
}
to this
const addToUsers = () => {
this.setState({here put your variable})
}

React - Update Value Of Input After Submit

I'm trying to make a feature where a user can edit a submitted value. So to be completely clear:
You would enter some text
Click submit and that value will be pushed into an array
You will be able to see your value on the dom
If you made an error, you can click on that input and change the value, also updating the state of that value in the already pushed array.
On a button click, you will update the state and have a newly edited value.
I'm stuck on the part of changing the state of the value of the pushed items in the array.
For example:
If I were to click on the field of 'Bob', edit it and click submit, the value of whatever I changed it to would also change the state of what was originally in my array to the new value.
This is what I have so far:
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
notes: ['hello', 'bob'],
val: ''
}
}
submit = () => {
const { notes, val } = this.state
notes.push(val)
this.setState({notes})
}
handleEdit = e => {
console.log(e)
}
render() {
return (
<div>
<input
type="text"
onChange={e => this.setState({val: e.target.value})}
/>
<button onClick={this.submit}>Submit</button>
{this.state.notes.map(item => {
return (
<form onSubmit={e => e.preventDefault()}>
<input
type="text"
defaultValue={item}
onChange={e => this.setState({val: e.target.value})}
/>
<button onClick={() => this.handleEdit(item)}>Submit
Change</button>
</form>
)
})}
</div>
)
}
}
Try this kind of thing :
handleEdit = (item) => {
const notes = this.state.notes.slice();
const index = notes.indexOf(item);
notes[index] = this.state.val;
this.setState({
notes
})
}

How to handle multiple radio button groups in one component in reactjs?

I'm trying to send list of selected radio button ids from multiple radio button groups on clicking send button,
My problem:
I am getting selected radio button from backend , then I should be able to change the radio button and send back to backend. but when I try to change the radio button it is not working.
What I did not understand:
How to handle the on change function, normally on change we can change the state but to change the state on load we should grab the values radio buttons. Finally I got struck here, not understanding how to move forward.
Here is the wireframe and code snippet:
function CardsList(props) {
const cards = props.cards;
return (
<div>
{cards.map((card, idx) => (
<div>
{card.cardName}
{
card.options.map((lo,idx) => (
<li key={idx}>
<input
className="default"
type="radio"
name={card.cardName}
checked={lo.selected}
/>))
}
<div>
))}
</div>
);
}
//array of cards coming from the backend
const cards = [
{cardName:'card1',options:[{radioName:'card1-radio1',selected:'true'},
{radioName:'card1-radio2',selected:'false'}]},
{cardName:'card2',options:[{radioName:'card2-radio1',selected:'true'},
{radioName:'card2-radio2',selected:'false'}]}
];
ReactDOM.render(
<CardsList cards={cards} />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can use an object as a lookup table that holds the group names as keys.
On each change you will need to find the relevant group with the relevant option and set the new state accordingly.
Important! - one thing to notice here, is that i changed the type of the selected property from a String to a Boolean. this will let me handle the conditions like this:
<input checked={option.selected} />
If you can't change it to a Boolean then you will need to handle the condition like this:
<input checked={option.selected === 'true'} />
Here is a running example:
//array of cards coming from the backend
const data = [
{
cardName: 'card1', options: [{ radioName: 'card1-radio1', selected: true },
{ radioName: 'card1-radio2', selected: false }]
},
{
cardName: 'card2', options: [{ radioName: 'card2-radio1', selected: true },
{ radioName: 'card2-radio2', selected: false }]
}
];
class CardsList extends React.Component {
constructor(props) {
super(props);
this.state = {
cards: []
};
}
componentDidMount() {
setTimeout(() => {
// mimic an async server call
this.setState({ cards: data });
}, 1000);
}
onInputChange = ({ target }) => {
const { cards } = this.state;
const nexState = cards.map(card => {
if (card.cardName !== target.name) return card;
return {
...card,
options: card.options.map(opt => {
const checked = opt.radioName === target.value;
return {
...opt,
selected: checked
}
})
}
});
this.setState({ cards: nexState })
}
onSubmit = () => { console.log(this.state.cards) };
render() {
const { cards } = this.state;
return (
<div>
{
cards.length < 1 ? "Loading..." :
<div>
{cards.map((card, idx) => (
<ul>
{card.cardName}
{
card.options.map((lo, idx) => {
return <input
key={idx}
type="radio"
name={card.cardName}
value={lo.radioName}
checked={!!lo.selected}
onChange={this.onInputChange}
/>
})
}
</ul>
))
}
< button onClick={this.onSubmit}>Print Cards</button>
</div>
}
</div>
);
}
}
ReactDOM.render(<CardsList />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
The reason why you can't change them is because of their current checked state which you are setting here:
<input
className="default"
type="radio"
name={card.cardName}
checked={lo.selected}
/>
An approach I have used for this exact scenario is storing the component's state (from the server) in my component's state (this.state), passing the state to the element: checked={this.state.isChecked}, and updating the element's state onClick.
Example:
class CardsList extends Component {
constructor(props){
super(props);
this.state = {isChecked: false};
this.inputOnClick = this.inputOnClick.bind(this);
}
//fetch data from server
fetchData(){
fetch('/api')
.then(res => res.json())
//this will be our initial state
.then(res => this.setState(res))
}
componentDidMount(){
this.fetchData();
}
//change radio button state on click
inputOnClick(e){
e.preventDefault();
//invert state value
this.setState((prevState, props) => {isChecked: !prevState.isChecked});
}
render(){
return (
<input
type="radio"
checked={this.state.isChecked}
onClick={this.inputOnClick}
/>
)
}
}
this answer may work with single radio button group , but i am facing
problem with multiple radio buttons with in multiple radio button
groups.if you see the array of cards , how does it know which radio
button group it belongs to.
We can modify the state based on the radio button's name.
Let's save all of your cards in your component's state. I know the cards are retrieved from the server and will be saved using setState but I am writing it like this for visual purposes.
this.state = {cards: [
{ cardName:'card1',
options:[
{radioName:'card1-radio1',selected:true},
{radioName:'card1-radio2',selected:false}
]
},
{ cardName:'card2',
options:[
{radioName:'card2-radio1',selected:true},
{radioName:'card2-radio2',selected:false}
]
}
]}
Now when we click on a radio button, we will use that radio button's name to update the state where it needs to be updated. Since React state needs to be immutable, we will create a deep copy of the state, modify it, and then set the state with it.
inputOnClick(e){
e.preventDefault();
var thisRadioBtn = e.target.name;
//make a deep copy of the state
const stateCopy = JSON.parse(JSON.stringify(this.state.cards));
//go through state copy and update it
stateCopy.forEach(card => {
card.options.forEach(option => {
if(option.radioName === thisRadioBtn){
//invert value
//make sure the values are booleans
option.selected = !option.selected;
}
});
});
//update the components state
this.setState({cards: stateCopy});
}
In June 2022, I'm facing a similar issue with you. My solution is just add tag <form> on both sections with no OnSubmit or action on it.

Categories

Resources