how to fix issues with react datepicker - reactjs

Below are 2 files that is expected to display details in a form, name, service, date, cost. The problem is that it doesn't display information entered in other input fields when I choose a future date. Whereas when I use the current date, it displays the information entered in other input fields as expected. Why is this the case please and how do i fix it?
import { useState, useEffect } from 'react';
import axios from 'axios';
const ConfirmBooking = () => {
//track state
const [data,setData] = useState([])
const Style = {
color: 'rgb(97, 113, 154)',
padding: '5px'
}
//GET data
useEffect(() => {
axios
.get('http://localhost:5000/api/bookings')
.then(res => {
console.log(res)
setData(res.data)
})
.catch(err => {
console.log(err)
})
}, [])
//DELETE data
const deleteHandler =(id) =>{
axios
.delete('http://localhost:5000/api/bookings/'+id)
.then(res => {
console.log(res.data);
}
)
.catch(error =>{
console.log(error)
})
}
if(!data?.length) return <div>loading...</div>
return (
<div className='bookings'>
<h4 style={Style}>Name:{" "}{data.at(-1).name}</h4>
<h4 style={Style} >Service:{" "}{data.at(-1).service}</h4>
<h4 style={Style} >Date:{" "}{data.at(-1).date}</h4>
<h4 style={Style} >Cost:{" "}{data.at(-1).cost}</h4><br></br>
<button className='Btn'>Edit</button>
<button className='Btn' onClick={ () => deleteHandler(data.at(-1))} >Delete</button>
</div>
)
}
export default ConfirmBooking;
import React, { useState } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom'
import DatePicker from 'react-datepicker';
import "react-datepicker/dist/react-datepicker.css";
const Form = () => {
const navigate = useNavigate();
const [myState, setMyState] = useState({
name: "",
service: "finance",
date: new Date(),
cost: "3$"
});
//event to handle all inputs except datepicker
const handleChange = (e)=> {
// const { name, value} = e.target;
const name = e.target.name;
const value = e.target.value
//to update the input myState
setMyState
({...myState, [name]: value });
}
const handleDateChange = (date) => {
setMyState({
date:date
})
}
const handleSubmit = (e) => {
e.preventDefault();
if (myState !== "") {
alert('booking success')
}
//Add data to database
axios.post('http://localhost:5000/api/bookings', myState)
.then(res => {
setMyState
(res.data);
console.log(res);
//redirect to another page
navigate('/ConfirmBooking')
})
.catch((error) => {
console.log(error)
})
}
return (
<form className='form' onSubmit={handleSubmit} >
<h2 className="headerForm">Create appointment</h2>
<div className="mb-3">
<label className="form-label">Name</label>
<input name='name' type="text" className="form-control" id="exampleFormControlInput1" value={myState.name} onChange={handleChange} />
<label className="form-label">Service</label>
<input name='service' type="text" className="form-control " id="exampleFormControlInput1" value={myState.service} onChange={handleChange} />
<label className="form-label"> Date</label>
<div>
<DatePicker
selected={myState.date}
onChange={handleDateChange}
startDate = {new Date()}
minDate={new Date()}
filterDate={date => date.getDay() !== 6 && date.getDay() !== 0}
/>
</div>
<label className="form-label">Cost</label>
<input name='cost' type="text" className="form-control" id="exampleFormControlInput1" value={myState.cost} onChange={handleChange} />
</div>
<button >Submit</button>
</form>
)
}
export default Form;

This method is changing the state without other properties:
...
const handleDateChange = (date) => {
setMyState({
date:date
})
}
...
If you want to change this property from state, you need to destructure previous value and change date.
const handleDateChange = (date) => {
setMyState({
...myState,
date:date
})
}

Related

Getting "doc is not a function" when writing to Firestore after creating a user

