I have two self-defined class components, from which I create two instances like:
<PersonAddModal details={details} open={open} setOpen={setOpen} addFather={false}/>
So the addFather property is different, the rest is the same.
The PersonAddModal looks as follows. (The Add father/Add mother text is displayed correctly though, depending on the val of addFather)
import React from 'react'
import { Modal } from 'react-responsive-modal';
class PersonAddModal extends React.Component
{
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event)
{
//...
}
getParentTypeString()
{
return this.props.addFather ? "father" : "mother";
}
render() {
return(
<div>
<button onClick={() => this.props.setOpen(true)}>{this.props.addFather ? <div>Add father</div>:<div>Add mother</div>}</button>
<Modal open={this.props.open} onClose={() => this.props.setOpen(false)}>
<h1>Add a {this.getParentTypeString()} of {this.props.details.firstName}</h1>
<form onSubmit={(event) => {this.handleSubmit(event)}}>
<input type="text" id="firstName" name="firstName" placeholder="Enter first name" required/>
<input type="text" id="lastName" name="lastName" placeholder="Enter last name" required/>
<button type="submit">Submit</button>
</form>
</Modal>
</div>
)};
}
export default PersonAddModal;
I really don't understand why (apparently) the val of addFather of the latest added component seems also to be used for the first component. Aren't they supposed to be independant from each other? Help is graetly appreciated!
Edit:
They;re being used as follows:
import React, { Component } from 'react';
import { Link } from 'react-router-dom'
import 'react-responsive-modal/styles.css';
import { Modal } from 'react-responsive-modal';
import PersonLink from './PersonLink'
import PersonAddModal from './PersonAddModal'
const PersonDetails = ({ match }) => {
const [details, setDetails] = React.useState({});
const [isLoading, setIsLoading] = React.useState(true);
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
fetch(`https://localhost:44332/api/person/${match.params.id}/details`)
.then(response => response.json())
.then((data) => { setDetails(data); setIsLoading(false); });
}, [match.params.id])
return (
<>
{
(isLoading || details.id == null) ? <h1>Loading..</h1>
:
<>
<h1>Person Details of {details.firstName} {details.lastName}</h1>
<h2>Parents </h2>
{
details.father != null && details.father != 0 ?
<PersonLink id={details.father } />
:
<>
<PersonAddModal details={details} open={open} setOpen={setOpen} addFather={true}/>
</>
}
{
details.mother != null && details.mother != 0 ?
<PersonLink id={details.mother} />
:
<PersonAddModal details={details} open={open} setOpen={setOpen} addFather={false}/>
}
{details.spouse != 0 ?
<>
<h2>Spouse</h2>
<PersonLink id={details.spouse}/>
</>
: <></>}
<h2>Siblings</h2>
{
details.siblings.map((sibling) => (<PersonLink id={sibling} />))
}
<h2>Children</h2>
{
details.children.map((child) => (<PersonLink id={child} />))
}
</>
}
</>
);
};
export default PersonDetails;
I've found the answer:
As you could see, I was creating two Modals, and my issue was that both were displaying the same. Turns out that because I was giving them both the same state (open/setOpen), no matter on which I clicked, always the same was opening.
Related
I've created a simple star rating component to make users able to review my books.
Here's the component:
import React, { useState } from 'react'
import { FaStar } from 'react-icons/fa'
const StarRating = (props) => {
const [rating, setRating] = useState(null);
return (
<Wrapper>
{[...Array(5)].map((star, i) => {
const ratingValue = i + 0;
return (
<label>
<input
type="radio"
name="rating"
onClick={() => setRating(props.ratingValue)}
/>
<FaStar color={ratingValue < rating ? "#01af93" : "#bbb"} />
</label>
)
})}
</Wrapper>
)
}
export default StarRating
So, if somebody clicks on the Stars the rating will appear (using an onClick handler).
I would like to display the ratings without the onClick handler now.
I've tried simply to add value={props.ratingValue} instead of onClick={() => setRating(props.ratingValue)} but it doesn't work.
Hope someone can help with what I'm doing wrong.
You have to move onClick handler and value to the parent container class. So changing state and keeping current input value must be done in your parent container. Below I share a code snippet for your sample.
import React, { useState } from "react";
import { FaStar } from "react-icons/fa";
const StarRating = (props) => {
console.log(props);
return (
<div>
{Array(5)
.fill(0)
.map((_, idx) => (
<label key={idx}>
<input
type="radio"
name="rating"
onChange={() => props.setRating(idx)}
value={props.ratingValue}
checked={idx === props.ratingValue}
/>
<FaStar color={idx < 3 ? "#01af93" : "#bbb"} />
</label>
))}
</div>
);
};
export const RatingContainer = () => {
const [rate, setRate] = useState(3);
return (
<div>
<StarRating setRating={(val) => setRate(val)} ratingValue={rate} />
</div>
);
};
I'm trying to edit an input value in a child component and send to the parent
:
https://codesandbox.io/s/sleepy-rain-skoss?file=/src/Editlabel.js:0-389
Parent:
import "./styles.css";
import EditLabel from "./Editlabel";
import { useEffect, useState } from "react";
export default function App() {
const [newName, setNewName] = useState();
useEffect(() => {
console.log("newName", newName);
}, [newName]);
return (
<div className="App">
<EditLabel
value={"hello"}
click={(changedName) => {
setNewName(changedName);
}}
/>
</div>
);
}
Child:
import React, { useState } from "react";
const EditLabel = ({ value, click }) => {
const [name, setName] = useState(value);
return (
<>
<input type={"text"} placeholder={name}></input>
<button
onClick={(e) => {
setName(e.target.value);
click(name);
}}
>
Edit
</button>
</>
);
};
export default EditLabel;
However, the console logs "hello" and then it just logs empty strings.
How can I make it work?
try this on your child's input box
<input type={"text"} placeholder={name} onChange={(e) => setName(e.target.value)}>
Change EditLabel to use a ref to capture the input value:
const EditLabel = ({ value, click }) => {
const inputRef = useRef(null);
return (
<>
<input ref={inputRef} type={"text"} placeholder={value}></input>
<button
onClick={() => {
click(inputRef.current.value);
}}
>
Edit
</button>
</>
);
};
Update App to use the values it gets via the click callback:
export default function App() {
const [newName, setNewName] = useState("hello");
useEffect(() => {
console.log("newName", newName);
}, [newName]);
return (
<div className="App">
<EditLabel
value={newName}
click={(changedName) => {
setNewName(changedName);
}}
/>
</div>
);
}
I'm building a React tab navigation component with emotion. I'm having trouble finding a solution that would allow me to:
Initially hide all content except for the buttons and not style the buttons.
When you click on a button activate the style and show the content associated with that button.
And finally when you click outside or the input is empty reset to initial state.
Here is the code:
Code
import React, { useState } from "react";
import ReactDOM from "react-dom";
import styled from "#emotion/styled";
import "./styles.css";
const StyledShowButton = styled("button", {
shouldForwardProp: (prop) => ["active"].indexOf(prop) === -1
})`
color: ${({ active }) => (active ? "red" : "black")};
`;
function App() {
const [active, setActive] = useState(0);
const [showInput, setShowInput] = useState(false);
const handleInputChange = (e) => {
if (e.target.value < 1) {
console.log("Reset Everyting");
}
};
const handleTabClick = (e) => {
const index = parseInt(e.target.id, 0);
if (index !== active) {
setActive(index);
}
if (!showInput) {
setShowInput(!showInput);
}
};
return (
<div className="App">
<StyledShowButton
type="button"
id={0}
active={active === 0}
onClick={handleTabClick}
>
First
</StyledShowButton>
<StyledShowButton
type="button"
id={1}
active={active === 1}
onClick={handleTabClick}
>
Second
</StyledShowButton>
{/* CONTENT */}
{active === 0 ? (
<input placeholder="First input" onChange={handleInputChange} />
) : (
<input placeholder="Second input" onChange={handleInputChange} />
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Just ask if I didn't make my self clear enough,
Thanks beforehand!
Erik
You can hide inpts in this way at first by assigning a null value to the active state.
You can also initialize values from 1 so that id and state state are not confused.
I made the arrangements.
You can review the code below.
You can also view it from this link. Code:
function App() {
const [active, setActive] = useState(null);
const [showInput, setShowInput] = useState(false);
const handleInputChange = (e) => {
if (e.target.value < 1) {
setActive(null);
}
};
const handleTabClick = (e) => {
const index = parseInt(e.target.id, 0);
if (index !== active) {
setActive(index);
}
if (!showInput) {
setShowInput(!showInput);
}
};
return (
<div className="App">
<StyledShowButton
type="button"
id={1}
active={active === 1}
onClick={handleTabClick}
>
First
</StyledShowButton>
<StyledShowButton
type="button"
id={2}
active={active === 2}
onClick={handleTabClick}
>
Second
</StyledShowButton>
{/* CONTENT */}
{active &&
(active === 1 ? (
<>
<input placeholder="First input" onChange={handleInputChange} />
</>
) : (
<input placeholder="Second input" onChange={handleInputChange} />
))}
</div>
);
}
I have set react router to redirect from "/" route to a custom route /:city/posts , but the redirect is not working as expected, onClick of button page refresh's , I have it setup working fine in other componts, not sure what is the issue
sandbox: https://codesandbox.io/s/focused-sammet-eitqr
import React, { useState } from "react";
import {
Form,
Container,
Button,
DropdownButton,
Dropdown
} from "react-bootstrap";
import { Redirect } from "react-router-dom";
const Location = () => {
let [city, setCity] = useState("");
let [acceptedTOS, setAcceptedTOS] = useState(false);
const handleFormSubmit = () => {
// if (city !== "" && acceptedTOS === true) {
return <Redirect to="/city/posts" />;
// }
};
console.log("city ====", city, "acceptedTOS===", acceptedTOS);
return (
<Container>
<Form onSubmit={handleFormSubmit}>
<h3>Select a City</h3>
<br />
<DropdownButton
id="dropdown-basic-button"
title={city !== "" ? city : "Canada"}
>
</DropdownButton>
<br />
<Form.Group>
<Form.Check
required
name="terms"
label="I have read and agreed to follow TOS"
onChange={() => {
setAcceptedTOS(true);
}}
// isInvalid={!!errors.terms}
// feedback={errors.terms}
id="validationFormik0"
/>
<Button type="submit">Submit form</Button>
</Form.Group>
</Form>
</Container>
);
};
export default Location;
You should be using a state for redirect,
const [redirect, setRedirect] = useState(false)
Add e.preventDefault() in handleFormSubmit,
const handleFormSubmit = (e) => {
e.preventDefault()
setRedirect(true) //set redirect to ture
};
Finally in your return you can redirect,
return (
<Container>
{redirect && <Redirect to="/:city/posts" />}
...
</Container>
)
I'm new at react/redux, but can realize some simple addProduct form for my app.
Today I tried to replace it with Formik from this Basic demo, but I cant't understand where should I place "dispatch" function (I tried it to everywhere).
May be I use connect in wrong way?
My new component is exactly like in Demo, except I replaced email with productName (and other additional fields). But I can't understand how to pass "values" from Formik to Redux store.
My old form component, without Formik, looks like this:
import React from 'react';
import { connect } from 'react-redux';
import { addProduct } from '../actions';
const AddProduct0 = ({ dispatch }) => {
let inputSKUNumber;
let inputProductName;
return (
<div>
<input
ref={(node) => {
inputSKUNumber = node;
}}
placeholder="SKU Number"
/>
<input
ref={(node) => {
inputProductName = node;
}}
placeholder="Product name"
/>
<button
onClick={() => {
dispatch(addProduct({ SKUNumber: inputSKUNumber.value, name: inputProductName.value }));
inputSKUNumber.value = '';
inputProductName.value = '';
}}
>
Add Product
</button>
</div>
);
};
const AddProduct = connect()(AddProduct0);
export default AddProduct;
My new component with formik looks like this:
import React from 'react';
import { connect } from 'react-redux';
import { withFormik } from 'formik';
import Yup from 'yup';
import { addProduct } from '../actions';
import './helper.css';
// Our inner form component. Will be wrapped with Formik({..})
const MyInnerForm = (props) => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
} = props;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="SKUNumber">SKU Number</label>
<input
id="SKUNumber"
placeholder="SKU Number"
type="number"
value={values.SKUNumber}
onChange={handleChange}
onBlur={handleBlur}
className={errors.SKUNumber && touched.SKUNumber ? 'text-input error' : 'text-input'}
/>
<div className="input-feedback">{touched.SKUNumber ? errors.SKUNumber : ''}</div>
<label htmlFor="productName">Product Name</label>
<input
id="productName"
placeholder="Product Name"
type="text"
value={values.productName}
onChange={handleChange}
onBlur={handleBlur}
className={errors.productName && touched.productName ? 'text-input error' : 'text-input'}
/>
<div className="input-feedback">{touched.productName ? errors.productName : ''}</div>
<button
type="button"
className="outline"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</button>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
<DisplayFormikState {...props} />
</form>
);
};
const EnhancedForm = withFormik({
mapPropsToValues: () => ({
SKUNumber: 12345678,
productName: 'Default Product',
}),
validationSchema: Yup.object().shape({
SKUNumber: Yup.number()
.max(99999999, 'SKU Number must be less than 8 digits')
.required('SKU Number is required!'),
productName: Yup.string()
.min(5, 'Product name must be longer than 5 symbols')
.max(50, 'Product name must be shorter than 50 symbols')
.required('Product name is required!'),
handleSubmit: (values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 1000);
// dispatch(addProduct(values));
},
displayName: 'BasicForm', // helps with React DevTools
})(MyInnerForm);
export const DisplayFormikState = props => (
<div style={{ margin: '1rem 0' }}>
<h3 style={{ fontFamily: 'monospace' }} />
<pre
style={{
background: '#f6f8fa',
fontSize: '.65rem',
padding: '.5rem',
}}
>
<strong>props</strong> = {JSON.stringify(props, null, 2)}
</pre>
</div>
);
const AddProduct = connect()(EnhancedForm);
export default AddProduct;
p.s. If someone have reputation here to add tag "formik", please, do it.
I also opened an issue on formik page. And there one of contributors gave me the answer. All works with that code:
handleSubmit(values, { props, setSubmitting }) {
props.dispatch(addProduct(values));
setSubmitting(false);
},
import React from 'react';
import { connect } from 'react-redux';
import { addProduct } from '../actions';
/* AddProduct не совсем контейнер, он просто вызывает диспатч,
ему не нужен стор, поэтому мы можем создать коннект коротким путем:
AddProduct = connect()(AddProduct); */
const AddProduct = ({ addMyProduct }) => {
let inputSKUNumber;
let inputProductName;
return (
<div>
<input
ref={(node) => {
inputSKUNumber = node;
}}
placeholder="SKU Number"
/>
<input
ref={(node) => {
inputProductName = node;
}}
placeholder="Product name"
/>
<button
onClick={() => {
addMyProduct({ SKUNumber: inputSKUNumber.value, name: inputProductName.value });
inputSKUNumber.value = '';
inputProductName.value = '';
}}
>
Add Product
</button>
</div>
);
};
const mapDispatchToProps = dispatch => ({
addMyProduct: (params) => dispatch(addProduct(params))
});
export default connect(null, mapDispatchToProps)(AddProduct);
You can use a higher variable or async function. Higher variable is a little bit lame but working.
let setSubmittingHigher;
// Our inner form component. Will be wrapped with Formik({..})
const MyInnerForm = (props) => {enter code here
.
.
.
handleSubmit(values, {props, setSubmitting}) {
setSubmittingHigher = setSubmitting;
.
.
.
const mapStateToProps = (state) => {
typeof setSubmittingHigher === 'function' && setSubmittingHigher(false);
return {}
};
const mapDispatchToProps = dispatch => ({
addMyProduct: (params) => dispatch(addProduct(params))
});
export default connect(mapStateToProps, mapDispatchToProps)(AddProduct);