Running code shows 'this.props.dispatch is not a function' after adding mapDispatchToProps - redux

Let me tell you, this code were working fine and showing all products in shop page but after adding mapDispatchToProps.
it giving error:- TypeError: this.props.getProducts is not a function
mapStateToProps is giving products.
Trying to post data using mapDispatchToProps.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getProducts } from '../actions/productsAction';
import { addToCart } from '../actions/cartAction';
class Shop extends Component {
constructor(props) {
super(props);
this.state = {
pid: '',
pname: '',
pprice: '',
pimg: '',
qty: '',
total_price: '',
getValue:[]
};
}
componentDidMount() {
this.props.dispatch(getProducts());
let getValue = localStorage.getItem("userData");
this.setState({
getValue: JSON.parse(getValue),
});
}
handleSubmit = (event) => {
event.preventDefault();
const pid = this.state.pid;
const pname = this.state.pname;
const pprice = this.state.pprice;
const pimg = this.state.pimg;
const qty = this.state.qty;
const total_price = this.state.total_price;
const email = this.state.getValue.email;
const CartData = {pid: pid, pname: pname, pprice: pprice, pimg: pimg, qty:qty, total_price:total_price}
this.props.addToCart(CartData);
};
render() {
return (
...html code
<form onSubmit={ this.handleSubmit }>
<input type="hidden" onChange={this.handleChange} name="pid" value={product._id} />
<input type="hidden" onChange={this.handleChange} name="pname" value={product.pname} />
<input type="hidden" onChange={this.handleChange} name="pprice" value={product.pprice} />
<input type="hidden" onChange={this.handleChange} name="qty" value="1" />
<input type="hidden" onChange={this.handleChange} name="pimage" value={product.pimg} />
<input type="hidden" onChange={this.handleChange} name="total_price" value={product.pprice} />
<button type="submit" class="pro-btn"><i class="icon-basket"></i></button>
</form>
...html code
const mapStateToProps = (state) => ({ products: state.products });
const mapDispatchToProps = { addToCart };
export default connect(mapStateToProps, mapDispatchToProps)(Shop);

You have not defined the mapDispatchToProps properly.
It should look like below,
const mapDispatchToProps = (dispatch) => {
return {
addToCart: () => dispatch(addToCart()),
getProducts: () => dispatch(getProducts())
}
};
And you should call the function directly using props
componentDidMount() {
this.props.getProducts();
let getValue = localStorage.getItem("userData");
this.setState({
getValue: JSON.parse(getValue),
});
}

You have two actions that you want to use in your component, so you'll want to include them both in your mapDispatchToProps object. The function notation described by #AmilaSenadheera will work, but the object shorthand is easier.
const mapDispatchToProps = {
addToCart,
getProducts
};
Then you should be able to call this.props.getProducts().

Related

How to upload Image from Next JS Strapi API

How can I add an image from NextJS to Strapi Media library? I Try to upload the image from the NextJS frontend, the image will be uploaded to my Strapi Media library and my Cloudinary account but the image will not be associated/linked to that particular post
Here is my code
path: components/ImageUpload.js
import { useState } from "react";
import { API_URL } from "../config/index";
import styles from "#/styles/FormImage.module.css";
export default function ImageUpload({ sportNewsId, imageUploaded }) {
const [image, setImage] = useState(null);
const handleFilechange = (e) => {
console.log(e.target.files);
setImage(e.target.files[0]);
};
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("files", image);
formData.append("ref", "sports");
formData.append("refid", sportNewsId);
formData.append("field", "image");
const res = await fetch(`${API_URL}/upload`, {
method: "POST",
body: formData,
});
if (res.ok) {
imageUploaded();
}
};
return (
<div className={styles.form}>
<h4>Upload Sport News Image</h4>
<form onSubmit={handleSubmit}>
<div className={styles.file}>
<input type="file" onChange={handleFilechange} />
<input type="submit" value="Upload" className="btn" />
</div>
</form>
</div>
);
}
path:pages/news/edit/[id].js
import Link from "next/link";
import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import moment from "moment";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import Layout from "#/components/Layout";
import { API_URL } from "#/config/index";
import styles from "#/styles/FormEdit.module.css";
import Modal from "#/components/Modal";
import ImageUpload from "#/components/ImageUpload";
export default function EditNews({ sportNews }) {
const [values, setValues] = useState({
name: sportNews.name,
detail: sportNews.detail,
date: sportNews.date,
time: sportNews.time,
});
const [previewImage, setPreviewImage] = useState(
sportNews.image ? sportNews.image.formats.thumbnail.url : null
);
const [showModal, setShowModal] = useState(false);
const router = useRouter();
const { name, detail, date, time } = values;
const handleSubmit = async (e) => {
e.preventDefault();
const emptyFieldCheck = Object.values(values).some(
(element) => element === ""
);
if (emptyFieldCheck) {
toast.error("Please fill all input field");
}
const response = await fetch(`${API_URL}/sports/${sportNews.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
});
if (!response.ok) {
toast.error("something went wrong!!!");
} else {
const sport = await response.json();
router.push(`/news/${sport.slug}`);
}
};
const imageUploaded = async (e) => {
const res = await fetch(`${API_URL}/sports/${sportNews.id}`);
const data = await res.json();
console.log("showing =>", data);
console.log(setPreviewImage);
setPreviewImage(data.image[0].formats.thumbnail.url);
setShowModal(false);
};
const handleInputchange = (e) => {
const { name, value } = e.target;
setValues({ ...values, [name]: value });
};
return (
<Layout title="Add New Sport News">
<Link href="/news">Go Back</Link>
<h2>Add Sport News</h2>
<ToastContainer />
<form onSubmit={handleSubmit} className={styles.form}>
<div className={styles.grid}>
<div>
<label htmlFor="name">Name</label>
<input
name="name"
id="name"
type="text"
value={name}
onChange={handleInputchange}
/>
</div>
<div>
<label htmlFor="date">Date</label>
<input
name="date"
id="date"
type="date"
value={moment(date).format("yyyy-MM-DD")}
onChange={handleInputchange}
/>
</div>
<div>
<label htmlFor="time">Time</label>
<input
name="time"
id="time"
type="text"
value={time}
onChange={handleInputchange}
/>
</div>
</div>
<div>
<label htmlFor="detail">Detail</label>
<textarea
name="detail"
id="detail"
type="text"
value={detail}
onChange={handleInputchange}
/>
</div>
<input className="btn" type="submit" value="Add News" />
</form>
{/* {console.log(previewImage)} */}
{previewImage ? (
<Image src={previewImage} height={100} width={180} />
) : (
<div>
<p>No Image Available</p>
</div>
)}
<div>
<button onClick={() => setShowModal(true)} className="btn-edit">
Update Image
</button>
</div>
<Modal show={showModal} onClose={() => setShowModal(false)}>
<ImageUpload sportNewsId={sportNews.id} imageUploaded={imageUploaded} />
</Modal>
</Layout>
);
}
export async function getServerSideProps({ params: { id } }) {
const res = await fetch(`${API_URL}/sports/${id}`);
const sportNews = await res.json();
return {
props: { sportNews },
};
}
this is the error message it is showing.
how do I resolve this error, any assistance will be appreciated
Thanks a lot
For a formData you have to add a header :
'Content-Type': 'multipart/form-data'
I have been struggling during hours to find this. I am uploading a file directly from an entry and not with the /upload route but it might work the same way. Using axios for the post method here is an example :
const form = new FormData();
const postData = {
name: 'test2',
};
form.append('files.image', file);
form.append('data', JSON.stringify(postData));
await axios
.post(getStrapiURL('/ingredients'), form, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then((response) => {
// Handle success.
console.log('Well done!');
console.log('Data: ', response.data);
})
.catch((error) => {
// Handle error.
console.log('An error occurred:', error.response);
});
From my observation, the problem is on the setPreviewImage line remove the [0] array brackets from the image in order to access the Cloudinary thumbnail Url you will get from the Strapi API after each image upload.
The function below should make it work
const imageUploaded = async (e) => {
const res = await fetch(`${API_URL}/sports/${sportNews.id}`);
const data = await res.json();
console.log("showing =>", data);
console.log(setPreviewImage);
setPreviewImage(data.image.formats.thumbnail.url);
setShowModal(false);
};

React Redux Input handle

I'm trying to handle simple input value using react-redux and then trying to display it. I know how to display it but i have no idea how to submit input value from component to redux store. I searched web and found nothing. Can someone explain how to do this? I'm totally new to react-redux
import React from "react";
import "./App.css";
import { connect } from "react-redux";
import { useState } from "react";
import { updateValue, addValue } from "./actions/inputActions";
function App(props) {
const [value, setValue] = useState("");
const handleChange = (e) => {
setValue(e.target.value);
};
return (
<div className="App">
<form onSubmit={(value) => props.submitValue(value)}>
<input onChange={handleChange} value={value} type="text" />
<button type="submit">Add</button>
</form>
<h1>{props.value}</h1>
</div>
);
}
const mapStateToProps = (state) => {
return {
value: state.value,
};
};
const mapDispatchToProps = (dispatch) => {
return {
submitValue: (e, value) => {
e.preventDefault();
dispatch(addValue(value));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Update your onSubmit function with the value stored in your local state, like this:
<form onSubmit={(e) => {
e.preventDefault();
props.submitValue(value)
}}>
<input onChange={handleChange} value={value} type="text" />
<button type="submit">Add</button>
</form>
And your mapDispatchToProps function like this:
const mapDispatchToProps = (dispatch) => {
return {
submitValue: (value) => {
dispatch(addValue(value));
},
};
};

Updating user profile information with redux in firebase

I am trying to use Redux in my React application to update the user profile within my Firebase database from my react component.
This is my component:
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
import { firestoreConnect } from "react-redux-firebase";
import { compose } from "redux";
import { editProfile } from "../../store/actions/editProfileActions";
class UserProfile extends Component {
state = {
firstName:"",
initials:"",
lastName:""
};
onChange = e => {
this.setState({
[e.target.id]: e.target.value
});
};
onSubmit = e => {
e.preventDefault();
console.log(this.state);
this.props.editProfile(this.state);
}
render() {
const { auth, profile } = this.props;
console.log(profile);
if (auth.isEmpty) return <Redirect to="/home" />;
return (
<div className="container">
<form onSubmit={this.onSubmit} className="white">
<h5 className="grey-text text-darken-3">Edit Profile</h5>
<div className="input-field">
<label htmlFor="title">First Name: {profile.firstName}</label>
<input type="text" id="firstName" onChange={this.onChange} />
</div>
<div className="input-field">
<label htmlFor="title">Initials: {profile.initials}</label>
<input type="text" id="initials" onChange={this.onChange} />
</div>
<div className="input-field">
<label htmlFor="title">Last Name: {profile.lastName}</label>
<input type="text" id="lastName" onChange={this.onChange} />
</div>
<div className="input-field">
<button className="btn black z-depth-0">Submit</button>
{ }
</div>
</form>
</div>
)
}
};
const mapStateToProps = state => {
return {
auth: state.firebase.auth,
profile: state.firebase.profile,
};
};
const mapDispatchToProps = dispatch => {
return {
editProfile: edit => dispatch(editProfile(edit))}
}
export default compose(
connect(mapStateToProps, mapDispatchToProps),
firestoreConnect([
{ collection: "profile"}
])
)(UserProfile);
The component correctly displays the current user information.
This is the action I have set up:
return async (dispatch, getState, { getFirestore, getFirebase }) => {
const firebase = getFirebase();
const user = await firebase
.auth()
.currentUser
.updateProfile({
firstName: profile.firstName
});
dispatch({ type: "EDITPROFILE_SUCCESS", user })
console.log("user = " + profile.firstName);
};
}
When I log the entered profile.firstName I get the entered data.
And my reducer:
const editProfileReducer = (state, action) => {
switch (action.type) {
case "EDITPROFILE_ERROR":
return {
...state,
editError: action.error
};
case "EDITPROFILE_SUCCESS":
return {
...state
};
default:
return state;
}
}
export default editProfileReducer;
Any idea what I am missing here?
In your reducer change the like below
case "EDITPROFILE_SUCCESS":
return {
...state,
user:action.user
};
Above is if you want to update the whole user object
If you want to change only name then
Let’s assume that profileName is in user object then
case "EDITPROFILE_SUCCESS":
return {
...state,
user:Object.assign({}, state.user, profileName:action.user.profileName)
};

InitialValues aren't populating redux form

At the moment, I'm having difficulty populating the input fields for a redux form with initial values. Could someone please tell me what's wrong with the following code, or if this is a known issue? Thanks for the help and support.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm, reset, initialize } from 'redux-form'
const renderField = props => (
<div>
<label>{props.placeholder}</label>
<div>
<input {...props}/>
{props.touched && props.error && <span>{props.error}</span>}
</div>
</div>
)
class EditArtist extends Component {
render() {
const { initialValues} = this.props;
console.log(initialValues)
return (
<form>
<Field name="name" component={renderField} type="text" placeholder="Name"/>
</form>
)
}
}
const validate = values => {
const errors = {};
return errors;
}
const mapStateToProps = (state) => ({
initialValues: {
name: "COOL"
}
});
export default connect(mapStateToProps)(reduxForm({
form: 'edit_artist_form',
validate,
enableReinitialize: true
})(EditArtist));

Initializing redux-form wizard

So I have a simple form wizard taken from an example in redux-form. I want to initialize fields with data received from an asynchronous API call.
The way it is right now, fields are not being populated without the enableReinitialize: true param in each page of the wizard, because initialization already happens once upon starting the server request.
If on the other hand I add enableReinitialize: true to the wizard pages then any user input will be discarded upon moving from page to page and initialValues will be set again.
I have also tried adding the keepDirtyOnReinitialize: true option in conjunction with enableReinitialize: true to each page, but to no avail.
What would be the correct way of creating a form wizard when you need to have a subset of fields be initialized by asynchronously fetched data which has to be overwriteable by user?
WizardForm.jsx:
import React, {Component, PropTypes} from 'react'
import WizardFormFirstPage from './WizardFormFirstPage.jsx'
import WizardFormSecondPage from './WizardFormSecondPage.jsx'
import WizardFormThirdPage from './WizardFormThirdPage.jsx'
import fetchData from "./fetchData.jsx";
import {connect} from "react-redux";
class WizardForm extends Component {
constructor(props) {
super(props);
this.nextPage = this.nextPage.bind(this);
this.previousPage = this.previousPage.bind(this);
this.state = {
page: 1
}
}
componentWillMount() {
if (!this.props.hasFetched)
this.props.fetchData();
}
nextPage() {
this.setState({ page: this.state.page + 1 })
}
previousPage() {
this.setState({ page: this.state.page - 1 })
}
render() {
const { onSubmit } = this.props;
const { page } = this.state;
return (
<div>
{page === 1 && <WizardFormFirstPage onSubmit={this.nextPage} initialValues={this.props.initialValues}/>}
{page === 2 &&
<WizardFormSecondPage previousPage={this.previousPage} initialValues={this.props.initialValues}
onSubmit={this.nextPage}/>}
{page === 3 &&
<WizardFormThirdPage previousPage={this.previousPage} initialValues={this.props.initialValues}
onSubmit={onSubmit}/>}
<label>{this.props.isFetching ? "Fetching data.." : "Fetched data successfully." }</label>
</div>
)
}
}
function mapStateToProps(state) {
return {
initialValues: state.prefill.data,
isFetching: state.prefill.fetching,
hasFetched: state.prefill.fetched
}
}
WizardForm = connect(
mapStateToProps,
{fetchData}
)(WizardForm);
WizardForm.propTypes = {
onSubmit: PropTypes.func.isRequired,
initialValues: PropTypes.object
};
export default WizardForm;
WizardFormFirstPage.jsx:
class WizardFormFirstPage extends Component {
constructor(props) {
super(props);
}
render() {
const {handleSubmit} = this.props;
return (
<form onSubmit={handleSubmit}>
<Field name="firstName" type="text" component={renderField} label="First Name"/>
<Field name="lastName" type="text" component={renderField} label="Last Name"/>
<Field name="downPayment" type="text" component={renderField}
label="Down Payment" normalize={normalizeDownPayment}/>
<div>
<button type="submit" className="next">Next</button>
</div>
</form>
)
};
}
WizardFormFirstPage = reduxForm({
form: "wizard",
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
//enableReinitialize: true, // <-- !
validate
})(WizardFormFirstPage);
export default WizardFormFirstPage;
fetchData.jsx:
export default function fetchData() {
return (dispatch) => {
dispatch({type: "FETCH_DATA_START"});
axios.get("http://rest.learncode.academy/api/nordischby/customer")
.then(response => {
dispatch({type: "FETCH_DATA_FINISH", data: response.data[0]});
})
.catch(error => {
console.error(error);
});
}
};
prefill.jsx:
const initialState = {
data: {},
fetching: false,
fetched: false,
error: null
};
const prefillReducer = (state = initialState, action) => {
switch (action.type) {
case "FETCH_DATA_START":
return {
...state,
fetching: true
};
case "FETCH_DATA_FINISH":
return {
...state,
data: action.data,
fetching: false,
fetched: true
};
default:
return state
}
};
export default prefillReducer;
I've found what was wrong. For future reference:
I'm now using the following options for reduxForm():
export default reduxForm({
form: "wizard",
destroyOnUnmount: false,
//forceUnregisterOnUnmount: true, // <-- This bad boy was wrong
keepDirtyOnReinitialize: true,
enableReinitialize: true,
validate
})(WizardFormThirdPage)

Resources