I'm trying to use formik <FieldArray /> where I can use the push arrayHelper to fill in the array data. In the example, they are creating the form straightaway from the button. However I want to extract it to use useCallback
Here's the initiative that I did
import React, { useCallback } from "react";
import { useField, FieldArray } from "formik";
import { Button } from "#material-ui/core";
import { FormikTextField, formStyles } from "./Utils";
function WitnessChild({ remove, push }) {
const classes = formStyles();
const [field] = useField("witnesses");
const handleAdd = useCallback(
() =>
push({
FirstName: "",
LastName: "",
AddressStreet: "",
AddressSuburb: "",
AddressPostcode: "",
Mobile: ""
}),
[field.value]
);
return (
<>
{field.value.length > 0 &&
field.value.map((witness, index) => (
<div>
<div className={`${classes.margin} row`}>
<Button
variant="contained"
component="label"
className={`${classes.instructions}`}
onClick={handleAdd}
>
Add New
</Button>
</div>
<div className={`${classes.margin} row`}>
<div className="col-6">
<FormikTextField
className={`${classes.instructions}`}
id={`witnesses.${index}.FirstName`}
name={`witnesses.${index}.FirstName`}
label="First Name"
/>
</div>
<div className="col-6">
<FormikTextField
className={`${classes.instructions}`}
id="witnessLastName"
name="witnessLastName"
label="Last Name"
/>
</div>
</div>
<div className={`${classes.margin} row`}>
<div className="col-6">
<FormikTextField
className={`${classes.instructions}`}
id="witnessAddressStreet"
name="witnessAddressStreet"
label="Street"
/>
</div>
<div className="col-6">
<FormikTextField
className={`${classes.instructions}`}
id="witnessAddressSuburb"
name="witnessAddressSuburb"
label="Suburb"
/>
</div>
</div>
<div className={`${classes.margin} row`}>
<div className="col-6">
<FormikTextField
className={`${classes.instructions}`}
id="witnessAddressPostcode"
name="witnessAddressPostcode"
label="Postcode"
/>
</div>
<div className="col-6">
<FormikTextField
className={`${classes.instructions}`}
id="witnessMobile"
name="witnessMobile"
label="Mobile"
/>
</div>
</div>
</div>
))}
</>
);
}
export default function WitnessDetails() {
return <FieldArray name="witnesses" render={WitnessChild} />;
}
I'm not sure why it returns:
×
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See link for tips about how to debug and fix this problem.
I can actually remove this error by creating the handleAdd func inside the button () => {}, however I don't think that's a good practice because it will be initiated every time. Can anyone tell me what is the best practice in extracting hooks like this?
Thanks
Related
I originally had two components displayed on two different pages via Routes: the RecordList and Create components. It all currently works properly when I use Routes. However, I'm trying to modify this project so that both components are on the same page at the same time instead of being on two different pages.
My goal is to have RecordList update (fetch the data from the server) once I click the "Add Record" button. Currently the Create component is working properly, however I have to refresh the page to see the changes on the RecordList component.
I'm having trouble understanding what I need to do to get RecordList to update when the record is posted to the server. Any guidance is helpful.
Would this be a scenario to use Lifting Up State? I didn't think so because I believe these two components are sibling components.
Do I need to re-render the RecordList component? Should I just combine these two components into one component? My guess was that it would be better for these to be two separate components.
For reference, I did not write all of this code. I'm trying to modify the project I built using the MERN tutorial on the MongoDB website.
Here is a screenshot of the page:
homepage
App.js:
import React from "react";
import './App.css';
// We use Route in order to define the different routes of our application
import { Route, Routes } from "react-router-dom";
// We import all the components we need in our app
import Navbar from "./components/navbar";
import RecordList from "./components/recordList";
import Edit from "./components/edit";
import Create from "./components/create";
const App = () => {
return (
<div>
<div className="container">
<Navbar />
</div>
<div className="container py-3 my-3 d-flex align-items-start justify-content-around flex-wrap flex-md-nowrap">
<div className="w-100 px-md-3 py-3 py-md-0"><RecordList /></div>
<div className="w-100 px-md-3 py-3 py-md-0"><Create /></div>
{/* <Routes>
<Route exact path="/" element={<RecordList />} />
<Route path="/edit/:id" element={<Edit />} />
<Route path="/create" element={<Create />} />
</Routes> */}
</div>
</div>
);
};
export default App;
recordList.js:
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
const Record = (props) => (
<tr>
<td>{props.record.date}</td>
<td>{props.record.destination}</td>
<td>{props.record.location}</td>
<td>
<Link className="btn btn-link" to={`/edit/${props.record._id}`}>Edit</Link> |
<button className="btn btn-link"
onClick={() => {
props.deleteRecord(props.record._id);
}}
>
Delete
</button>
</td>
</tr>
);
export default function RecordList() {
const [records, setRecords] = useState([]);
// This method fetches the records from the database.
useEffect(() => {
async function getRecords() {
const response = await fetch(`http://localhost:5000/record/`);
if (!response.ok) {
const message = `An error occurred: ${response.statusText}`;
window.alert(message);
return;
}
const records = await response.json();
setRecords(records);
}
getRecords();
return;
}, [records.length]);
// This method will delete a record
async function deleteRecord(id) {
await fetch(`http://localhost:5000/${id}`, {
method: "DELETE"
});
const newRecords = records.filter((el) => el._id !== id);
setRecords(newRecords);
}
// This method will map out the records on the table
function recordList() {
return records.map((record) => {
return (
<Record
record={record}
deleteRecord={() => deleteRecord(record._id)}
key={record._id}
/>
);
});
}
// This following section will display the table with the records of individuals.
return (
<div>
<h3>Record List</h3>
<table className="table table-striped" style={{ marginTop: 20 }}>
<thead>
<tr>
<th>Date</th>
<th>Destination</th>
<th>Location</th>
<th>Action</th>
</tr>
</thead>
<tbody>{recordList()}</tbody>
</table>
</div>
);
}
create.js:
import { useNavigate } from "react-router";
export default function Create() {
const [form, setForm] = useState({
date: "",
destination: "",
location: "",
});
const navigate = useNavigate();
// These methods will update the state properties.
function updateForm(value) {
return setForm((prev) => {
return { ...prev, ...value };
});
}
// This function will handle the submission.
async function onSubmit(e) {
e.preventDefault();
// When a post request is sent to the create url, we'll add a new record to the database.
const newPerson = { ...form };
await fetch("http://localhost:5000/record/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newPerson),
})
.catch(error => {
window.alert(error);
return;
});
setForm({ date: "", destination: "", location: "" });
navigate("/");
}
// This following section will display the form that takes the input from the user.
return (
<div>
<h3>Add Mileage Record</h3>
<form onSubmit={onSubmit}>
<div className="row my-3">
<div className="col">
<div className="form-group">
<label htmlFor="date">Date</label>
<input
type="text"
className="form-control"
id="date"
value={form.date}
onChange={(e) => updateForm({ date: e.target.value })}
/>
</div>
</div>
<div className="col">
<div className="form-group">
<label htmlFor="destination">Destination</label>
<input
type="text"
className="form-control"
id="destination"
value={form.destination}
onChange={(e) => updateForm({ destination: e.target.value })}
/>
</div>
</div>
</div>
<div className="row my-3">
<div className="col">
<div className="form-group">
<label htmlFor="location">Location</label>
<input
type="text"
className="form-control"
id="location"
value={form.location}
onChange={(e) => updateForm({ location: e.target.value })}
/>
</div>
</div>
<div className="row my-3">
<div className="col">
<div className="form-group">
<input
type="submit"
value="Add Record"
className="btn btn-primary"
/>
</div>
</div>
</div>
</div>
</form>
</div>
);
}
you have several ways to solve this problem:
you can move records state from recordList to App and pass records to recordList component and pass setRecords to create component as props, so you can update records when statusCode of http://localhost:5000/record/add is 200.
you can use stateManagement packages like redux, redux-toolkit, jotai, ... to has a manager to manage changes on the entire application side
you can use context for save records to access records everywhere in the application.
you can use react-query package to manage get and save data together. you can react-query-useful-hooks for optimal and easier use of rickett
I'm trying to create a Job platform, Where we can Sign up/login as either "Recruiter" or as a "Candidate". The complication I'm facing is I've created a form for both recruiter and candidate. Now I'm trying to switch between those componets. I've come this far
import useToggle from "#rooks/use-toggle"
export default function SignUp() {
const [recruitForm, setRecruitForm] = useToggle(true);
const [candidateForm, setCandidateForm] = useToggle(false);
}
return (
<div>
<form>
<div className="text-md tracking-wide p-0">
SignUp Form
</div>
<div className="flex flex-row gap-8">
<div>
<button
onClick={setRecruitForm}>
Recruiter
</button>
<>
{recruitForm && <RecruiterForm /> }
</>
</div>
<div>
<button
onClick={setCandidateForm}
type="button">
Candidate
</button>
<>
{candidateForm && <CandidateForm /> }
</>
</div>
</div>
</form>
</div>
</div>
)
}
Components are within their own context. But I have trouble even coming up with an idea how to handle or switch components without messing up the styling, the way it needs to be is when one instance opens, close the other conundrum / Form.
I'm sharing the output I've got using the useToggle react rooks
Any ideas on how to achieve it with any other hook or with useToggle hook itself or please let me know what I'm doing wrong.
https://www.loom.com/share/d2251bc3b9594782aa0a17aae92c997e {This is the result I've got}
Since thay are two Items you can make use of a boolean and just one state
const [active, setActive] = useState(false)
return (
<div>
<form>
<div className="text-md tracking-wide p-0">
SignUp Form
</div>
<div className="flex flex-row gap-8">
<div>
<button
onClick={()=>setActive(true)}>
Recruiter
</button>
<>
{active && <RecruiterForm /> }
</>
</div>
<div>
<button
onClick={()=>setActive(false)}
type="button">
Candidate
</button>
<>
{!active && <CandidateForm /> }
</>
</div>
</div>
</form>
</div>
</div>
)
}
as far as I can understand from your question, that you have created 2 from components for recruiter and candidate and want to show either on of em at a time. If not please do comment, I'll rectify the answer.
instead of checking your toggles when you will be rendering components, do before that, also you only need one toggle for this, better use a use state.
The initial false state for recruiter and true candidate
import React, {useState} from 'react';
export default function SignUp() {
const [switchForm, setSwitchForm] = useState(false);
return (
<div>
<form>
<div className="text-md tracking-wide p-0">
SignUp Form
</div>
<div className="flex flex-row gap-8">
<button
onClick={setSwitchForm(false)}>
Recruiter
</button>
<button
onClick={setSwitchForm(true)}
type="button">
Candidate
</button>
</div>
<div className="flex flex-row gap-8">
<>
{switchForm ? <CandidateForm /> : <RecruiterForm />}
</>
</div>
</form>
</div>
</div>
)};
This is not the actual/accurate code but just to give you the logical idea.
Also just a suggestion, use a custom hook for your parent component (signup.jsx) and put hooks there and import it from their to parent component and may come in use when you are going to submit the forms so that the states
and handler functions can have a common place to share.
I'm building an app with React and I'm trying to implement a modal appearing after error occurring.
This is a simply login card with input boxes and I do a post request to login or sign-up new users.
If there's any error in my form or users already exists I want to popup a modal with error messages like the following.
I can't understand why it isn't showing. The error is ok and the backdrop is visible. Any help is appreciated.
There's my code:
This is the JSX code:
import { useHttpClient } from '../hooks/http-hook';
const { isLoading, error, sendRequest, clearError } = useHttpClient();
...
return (
<React.Fragment>
<ErrorModal error={error} onClear={clearError} />
<div className="container">
{isLoading && <LoadingSpinner asOverlay />}
<div className="card card-container">
<img id="profile-img" className="profile-img-card" alt="Login Avatar" src={require("../../assets/images/avatar.png")} />
<p id="profile-name" className="profile-name-card"></p>
<form className="form-signin" onSubmit={loginSubmitHandler}>
<span id="reauth-email" className="reauth-email"></span>
{!isLoginMode && (
<Input
element="input"
id="username"
type="text"
title="Username"
placeholder="USERNAME"
validators={[VALIDATOR_REQUIRE()]}
errorText="Required."
onInput={inputHandler}
/>
)}
<Input
element="input"
id="email"
type="text"
title="EMAIL"
placeholder="EMAIL"
validators={[VALIDATOR_EMAIL()]}
errorText="Invalid email address."
onInput={inputHandler}
/>
<Input
element="input"
id="password"
type="password"
title="PASSWORD"
placeholder="PASSWORD"
validators={[VALIDATOR_MINLENGTH(5)]}
errorText="Your password must have at least 5 characters."
onInput={inputHandler}
/>
<Button type="submit" disabled={!formState.isValid}>
{isLoginMode ? 'LOGIN' : 'SIGNUP'}
</Button>
</form>
<Button inverse onClick={switchModeHandler}>
SWITCH TO {isLoginMode ? 'SIGNUP' : 'LOGIN'}
</Button>
</div>
</div>
</React.Fragment>
)
This is my Modal.js
import React from 'react';
import ReactDOM from 'react-dom';
import { CSSTransition } from 'react-transition-group';
import Backdrop from './Backdrop';
import './Modal.css';
const ModalOverlay = props => {
const content = (
<div className={`modal ${props.className}`} style={props.style}>
<header className={`modal__header ${props.headerClass}`}>
<h2>{props.header}</h2>
</header>
<form
onSubmit={
props.onSubmit ? props.onSubmit : event => event.preventDefault()
}
>
<div className={`modal__content ${props.contentClass}`}>
{props.children}
</div>
<footer className={`modal__footer ${props.footerClass}`}>
{props.footer}
</footer>
</form>
</div>
);
return ReactDOM.createPortal(content, document.getElementById('modal-hook'));
};
const Modal = props => {
return (
<React.Fragment>
{props.show && <Backdrop onClick={props.onCancel} />}
<CSSTransition
in={props.show}
mountOnEnter
unmountOnExit
timeout={200}
classNames="modal"
>
<ModalOverlay {...props} />
</CSSTransition>
</React.Fragment>
);
};
export default Modal;
And this my ErrorModal.js
import React from 'react';
import Modal from './Modal';
import Button from '../formElements/Button';
const ErrorModal = props => {
return (
<Modal
onCancel={props.onClear}
header="An Error Occurred!"
show={!!props.error}
footer={<Button onClick={props.onClear}>Okay</Button>}
>
<p>{props.error}</p>
</Modal>
);
};
export default ErrorModal;
It was simply my bootstrap.css to create a mess.....
<link rel="stylesheet" href="%PUBLIC_URL%/stylesheets/bootstrap/bootstrap.css">
I simply renamed the modal class in custom_modal
I'm using map to view all posts using axios. And I just want show when I click a specific post to see more information. I'm using react parameters. But it's not working.
Here is my one component
import React, {Component} from 'react';
import Album from './album'
import {Link, BrowserRouter as Router, Route} from 'react-router-dom'
import axios from "axios"
class ViewDataAPI extends Component{
state = {
posts: []
}
componentDidMount(){
axios.get('https://jsonplaceholder.typicode.com/comments')
.then(response => {
this.setState({
posts: response.data
})
})
.catch(error => console.log('error'))
}
render(){
let { posts } = this.state
if(posts.length === 0){
return <h1>Loading...</h1>
}
else{
return(
<Router>
<div className="header">
<div className="container">
<div className="row">
<div className="col-lg-12 col-sm-12 col-xs-12">
<div className="text-center mb-20">
<h1>View Data From API</h1>
<p>using jsx-component, props, state, map in react </p>
</div>
</div>
</div>
<div className="row">
{
posts.map(post =>
{
return (
<Album
key={post.id}
name={post.name}
email = {post.email}
body = {post.body}
view = {post.id}
/>
)
}
)
}
</div>
{/* here is im using params, and to match by clicking specific id to show/view more information */}
<div className="row">
{posts && (
<Route path="/album/:albumId"
render = {({match}) => (
<ViewPosts {...posts.find(pv => pv.id === match.params.albumId)} />
)}
/>
)}
</div>
</div>
</div>
</Router>
)
}
}
}
export default ViewDataAPI;
// This component using for show details
const ViewPosts = ({posts}) =>{
return(
<div className="col-lg-6">
<div className="card border-dark mb-3">
<div className="card-body text-dark">
<div className="album">
<h3>{posts.name}</h3>
<h3>{posts.email}</h3>
<Link to="./">Back To Home</Link>
</div>
</div>
</div>
</div>
);
}
This is album component that has a link
import React, {Component} from 'react'
import {Link} from "react-router-dom"
class Album extends Component{
render(){
return(
<div className="col-lg-6">
<div className="card border-dark mb-3">
<div className="card-body text-dark">
<div className="album">
<h3>{this.props.name}</h3>
<p>{this.props.email}</p>
<p>{this.props.body}</p>
<Link to={`/album/${this.props.view}`}>View</Link>
</div>
</div>
</div>
</div>
);
}
}
export default Album;
https://react-pin.netlify.com/
Please follow the above link to what I'm trying to do. Please first go to one "View Data From API"
My github link https://github.com/sultan0/reactpin
The route param is a string. There is no implicit type conversion
with === Operator. Therefore you have to do it explicitly. Pls. see
Comparison operators for a further explanation.
The spread ... Operator is misplaced here.
The solution is:
<ViewPosts posts={posts.find(pv => pv.id === parseInt(match.params.albumId))} />
Update
You would like to use the Switch component from react router:
Switch is unique in that it renders a route exclusively. In contrast, every Route that matches the location renders inclusively.
Pls refer to react router documentation.
I created a pull request as an example. Hope it helps.
I have a simple Redux Form and tried to follow the example given here https://redux-form.com/6.2.0/docs/GettingStarted.md/ here is my code
user-form.js
import React from 'react';
import {Field, reduxForm} from 'redux-form';
class UserForm extends React.Component {
/**
* User Form goes here...
*/
render() {
const { handleSubmit } = this.props;
return (
<form role="form" onSubmit={handleSubmit}>
<div className="box-body">
<div className="form-group">
<label htmlFor="name">Full Name</label>
<Field
name="name"
component="input"
type="text"
className="form-control"
placeholder="Enter full name..."/>
</div>
<div className="form-group">
<label htmlFor="email">Email address</label>
<Field
name="email"
type="email"
component="input"
className="form-control"
placeholder="Enter email"/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Field
name="password"
type="password"
component="input"
className="form-control"
placeholder="Password"/>
</div>
</div>
<div className="box-footer">
{/* <button type="submit" className="btn btn-primary">Save</button> */}
<button type="submit" className="btn btn-primary" value="Save">Save</button>
</div>
</form>
);
}
}
UserForm = reduxForm({
form: 'user'
})(UserForm);
export default UserForm;
Above Form is rendered by a UserPage Container
user-page.js
import React from 'react';
import Page from '../../page';
import UserForm from '../components/user-form';
import UserList from '../components/user-list';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import UserAction from '../actions';
import {showLoading, hideLoading} from 'react-redux-loading-bar';
/**
* We might want to create an Abstract Form component to
* include all common form features.
*/
class UserPage extends Page {
handleUserSubmit(values) {
console.log(values);
}
/**
* Content is loaded into page
*/
content() {
return (
<div className="row">
{/* left column */}
<div className="col-md-6">
{/* general form elements */}
<div className="box box-primary">
<div className="box-header with-border">
<h3 className="box-title">New User</h3>
</div>
{/* /.box-header */}
<UserForm onSubmit={this.handleUserSubmit}/>
</div>
{/* /.box */}
</div>
{/*/.col (left) */}
{/* right column */}
<div className="col-md-6">
{/* UserList made of <User /> */}
{this.userList()}
{/* /.box */}
</div>
{/*/.col (right) */}
</div>
);
}
}
const mapStateToProps = (state) => ({ //this gets state from reducer and maps to props
users: state.userList.users,
fetched: state.userList.fetched,
error: state.userList.error
});
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators({
dispatchShowLoading: showLoading,
dispatchHideLoading: hideLoading,
dispatchUserList: UserAction.userList
}, dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(UserPage);
My Form successfully renders and I can see all the actions being dispatched inside the Redux Dev tools window, but when I try to enter text into the fields it won't do any thing, however the actions are dispatched like I said.
Sorry if this sounds a very basic question. I am relatively new to React and Redux and for that matter to Javascript.
In order to make redux form work, its reducer needs to be included and I forgot to include one, this fixed my issue.
import { reducer as formReducer } from 'redux-form';
const allReducers = combineReducers({
form: formReducer,
});
export default allReducers;