Reset state without Effects using key - reactjs

I came across exercise from beta.react docs concerning issue: Reset state without Effects.
You may find it in the bottom : Challenge 3 of 4: Reset state without Effects.
There's a component that receives object of person data to present it in editable form.
As for start it tells you that useEffect is redundant.
import React, { useState } from "react";
//ExportContact.jsx
export default function EditContact({ savedContact, onSave }) {
const [name, setName] = useState(savedContact.name);
const [email, setEmail] = useState(savedContact.email);
// useEffect(() => {
// setName(savedContact.name);
// setEmail(savedContact.email);
// }, [savedContact]);
return (
<section>
<label>
Name:{" "}
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
<label>
Email:{" "}
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<button
onClick={() => {
const updatedData = {
id: savedContact.id,
name: name,
email: email
};
onSave(updatedData);
}}
>
Save
</button>
<button
onClick={() => {
setName(savedContact.name);
setEmail(savedContact.email);
}}
>
Reset
</button>
</section>
);
}
Suggested solution is to split into another component that will receive key of contact id.
Like that where EditForm contains everything EditContact had so far.
export default function EditContact(props) {
return (
<EditForm
{...props}
key={props.savedContact.id}
/>
);
}
I'm just wondering how would it be different to add key prop with contact id value right into the parent component like this:
<EditContact
key={selectedContact.id}
savedContact={selectedContact}
onSave={handleSave}
/>
Instead of splitting EditContact into artificial subcomponent only to receive a key prop.

The difference is explained in the official link about spread attributes.
Below is the official summary:
Spread attributes can be useful but they also make it easy to pass unnecessary props to components that don’t care about them or to pass invalid HTML attributes to the DOM. We recommend using this syntax sparingly.

Related

Set item in onclick is not logging expected output

I'm writing a simple react code that adds a value to a list onClick of a button. and after adding, I'm logging it in the same block. Currently, my issue is, that the logging is happening with n-1 entered string. i.e. If I enter egg and then add milk, after adding milk, I see egg logged and so on. Here is my code.
function App() {
const [list, setList] = useState([]);
const [gItem, setGItem] = useState("");
const AddItem = (e) => {
e.preventDefault();
setList([...list, gItem]);
console.log(list);
};
return (
<>
<form className="grocery-form">
<h3>grocery bud</h3>
<div className="form-control">
<label htmlFor="name"></label>
<input
type="text"
placeholder="e.g. eggs"
className="grocery"
name="name"
id="name"
onChange={(e) => setGItem(e.target.value)}
/>
<button className="submit-btn" type="submit" onClick={AddItem}>
Submit
</button>
</div>
</form>
<div className="grocery-container">
<List items={list} />
</div>
</>
);
}
I'm unable to understand where I'm going wrong.
setList updates state asynchronously so if you log state after using it the previous value will be displayed, to make it log the current state after this list was changed you can use useEffect hook like this:
useEffect(() => {
console.log(list);
}, [list])

React - how to target value from a form with onClick

New to react and currently working on a project with a backend.
Everything functions correctly apart from targeting the value of user selection.
basically whenever a user enters a number the setId is saved properly to the const with no problems while using the onChange method.
this method would render my page every change on text.
I am trying to save the Id only when the user clicks the button. however,
event.target.value does not work with onClick.
I tried using event.currentTarget.value and this does not seem to work.
Code:
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input value={id} onChange={(e) => setId(e.target.value)} type="number" />
{/* <button value={id} type="button" onClick={(e) => setId(e.currentTarget.value)}>Search</button> */}
</form>
Handle Submit:
const handleSubmit = (e) => {
e.preventDefault();
console.log(id)
}
is there a way of doing this with onclick? since I wouldn't like my component to render on every typo and only once a user has clicked the button.
Componenet:
interface GetOneCompanyProps {
company: CompanyModel;
}
interface RouteParam {
id: any;
}
interface CompanyById extends RouteComponentProps<RouteParam> {
}
function GetOneCompany(): JSX.Element {
const [id, setId] = useState('4');
const [company, setCompany] = useState<any>('');
const handleSubmit = (e) => {
e.preventDefault();
console.log(id)
}
async function send() {
try {
const response = await axios.get<CompanyModel>(globals.adminUrls.getOneCompany + id)
store.dispatch(oneCompanyAction(response.data));
console.log(response);
const company = response.data;
setCompany(company)
} catch (err) {
notify.error(err);
}
}
useEffect(() => {
send();
}, [id]);
return (
<div className="getOneCompany">
<h1>hi </h1>
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input value={id} onChange={(e) => setId(e.target.value)} type="number" />
{/* <button value={id} type="button" onClick={(e) => setId(e.currentTarget.value)}>Search</button> */}
</form>
<div className="top">
</div>
<br/>
Company: {id}
<br/>
Client Type: {company.clientType}
<br/>
Company Name: {company.name}
<br/>
Email Adress: {company.email}
<br/>
</div>
);
}
export default GetOneCompany;
Hope I am clear on this.
Thanks.
You can turn your input from being a controlled input to an uncontrolled input, and make use of the useRef hook. Basically, remove most of your attributes from the input element, and grab the current value of the input form on click of the button. From there, you can do whatever you want with the input value.
const inputRef = useRef()
...other code
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input type="number" ref={inputRef} />
<button value={id} type="button" onClick={() => console.log(inputRef.current.value)}>Search</button>
</form>
...other code
I'm afraid to say that here onChange is mandatory as we also are interested in the value which we set by setId. onClick can't be used as we can't set the value in the input.
Hope I'm clear.
Thankyou!

