How to pass value from custom component class to redux-form? - reactjs

I'm calling a custom component in my redux-form.
<Field name="myField" component={SiteProjectSelect}/>
This component is a combination of two combo boxes. The second box is dependant on the value of the first on - i.e. depending on what site you select, you can choose from a list of projects. What I'd like to do is get the form to receive the selected site and the selected projects. However, I'm not sure how to pass the values to the redux-form.
class SiteProjectSelect extends Component {
constructor() {
super();
this.state = {
selectedSite: null,
selectedProject: null,
};
}
handleSiteSelection = selectedSite => {
console.log(selectedSite)
this.setState({ selectedSite, selectedProject: null });
};
handleProjectSelection = selectedProject => {
this.setState({ selectedProject });
this.props.input.onChange(selectedProject.value);
};
render() {
const selectedRow = this.state.selectedSite ? projects.find((node) => node.site === this.state.selectedSite.value) : "";
const filteredProjectOptions = selectedRow ? selectedRow.projects.map(project => ({ value: project, label: project })) : []
return (
<div {...this.props} >
<label>Site</label>
<div style={{ marginBottom: '20px' }} >
<Select
name="site"
value={this.state.selectedSite}
onChange={this.handleSiteSelection}
options={siteOptions}
isSearchable
/>
</div>
<div style={{ marginBottom: '20px' }} >
<label>Project</label>
<Select
name="project"
value={this.state.selectedProject}
onChange={this.handleProjectSelection}
options={filteredProjectOptions}
isMulti
isSearchable
closeMenuOnSelect={false}
/>
</div>
</div>
);
}
}

I did finally figure it out. For anyone else who stumbles across this, here's what I needed to know. To use a custom component,
Use the onChange prop to set the new value of the Field. You do this by calling the onChange function, this.props.input.onChange(your-components-new-value-here) when you need to change the value of the component and passing it the new value.
This new value will now be stored in the value prop: this.props.input.value. So, wherever in the render function for your component you need to pass/display the current value of your component, use the value prop. It has to be the value prop and not another variable such as what you passed to your onChange function. What this does is give control of what's displayed to the state of your redux-form which the value prop is tied to. Why is this useful? For example, you could take the user to a form review page when they're done and then back to the form if the user wants to make some more changes. How would redux-form know how to repopulate all of what's displayed without getting the user to fill in the form again? Because the display is dependant on the state, not user input! Took me a while to make sense of all this!!
In my example, where I was using two react-select components, one of which was dependant on the other, I ended up having to use the Fields component which allowed me to have two Fields in my component rather than just the one. Once I implemented this, it also became evident that I didn't need to have a separate state within my component as the value of both Fields is always accessible via the value prop for each of them. So, yes, I could have just used a stateless function after all!
I call my component with:
<Fields names={["site", "projects"]} component={SiteProjectSelect} />
My final working component:
class SiteProjectSelect extends Component {
handleSiteSelection = selectedSite => {
this.props.site.input.onChange(selectedSite);
this.props.projects.input.onChange(null);
};
handleProjectSelection = selectedProjects => {
this.props.projects.input.onChange(selectedProjects);
};
renderSite = () => {
const {
input: { value },
meta: { error, touched }
} = this.props.site;
return (
<div>
<label>Site</label>
<div style={{ marginBottom: '20px' }}>
<Select
name="site"
value={value}
onChange={this.handleSiteSelection}
options={siteOptions}
isSearchable
/>
</div>
<div className="red-text" style={{ marginBottom: '20px' }}>
{touched && error}
</div>
</div>
);
};
renderProjects = () => {
var {
input: { value },
meta: { error, touched }
} = this.props.projects;
const selectedSite = this.props.site.input.value;
const selectedRow = selectedSite
? projects.find(node => node.site === selectedSite.value)
: '';
const filteredProjectOptions = selectedRow
? selectedRow.projects.map(project => ({
value: project,
label: project
}))
: [];
return (
<div>
<div style={{ marginBottom: '20px' }}>
<label>Projects</label>
<Select
name="projects"
value={value}
onChange={this.handleProjectSelection}
options={filteredProjectOptions}
isMulti
isSearchable
closeMenuOnSelect={false}
/>
</div>
<div className="red-text" style={{ marginBottom: '20px' }}>
{touched && error}
</div>
</div>
);
};
render() {
return (
<div>
{this.renderSite()}
{this.renderProjects()}
</div>
);
}
}

Related

How to edit a todo in a todo list without hooks and redux

