I want to know about dispatch function and mapDispatchToProps function.How they work and when to use.Any guideline for them?
I am currently working on a MERN stack project where i need to post a name on mongoose.I successfully post on database but after posting i also want to send it to redux store via dispatching an action with data.But I am getting dispatch is not a function error.Why I an geting this error.I have stated const {dispatch} = props in constructor function
Here is my about.js page code
import React, { Component } from 'react';
import fetch from 'isomorphic-unfetch'
import { connect } from 'react-redux'
import {addName} from '../Actions/actionCreators'
class about extends Component {
static async getInitialProps({ req }){
try{
const protocol = req.headers['x-forwarded-proto'] || 'http'
const baseUrl = req ? `${protocol}://${req.headers.host}` : ''
const url = `${baseUrl}/api/users`
return { apiUrl: url }
}catch{
return { error: 'Could not load configs'}
}
}
constructor(props){
super(props)
const {dispatch} = props
this.state = { name: '' }
this.addname = this.addname.bind(this)
}
addname(e){
e.preventDefault()
const {dispatch} = this.props
const name = e.target.name.value
const newName = {
name : name
}
fetch(this.props.apiUrl, {
method: 'POST',
body: JSON.stringify(newName),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})
.then( r =>
r.json()
)
.then( (data) => dispatch(addName(data.name)) )
.catch(err => console.error('POST error', err));
}
render() {
const markup = this.props.name.name.length > 0 ? this.props.name.name.map((elem, i) => (
<div key={i}>
{this.props.name.name[i]}
</div>
)) : <p></p>
return (
<div>
<h2> About me </h2>
{markup}
<form action="" onSubmit={this.addname}>
<br/>
<input type="text" name="name" placeholder="Your Name" />
<br/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return{
submitform : (e) => {
e.preventDefault()
dispatch(addName(e.target.name.value))
}
}
}
const mapStateToProps = state => {
return{
name : state.name
}
}
export default connect(mapStateToProps, mapDispatchToProps)(about)
You can notice <form action="" onSubmit={this.addname}> here addName is a function which posts the name to databse.
You can also submitform in
const mapDispatchToProps = dispatch => {
return{
submitform : (e) => {
e.preventDefault()
dispatch(addName(e.target.name.value))
}
}
}
this submitform submits directly to redux-state.but i want to submit first to DB and then redux-state
Since you are using mapDispatchToProps you won't have dispatch available as prop to the component. The idea way would be to expose addName from mapDispatchToProps
class about extends Component {
static async getInitialProps({ req }){
try{
const protocol = req.headers['x-forwarded-proto'] || 'http'
const baseUrl = req ? `${protocol}://${req.headers.host}` : ''
const url = `${baseUrl}/api/users`
return { apiUrl: url }
}catch{
return { error: 'Could not load configs'}
}
}
constructor(props){
super(props)
const {dispatch} = props
this.state = { name: '' }
this.addname = this.addname.bind(this)
}
addname(e){
e.preventDefault()
const {addName} = this.props
const name = e.target.name.value
const newName = {
name : name
}
fetch(this.props.apiUrl, {
method: 'POST',
body: JSON.stringify(newName),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})
.then( r =>
r.json()
)
.then( (data) => addName(data.name) )
.catch(err => console.error('POST error', err));
}
render() {
const markup = this.props.name.name.length > 0 ? this.props.name.name.map((elem, i) => (
<div key={i}>
{this.props.name.name[i]}
</div>
)) : <p></p>
return (
<div>
<h2> About me </h2>
{markup}
<form action="" onSubmit={this.addname}>
<br/>
<input type="text" name="name" placeholder="Your Name" />
<br/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return{
submitform : (e) => {
e.preventDefault()
dispatch(addName(e.target.name.value))
},
addName: (name) => {
dispatch(addName(name))
}
}
}
Related
for learning purposes I'm creating a CRUD todo list with React and JSON-server. I got stuck with PATCH method, as it only updates data in JSON-server on the first click. I want to update the data with the component's state value.
Service file with requests:
const serverAddress = 'http://localhost:8000';
const collection = 'todoItems';
const fetchAll = async () => {
const response = await fetch(`${serverAddress}/${collection}`);
const todoItems = response.json();
return todoItems;
};
const complete = async (id) => {
const completed = {
completed : true
}
// how to set the 'completed' value in json-server based on item's state?
const response = await fetch(`${serverAddress}/${collection}/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(completed ),
});
const data = await response.json();
return data;
}
const TodoItemsService = {
fetchAll,
create,
remove,
complete
};
export default TodoItemsService;
Card component which holds all todo items:
const Card = () => {
const [todoItems, setTodoItems] = useState([]);
const fetchAllTodoItems = async () => {
const fetchedTodoItems = await TodoItemsService.fetchAll();
setTodoItems(fetchedTodoItems);
};
useEffect(() => {
(async () => {
fetchAllTodoItems();
})();
}, []);
const handleComplete = async (id) => {
await TodoItemsService.complete(id);
}
return (
<div className='card'>
<CardHeader />
<AddTodoForm onAddTodoItem={handleAddTodoItem} />
<TodoItemsContainer
todoItems={todoItems}
onDelete={handleDelete}
onComplete={handleComplete}
/>
</div>
)
}
export default Card;
TodoItemsContainer component
const TodoItemsContainer = ({ todoItems, onDelete, onComplete }) => {
return (
<div className='todo-items-container'>
{todoItems.length === 0 &&
<div className='empty'>
<img src={NoTodoItems} alt="" />
</div>}
{todoItems.map(({ id, text }) => (
<TodoItem
key={id}
id={id}
text={text}
onDelete={onDelete}
onComplete={onComplete}
/>
))}
</div>
)
}
export default TodoItemsContainer;
TodoItem component
const TodoItem = ({ id, text, onDelete, onComplete }) => {
const [isComplete, setIsComplete] = useState(false);
const handleIsCompleteById = () => {
onComplete(id);
setIsComplete(!isComplete);
};
const handleDeleteTodoItemById = () => {
onDelete(id);
};
return (
<div className={`todo-item ${isComplete ? 'complete' : ''}`}>
<p>{text}</p>
<div>
<TodoItemComplete onComplete={handleIsCompleteById}/>
<TodoItemDelete onDelete={handleDeleteTodoItemById}/>
</div>
</div>
)
}
export default TodoItem;
TodoItemComplete button component
const TodoItemComplete = ({ onComplete }) => {
return (
<button type='button' onClick={onComplete}>
<div className='icon'>
{<SVGComplete />}
</div>
</button>
)
}
export default TodoItemComplete;
From React perspective it works fine, it marks the item as complete based on state, but I also want to reflect todo item's status as complete in my json-server. Does anyone have any tips or can see the mistake?
Simply had to pass the state as the second param in complete service and other functions that handle complete action.
I'm successfully updating my plant object to my cluster, but it takes a page reload in order for me to get that updated data. I'm assuming that I may need a useEffect to call my fetch again but I'm unsure how I would do that after my PATCH request.
Does anyone have any suggestions to how I would fetch my updated data after I've updated.
Context
import { createContext, useReducer } from 'react'
export const PlantsContext = createContext()
export const plantsReducer = (state, action) => {
switch(action.type) {
case 'SET_PLANTS':
return {
plants: action.payload
}
case 'CREATE_PLANT':
return {
plants: [action.payload, ...state.plants]
}
case 'DELETE_PLANT':
return {
plants: state.plants.filter((p) => p._id !== action.payload._id)
}
case 'UPDATE_PLANT':
return {
plants: state.plants.map((p) => p._id === action.payload._id ? action.payload : p)
}
default:
return state
}
}
export const PlantsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(plantsReducer, {
plants: null
})
return (
<PlantsContext.Provider value={{...state, dispatch}}>
{ children }
</PlantsContext.Provider>
)
}
My 'update' function inside PlantDetails component, setting a new water date
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
}
My Home component where that update should render through after PATCH request
const Home = () => {
const { plants, dispatch } = usePlantsContext();
useEffect(() => {
const fetchPlants = async () => {
console.log("called");
// ONLY FOR DEVELOPMENT!
const response = await fetch("/api/plants");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_PLANTS", payload: json });
}
};
fetchPlants();
}, [dispatch]);
return (
<div className="home">
<div className="plants">
{plants &&
plants.map((plant) => <PlantDetails key={plant._id} plant={plant} />)}
</div>
<PlantForm />
</div>
);
};
export default Home;
usePlantContext
import { PlantsContext } from "../context/PlantContext";
import { useContext } from "react";
export const usePlantsContext = () => {
const context = useContext(PlantsContext)
if(!context) {
throw Error('usePlantsContext must be used inside an PlantsContext Provider')
}
return context
}
Complete PlantsDetails Component
import { usePlantsContext } from "../hooks/usePlantsContext";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { useState } from "react";
import CalendarComponent from "./CalendarComponent";
const PlantDetails = ({ plant }) => {
const [watered, setWatered] = useState(false)
const [newWaterDate, setNewWaterDate] = useState("")
const { dispatch } = usePlantsContext();
const handleClick = async () => {
const response = await fetch("/api/plants/" + plant._id, {
method: "DELETE",
});
const json = await response.json();
if (response.ok) {
dispatch({ type: "DELETE_PLANT", payload: json });
}
};
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
// setWatered(false)
}
return (
<div className="plant-details">
<h4>{plant.plantName}</h4>
<p>{plant.quickInfo}</p>
<p>
{formatDistanceToNow(new Date(plant.createdAt), { addSuffix: true })}
</p>
<span onClick={handleClick}>delete</span>
<div>
<p>next water date: {plant.nextWaterDate}</p>
{/* <input type="checkbox" id="toWater" onChange={() => setWatered(true)}/> */}
<label value={watered} for="toWater">watered</label>
<CalendarComponent setNextWaterDate={setNewWaterDate}/>
</div>
<button onClick={updatePlant}>update</button>
</div>
);
};
export default PlantDetails;
Plant Controller
const updatePlant = async (req, res) => {
const { id } = req.params
if(!mongoose.Types.ObjectId.isValid(id)) {
return res.status(404).json({ error: "No plant" })
}
const plant = await Plant.findByIdAndUpdate({ _id: id }, {
...req.body
})
if (!plant) {
return res.status(400).json({ error: "No plant" })
}
res.status(200).json(plant)
}
Thank you for looking at my question, would appreciate any suggestion.
Hey folks really hope someone can help me here. I'm successfully updating my object in my mongo cluster, it updates but it does not render that update straight away to the browser. It will only update after a reload or when I run my update function again, it doesn't fetch that update straight away and I can't understand why. Does anyone have any suggestions?
I'm using context and reducer.
PlantDetails
import { usePlantsContext } from "../hooks/usePlantsContext";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { useState } from "react";
import CalendarComponent from "./CalendarComponent";
const PlantDetails = ({ plant }) => {
const [watered, setWatered] = useState(false)
const [newWaterDate, setNewWaterDate] = useState("")
const { dispatch } = usePlantsContext();
const handleClick = async () => {
const response = await fetch("/api/plants/" + plant._id, {
method: "DELETE",
});
const json = await response.json();
if (response.ok) {
dispatch({ type: "DELETE_PLANT", payload: json });
}
};
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
console.log('updated')
setWatered(false)
}
return (
<div className="plant-details">
<h4>{plant.plantName}</h4>
<p>{plant.quickInfo}</p>
<p>
{formatDistanceToNow(new Date(plant.createdAt), { addSuffix: true })}
</p>
<span onClick={handleClick}>delete</span>
<div>
<p>next water date: {plant.nextWaterDate}</p>
<input onChange={(e) => setNewWaterDate(e.target.value)}/>
<button onClick={updatePlant}>update</button>
<input value={watered} type="checkbox" id="toWater" onChange={() => setWatered(true)}/>
<label for="toWater">watered</label>
{watered && <CalendarComponent updatePlant={updatePlant} setNextWaterDate={setNewWaterDate}/>}
</div>
</div>
);
};
export default PlantDetails;
Context which wraps my
import { createContext, useReducer } from 'react'
export const PlantsContext = createContext()
export const plantsReducer = (state, action) => {
switch(action.type) {
case 'SET_PLANTS':
return {
plants: action.payload
}
case 'CREATE_PLANT':
return {
plants: [action.payload, ...state.plants]
}
case 'DELETE_PLANT':
return {
plants: state.plants.filter((p) => p._id !== action.payload._id)
}
case 'UPDATE_PLANT':
return {
plants: state.plants.map((p) => p._id === action.payload._id ? action.payload : p )
}
default:
return state
}
}
export const PlantsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(plantsReducer, {
plants: null
})
return (
<PlantsContext.Provider value={{...state, dispatch}}>
{ children }
</PlantsContext.Provider>
)
}
My plantController (update)
const updatePlant = async (req, res) => {
const { id } = req.params
if(!mongoose.Types.ObjectId.isValid(id)) {
return res.status(404).json({ error: "No plant" })
}
const plant = await Plant.findByIdAndUpdate({ _id: id }, {
...req.body
})
if (!plant) {
return res.status(400).json({ error: "No plant" })
}
res.status(200)
.json(plant)
}
Home component
import { useEffect } from "react";
import PlantDetails from "../components/PlantDetails";
import PlantForm from "../components/PlantForm";
import CalendarComponent from "../components/CalendarComponent";
import { usePlantsContext } from "../hooks/usePlantsContext";
const Home = () => {
const { plants, dispatch } = usePlantsContext();
useEffect(() => {
const fetchPlants = async () => {
console.log("called");
// ONLY FOR DEVELOPMENT!
const response = await fetch("/api/plants");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_PLANTS", payload: json });
}
};
fetchPlants();
}, [dispatch]);
return (
<div className="home">
<div className="plants">
{plants &&
plants.map((plant) => <PlantDetails key={plant._id} plant={plant} />)}
</div>
<PlantForm />
</div>
);
};
export default Home;
Any help would be greatly appreciated.
My patch requests were going through smoothly but my state would not update until I reloaded my page. It was not returning the document after the update was applied.
https://mongoosejs.com/docs/tutorials/findoneandupdate.html#:~:text=%3B%20//%2059-,You,-should%20set%20the
Well my problem its that always that i send the login info to the backend, react return me Cannot Post /login. The problem its that i have a res.redirect to the main page but it dosent work and i dont know if its an error of Axios or the controller, because the information arrives correctly. What do you think? What issue im having?
React Login
import React, {Component} from "react";
import "./user.css"
import Api from "../Instrumentos/apiInstrumentos"
import { withRouter } from "react-router";
class User extends Component {
constructor(props){
super(props);
this.state = {
user: []
}
};
async componentDidMount(){
try{
console.log()
const id = this.props.match.params.id;
let user = await fetch(`http://localhost:5000/user/${id}`).then(response =>
response.json())
this.setState({
user: user
});
console.log(this.state.user)
}
catch(error){
console.log(error);
}
}
render(){
let user = this.state.user
return (
<section id="user-detail">
<section id="user_saved">
<article>
<figure id="user_figure">
<img src={`http://localhost:5000${user.photo}`} />
</figure>
<h3 id="bienvenido_user">Bienvenido {user.nombre}</h3>
<form className="margin-sections">
<fieldset>
<input type="text" name="" placeholder={user.nombre}
className="input-profile"></input>
</fieldset>
<fieldset>
<input type="text" name="apellido" placeholder={user.apellido}
className="input-profile"></input>
</fieldset>
<fieldset>
<input type="password" name="password" placeholder={user.password}
className="input-profile"></input>
</fieldset>
<fieldset>
<button className="button-login button-detail"
type="submit">Enviar</button>
</fieldset>
</form>
</article>
<article>
<h3 id="Instrumentos_guardados">Instrumentos Guardados</h3>
<Api />
</article>
</section>
</section>
);
}
}
useForm Hook
import {useState} from 'react';
import Axios from 'axios';
export const useForm = (initialForm, validateForm) => {
const [form, setForm] = useState(initialForm);
const [errors, setErrors] = useState({});
const [loading,] = useState(false);
const [response,] = useState(null);
const handleChange = (e) => {
const { name,value } = e.target;
setForm({
...form,
[name]: value
});
};
const handleBlur = (e) => {
handleChange(e);
setErrors(validateForm(form));
};
const handleSubmit = (e) => {
setErrors(validateForm(form));
Axios
.post('http://localhost:5000/usuarios/login', form)
.then(response => {
console.log(response)
})
.then(data => console.log(data))
.catch(error => {
console.log(error)
})
};
return {
form,
errors,
loading,
response,
handleChange,
handleBlur,
handleSubmit
};
}
Backend Controller
const main = {
acceso: async (req, res) => {
console.log(req.body)
return res.redirect("http://localhost:3000")
}
}
Backend Routes
const express = require('express');
const router = express.Router();
const path = require("path");
const fs = require("fs");
const user = require("../controllers/user");
const multer = require("multer");
// ************ Multer ************
const dest = multer.diskStorage({
destination: function (req, file, cb) {
let dir = path.resolve(__dirname,"../../public/uploads","users",
String(req.body.nombre).trim().replace(/\s+/g, ''))
if (!fs.existsSync(dir)){
fs.mkdirSync(dir);
}
cb(null, dir)
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now()+ path.extname(file.originalname))
}
})
const upload = multer({storage:dest});
// ************ Routes ************
router.post("/login", user.acceso)
router.post('/guardar', upload.single("file") , user.post)
module.exports = router;
/*** Entry Point ***/
Entry point Route definition
const users = require("./routes/user");
app.use("/usuarios", users);
/*** Console.log ***/
Console.log Response { email: 'juansepincha#gmail.com', password: '12345' }
From now thanks for all and have a great weekend!
I'm making a react component which has two input fields.One have the key : type,another the key: range.The problem is that when i submit the data i dont know how to save it as an array or something,to stack more pairs of information,because i need to display a progress bar based on the information from the input field. Could you help me please?
Here is my Slice:
export const skillSlice = createSlice({
name: "skill",
initialState: {
name:'',
range:null
},
reducers: {
setSkill: (state, action) => {
console.log("action", action.payload);
state.name = action.payload?.name;
state.range = action.payload?.range;
}
}
});
export const addNewSkill = createAsyncThunk(
'skills/addNewSkill',
async (_,{rejectWithValue,dispatch}) =>{
try{
const response = await fetch('/api/skills',{
method:'POST',
headers:{
'Content-name' : 'application/json',
},
});
if(!response.ok){
throw new Error('Can\'t add skill. Server error')
}
const data = await response.json();
dispatch(setSkill(data))
}catch(error){
return rejectWithValue(error.message);
}
}
)
export const fetchSkills = createAsyncThunk(
'skills/fetchSkills',
async (_, {rejectWithValue}) => {
try{
const response = await fetch('/api/skills',{
method:'GET',
})
// console.log(response)
if(!response.ok){
throw new Error ('Server Error!');
}
const data = await response.json();
// console.log(data)
return data;
} catch(error){
return rejectWithValue(error.message);
}
}
);
const { setSkill } = skillSlice.actions;
export const selectSkill = (state) => state?.skill;
export default skillSlice.reducer;
And here is the component:
import React, { useState,useEffect } from 'react'
import { Formik, Form, useFormik } from 'formik'
import * as Yup from 'yup'
import FormikControl from '../Form/FormikControl'
import DisplayFormikState from '../Form/DisplayFormikState.js'
import { useDispatch, useSelector } from 'react-redux'
import { addNewSkill,fetchSkills,selectSkill } from '../../features/skills/skillSlice'
const Skills = () =>{
const dispatch = useDispatch();
const [skill, setSkills] = useState({
name: '',
range: null
});
useEffect(()=>{
dispatch(fetchSkills());
},[dispatch])
const userInfo = useSelector(selectSkill);
const skillList = useSelector(state => state.skillState)
const { status, error } = useSelector(state => state.skillState)
const handleChange = (e) => {
const { name, value } = e.target;
setSkills({ ...skill, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
dispatch(addNewSkill(skill));
};
const formik = useFormik({
// initialValues:{
// name: skill.name,
// range: skill.range
// },
validationSchema:Yup.object({
}),
})
return(
<>
<section id="skills">
<h1 className='SkillSection'>Skills</h1>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="type">Type</label>
<input
id='type'
name='name'
type='text'
placeholder='Enter skill name'
onChange={handleChange}
// value={formik.values.name}
/>
</div>
<div>
<label htmlFor="level">Level</label>
<input
id='level'
type='text'
name='range'
placeholder='Enter range'
onChange={handleChange}
// value={formik.values.range}
/>
</div>
<button type='submit'>Submit</button>
</form>
</section>
</>
)
}
export default Skills
In the above code the initial state isn't an array because when i tried to push values to it i got undefined,so,i left the working state not to get confused. Thanks in advance!