In React with Formik how can I build a search bar that will detect input value to render the buttons?

New to Formik and React I've built a search component that I'm having issues with the passing of the input value and rendering the buttons based on input length.
Given the component:
const SearchForm = ({ index, store }) => {
const [input, setInput] = useState('')
const [disable, setDisable] = useState(true)
const [query, setQuery] = useState(null)
const results = useLunr(query, index, store)
const renderResult = results.length > 0 || query !== null ? true : false
useEffect(() => {
if (input.length >= 3) setDisable(false)
console.log('input detected', input)
}, [input])
const onReset = e => {
setInput('')
setDisable(true)
}
return (
<>
<Formik
initialValues={{ query: '' }}
onSubmit={(values, { setSubmitting }) => {
setInput('')
setDisable(true)
setQuery(values.query)
setSubmitting(false)
}}
>
<Form className="mb-5">
<div className="form-group has-feedback has-clear">
<Field
className="form-control"
name="query"
placeholder="Search . . . . ."
onChange={e => setInput(e.currentTarget.value)}
value={input}
/>
</div>
<div className="row">
<div className="col-12">
<div className="text-right">
<button type="submit" className="btn btn-primary mr-1" disabled={disable}>
Submit
</button>
<button
type="reset"
className="btn btn-primary"
value="Reset"
disabled={disable}
onClick={onReset}
>
<IoClose />
</button>
</div>
</div>
</div>
</Form>
</Formik>
{renderResult && <SearchResults query={query} posts={results} />}
</>
)
}
I've isolated where my issue is but having difficulty trying to resolve:
<Field
className="form-control"
name="query"
placeholder="Search . . . . ."
onChange={e => setInput(e.currentTarget.value)}
value={input}
/>
From within the Field's onChange and value are my problem. If I have everything as posted on submit the passed query doesn't exist. If I remove both and hard code a true for the submit button my query works.
Research
Custom change handlers with inputs inside Formik
Issue with values Formik
Why is OnChange not working when used in Formik?
In Formik how can I build a search bar that will detect input value to render the buttons?
You need to tap into the props that are available as part of the Formik component. Their docs show a simple example that is similar to what you'll need:
<Formik
initialValues={{ query: '' }}
onSubmit={(values, { setSubmitting }) => {
setInput('')
otherStuff()
}}
>
{formikProps => (
<Form className="mb-5">
<div className="form-group has-feedback has-clear">
<Field
name="query"
onChange={formikProps.handleChange}
value={formikProps.values.query}
/>
</div>
<button
type="submit"
disabled={!formikProps.values.query}
>
Submit
</button>
<button
type="reset"
disabled={!formikProps.values.query}
onClick={formikProps.resetForm}
>
</Form>
{/* ... more stuff ... */}
)}
</Formik>
You use this render props pattern to pull formiks props out (I usually call them formikProps, but you can call them anything you want), which then has access to everything you need. Rather than having your input, setInput, disable, and setDisable variables, you can just reference what is in your formikProps. For example, if you want to disable the submit button, you can just say disable={!formikProps.values.query}, meaning if the query value in the form is an empty string, you can't submit the form.
As far as onChange, as long as you give a field the correct name as it corresponds to the property in your initialValues object, formikProps.handleChange will know how to properly update that value for you. Use formikProps.values.whatever for the value of the field, an your component will read those updates automatically. The combo of name, value, and onChange, all handled through formikProps, makes form handing easy.
Formik has tons of very useful prebuilt functionality to handle this for you. I recommend hanging out on their docs site and you'll see how little of your own code you have to write to handle these common form behaviors.

How to break down component and make sub component in reactjs?