I need to save the username in my Firestore Database while creating the user. I'm using Firebase (v9) with React. The code is Below.
A user is created but in the Firestore Database user is not added.
What is the way to use setDoc inside createUserWithEmailAndPassword
Can someone help me with the code?
import Box from "#mui/material/Box";
import OutlinedInput from "#mui/material/OutlinedInput";
import Button from "#mui/material/Button";
import Alert from "#mui/material/Alert";
import { Link, Outlet } from "react-router-dom";
import {
collection,
query,
onSnapshot,
setDoc,
serverTimestamp,
doc,
} from "firebase/firestore";
import { createUserWithEmailAndPassword } from "firebase/auth";
import { db, auth } from "../../../firebase";
import IGLogo from "../../../images/instagram-logo.png";
import "./SignUpForm.scss";
function SignUpForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [username, setUsername] = useState("");
const [successalert, setSuccessAlert] = useState(undefined);
const [failalert, setFailAlert] = useState(undefined);
//const [user, setUser] = useState(undefined);
useEffect(() => {
const timeId = setTimeout(() => {
// After 3 seconds set the show value to false
setSuccessAlert(undefined);
}, 3000);
return () => {
clearTimeout(timeId);
};
});
useEffect(() => {
const timeId = setTimeout(() => {
// After 3 seconds set the show value to false
setFailAlert(undefined);
}, 3000);
return () => {
clearTimeout(timeId);
};
});
useEffect(() => {
const timeId = setTimeout(() => {
// After 3 seconds set the show value to false
setFailAlert(undefined);
}, 3000);
return () => {
clearTimeout(timeId);
};
});
const instagramSignUp = (event) => {
event.preventDefault();
const q = query(collection(db, "users"));
onSnapshot(q, (querySnapshot) => {
querySnapshot.docs.forEach((doc) => {
if (doc.id === username) {
setFailAlert({ type: "userexist" });
} else {
createUserWithEmailAndPassword(auth, email, password)
.then((userCreated) => {
setDoc(doc(db, "users", username), {
timestamp: serverTimestamp(),
})
.then(() => {
setSuccessAlert({ type: "success" });
console.log("user created in collection");
})
.catch((error) => {
console.log(error.message);
});
})
.catch((error) => {
console.log(
"createUserWithEmailAndPassword = " +
error.message
);
});
}
});
});
};
return (
<>
<div className="component__signupalerts">
{successalert?.type === "success" && (
<Alert variant="filled" severity="success">
Account Created Successfully. Please check your Email
for Verification.
</Alert>
)}
{failalert?.type === "userexist" && (
<Alert variant="filled" severity="error">
Username already taken
</Alert>
)}
</div>
<div className="component__signupform">
<img src={IGLogo} alt="" />
<Box component="form" className="component__signupform--box">
<OutlinedInput
className="component__loginform--input"
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<OutlinedInput
className="component__signupform--input"
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<OutlinedInput
className="component__signupform--input"
type="password"
placeholder="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<Button
className="component__signupform--button"
type="submit"
variant="contained"
onClick={instagramSignUp}
>
Sign Up
</Button>
<Link to="/" className="component__signupform--button">
Sign In
</Link>
</Box>
</div>
<Outlet />
</>
);
}
export default SignUpForm;
Console.log "doc is not a function"
The problem is that you have two definitions of doc in your code:
First you import { ...., doc, ... } from "firebase/firestore";.
Then you also define it in querySnapshot.docs.forEach((doc) => {.
The doc in that second line hides the one that you imported.
The easiest fix is to name the variable of forEach something else:
onSnapshot(q, (querySnapshot) => {
querySnapshot.docs.forEach((docSnapshot) => {
if (docSnapshot.id === username) {
...

Testing with jest a register form ( react App)

It's my first time doing testing in general and I am trying to test a register page (check if the forms input are correctly working and if will create the user at the end). This is my register page:
import React, { useState, useRef } from "react";
import Form from "react-validation/build/form";
import Input from "react-validation/build/input";
import CheckButton from "react-validation/build/button";
import { isEmail } from "validator";
import AuthService from "../services/auth.service";
const required = (value) => {
if (!value) {
return (
<div className="alert alert-danger" role="alert">
This field is required!
</div>
);
}
};
const validEmail = (value) => {
if (!isEmail(value)) {
return (
<div className="alert alert-danger" role="alert">
This is not a valid email.
</div>
);
}
};
const vusername = (value) => {
if (value.length < 3 || value.length > 20) {
return (
<div className="alert alert-danger" role="alert">
The username must be between 3 and 20 characters.
</div>
);
}
};
const vpassword = (value) => {
if (value.length < 6 || value.length > 40) {
return (
<div className="alert alert-danger" role="alert">
The password must be between 6 and 40 characters.
</div>
);
}
};
const Register = (props) => {
const form = useRef();
const checkBtn = useRef();
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [successful, setSuccessful] = useState(false);
const [message, setMessage] = useState("");
const onChangeUsername = (e) => {
const username = e.target.value;
setUsername(username);
};
const onChangeEmail = (e) => {
const email = e.target.value;
setEmail(email);
};
const onChangePassword = (e) => {
const password = e.target.value;
setPassword(password);
};
const handleRegister = (e) => {
e.preventDefault();
setMessage("");
setSuccessful(false);
form.current.validateAll();
if (checkBtn.current.context._errors.length === 0) {
AuthService.register(username, email, password).then(
(response) => {
setMessage(response.data.message);
setSuccessful(true);
},
(error) => {
const resMessage =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
setMessage(resMessage);
setSuccessful(false);
}
);
}
};
return (
<div className="col-md-12">
<div className="card card-container">
<img
src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
alt="profile-img"
className="profile-img-card"
/>
<Form onSubmit={handleRegister} ref={form}>
{!successful && (
<div>
<div className="form-group">
<label htmlFor="username">Username</label>
<Input
type="text"
className="form-control"
name="username"
value={username}
onChange={onChangeUsername}
validations={[required, vusername]}
/>
</div>
<div className="form-group">
<label htmlFor="email">Email</label>
<Input
type="text"
className="form-control"
name="email"
value={email}
onChange={onChangeEmail}
validations={[required, validEmail]}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Input
type="password"
className="form-control"
name="password"
value={password}
onChange={onChangePassword}
validations={[required, vpassword]}
/>
</div>
<div className="form-group">
<button className="btn btn-primary btn-block">Sign Up</button>
</div>
</div>
)}
{message && (
<div className="form-group">
<div
className={ successful ? "alert alert-success" : "alert alert-danger" }
role="alert"
>
{message}
</div>
</div>
)}
<CheckButton style={{ display: "none" }} ref={checkBtn} />
</Form>
</div>
</div>
);
};
export default Register;
My auhService is this :
import axios from "axios";
const API_URL = "http://localhost:8080/api/auth/";
const register = (username, email, password) => {
return axios.post(API_URL + "signup", {
username,
email,
password,
});
};
const login = (username, password) => {
return axios
.post(API_URL + "signin", {
username,
password,
})
.then((response) => {
if (response.data.accessToken) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
});
};
const logout = () => {
localStorage.removeItem("user");
};
const getCurrentUser = () => {
return JSON.parse(localStorage.getItem("user"));
};
export default {
register,
login,
logout,
getCurrentUser,
};
And this is my App.test.js :
import { configure, shallow, mount} from "enzyme";
import App from "./App"
import React, {useState} from "react";
import Adapter from "enzyme-adapter-react-16";
import Enzyme from "enzyme";
import Register from "./components/Register";
configure({ adapter: new Adapter() });
it("renders without crashing", () => {
shallow(<App />);
});
describe("<Register />", () => {
let wrapper;
const setState = jest.fn();
const useStateSpy = jest.spyOn(React, "useState")
useStateSpy.mockImplementation((init) => [init, setState]);
beforeEach(() => {
wrapper = Enzyme.mount(Enzyme.shallow(<Register />).get(0))
});
afterEach(() => {
jest.clearAllMocks();
});
describe("username input", () => {
it("Should capture title correctly onChange", () => {
const title = wrapper.find("input").at(0);
title.instance().value = "New user";
title.simulate("change");
expect(setState).toHaveBeenCalledWith("New user");
});
});
describe("email input", () => {
it("Should capture content correctly onChange", () => {
const content = wrapper.find("new Email").at(1);
content.instance().value = "Testing";
content.simulate("change");
expect(setState).toHaveBeenCalledWith("new Email");
});
})
});
By far I was trying the first two inputs(username and email), but I'm getting an error: "TypeError: Cannot read property 'child' of undefined". What shall I do here? I've been trying everyhting. Thank you guys!

next.js and useSWR makes an error "Too many re-renders" and I'm not sure why?

I'm new to React and even more new to Next.js
I've got an input where the user search a city by name in a list of all the cities available.
I've read that useSWR could be interesting to use (before that I made the request with axios inside a useEffect).
Once I got the array of cities I do a map to return all the matches with the request (and then I use it to do an autocomplete).
But I get an error like this :
"Error: Too many re-renders. React limits the number of renders to prevent an infinite loop."
If I just fetch the data, it works but if I do the map on the array I get an infinite loop and I don't know why.
my code :
import React, { useState, useEffect } from "react";
import styles from "./searchBar.module.css";
import { useRouter } from "next/router";
import axios from "axios";
import useSWR from "swr";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch, faAngleDown } from "#fortawesome/free-solid-svg-icons";
import installationType from "../public/installation_type_ids.json";
const SearchBar = ({
setSearchSport,
setSearchCity,
searchCity,
searchSport,
setSearchType,
searchType,
setPage,
}) => {
const router = useRouter();
// States for search bar request
const [city, setCity] = useState(searchCity);
const [cityData, setCityData] = useState([]);
const [sport, setSport] = useState(searchSport);
const [title, setTitle] = useState("");
const [type, setType] = useState(searchType);
const [autoComplete, setAutoComplete] = useState([]);
const [displayAutoComplete, setDisplayAutoComplete] = useState(false);
// handle submit button
const handleSubmit = (e) => {
e.preventDefault();
setSearchCity(city);
setSearchSport(sport);
type ? setSearchType(type) : setSearchType("ALL");
setPage(0);
if (router.pathname !== "/points-de-rencontre-sportive")
router.push("/points-de-rencontre-sportive");
};
const url = "https://bouge-api.herokuapp.com/v1.0/city/ids";
const fetcher = (...args) => fetch(...args).then((res) => res.json());
const { data: result, error } = useSWR(url, fetcher);
if (error) return <h1>Oups ! Une erreur est survenue...</h1>;
if (!result) return <h1>Chargement en cours...</h1>;
const handleTest = (e) => {
setCity(e.target.value);
e.target.value === 0
? setDisplayAutoComplete(false)
: setDisplayAutoComplete(true);
if (result && result.data) {
const dataMapped = result.data.map((city) => {
return { city: city.name, type: "city" };
});
let tab = [];
dataMapped.map((item, i) => {
item.name
if (item.name.toLowerCase().includes(city)) {
tab.push(item);
}
return setAutoComplete(tab);
});
}
};
// autocomplete rendering
const renderAutoComplete = autoComplete.map((elem, index) => {
console.log(elem);
if (index <= 9) {
return (
<div
className={styles.autocompleteDiv}
key={index}
onClick={() => {
if (elem.type === "city") {
setCity(elem.city);
}
if (elem.type === "sport") {
setSport(elem.sport);
}
setDisplayAutoComplete(false);
}}
>
<p>{elem.type === "city" ? elem.city : elem.sport}</p>
</div>
);
} else {
return null;
}
});
return (
<div className={styles.searchBar}>
<form className={styles.form} onSubmit={handleSubmit}>
<div>
<label htmlFor="city">Ville</label>
<input
type="text"
id="city"
placeholder="Où veux-tu jouer?"
value={city}
onChange={handleTest}
autoComplete="off" // disable chrome auto complete
/>
</div>
<div>
<label htmlFor="sport">Sport</label>
<input
type="text"
id="sport"
placeholder="Spécifie le sport"
value={sport}
onChange={(e) => {
setSport(e.target.value);
}}
autoComplete="off" // disable chrome auto complete
/>
</div>
<div>
<label htmlFor="title">Nom</label>
<input
type="text"
id="title"
placeholder="Recherche par nom"
value={title}
onChange={(e) => {
setTitle(e.target.value);
let tab = [];
installationType.map((item, i) => {
if (item.installation_type.includes(title)) {
tab.push(item);
}
return setAutoComplete(tab);
});
console.log(tab);
}}
autoComplete="off" // disable chrome auto complete
/>
</div>
<div>
<label htmlFor="type">Type</label>
<select
type="text"
id="type"
placeholder="Type de structure"
value={type}
>
<option value="ALL" defaultValue>
Type de structure
</option>
<option value="AS">Association</option>
<option value="PRIV">Privé</option>
<option value="PUB">Public</option>
<option value="EVENT">Activité</option>
</select>
<i>
<FontAwesomeIcon
icon={faAngleDown}
className={styles.selectIcon}
></FontAwesomeIcon>
</i>
</div>
<button>
<i>
<FontAwesomeIcon
icon={faSearch}
className="fa-lg"
></FontAwesomeIcon>
</i>
Rechercher
</button>
</form>
{displayAutoComplete ? (
<div className={styles.searchResults}>{renderAutoComplete}</div>
) : null}
</div>
);
};
export default SearchBar;
After you fetched data, you call setCityData method to update city data, this cause component re-render and run code in your SearchBar component again, so it call setCityData again and then continue re-render => infinite re-render.
I think you should put it into a useEffect:
useEffect(() => {
if (result && result.data) {
const dataMapped = result.data.map((city) => {
return { city: city.name, type: "city" };
});
setCityData(dataMapped)
}
}, [result])
so it will update city data only when result has data

Default value in input field lost after click

So, i'm making this social network app and it has user profile. if user wants to update profile, eg. name, by opening modal EditUser, the old value of users name should be there, in input filed, and user needs to have opportunity to change/update that.
I used 'defaultValue', and there is it, in input field, but if i don't change anything in that field, just click 'update', it will be lost. updated value is empty string then, and not the value that is showing in that field. how can i fix this?
Also interested how to set as default user image. so, user has profile image, and on update if user changes only name, not the picture or something else, everything else should be the same, but photo is also like input text field lost.
here is what i tried:
MyProfile.tsx
import React, { useState, useEffect, useContext } from 'react'
import './myprofile.css'
import Axios from 'axios'
import SinglePost from '../single_post/SinglePost'
import { AppContext } from '../context/AppContext'
import UpdateProfile from '../modals/UpdateProfile'
function MyProfile() {
const [userInfo, setUserInfo] = useState({
firstName: '',
lastName: '',
userBio: 'Write something about yourself.',
userPhoto: ''
})
const [isEditOpen, setIsEditOpen] = useState(false)
const { userID, setUserID } = useContext(AppContext)
// open modal on click 'edit'
const editUser = () => {
setIsEditOpen(true)
}
// get user data
const storedToken = localStorage.getItem('token')
useEffect(() => {
const config = {
headers: { "x-auth-token": `${storedToken}` }
}
Axios
.get('/api/auth/user', config)
.then(res => {
console.log('response', res)
const user = res.data.user
setUserID(user._id)
setUserInfo({
firstName: user.first_name,
lastName: user.last_name,
userBio: user.user_bio,
userPhoto: user.profile_image
})
})
.catch(err => console.log(err))
}, [])
return (
<div className="profile-container">
<button className="btn-edit" onClick={editUser}>
<i className="fa fa-edit"></i>
</button>
<div className="user-info">
<div className="img-circular">
<img className="user-profile-img2" src={userInfo.userPhoto}></img>
</div>
<p className="user-name">{userInfo.firstName} {userInfo.lastName}</p>
<p className="about-user">{userInfo.userBio}</p>
</div>
<div className="user-posts">
<p className="my-posts-title">My Posts</p>
</div>
{isEditOpen && <UpdateProfile
userID={userID}
setIsEditOpen={setIsEditOpen}
isEditOpen={isEditOpen}
setUserInfo={setUserInfo}
userInfo={userInfo}
/>}
</div>
)
}
export default MyProfile
UpdateProfile.tsx
import React, { useState, useRef, useEffect } from 'react'
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, FormGroup, Label, Input } from 'reactstrap'
import Axios from 'axios'
import '../user_profile/myprofile.css'
function UpdateProfile(props: any) {
const [firstNameUpdated, setFirstNameUpdated] = useState('')
const [lastNameUpdated, setLastNameUpdated] = useState('')
const [userBioUpdated, setUserBioUpdated] = useState('')
const inputNameRef = useRef<HTMLInputElement | any>(null)
useEffect(() => {
console.log(inputNameRef.current, props.userInfo.firstName)
inputNameRef.current && (inputNameRef.current.value = props.userInfo.firstName)
}, [])
// upload image
const [file, setFile] = useState('')
const [uploaded, setUploaded] = useState('')
const handleImageUpload = (e: any) => {
e.preventDefault();
setFile(e.target.files[0])
};
const onClickHandler = (e: any) => {
const formData = new FormData()
formData.append('fileImage', file)
Axios.post("/api/image", formData, {})
.then(res => {
//console.log(`UPLOADED: http://localhost:5000/${res.data.fileImage}`)
setUploaded(`http://localhost:5000/${res.data.fileImage}`)
})
.catch(err => console.log(err))
}
// update user
const updateUser = (e: any) => {
e.preventDefault()
props.setIsEditOpen(false)
const formData = new FormData()
formData.append('fileImage', file)
formData.append('first_name', firstNameUpdated)
formData.append('last_name', lastNameUpdated)
formData.append('user_bio', userBioUpdated)
const config: any = { header: { "Content-Type": "multipart/form-data" } }
Axios
.put(`/api/users/${props.userID}`, formData, config)
.then(res => {
const user = res.data
props.setUserInfo({
firstName: user.first_name,
lastName: user.last_name,
userBio: user.user_bio,
userPhoto: user.profile_image
})
})
.catch(err => console.log(err))
}
return (
<div>
{props.isEditOpen &&
<Modal isOpen={props.isEditOpen} toggle={() => props.setIsEditOpen(!props.isEditOpen)} backdrop="static">
<ModalHeader>Update your profile</ModalHeader>
<ModalBody>
<FormGroup>
<Label>Profile Image</Label>
<Input type="file" name="fileImage" onChange={handleImageUpload}></Input>
</FormGroup>
<Button onClick={onClickHandler} className="btn-upload-img">Upload file</Button>
<div className="inline">
{uploaded ? <img src={uploaded} style={{ width: "100px" }}></img> : <img src={props.userInfo.userPhoto} style={{ width: "100px" }}></img>}
</div>
<FormGroup>
<Label>First Name</Label>
<Input type="text" onChange={(e: any) => setFirstNameUpdated(e.target.value)} defaultValue={props.userInfo.firstName}></Input>
</FormGroup>
<FormGroup>
<Label>Last Name</Label>
<input type="text" onChange={(e: any) => setLastNameUpdated(e.target.value)} defaultValue={props.userInfo.lastName} ></input>
</FormGroup>
<FormGroup>
<Label>About me</Label>
<Input type="text" onChange={(e: any) => setUserBioUpdated(e.target.value)} defaultValue={props.userInfo.userBio}></Input>
</FormGroup>
</ModalBody>
<ModalFooter>
<Button color="success" onClick={updateUser} className="btn-update">Update</Button>
<Button color="danger" onClick={() => props.setIsEditOpen(false)}>Cancel</Button>
</ModalFooter>
</Modal>}
</div>
)
}
export default UpdateProfile
I'm just trying to get the old value in input field (which i did), and user can choose if wants to change that or not. if not, old value should stay in updated profile, but in my case, on click 'update' it is lost in user profile.
In UpdateProfile, you should initialise the states with the value you got in props and later they could change.
const [firstNameUpdated, setFirstNameUpdated] = useState(props.userInfo.firstName)
const [lastNameUpdated, setLastNameUpdated] = useState(props.userInfo.lastName)
const [userBioUpdated, setUserBioUpdated] = useState(props.userInfo.userBio)
Initialising the state could solve all your problem.

ReactJS + Redux Edit form

I have the following form:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { updateExpert, fetchExpert } from "../../store/actions/expertActions";
class ExpertForm extends Component {
state = {
expert: {}
};
componentWillMount() {
console.log("ComponentWillMount");
const id = this.props.match.params.id;
console.log("Will fetch expert with id", id);
this.props.fetchExpert(id);
}
handleChange = e => {
console.log(e);
this.setState({
expert: {
...this.state.expert,
[e.target.id]: e.target.value
}
});
};
componentWillReceiveProps(nextProps) {
const newExpert = nextProps.expert;
console.log("got new expert ", newExpert);
this.setState({
expert: nextProps.expert
});
}
handleSubmit = e => {
e.preventDefault();
const originalExpert = this.props.expert;
console.log("Expert before", originalExpert);
// const updatedExpert = {
// firstName: this.state.expert.firstName,
// lastName: this.state.expert.lastName,
// bio: this.state.expert.bio,
// country: originalExpert.country,
// interestIds: originalExpert.interestIds,
// city: originalExpert.city,
// summary: originalExpert.summary,
// websiteText: originalExpert.websiteText,
// websiteUrl: originalExpert.websiteUrl
// };
const updatedExpert = this.state.expert;
console.log("Expert after", updatedExpert);
//call action
this.props.updateExpert(originalExpert.userId, updatedExpert);
};
render() {
const { expert } = this.props;
return (
<div className="container">
<div className="card">
<form onSubmit={this.handleSubmit} className="white">
<div className="card-content">
<h5 className="grey-text text-darken-3">Update expert</h5>
<div className="row">
<div className="input-field col s6">
<label htmlFor="firstName">First Name</label>
<input
onChange={this.handleChange}
type="text"
id="firstName"
/>
</div>
<div className="input-field col s6">
<label htmlFor="lastName">Last Name</label>
<input
onChange={this.handleChange}
type="text"
id="lastName"
/>
</div>
</div>
<div className="input-field">
<label htmlFor="bio">Bio</label>
<textarea
className="materialize-textarea"
id="bio"
onChange={this.handleChange}
/>
</div>
<div className="input-field">
<label htmlFor="summary">Summary</label>
<textarea
className="materialize-textarea"
id="summary"
onChange={this.handleChange}
/>
</div>
<div className="row">
<div className="input-field col s6">
<label htmlFor="country">Country</label>
<textarea
className="materialize-textarea"
id="country"
onChange={this.handleChange}
/>
</div>
<div className="input-field col s6">
<label htmlFor="city">City</label>
<textarea
className="materialize-textarea"
id="city"
onChange={this.handleChange}
/>
</div>
</div>
<div className="row">
<div className="input-field col s6">
<label htmlFor="websiteText">Website text</label>
<textarea
className="materialize-textarea"
id="websiteText"
onChange={this.handleChange}
/>
</div>
<div className="input-field col s6">
<label htmlFor="websiteUrl">Website URL</label>
<textarea
className="materialize-textarea"
id="websiteUrl"
onChange={this.handleChange}
/>
</div>
</div>
</div>
<div className="card-action">
<div className="input-field">
<button className="btn pink lighten-1 z-depth-0">Update</button>
</div>
</div>
</form>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
expert: state.experts.item
});
const mapDispatchToProps = dispatch => {
return {
updateExpert: (id, expert) => dispatch(updateExpert(id, expert)),
fetchExpert: id => dispatch(fetchExpert(id))
};
};
export default connect(
mapStateToProps, //mapstatetoprops
mapDispatchToProps //mapdispatchtoprops
)(ExpertForm);
Now this form is used mostly to edit an item of the Expert type, not adding it. Which means I should prefill it with the information already stored in the database.
However when I try to set the value directly on an input like so:
<input
value={expert.firstName}
onChange={this.handleChange}
type="text"
id="firstName"
/>
I get the following error:
index.js:1452 Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
This is the ExpertList component from which the user accesses this ExpertForm:
import React, { Component } from "react";
import PropTypes from "prop-types";
import ExpertItem from "./expert-item";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { fetchExperts } from "../../store/actions/expertActions";
class ExpertList extends Component {
componentWillMount() {
console.log("ComponentWillMount");
this.props.fetchExperts();
}
componentWillReceiveProps(nextProps) {
console.log("Rceived new props");
}
render() {
const { experts } = this.props;
const expertsDom = experts.map(expert => (
<Link to={"/expert/edit/" + expert.userId}>
<ExpertItem key={expert.userId} expert={expert} />
</Link>
));
return <div className="expert-list section">{expertsDom}</div>;
}
}
const mapStateToProps = state => ({
experts: state.experts.items
});
export default connect(
mapStateToProps,
{ fetchExperts }
)(ExpertList);
These are my actions :
import {
FETCH_EXPERTS,
UPDATE_EXPERT,
ADD_EXPERT,
FETCH_EXPERT
} from "./types";
import axios from "../../network/axios";
export const createExpert = expert => {
return (dispatch, getState) => {
//make async call to database
dispatch({ type: ADD_EXPERT, expert: expert });
// type: ADD_EXPERT;
};
};
export const fetchExpert = id => {
return (dispatch, getState) => {
console.log("fetching expert with id ", id);
axios
.get("/connections/experts")
.then(response => {
const selectedExpert = response.data.filter(e => {
return e.userId === id;
})[0];
console.log("ExpertsData ", selectedExpert);
// const newState = Object.assign({}, this.state, {
// experts: newExperts
// });
dispatch({
type: FETCH_EXPERT,
payload: selectedExpert
});
})
.catch(error => {
console.log(error);
});
};
};
//Thunk allows us to call dispatch directly so that we can make async requests
//We can consider dispatch a resolver/promise, calling dispatch is just sending
//the data back
export const fetchExperts = () => {
return (dispatch, getState) => {
console.log("fetching");
console.log("getstate ", getState());
const accessToken = getState().auth.authInfo.accessToken;
console.log("authToken ", accessToken);
axios
.get("/connections/experts")
.then(response => {
const newExperts = response.data;
console.log("ExpertsData ", newExperts);
// const newState = Object.assign({}, this.state, {
// experts: newExperts
// });
dispatch({
type: FETCH_EXPERTS,
payload: newExperts
});
})
.catch(error => {
console.log(error);
});
};
};
export const updateExpert = (id, expertData) => {
return dispatch => {
console.log("updating expert", id, expertData);
axios
.put("/experts/" + id, expertData)
.then(response => {
const updatedExpert = response.data;
dispatch({
type: UPDATE_EXPERT,
payload: updatedExpert
});
})
.catch(error => {
console.log(error);
});
};
};
And this is my reducer:
import {
FETCH_EXPERTS,
UPDATE_EXPERT,
FETCH_EXPERT
} from "../../store/actions/types";
const initialState = {
items: [],
item: {}
};
const expertReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_EXPERT:
console.log("reducer fetch by id");
return {
...state,
item: action.payload
};
case FETCH_EXPERTS:
console.log("reducer fetch");
return {
...state,
items: action.payload
};
case UPDATE_EXPERT:
console.log("reducer update");
return {
...state,
item: action.payload
};
default:
return state;
}
};
export default expertReducer;
Instead of using value property, You need to use defaultValue as described here in Default Values section if You want to have a default value for input field.
The problem is that your value is undefined before Redux's state is loaded. You can solve this by giving it an empty string by default, something like this:
<input
value={typeof expert.firstName === 'undefined' ? '' : expert.firstName}
onChange={this.handleChange}
type="text"
id="firstName"
/>

Resources