I have been stuck on this for days reading up on tutorials and articles but can not figure this out. Whenever I click on the pencil icon, I want it to edit the current do to. I have 4 components, the form (searchbar where i add todo), the app.js, the todoList, and a todo.js component. I am keeping all the state in the app and state in the form to keep track of the terms I am entering.
I am thinking I would need to create an editTodo method in the app and pass it down as a prop to the list and then the todoItem. Most tutorials or help online uses hooks or redux but I am learning vanilla React first. I am not asking for the answer directly but rather the steps or thought process to implement editing a todo item in the todolist. I am not sure even if my todo app is correct in the places where I am keeping state. I may get slack for asking.. but I do not know what else to do. Here is my code..
class App extends React.Component {
state = {
todos: []
}
addTodo = (todo) => {
const newToDos = [...this.state.todos, todo];
this.setState({
todos: newToDos
});
};
deleteTodo = (id) => {
const updatedTodos = this.state.todos.filter((todo) => {
return todo.id !== id;
});
this.setState({
todos: updatedTodos
});
}
editTodo = (id, newValue) => {
}
render() {
return (
<div className="container">
<div className="row">
<div className="col">
<Form addTodo={this.addTodo} />
</div>
</div>
<div className="row">
<div className="col">
<ToDoList
todos={this.state.todos}
deleteTodo={this.deleteTodo}
editingTodo={this.state.editingTodo}/>
</div>
</div>
</div>
)
}
}
export default App;
const ToDoList = ({todos, deleteTodo, editingTodo}) => {
const renderedList = todos.map((todo, index) => {
return (
<ul className="list-group" key={todo.id}>
<ToDoItem todo={todo} deleteTodo={deleteTodo} editingTodo={editingTodo}/>
</ul>
)
});
return (
<div>
{renderedList}
</div>
)
}
export default ToDoList;
const ToDoItem = ({todo, deleteTodo}) => {
return (
<div>
<li style={{display: 'flex', justifyContent: 'space-between' }} className="list-group-item m-3">
{todo.text}
<span>
<FontAwesomeIcon
icon={faPencilAlt}
style={{ cursor: 'pointer'}}
/>
<FontAwesomeIcon
icon={faTrash}
style={{ marginLeft: '10px', cursor: 'pointer'}}
onClick={ () => deleteTodo(todo.id)}
/>
</span>
</li>
</div>
);
}
export default ToDoItem;
I don't think the form component is relevant here as I am trying to edit a todo item so will not include it here. If I do need to include it, let me know. It may not look like I have tried to implement this functionality, but either I could not find what I was looking for, understand the code, or just do not know how to implement it.
Update:
I added an isEditing field in the form component to my todo items so that maybe it can help me know if an item is being editing or not. I also redid the editTodo method.
class Form extends React.Component {
state = { term: ''};
handleSubmit = (e) => {
e.preventDefault();
this.props.addTodo({
id: shortid.generate(),
text: this.state.term,
isEditing: false
});
this.setState({
term: ''
});
}
editTodo = (id, newValue) => {
const editedTodos = [...this.state.todos].map((todo) => {
if(todo.id === id) {
todo.isEditing = true;
todo.text = newValue;
}
return todo.text;
});
this.setState({
todos: [...this.state.todos, editedTodos]
});
}
I also passed that method down to the todoList and then to the todoItem like so
const ToDoItem = ({todo, deleteTodo, editTodo}) => {
const renderContent = () => {
if(todo.isEditing) {
return <input type='text' />
} else {
return <span>
<FontAwesomeIcon
icon={faPencilAlt}
style={{ cursor: 'pointer'}}
onClick={ () => editTodo(todo.id, 'new value')}
/>
<FontAwesomeIcon
icon={faTrash}
style={{ marginLeft: '10px', cursor: 'pointer'}}
onClick={ () => deleteTodo(todo.id)}
/>
</span>
}
}
return (
<div>
<li style={{display: 'flex', justifyContent: 'space between'}} className="list-group-item m-3">
{{!todo.isEditing ? todo.text : ''}}
{renderContent()}
</li>
</div>
);
}
So whenever I click on the the edit icon, it successfully shows 'new value' but now also adds an extra todo item which is blank. I figured out how to add the input field so that it shows also. I am accepting the answer Brian provided since it was the most helpful in a lot of ways but have not completed the functionality for editing a todo.
am thinking I would need to create an editTodo method in the app and pass it down as a prop to the list and then the todoItem.
This is exactly what you need to do. And yet:
editTodo method has no logic in it.
ToDoList component receives editingTodo method as a prop instead of defined editTodo.
You are indeed passing the editingTodo futher down to ToDoItem but you are not utilising it there const ToDoItem = ({todo, deleteTodo}) => ...
You don't have an onClick listener on the pencil icon, so nothing can happen.
I don't know how you are planning on doing the editing (modal window with a form, or replacing the text with an input field), either way the bottom line is that you need to trigger your pencil onClick listener with () => editTodo(id, newText).
My recommendation would be - address all 5 points above and for now just hardcode the new value, just to test it out: () => editTodo(id, 'updated value!') and check that everything works. You can worry about getting the real value in there as your next step.

in react i want to select only one user my code is selecting every user

im facing issue when i click add friend button every button changes to requested how can i particularly set it to one user only which i clciked i tried few things but it is not working it is selecting every other user. i used handleproductselect function but it is not working i have given them individual id still it is not working
class SearchModal extends Component {
constructor(props){
super(props);
this.state = {
Input:"Add Friend",
backgroundColor: 'white',
active_id: null,
}
}
async handleProductSelect(elementid){
const id = elementid;
const { backgroundColor } = this.state;
let newBackgroundColour = backgroundColor === 'white' ? 'yellow' : 'white';
this.setState({
Input : "Requested",
backgroundColor: newBackgroundColour,
active_id: id
})
console.log(id)
}
render() {
const {currentUser} = this.props;
return (
<div>
<Modal show={this.state.show} onHide={this.handleClose}
>
<Modal.Header closeButton>
<Modal.Title>
<input
type="text"
placeholder="Search.."
value={search}
onChange={this.onTextboxChangeSearch}
></input>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<h3>Users</h3>
<div>
<ul className="collection">
{userdetails.map((element) => {
if(currentUser.user.username !== element.username){
return(
<div key={element._id}>
<li>{element.username}{' '}<input
type="button"
id={element._id}
onClick={this.handleProductSelect.bind(this,element._id )}
value={this.state.Input}
style = {{backgroundColor: ( element._id === this.state.active_id ? 'yellow' : this.state.backgroundColor)}}></input></li>
</div>
);
}else{
return(
<div key={element._id}>
<li>{element.username}</li>
</div>
);
}
})}
</ul>
</div>
</Modal.Body>
</Modal>
</div>
)
}
}
Issue
You've correctly used state to store the "active id", but you use only a single state to represent the buttons' values.
<input
type="button"
id={element._id}
onClick={this.handleProductSelect.bind(this, element._id)}
value={this.state.Input} // <-- same single state for all buttons!
style = {{
backgroundColor: (element._id === this.state.active_id ? 'yellow' : this.state.backgroundColor)
}}
/>
Solution
Since I think the intent is to keep the buttons that have been "activated", i.e. you want the label "Requested" to remain, you should add some state to store all the requested active ids. There is also no need to store the static content in state that is the button label, same with background color, this is all derived data based on the state.active_id value.
this.state = {
active_id: null,
requestedIds: {},
}
Update handleProductSelect to be a curried arrow function. The arrow functions will bind the this of the class-component to the callback. The curried function allows you to not need an anonymous callback function just to attach the handler
handleProductSelect = id => () => {
this.setState(prevState => ({
active_id: prevState.active_id === id ? null : id, // toggle active id
requestedIds: {
...prevState.requestedIds,
[id]: id, // add requested id
},
}));
}
Update the Input to check if the requestedIds has a key for the current element _id and conditionally render the button label. Similarly, check the active id for the background color.
<input
type="button"
id={element._id}
onClick={this.handleProductSelect(element._id)}
value={this.state.requestedIds[element._id] ? 'Requested' : 'Add Friend'}
style = {{
backgroundColor: (element._id === this.state.active_id ? 'yellow' : 'white')
}}
/>

React current item is always the first item from the rendered component list

I am using material ui with react. Following is the component that renders the rating elements based on passed array input.
class SkillSlider extends Component {
constructor(props) {
super(props)
this.state = {
skills: this.props.job.skill.map(skill => ({ name: skill.name, value: 1, hover: -1 }))
}
}
render() {
let { skills } = this.state;
console.log("rendering..", skills)
return (
<div>
{skills.map((item, i) => {
console.log(">>>>", item.name)
return (
<div key={i} >
<Box color={'white'} ml={2}>{item.name}</Box>
<Rating
name="hover-feedback"
value={item.value}
precision={0.5}
onChange={(e, value) => { console.log("on change", item, value) }}
/>
</div>
);
})}
</div>
);
}
}
export default SkillSlider;
consider my skill in job inside the props is,
this.props.job.skill --> [{name:Java}, {name:React}, {name:AWS}]
The above component is getting rendered as per expectation but when I click on the rating, in the on change handler, I always get the first element and not the current element.
e.g. in my example when I click on AWS, the log shows Java.
Here is the sandbox, https://codesandbox.io/s/eloquent-chatterjee-609ww?file=/src/App.js
It is because you are using same name for all the rating components when mapping dynamic values. You have to generate names dynamically like:
<Rating
name={item.name}
value={item.value}
precision={0.5}
onChange={(e, value) => { console.log("on change", item, value) }}
/>

Change selected value component in react-select

Is there any way to change the selected value component design, At my option menu, I show CscId and CscDesc but when I select the option, I only want to show CscId only. Is there any way to change the selected value component? I google for this one and it already took 1 day. Please Help me.
Here is my react-select
import React from "react";
import Select from 'react-select';
const costcenterselect = ({ value, onChange, id, datasource }) => {
const formatOptionLabel = ({ CscID, CscDesc }) => (
<div style={{ display: "flex"}}>
<div style={{width:'40%'}}>{CscID}</div>
<div>{CscDesc}</div>
</div>
);
return (
<div>
<Select
id={id}
menuIsOpen={true}
formatOptionLabel={formatOptionLabel}
getOptionValue={option => `${option.CscID}`}
options={datasource}
onChange={onChange}
defaultValue={value}
/>
</div>
)
}
export default costcenterselect;
You can do it using formatOptionLabel itself. It has a second argument which provides you with meta information like context which you can use to conditionally render. Here is a working demo.
You can see that context === value allows you to render for selected value while context === menu renders for the options.
const formatOptionLabel = ({ CscID, CscDesc }, { context }) => {
if (context === "value") {
return <div>{CscID}</div>;
} else if (context === "menu") {
return (
<div style={{ display: "flex" }}>
<div style={{ width: "40%" }}>{CscID}</div>
<div>{CscDesc}</div>
</div>
);
}
};

In react JS fields losing focus after first onChange

I am using redux-form But When I am start typing focus goes out first time in react.
In my component below, the input field loses focus after typing a character. While using Chrome's Inspector, it looks like the whole form is being re-rendered instead of just the value attribute of the input field when typing.
Please see below code:
<Field
name='description'
// onChange={this.handleChange.bind(this)}
//value={this.state.description}
component={props => {
return (
<MentionTextArea {...props} userTags={userTags} tags={postTags}/>
)
}}
MentionTextArea Component:
import React, {Component, PropTypes} from 'react'
import { MentionsInput, Mention } from 'react-mentions'
import defaultStyle from './defaultStyle'
class MentionTextArea extends Component {
constructor(props) {
super(prop)
}
handleOnChange (e) {
this.props.input.onChange(e.target.value);
}
render() {
// const { input, meta, ...rest } = this.props;
return (
<MentionsInput
value={this.props.input.value || ''}
onChange={this.handleOnChange.bind(this)}
singleLine={false}
style={ defaultStyle }
markup="#[__display__](__type__:__id__)"
>
<Mention trigger="#"
data={this.props.userTags}
type="userTags"
style={{ backgroundColor: '#d1c4e9' }}
renderSuggestion={ (suggestion, search, highlightedDisplay) => (
<div className="user">
{ highlightedDisplay }
</div>
)}
/>
<Mention trigger="#"
data={this.props.tags}
type="tags"
style={{ backgroundColor: '#d1c4e9' }}
renderSuggestion={ (suggestion, search, highlightedDisplay) => (
<div className="user">
{ highlightedDisplay }
</div>
)}
/>
</MentionsInput>
);
}
}
export default MentionTextArea
Please help!
Thanks in advance,
It's common problem for people new to redux-form please check this issue you'll find an answer there.
You must define the stateless function outside of your render() method, or else it will be recreated on every render and will force the Field to rerender because its component prop will be different. Example from official redux-form documentation:
// outside your render() method
const renderField = (field) => (
<div className="input-row">
<input {...field.input} type="text"/>
{field.meta.touched && field.meta.error &&
<span className="error">{field.meta.error}</span>}
</div>
)
// inside your render() method
<Field name="myField" component={renderField}/>

Resources