I am stuck around a project and honestly I don't know how to solve it (I am quite new before you judge)
so this is my code:
class EditProfile extends Component {
state = {
företagsnamn: '',
organisationsnummer: '',
};
handleChange = e => {
this.setState({
[e.target.id]: e.target.value
});
};
handleSubmit = e => {
e.preventDefault();
// console.log(this.state);
this.props.editProfile(this.state);
this.props.history.push("/dash");
};
render() {
const { auth, profile } = this.props;
if (auth.isEmpty) return <Redirect to="/dash" />;
return (
<div >
<form className="white" onSubmit={this.handleSubmit}>
<div className="row">
<div className="col xl6 l6 m6 s12">
<label>Foretagsnamn:</label>
<input
type="text"
disabled
placeholder={profile.foretagsnamn}
id="foretagsnamn"
onChange={this.handleChange}
/>
</div>
<div className="col xl6 l6 m6 s12">
<label>organisationsnummer:</label>
<input
type="number"
placeholder={profile.organisationsnummer}
id="organisationsnummer"
onChange={this.onChange}
/>
</div>
</div>
<div className="input-field">
<button className="btn orange lighten-1" style={{width:'100%'}} >Submit</button>
{ }
</div>
</form>
</div>
}}
const mapStateToProps = state => {
return {
auth: state.firebase.auth,
profile: state.firebase.profile
};
};
const mapDispatchToProps = dispatch => {
return {
editProfile: profil => dispatch(editProfile(profil))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(EditProfile);
this was the action
export const editProfile = (profil) => {
return (dispatch, getState, { getFirestore, getFirebase }) => {
const firebase = getFirebase();
const firestore = getFirestore();
const profile = getState().firebase.auth
console.log(profile)
const authorId = getState().firebase.auth.uid;
// const foretagsnamn = getFirestore().firestore.collection('users').doc(profile.uid).foretagsnamn
// firebase.auth()
firestore.collection('users').doc(profile.uid).set({
// foretagsnamn: foretagsnamn,
// organisationsnummer: profil.organisationsnummer,
adress: profil.adress,
ort: profil.ort,
telefonnummer: profil.telefonnummer,
postnummer: profil.postnummer,
}, { merge: true }
).then(() => {
dispatch({ type: 'UPDATE_SUCCESS' });
}).catch(err => {
dispatch({ type: 'UPDATE_ERROR' }, err);
});
}}
and this the reducer
const editProfileReducer = (state = initState, action) => {
switch (action.type) {
case "EDITPROFILE_ERROR":
return {
...state,
editError: action.error
};
case "EDITPROFILE_SUCCESS":
return {
...state,
user:action.user
};
default:
return state;
}
}
export default editProfileReducer;
however when I press the button submit it shows this error:
FirebaseError: Function CollectionReference.doc() requires its first argument to be of type non-empty string, but it was: undefined
PS: Solved. The action was wrong. I changed ´´´const profile = getState().firebase.auth.```**instead of profile. **
Stays open if someone needs.
Related
My update action is dispatching but returns back to default in the redux dev tool i can see the change in state for the update action but when getting all contacts i see the initial state back. pls i'd really need your help cause i honestly don't know what is causing. it
My action code
import { employeeConstants } from "./index";
import axiosInstance from "../api";
export const addEmployee = (employeeData) => {
return async (dispatch) => {
try {
const { data } = await axiosInstance.post("/employee", employeeData);
dispatch({ type: employeeConstants.ADD_CONTACT, payload: data });
} catch (error) {
console.log(error);
return error;
}
};
};
export const fetchEmployees = async (dispatch) => {
try {
const { data } = await axiosInstance.get("/employees");
dispatch({ type: employeeConstants.GET_CONTACTS, payload: data });
console.log(data);
} catch (err) {
console.log(err);
}
};
export const deleteEmployee = (_id) => {
return async (dispatch) => {
try {
await axiosInstance.delete(`/employee/${_id}`);
dispatch({ type: employeeConstants.DELETE_CONTACT, payload: _id });
} catch (error) {
console.log(error);
}
};
};
export const editEmployee = (employee) => {
return async (dispatch) => {
try {
await axiosInstance.put(`/employee/edit/:id`);
dispatch({ type: employeeConstants.UPDATE_CONTACT, payload: employee });
console.log(employee);
} catch (err) {
console.log(err);
}
};
};
export const getEmployee = (employee) => {
return (dispatch) => {
axiosInstance
.get("/employee/:id")
.then((res) => {
dispatch({
type: employeeConstants.GET_CONTACT,
payload: employee,
});
})
.catch((error) => {
console.log(error);
});
};
};
This is what My reducer code looks like
import { employeeConstants } from "../actions";
const initialState = {
employees: [],
employee: {},
};
const usersReducer = (state = initialState, action) => {
switch (action.type) {
case employeeConstants.GET_CONTACTS:
return {
...state,
employees: action.payload,
};
case employeeConstants.ADD_CONTACT:
return {
...state,
employee: action.payload,
};
case employeeConstants.DELETE_CONTACT:
return {
...state,
employees: state.employees.filter((employee) => {
return employee._id !== action.payload;
}),
};
case employeeConstants.UPDATE_CONTACT:
return {
...state,
employees: state.employees.map((employee) =>
employee._id === action.payload._id ? action.payload : employee
),
employee: action.payload,
};
case employeeConstants.GET_CONTACT:
return {
...state,
employee: action.payload,
};
default:
return state;
}
};
export default usersReducer;
The Home component
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { toast } from "react-toastify";
import { Link } from "react-router-dom";
import { deleteEmployee, fetchEmployees } from "../actions/employee.actions";
const Home = () => {
const employees = useSelector(state => state.users.employees);
const dispatch = useDispatch();
const showAllEnployees = fetchEmployees;
useEffect(() => {
dispatch(showAllEnployees);
}, [showAllEnployees, dispatch]);
const deleteHandler = (id) => {
dispatch(deleteEmployee(id));
toast.success("employee deleted successfully!!");
};
return (
<div className="container">
<div className="row d-flex flex-column">
<Link to="/add" className="btn btn-outline-dark my-5 ml-auto ">
Add employee
</Link>
<div className="col-md-10 mx-auto my-4">
<table className="table table-hover">
<thead className="table-header bg-dark text-white">
<tr>
<th scope="col">Id</th>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Phone</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{employees ? (
employees.map((employee, _id) => (
<tr key={employee._id}>
<td>{_id + 1}</td>
<td>{employee.name}</td>
<td>{employee.email}</td>
<td>{employee.phone}</td>
<td>
<Link
to={`/edit/${employee._id}`}
className="btn btn-sm btn-primary mr-1"
>
Edit
</Link>
<button
type="button"
onClick={() => deleteHandler(employee._id)}
className="btn btn-sm btn-danger"
>
Delete
</button>
</td>
</tr>
))
) : (
<tr>
<th>No employees found</th>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default Home;
My Edit Component
import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import { useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import { editEmployee, getEmployee } from "../actions/employee.actions";
const EditContact = () => {
const { id } = useParams();
const dispatch = useDispatch();
const history = useNavigate();
const { employees } = useSelector((state) => state.users);
const currentContact = employees.find((employee) => employee._id === id);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
useEffect((_id) => {
getEmployee(id);
}, [dispatch, id]);
useEffect(() => {
if (currentContact) {
setName(currentContact.name);
setEmail(currentContact.email);
setPhone(currentContact.phone);
}
}, [currentContact]);
const handleSubmit = (e) => {
e.preventDefault();
const checkEmail = employees.find(
(employee) => employee._id !== id && employee.email === email && employee
);
const checkPhone = employees.find(
(employee) => employee._id !== id && employee.phone === phone && employee
);
if (!email || !name || !phone) {
return toast.warning("Please fill in all fields!!");
}
if (checkEmail) {
return toast.error("This email already exists!!");
}
if (checkPhone) {
return toast.error("This phone number already exists!!");
}
const employee = {
_id:id,
name,
email,
phone
}
dispatch(editEmployee(employee));
toast.success("Contact updated successfully!!");
history("/");
};
return (
<div className="container">
{currentContact ? (
<div className="row d-flex flex-column">
<button
className="btn btn-dark ml-auto my-5"
onClick={() => history("/")}
>
Go back
</button>
<h3 style={{ textAlign: "center" }}>EDIT STUDENT {id} </h3>
<div className="col-md-6 mx-auto shadow p-5">
<form key={id} onSubmit={handleSubmit}>
<div className="form-group">
<input
className="form-control"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div className="form-group">
<input
className="form-control"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="form-group">
<input
className="form-control"
placeholder="Phone"
value={phone}
onChange={(e) => setPhone(e.target.value)}
/>
</div>
<div className="form-group d-flex align-items-center justify-content-between my-2">
<button
type="submit"
className="btn btn-primary"
onClick={() => handleSubmit}
>
Update Contact
</button>
<button
type="button"
className="btn btn-danger"
onClick={() => history("/")}
>
cancel
</button>
</div>
</form>
</div>
</div>
) : (
<h1 className="text-center">No Contact {id} Found</h1>
)}
</div>
);
};
export default EditContact;
My backend route code
const express = require("express");
const asynchHandler = require('express-async-handler');
const router = express.Router();
const Employee = require("../models/employee");
router.post("/employee", async (req, res) => {
const employee = new Employee({
name: req.body.name,
email: req.body.email,
phone: req.body.phone,
});
try {
const savedEmployee = await employee.save();
res.json(savedEmployee);
} catch (error) {
res.json({ message: error });
}
});
router.get("/employees", async (req, res) => {
try {
const getEmployees = await Employee.find();
if(getEmployees) {
res.json(getEmployees);
}
} catch (err) {
res.json({ message: err });
}
});
router.delete("/employee/:id", async (req, res) => {
try {
const deleteEmployee = await Employee.findByIdAndRemove({
_id: req.params.id,
});
res.json(deleteEmployee);
} catch (err) {
res.json({ message: err });
}
});
router.get("/employee/:id", async (req, res) => {
try {
const employee = await Employee.findByIdAndUpdate(req.params.id);
res.json(employee);
} catch (err) {
res.json({ message: err });
}
});
router.put("/employee/edit/:id", async (req, res) => {
try {
const updateEmployee = await Employee.findOneAndUpdate(
{ _id: req.params.id },
{
$set: {
name: req.body.name,
email: req.body.email,
phone: req.body.phone,
},
},
{ new: true, runValidators:true }
);
// updateEmployee.save();
res.json(updateEmployee);
} catch (err) {
res.json({ message: err });
}
});
//single user
router.get("/employee/:id", async (req, res) => {
try {
const getEmployee = await Employee.findById({ _id: req.params.id });
if(getEmployee) {
res.json(getEmployee);
}
} catch (err) {
res.json({ message: err });
}
});
module.exports = router;
Actually it was dispatching but you have not added default case in reducer i.e., to get the state.
const initialState = {
employees: [],
employee: {}
};
const usersReducer = (state = initialState, action) => {
switch (action.type) {
case "GET_CONTACTS":{
return {
...state,
employees: action.payload,
};
}
default:
return state
}
And make sure that data variable of get request contains list of employees and you are using same action type name in reducer and dispatch.
try to use same action type in the reducer also
const usersReducer = (state = initialState, action) => {
switch (action.type) {
case employeeConstants.GET_CONTACTS: //like this so there is no chance of error
return {
...state,
employees: action.payload,
};
How Should I solve this problem?
After uploading an image by react state, this state seems to prevents putting props"project" in Action in Redux.
this.setState({ uploadfile: "" })
The error says
FirebaseError: Function addDoc() called with invalid data. Unsupported field value: a custom File object (found in field uploadfile in document projects/c0hQXdRAgIe1lIzq1vTy)
class CreateProject extends Component {
state = {
title:'',
content:'',
uploadfile:'',
setImageUrl:'',
}
handleChange = (e) =>{
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = (e) =>{
e.preventDefault();
this.setState({ uploadfile: "" })
this.props.createProject(this.state)
this.props.history.push('/')
}
onDrop = acceptedFiles => {
if (acceptedFiles.length > 0) {
this.setState({ uploadfile: acceptedFiles[0] })
}
}
handleSubmitImg = (e) =>{
e.preventDefault()
//this.props.sampleteFunction()
};
parseFile = (file) =>{
const updatedFile = new Blob([file], { type: file.type });
updatedFile.name = file.name;
return updatedFile;
}
onSubmit = (event) => {
event.preventDefault();
var updatedFile = this.state.uploadfile;
if (updatedFile === "") {
}
console.log("aaaaaaaaaaaa"+updatedFile)
const uploadTask = storage.ref(`/images/${updatedFile.name}`).put(updatedFile);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
this.next,
this.error,
this.complete
);
};
next = snapshot => {
const percent = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
};
error = error => {
console.log(error);
};
complete = () => {
var updatedFile = this.state.uploadfile
storage
.ref("images")
.child(updatedFile.name)
.getDownloadURL()
.then(fireBaseUrl => {
this.setState({ setImageUrl: fireBaseUrl })
//this.state.setImageUrl(fireBaseUrl);
});
};
render() {
const maxSize = 3 * 1024 * 1024;
const dropzoneStyle = {
width: "100%",
height: "auto",
borderWidth: 2,
borderColor: "rgb(102, 102, 102)",
borderStyle: "dashed",
borderRadius: 5,
}
const {auth} = this.props
console.log("UP"+this.state.uploadfile );
if(!auth.uid) return <Redirect to="/signin" />
return (
<Dropzone
onDrop={this.onDrop}
accept="image/png,image/jpeg,image/gif,image/jpg"
inputContent={(files, extra) => (extra.reject ? 'Image files only' : 'Drag Files')}
styles={dropzoneStyle}
minSize={1}
maxSize={maxSize}
>
{({ getRootProps, getInputProps }) => (
<div className="container">
<form onSubmit={this.handleSubmit} className="white">
<h5 className="grey-text text-darken-3">
Create Project
</h5>
<div className="input-field">
<label htmlFor="title">Title</label>
<input type="text" id="title" onChange={this.handleChange}/>
</div>
<div className="input-field">
<label htmlFor="content">Project Content</label>
<textarea id="content" className="materialize-textarea" onChange={this.handleChange}></textarea>
</div>
<div className="input-field">
<button className="btn pink lighten-1 z-depth-0">Create</button>
</div>
</form>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>File Drag</p>
{this.state.uploadfile ? <p>Selected file: {this.state.uploadfile.name}</p> : null}
{this.state.uploadfile ? (<Thumb key={0} file={this.state.uploadfile } />) :null}
</div>
<form onSubmit={this.onSubmit}>
<button>Upload</button>
</form>
</div>
)}
</Dropzone>
)
}
}
const matchStateToProps = (state) => {
return{
auth: state.firebase.auth
}
}
const mapDispatchToProps = (dispatch) => {
return{
createProject: (project) => dispatch(createProject(project))
}
}
export default connect(matchStateToProps, mapDispatchToProps)(CreateProject)
Action
export const createProject = (project) => {
return (dispatch, getState, { getFirebase, getFirestore }) => {
// make async call to database
const firestore = getFirestore();
const profile = getState().firebase.profile;
const authorId = getState().firebase.auth.uid;
firestore.collection('projects').add({
...project,
authorUserName: profile.userName,
authorId: authorId,
createdAt: new Date()
}).then(() => {
dispatch({ type: 'CREATE_PROJECT', project})
}).catch((err) => {
dispatch({ type: 'CREATE_PROJECT_ERROR', err })
})
}
};
I am developing a web application which I would like to use react redux. but my app is not dispatching. If I want to careate a new project and send request to action to dispatch it does not dispatch.
class CreateProject extends Component {
state = {
title: '',
content: ''
}
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
// console.log(this.state);
this.props.createProject(this.state);
}
render() {
return (
<div className="container">
<form className="white" onSubmit={this.handleSubmit}>
<h5 className="grey-text text-darken-3">Create a New Project</h5>
<div className="input-field">
<input type="text" id='title' onChange={this.handleChange} />
<label htmlFor="title">Project Title</label>
</div>
<div className="input-field">
<textarea id="content" className="materialize-textarea" onChange={this.handleChange}></textarea>
<label htmlFor="content">Project Content</label>
</div>
<div className="input-field">
<button className="btn pink lighten-1">Create</button>
</div>
</form>
</div>
)
}
Projectreducer.js
const projectReducer = (state = initState, action) => {
switch (action.type) {
case 'CREATE_PROJECT':
console.log('create project', action.project);
return state;
case 'CREATE_PROJECT_ERROR':
console.log('çreate project error', action.err);
return state;
default:
return state;
}
};
export default projectReducer;
I have consoled on the function on project action is show the item on the console.
When I try consule of the return statement nothing happens
projectAction.js
export const createProject = (project) => {
return { type: 'CREATE_PROJECT', project }
return (dispatch, getState, {getFirebase, getFirestore}) => {
make async call to database
const firestore = getFirestore();
console.log(firestore);
firestore.collection('projects').add({
...project,
authFirstName: ' nm',
authorLastName: 'kjbbggh',
authorId: 12345,
createdAt: new Date()
}).then(()=>{
dispatch({ type: 'CREATE_PROJECT', project });
}).catch((err) => {
dispatch({type: 'ÇREATE_PROJECT_ERROR', err})
})
}
};
I'm having a problem with redux.
when I use
store.dispatch( addExpense( {description: "Rent"} ) );
in my app.js file it works and the object is added. But when I try to use it in a context of an component in a separated file it doesn't. The console does't throw any error.
When I use other action like "search" in the same component it works fine. So there's no problem with the connection. It seems that for some reasons it can't change the state.
//ACTIONS
export const addExpense = ( { description="", amount=0 } = {}) => ({
type: "ADD_EXPENSE",
expense: {
description,
amount
}
})
//REDUCERS
const expenseReducer = ( state = [], action) => {
switch(action.type) {
case "ADD_EXPENSE":
return [...state, action.expense]
case "EDIT_EXPENSE": //<- this works
return state.map( (expense) => {
if (expense.id === action.id)
return {
...expense, ...action.update }
else return expense
} )
default: return state
}
const filterReducer = ( state = {text:""}, action) => {
switch(action.type){
case "FIND_TEXT": //<- this works
return { ...state, text:action.text }
default: return state;
}
}
//COMPONENT
const AddEx = ( props ) => (
<div>
<form onSubmit={(e) => {
e.preventDefault;
props.dispatch(addExpense ( {description: document.getElementById("addedEx").value} ))
console.log(document.getElementById("addedEx").value);
//it shows the correct value in the console but the state stays unchanged
} } >
<input type="text" id="addedEx"/>
<button type="submit">submit</button>
</form>
//SEARACH -> works
<input
type="text" value={props.filter.text}
onChange={(e) => {
props.dispatch(findText({text:e.target.value}))
}}
/>
</div>
)
You are adding the value to state instead of expenses, do return [...state.expense, action.expense]
//REDUCERS
const expenseReducer = (state = [], action) => {
switch (action.type) {
case 'ADD_EXPENSE':
return {
...state,
expense: [...state.expense, action.expense]
};
case 'EDIT_EXPENSE': //<- this works
return state.map(expense => {
if (expense.id === action.id)
return {
...expense,
...action.update
};
else return expense;
});
default:
return state;
}
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.0.3/react-redux.min.js"></script>
<script src="http://wzrd.in/standalone/uuid%2Fv1#latest"></script>
<div id="root"></div>
<script type="text/babel">
const { Provider, connect } = ReactRedux;
const { applyMiddleware, createStore, combineReducers } = Redux;
const ADD_EXPENSE = 'ADD_EXPENSE';
function addExpense(payload) {
return { type: ADD_EXPENSE, payload };
}
const initialState = {
expense: [],
};
function rootReducer(state = initialState, action) {
if (action.type === ADD_EXPENSE) {
return {
...state,
expense: [...state.expense, action.payload]
};
}
return state;
}
const store = createStore(rootReducer);
const mapStateToProps = state => {
return { expense: state.expense };
};
function mapDispatchToProps(dispatch) {
return {
addExpense: expense => dispatch(addExpense(expense))
};
}
const ConnectedList = ({ expense, addExpense }) => {
return (
<div>
<ul className="list-group list-group-flush">
{expense.map(el => (
<li className="list-group-item" key={el.id}>
{`${el.description} - $${el.amount}`}
</li>
))}
</ul>
</div>
);
};
const List = connect(
mapStateToProps,
)(ConnectedList);
class ExpenseForm extends React.Component {
state = {
description: '',
amount: 0,
}
handleSubmit = (e) => {
e.preventDefault();
if (!this.state.description || this.state.amount === 0) {
return
}
const { description, amount } = this.state;
this.props.addExpense({
description,
amount,
id: uuidv1()
});
this.setState({
description: '',
amount: 0
})
}
handleInput = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input name="description" placeholder="Description" onChange={this.handleInput} value={this.state.description} />
<input type="number" name="amount" placeholder="Amount" onChange={this.handleInput} value={this.state.amount} />
<input type="submit" />
</form>
)
}
}
const Form = connect(null, mapDispatchToProps)(ExpenseForm);
class App extends React.Component {
render() {
return (
<div>
<List />
<Form />
</div>
);
}
}
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
</script>
I found the solution. It didn't work because e.preventDefault lacked the (). so simple change from e.preventDefault to e.preventDefault () fixed it. silly mistake.
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"
/>