{
this.state.editMode
?
<Card.Body>
<Form>
<Form.Group>
<Form.Control id="des" as="textarea" rows={3} placeholder="Description"
value={this.state.description}
onChange={(des) => {
this.setState({description: des.target.value})
}}/>
</Form.Group>
<Form.Group>
<Form.Control id="link" type="text" placeholder="Link (Optional)"
value={this.state.link}
onChange={(link) => {
this.setState({link: link.target.value})
}}/>
</Form.Group>
</Form>
</Card.Body>
:
<Card.Body>
<p className="cardBody alignLeft">{this.state.description}</p>
<a href={"https://" + this.state.link}
target="#">{this.state.link}</a>
</Card.Body>
}
I want to make two sub component here. One for if editmode is true and another if editmode is false.
But problem is that, this subcomponent also need to use the state variable of parent class. And also, if i change something on sub component, parent components state need to be changed. How can I do that?
You need to pass the state of the parent as props for the child component, which is a standard practice as mentioned here - https://reactjs.org/docs/lifting-state-up.html
If you want to change the state of the parent within the child, you can also pass a function as a prop to the child. The call to the function in child component will trigger state change in parent.
https://dev.to/vadims4/passing-down-functions-in-react-4618
A better way for large component trees is through use of dispatch via React context. This is explained in
https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down
As others suggested, you can pass state as props and setState as a prop as well to update parent from child.
Here is an example that might help you understand how to split components and passing values and setState using props.
Note: I have used React Hooks, you can modify it based on class based components.
import React, { useState } from "react";
const App = () => {
const [editMode, setEditMode] = useState(false);
const [data, setData] = useState({
description: "",
link: ""
});
const editModeHandler = (key, value) =>
{
setData((prevState) =>
{
return {...prevState, [key]: value}
})
//setEditMode(true)
};
return <div className="App">{editMode
? <GroupOne value = {description} change = {editModeHandler}/>
: <GroupTwo value = {link}/>}</div>;
};
export default App;
/*Edit Mode True*/
const GroupOne = (props) => {
const { value, change } = props;
return (
<Card.Body>
<Form>
<Form.Group>
<Form.Control
id="des"
as="textarea"
rows={3}
placeholder="Description"
value={value}
onChange={(des) => change("description",des.target.value)}
/>
</Form.Group>
<Form.Group>
<Form.Control
id="link"
type="text"
placeholder="Link (Optional)"
value={value}
onChange={(des) => change("link",des.target.value)}
/>
</Form.Group>
</Form>
</Card.Body>
);
};
/*Edit Mode False*/
const GroupTwo = (props) => {
const { value } = props;
return (
<Card.Body>
<p className="cardBody alignLeft">{value}</p>
<a href={"https://" + value} target="#">
{value}
</a>
</Card.Body>
);
};
You should create a second component and pass the state variables as to the second components. For more information read this documentation https://reactjs.org/docs/components-and-props.html

React Formik : How to useEffect with formik values?

I would like to use useEffect to rerender a field if values of formik has changed. But it doesn't work..
Actually I created a little example to show my problem.
Here I have one field 'title', if this field has changed (when we write in it) it should call useEffect and print 'update!' but it doesn't!!
const FormikWidgetConfigurator = (props) => {
useEffect(() => {
// doesn't work if values has changed
console.log('update!')
}, [props.values]);
return (
<Form onSubmit={ props.handleSubmit } noValidate>
<Form.Group className='py-3' >
<Col md='6' style={{ padding: '0px' }}>
<Form.Group controlId='title'>
<Form.Control
type='text'
value={ props.values.title }
onChange={props.handleChange}
/>
</Form.Group>
</Col>
</Form.Group>
</Form>
)
}
const WidgetConfigurator = withFormik({
mapPropsToValues(props) {
return {
title: 'No Title'
};
},
validationSchema: props => Yup.object().shape({title: Yup.string()}),
handleSubmit(values, { setSubmitting }) {// handle submit}
})(FormikWidgetConfigurator);
export default WidgetConfigurator;
EDIT: Actually it works as expected. (i didn't change anything)
Thanks!
Using vanilla Formik your approach works.
My guess is that the issue is in your custom components, Form.Control, or Form.Group
It's not a great solution, but a hack I found is to have an invisible button inside a Formik form; it has access to all of Formik's attributes and will do whatever logic is needed in its onClick. Then from a useEffect you can simulate the click of that button via document.getElementById("..").click().
// Style it to be invisible
<Button id="testButton" type="button" onClick={() => {
setFieldValue('test', '123');
setTouched({});
// etc. any Formik action
}}>
</Button>
useEffect:
useEffect(() => {
document.getElementById("testButton").click(); // Simulate click
}, [dependency);

Resources