I am using react-select
I have a form that creates a new contact. In my redux store I have groups that are already created. When I click the Select to show the options I would like to load the options from my redux store.
The "groups" from redux store has only one value, that is title: String in the GroupModel in the backend.
I understand that react-select needs to have a label: '', value: ''
If I create an array myself and pass the values in it works fine. But with redux nothing is working. I cant find any answers anywhere online which seems trivial to me....
Here is my component below
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import Select from 'react-select';
import { createContact } from '../../actions/index';
import { Button, Form, FormGroup, Input, Label } from 'reactstrap';
import Divider from '../common/Divider';
const ContactForm = ({ hasLabel }) => {
const dispatch = useDispatch()
// State
const [contact, setContact] = useState({
group: '',
})
// Submit Handler
const handleSubmit = e => {
e.preventDefault()
dispatch(createContact(contact))
};
// Change Handler
const handleChange = e => {
setContact({...contact, [e.target.name]: e.target.value})
};
// bringing in our state from redux
const groups = useSelector((state) => state.groups)
return (
<>
<Form onSubmit={handleSubmit}>
<div>
<FormGroup>
<Label>
Choose Group/List</Label>
<Select
name="group"
options={groups}
getOptionlabel={({title}) => title}
getOptionValue={({_id}) => _id }
onChange={() => {}}
isMulti
/>
</FormGroup>
</div>
</Form>
</>
);
};
ContactForm.propTypes = {
hasLabel: PropTypes.bool
};
ContactForm.defaultProps = {
layout: 'basic',
hasLabel: false
};
export default ContactForm;
Ok.... So my solution above was pretty much correct. The issue I had was the getOptionlabel needed to be getOptionLabel <--- notice I forgot to capitalize the L in label....
I hope someone who needs to use react-select with redux finds this post and it helps.
So basically just bring in your redux state with useSelector or connect,
then make sure to use the props below in your Select component
getOptionLabel={({title}) => title}
getOptionValue={({_id}) => _id}
Related
I successfully built a form to create and save new products. Now I'm working on the page to modify previously created products. I can tell my API is returning data because the title shows up. However, it seems like the form is rendering before the state is populated and therefore the field is empty.
I think that the title is working because it's not reading from the state, it's reading directly from the product.name value which was returned from my API. However, for my form, I believe I need to point my field to the state which is empty at the moment the component rendered.
I'm just not sure how to only render the form once the API is done loading data and state is populated?
Any help greatly appreciated, have been trying to figure this out off and on for months.
I'm using redux toolkit: https://redux-toolkit.js.org/rtk-query/usage/queries#frequently-used-query-hook-return-values
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router';
import { useNavigate } from 'react-router-dom'
import AHeaderNav from '../../../../components/settings/AHeaderNav/AHeaderNav'
import './AdminProductEdit.scss';
import Wysiwyg from '../../../../components/global/Wysiwyg/Wysiwyg';
import SelectInput from '../../../../components/global/SelectInput/SelectInput'
import Breadcrumbs from '../../../../components/global/breadCrumbs/breadCrumbs';
import AdminBackButton from '../../../../components/settings/AdminBackButton/AdminBackButton'
import ImageC from '../../../../components/global/ImageC';
import { useGetProductAuthQuery, useUpdateProductMutation } from '../../../../api/apiSlice';
import { Spinner } from '../../../../components/global/Spinner/Spinner';
const AdminProductEdit = () => {
const { id: productId } = useParams()
const {
data: product = {},
isFetching,
isSuccess,
isLoadingCurrentPoduct } = useGetProductAuthQuery(productId, {
//pollingInterval: 3000,
refetchOnMountOrArgChange: true,
skip: false
})
const [updateProduct, { isLoading }] = useUpdateProductMutation()
const [productName, setProductName] = useState(product.productName)
const navigate = useNavigate()
const onProductNameChanged = e => setProductName(e.target.value)
if(!isSuccess) {
return <Spinner text="Loading..." />
} else {
const canSave = [productName].every(Boolean) && isSuccess && typeof productId === 'number'
const onSaveProductClicked = async () => {
if(canSave) {
try {
await updateProduct({productId,
productName,
productDescription,
productCategory,
shopNowUrl,
assetDescription})
navigate(`../settings/products`)
} catch (err) {
console.error(err)
}
}
}
return (
<div>
<AHeaderNav/>
<div className="topBannerImage">
<ImageC imageSrc={product.assetUrl}/>
</div>
<div className="contentWrapper">
<h3>Editting '{product.productName}'</h3>
<label>
Product Name
<input
type="text"
name="productName"
value={[productName]} // this is rendering an empty field.
// To me it seems like since this portion of the page is only
// supposed to be rendered once the API is successful, then
// this should be populated? What am I missing?
onChange={onProductNameChanged}
disabled={isLoading}
/>
</label>
<div className="button-group align-spaced">
<AdminBackButton/>
<button
className="submit button"
onClick={onSaveProductClicked}
disabled={!canSave}
>Save changes</button>
</div>
</div>
{spinner}
</div>
)
}
}
export default AdminProductEdit;
I created a custom react component. I am able to fetch the state of the checkbox, when it is changed on a user action. However i am not able to set the checked state when i invoke the component and set the checked state to the prop. Here is my code
import React, { FunctionComponent, SyntheticEvent, useState } from 'react';
import nanoid from 'nanoid';
import classNames from 'classnames';
import { mapToCssModules } from 'utils';
import VolumeComponentProps from 'utils/props';
export interface CheckboxProps extends VolumeComponentProps {
/** Is selected */
checked?: boolean;
/** disabled - Sets or Gets the property if the input is disabled or not */
disabled?: boolean;
/** on Change event, raised when the input is clicked */
onInputChange?: (e: SyntheticEvent, updatedState: boolean) => void;
}
export const Checkbox: FunctionComponent<CheckboxProps> = ({
checked = false,
children,
className,
cssModule,
disabled = false,
onInputChange,
...attributes
}: CheckboxProps) => {
const valueCheck = checked ? true : false;
const [inputId] = useState(`checkbox_${nanoid()}`);
const [isChecked, setIsChecked] = useState(valueCheck);
const containerClasses = mapToCssModules(classNames(className, 'vol-checkbox'), cssModule);
const checkboxInputClass = mapToCssModules(classNames('vol-checkbox__input'), cssModule);
const checkboxClass = mapToCssModules(classNames('vol-checkbox__input-control'), cssModule);
const onChangeHandler = (e: SyntheticEvent) => {
setIsChecked((e.currentTarget as HTMLInputElement).checked);
if (onInputChange) {
onInputChange(e, isChecked);
}
};
return (
<>
{isChecked && <span>true</span>}
<label className={containerClasses} htmlFor={inputId}>
<input
{...attributes}
id={inputId}
className={checkboxInputClass}
disabled={disabled}
checked={isChecked}
type="checkbox"
onChange={onChangeHandler}
/>
<span className={checkboxClass} />
</label>
</>
);
};
export default Checkbox;
So, if i invoke my checkbox as
<Checkbox onChange=()=>{} checked />
The checked value is not set to the checkbox, it works when i click it manually.
Update
This issue is only happening from Storybook. When i create a knob Checked, to alter states, that feature is not working. Here is my story.
import React from 'react';
import { storiesOf } from '#storybook/react';
import { boolean } from '#storybook/addon-knobs';
import { action } from '#storybook/addon-actions';
import Checkbox from './Checkbox';
storiesOf('Pure|Checkbox ', module).add(' Checkbox', () => {
return (
<Checkbox
disabled={boolean('Disabled', false)}
checked={boolean('Checked', false)}
onInputChange={action('onChangeHandler')}
/>
);
});
Your code should work, but you can simplify it by just toggling the isChecked state when the onChange event is fired. onChange means value changed and since a checked input has only two possible variables, we can safely assume that the value toggles, so no need to get the value from the event object each time.
Like this:
const onChangeHandler = () => {
setIsChecked(!isChecked);
if (onInputChange) {
onInputChange(e, !isChecked); // also, this line should pass the updated value
}
};
Objective
Setup a dynamic form controlled by the user using react-redux and revalidate to run validation checks on my form.
Problem:
Since my form is dynamic I need to make dynamic validations. In order to do this my form data needs to be passed in as props to my component, which can be used as a second argument in the validate function from revalidate
My approach
To do this I am waiting until the component is mounted, building the form, passing it to redux, and then will map state to props. As the user adds more rows I will update state and then the component will render. I will use shouldComponentUpdate() to avoid any render loops.
The Error
My error is regarding the dispatch. When I try to run the dispatch (which will pass the form into redux) I get Dispatch is not a function error.
I am not supper comfortable with the connect() as I have to wrap redux with it as well as firebase. This syntax really confuses me.
Question
I believe the issue with with how I am exporting the component where I am using HOC like withFirebase, Redux, and Connect. Somewhere along the way I am losing scope to the connect. Can someone shed light into what it is I am doing wrong?
Component
import React, { Component } from "react";
import { reduxForm, Field } from "redux-form";
import { Container, Form, Col, Button } from "react-bootstrap";
import MaterialIcon from '../../material-icon/materialIcon';
import { withFirestore } from "react-redux-firebase";
import { connect } from "react-redux";
import TextInput from "../../forms/textInput";
import { combineValidators, isRequired } from "revalidate";
import { setupStudentForm } from '../../../store/actions/students';
const validate = (values, ownprops) => {
// Will be passing in validation rules, once form is apssed in with props via mapStateToProps.
}
export class CreateStudentsForm extends Component {
// Using constrcutor so componentDidMount() can render() cann access variables
constructor(props) {
super(props);
this.state = {
rows: 2,
}
this.formArray = [];
this.form = null;
}
componentDidMount() {
// Once component is rendered, setup form and send to redux
for (let i = 1; i !== this.state.rows + 1; i++) {
let firstNameField = {
fieldName: `firstName${i}`,
label: 'First Name',
required: true,
type: "text",
}
let lastNameField = {
fieldName: `lastName${i}`,
label: 'Last Name',
required: true,
type: "text",
}
this.formArray.push([firstNameField, lastNameField]);
}
this.props.setupStudentFormHandler(this.formArray);
}
// Ensure we do not get stuck in render loop
shouldComponentUpdate(nextProps, nextState){
if(nextProps !== this.props){
return true
} else {
return false
}
}
render() {
// Allows user to add another row
const addRow = () => {
this.setState({
rows: this.state.rows + 1
})
}
// Map through form array and create template
if (this.formArray) {
let form = this.formArray.map((field, index) => {
return (
<Form.Row key={index} className="animated fadeIn">
<Col xs={5}>
<Form.Group className="mb-0 noValidate">
<Field
label={field[0].label}
attempt={this.props.attempt}
name={field[0].fieldName}
type={field[0].type}
component={TextInput}
/>
</Form.Group>
</Col>
<Col xs={5}>
<Form.Group className="mb-0 noValidate">
<Field
label={field[1].label}
attempt={this.props.attempt}
name={field[1].fieldName}
type={field[1].type}
component={TextInput}
/>
</Form.Group>
</Col>
<Col xs={2}>
<MaterialIcon icon="delete" className="mt-4" />
</Col>
</Form.Row>
)
})
}
return (
<Container>
{this.form}
<Button variant="outline-success" onClick={addRow}>Add Another Student</Button>
</Container>
)
}
}
const mapStateToProps = state => {
return {
// Get access to student state which will have form
studentForm: state.students
};
};
const mapDispatchToProps = dispatch => {
return {
//Send formArray to redux
setupStudentFormHandler: (form) => dispatch(setupStudentForm(form))
};
};
export default withFirestore(
reduxForm({
form: "createStudents", validate
})(connect(
mapDispatchToProps,
mapStateToProps
)(CreateStudentsForm)
)
);
mapStateToProps is the first argument of connect, mapDispatchToProps is the second. Try swapping the order:
connect(
mapStateToProps,
mapDispatchToProps
)(CreateStudentsForm)
As showed in official docs of redux -> https://redux.js.org/basics/usage-with-react, the logic and presetation must been tied together.
The question is - is it real, that we cannot separate them to make our code more clear?
For example some component from official redux.js.org docs. So, the logic and pres are together in it and it's not looking so good and clear:
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
let AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form
onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}}
>
<input
ref={node => {
input = node
}}
/>
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
AddTodo = connect()(AddTodo)
export default AddTodo
P.S.
Also I saw this question: Separating presentational and logic components react/redux , this is not the same.
UPD:
I had separate the logic into onSubmit module and component presentation. But > for now I get an error - TypeError: Cannot call a class as a function :
/* PRESENTATION */
import React from 'react';
import { connect } from 'react-redux';
import AddTodo from '../../Actions/AddTodo'
import addTodo from '../../Modules/handleClick'
class AddTodos extends React.Component{
componentDidMount() {
console.log(addTodo()); // for test. Get "TypeError: Cannot call a class as a function"
}
render() {
return (
<form>
<input type="text" placeholder="Your text" />
<button type="submit">Add todos</button>
</form>
);
}
}
export default AddTodos;
/* ONSUBMIT MODULE */
import { connect } from 'react-redux';
import AddTodo from '../Actions/AddTodo'
let addTodo = ({ dispatch }) => {
if (document.readyState === 'complete') {
let form = document.querySelector('form');
form.addEventListener('submit', handleClick);
function handleClick(e) {
e.preventDefault();
let input = document.querySelector('input');
dispatch(AddTodo(input.value));
input.value = '';
}
}
}
addTodo = connect()(addTodo);
export default addTodo;
From the same documentation you gave:
Sometimes it's hard to tell if some component should be a
presentational component or a container. For example, sometimes form
and function are really coupled together, such as in the case of this
tiny component:
AddTodo is an input field with an “Add” button
Technically we could split it into two components but it might be too
early at this stage. It's fine to mix presentation and logic in a
component that is very small. As it grows, it will be more obvious how
to split it, so we'll leave it mixed.
So yes you are right, it is better to separate our presentational and container components and it is explained very well in the documentation as you stated. The example you gave is just an exception.
Here how you can separate the components:
AddTodo
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { addTodo } from "../actions";
import AddTodoForm from "./AddTodoForm";
const AddTodo = ( props ) => {
const handleAddTodo = todo => props.dispatch( addTodo( todo ) );
return (
<AddTodoForm addTodo={handleAddTodo} />
);
};
export default connect()( AddTodo );
AddTodoForm
import React from "react";
const AddTodoForm = ( props ) => {
let input;
const handleClick = () => {
props.addTodo( input.value );
input.value = "";
};
return (
<div>
<input
ref={( node ) => {
input = node;
}}
/>
<button
onClick={handleClick}
>
Add Todo
</button>
</div>
);
};
export default AddTodoForm;
I strongly advise you to watch Redux videos on Egghead from Dan Abramov (creator of Redux). You will understand the logic of Redux from scratch after watching those videos. There are two parts, watch both parts. While I studying Redux I watched those and write all the codes while watching. After that I created a repo: https://github.com/devserkan/react-with-idiomatic-redux
You can clone this repo and play as you wish.
I am confused with props or state to use here. If I use state in #connect I get error and does not work. when I use props it does not work with onchange handler to set new props. Please help how should I make input working with state or props. I am retrieving initial data from api.
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import { asyncConnect } from 'redux-async-connect';
import {load, isLoaded} from 'redux/modules/overview';
#asyncConnect([{
promise: ({ store: { dispatch, getState }, params: { id }, }) => {
const promises = [];
if (!isLoaded(getState())) {
promises.push(dispatch(load(id)));
}
return Promise.all(promises);
}
}])
#connect(
state => ({
overview: state.overview.data
}),
dispatch => bindActionCreators({load}, dispatch))
export default class Overview extends React.Component {
changeinput1(e) {
this.props.overview.title = e.target.value;
// changing value does not work here
}
constructor (props) {
super();
this.state = {
overview: null,
}
}
render() {
return (
<div>
<label>Input 1</label>
<input type="text" className="form-control" id="title" name="title" maxlength="35" value={this.props.overview.title} onChange={this.changeinput1.bind(this)}/>
</div>
)
}
}
I also want to do validation and want to save input value on onBlur so I dont want to use form.
if you want change reducer's(here suppose to be 'overview') value, you should define an action then dispatch it, not change it directly, the state get from store is readonly in the component