Updating user profile information with redux in firebase - 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)
};

Related

How to update the state in pinia with Vue 3

I'm trying to save the input values in the Pinia store, but the state is not updating. So I want onSubmit function to save the input values in a store.
My code :
Create.vue
<script setup>
import { reactive } from "vue";
import { useCounterStore } from '#/stores/counter';
const counter = useCounterStore();
const form = reactive({
first: "",
second: "",
email: ""
});
const onSubmit = (e) => {
e.preventDefault();
counter.$patch({ firstName: form.first.value });
counter.$patch({ secondName: form.second.value });
counter.$patch({ email: form.email.value });
}
</script>
<template>
<form #submit="onSubmit">
{{ counter.getFirst + 'MYFIRST' }} {{ counter.getSecond + 'MYSECOND' }} {{ counter.getEmail + 'MYEMAIL' }}
<div class="row mt-4">
<div class="col-6">
<label for="exampleFormControlInput1" class="form-label">First</label>
<input v-model="form.first" type="text" class="form-control" id="exampleFormControlInput1"
placeholder="First name">
</div> <div class="col-6">
<label for="exampleFormControlInput1" class="form-label">Second</label>
<input v-model="form.second" type="text" class="form-control" id="exampleFormControlInput1"
placeholder="Second name">
</div>
<div class="col-6 mt-2">
<label for="exampleFormControlInput1" class="form-label">Email</label>
<input v-model="form.email" type="email" class="form-control" id="exampleFormControlInput1"
placeholder="name#example.com">
</div>
<div class="col-12 mt-3">
<button #click="onSubmit" type="button" class="btn btn-dark push- right">Create</button>
<button type="button" class="btn btn-dark">All users</button>
</div>
</div>
</form>
</template>
Pinia store: counter.js
import { ref, computed, reactive } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
let firstName = ref('');
let secondName = ref('');
let email = ref('');
const getFirst = computed(() => firstName.value)
const getSecond = computed(() => secondName.value)
const getEmail = computed(() => email.value)
function increment() {
count.value++
}
return { count, getFirst, getSecond, getEmail, increment }
})
i am not sure how your store look like... but it should by something like:
`
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
}
})`
in your component, change the name of the store instance, lets say counterStore.
const counterStore = useCounterStore();
and now you can access the state by name:
counterStore.counter++
while this will work, it is not the best practice...
you should use actions to manipulate the state...
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
actions: {
increment() {
this.count++
},
},
})
and then you can call the action like a regular method:
counterStore.increment()
I think you're in the process of learning Vue, don't give up!
Give a feedback in comment and put your post on "solved" status if this help you.
The solution :
In store file :
import { defineStore } from 'pinia';
export const useAuthStore = defineStore('auth', {
state: () => ({
loginForm: {
firstName: '',
secondName: '',
email: ''
}
})
});
In Create.vue :
<script setup>
import { reactive } from "vue";
import { useAuthStore } from '#/stores/auth';
const authstore = useAuthStore();
const form = reactive({
first: "",
second: "",
email: ""
});
...
const onSubmit = (e) => {
e.preventDefault();
authStore.loginForm.firstName = form.first;
authStore.loginForm.secondName = form.second;
authStore.loginForm.email = form.email;
}
...
<script/>

Next-auth Credentials Provider authenticated state doesn't update

I'm using next-auth 4.18.4 and can't figure out how to set up the Credential provider properly. At this point, when the user logs in, the status remains unauthenticated, and only updates to authenticated when I refresh the page. This is what I have in api/auth/[...nextauth].ts:
import NextAuth from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'
import { verifyPassword } from '../../../lib/auth'
import conn from '../../../lib/db'
export default NextAuth({
providers: [
CredentialsProvider({
async authorize(credentials) {
const query = `SELECT * FROM users WHERE username = $1`
const values = [ credentials.username ]
let user
try {
const result = await conn.query(query, values)
if (result.rows.length > 0) user = result.rows[0]
} catch (err) {
console.log(`Error fetching user from DB: ${err.stack}`)
throw new Error(err)
}
if (!user) throw new Error('No user found!')
const isValid = await verifyPassword(
credentials?.password,
user.password
)
if (!isValid) throw new Error('Could not log you in!')
return {
uid: user.id,
username: user.username,
profilePic: user.profile_pic
}
},
}),
],
})
This is my login page:
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { zodResolver } from '#hookform/resolvers/zod'
import { signIn } from 'next-auth/react'
import { useRouter } from 'next/router'
import Input from "../components/input"
const validationSchema = z
.object({
userName: z
.string()
.min(1, { message: 'Username is required' })
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{5,10}$/, {
message: '5-10 upper and lowercase letters, and digits',
}),
password: z
.string()
.min(5, { message: 'Between 5-10 characters' })
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{5,10}$/, {
message: 'Upper and lowercase letters, and digits',
})
})
export default function Auth() {
const {
register,
handleSubmit,
watch,
formState: { errors, isValid },
setValue
} = useForm({
mode: 'all',
resolver: zodResolver(validationSchema),
})
const router = useRouter()
async function submitHandler(data: any) {
const result = await signIn('credentials', {
redirect: false, // don't redirect if user enters wrong credentials
username: data.userName,
password: data.password
})
// console.log(result) // testing
// If the 'error' property is null (meaning log in was successful)
if (!result?.error) {
// Let's clear the input fields
setValue('userName', '')
setValue('password', '')
// And redirect the user to the main page
router.replace('/')
} else {
// If the 'error' property was false, let's print the login error
console.log(`Error: ${JSON.stringify(result.error)}`)
}
}
return (
<div className='text-white max-w-4xl mx-auto pt-10 pb-20 px-2'>
<h1 className='text-2xl text-center pb-8'>Log in</h1>
<form
onSubmit={handleSubmit(submitHandler)}
className='space-y-4 flex flex-col items-center '
>
<Input
id='userName'
type='text'
label='Username'
register={register}
registerOptions={{ required: true }}
errors={errors}
isRequired={true}
/>
<Input
id='password'
type='password'
label='Password'
register={register}
registerOptions={{ required: true }}
errors={errors}
isRequired={true}
/>
<button
type='submit'
disabled={!isValid}
className={`p-3 border-[1px] border-slate-500 rounded-md hover:enabled:bg-white hover:enabled:bg-opacity-20 disabled:cursor-not-allowed w-[90%]`}
>
{isValid ? 'Log In' : 'Please, fill the form'}
</button>
</form>
</div>
)
}
And my index page:
import { useSession } from 'next-auth/react'
export default function Home() {
const { data: session, status } = useSession()
console.log(status);
return (
<div>
<h1 className='text-2xl text-white text-center p-4'>Home Page</h1>
{status === 'authenticated' ?
(<h2 className='text-2xl text-white p-14'>Logged in!</h2>)
:
(<h2 className='text-2xl text-white p-14'>Not logged in!</h2>)
}
</div>
)
}
And the session provider in _app.ts:
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import Layout from '../components/layout'
import { SessionProvider } from 'next-auth/react'
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={session}>
<Layout>
<Component {...pageProps} />
</Layout>
</SessionProvider>
)
}
By the way, I forgot to add that after logging in, I can see the next-auth.session cookie being created in the browser, but still, status doesn't change until I reload.

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);
};

Redux State not updating in the Redux DevTool

When I click on a button, it dispatches a function that is meant to login a user and return the user's data. But the Redux store seems not to be updated after the dispatch. When I checked the Redux devtool, It shows that the actions are dispatching appropriately with their payloads but the state remains as the initial state, it doesn't get updated after each dispatched action.
This images below display the action and state of the redux devtool.
Dispatched actions
State display initial state
I don't know I have done wrong, my code are as follows.
userInitialState.js
export default {
user: {},
error: null,
loading: false
};
userLoginAction.js
import axios from 'axios';
import * as types from './actionTypes';
export const signInUserSuccess = payload => ({
type: types.SIGNIN_USER_SUCCESS,
payload
});
export const signingInUser = () => ({
type: types.SIGNING_IN_USER
});
export const signInUserFailure = () => ({
type: types.SIGNIN_USER_FAILURE
});
export const userSignIn = (data) => {
const url = 'https://eventcity.herokuapp.com/api/v1/users/login';
return (dispatch) => {
dispatch(signingInUser());
return axios({
method: 'post',
url,
data
})
.then((response) => {
const user = response.data;
dispatch(signInUserSuccess(user));
})
.catch(() => {
dispatch(signInUserFailure());
});
};
};
userLoginReducer.js
import * as types from '../actions/actionTypes';
import userInitialState from './userInitialState';
const userReducer = (state = userInitialState, action = {}) => {
switch (action.types) {
case types.SIGNING_IN_USER:
return {
...state,
user: {},
error: null,
loading: true
};
case types.SIGNIN_USER_FAILURE:
return {
...state,
user: {},
error: { message: 'Error loading data from the API' },
loading: false
};
case types.SIGNIN_USER_SUCCESS:
return {
...state,
user: action.payload,
error: null,
loading: false
};
default:
return state;
}
};
export default userReducer;
rootReducer.js
import { combineReducers } from 'redux';
import userReducer from './userReducer';
const rootReducer = combineReducers({
userReducer
});
export default rootReducer;
configureStore.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from '../reducer/rootReducer';
const configureStore = () => createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)));
export default configureStore;
SignInModal.js
import React, { Component } from 'react';
class SignInModal extends Component {
state = {
username: '',
password: ''
};
componentDidMount() {
this.props.userSignIn({});
}
onUsernameChange = e => {
const username = e.target.value;
this.setState(() => ({
username
}));
};
onPasswordChange = e => {
const password = e.target.value;
this.setState(() => ({
password
}));
};
onSubmitForm = e => {
e.preventDefault();
const user = {
username: this.state.username,
password: this.state.password
};
this.props.userSignIn(user);
};
render() {
console.log(this.props.user)
return (
<div>
<div
className="modal fade"
id="exampleModalCenter"
tabIndex="-1"
role="dialog"
aria-labelledby="exampleModalCenterTitle"
aria-hidden="true"
>
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title" id="exampleModalLongTitle">
Sign In Form
</h5>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<form onSubmit={this.onSubmitForm} id="signin">
<div className="form-group">
<label htmlFor="username">Username or Email</label>
<input
type="text"
className="form-control"
name="username"
placeholder="Username or email"
value={this.state.username}
onChange={this.onUsernameChange}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input
type="password"
className="form-control"
placeholder="Password"
name="password"
value={this.state.password}
onChange={this.onPasswordChange}
/>
</div>
</form>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" data-dismiss="modal">
Close
</button>
<button type="submit" className="btn btn-primary" form="signin">
Save changes
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default SignInModal;
SignInModalContainer
import { connect } from 'react-redux';
import { userSignIn } from '../actions/userLoginAction';
import SignInModal from '../components/SignInModal';
const mapStateToProps = state => ({
user: state.userReducer
});
const mapDispatchToProps = dispatch => ({
userSignIn: data => dispatch(userSignIn(data))
});
export default connect(mapStateToProps, mapDispatchToProps)(SignInModal);
I have found the problem. I was using action.types in the switch statement. Instead of using switch(action.type).

Uncaught TypeError when calling an action creator with data from a form input

I am new and would really appreciate your help. I have a form with four inputs (firstName, lastName, position and email) and want to pass the data the user puts in to the state to create an user object. But I always receive this error: Uncaught TypeError: Cannot read property 'firstName' of undefined
Maybe I did map the state wrong? I honestly don't know.
Here is my code:
The form I created which takes the users input:
import React, { Component } from 'react';
import TextField from 'material-ui/TextField';
import { Field, FieldArray, reduxForm} from 'redux-form';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import validate from './validate';
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin(); //Needed, otherwise an error message is shown in the console
//Texteingabefeld
const renderTextField = ({input, label, meta: {touched, error}, ...custom}) => (
<TextField
hintText={label}
floatingLabelText={label}
errorText={touched && error}
{...input}
{...custom}
/>
);
const renderUsers = ({fields, meta: { touched, error }}) => (
<div>
<div>
<button className="btn btn-primary"
type="button" onClick={() => fields.push({})}>
<span className="glyphicon glyphicon-plus-sign"/>Add User
</button>
{touched && error && <span>{error}</span>}
</div>
<Field name="firstName" component={renderTextField} label="First Name"/>
<Field name="lastName" component={renderTextField} label="Last Name"/>
<Field name="position" component={renderTextField} label="Position"/>
<Field name="email" component={renderTextField} label="Email"/>
{fields.map((user, index) =>
<div key={index}>
<Field name={`firstName${index}`} component={renderTextField} label="First Name"/>
<Field name={`lastName${index}`} component={renderTextField} label="Last Name"/>
<Field name={`position${index}`} component={renderTextField} label="Position"/>
<Field name={`email${index}`} component={renderTextField} label="Email"/>
<button className="btn btn-xs btn-danger"
type="button"
title="Remove User"
onClick={() => fields.remove(index)}>
<span className="glyphicon glyphicon-minus-sign"/>
</button>
</div>
)}
</div>
);
const UserCreation = props => {
const { handleSubmit, pristine, reset, submitting} = props;
return (
<form onSubmit={handleSubmit}>
<FieldArray name="users" component={renderUsers}/>
<div>
<button className="btn btn-primary btn-success"
type="submit"
disabled={pristine || submitting}>
<span className="glyphicon glyphicon-send" />
Submit
</button>
{' '}
<button type="button"
className="btn btn-primary btn-danger"
disabled={pristine || submitting}
onClick={reset}>
Cancel
</button>
</div>
</form>
);
}
export default reduxForm({
form: 'UserCreationForm',
validate
})(UserCreation);
The Component which fires the action when the user submits the form:
import React, {Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import UserCreation from '../components/UserCreation';
import { addUser } from '../actions/UserActions';
class UserControlPage extends Component {
componentWillMount() {
}
handleSubmit=(values) => {
addUser(values);
}
render() {
return (
<div>
<legend>
<span className="glyphicon glyphicon-user" aria-hidden="true"></span> User creation
</legend>
<UserCreation onSubmit={this.handleSubmit}/>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({addUser}, dispatch);
}
function mapStateToProps(state) {
return {
users: state.users
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UserControlPage);
The action creator:
import axios from 'axios';
import {ADD_USER} from './index';
export const addUser = (user) => {
return {
type: ADD_USER,
payload: {
// id: id,
firstName: user.payload.firstName,
lastName: user.payload.lastName,
position: user.payload.position,
email: user.payload.email
}
};
}
The reducer:
import {ADD_USER} from '../actions/index';
const INITIAL_STATE = {};
export default function UserReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case ADD_USER:
return [...state, {
// id: action.id,
firstName: action.firstName,
lastName: action.lastName,
position: action.position,
email: action.email
}];
default:
return state;
}
}
The action creator has to get data without the payload, like this:
export const addUser = (user) => {
return {
type: ADD_USER,
payload: {
firstName: user.firstName,
lastName: user.lastName,
position: user.position,
email: user.email
}
}
}
I see a couple things wrong. First of all, you're attaching your data to a payload object inside of your action, but you're not reading from that object in your reducer. So in the reducer, for example, where you have action.firstName, it should actually be action.payload.firstName.
Secondly, what you're returning from the reducer is not right. Your initial state is an object, but you're returning an array. So your state is all kinds of messed up as soon as you mutate state. Try this instead:
import {ADD_USER} from '../actions/index';
const INITIAL_STATE = {};
export default function UserReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case ADD_USER:
return {
...state,
user: action.payload
};
default:
return state;
}
}
Update
I see yet another problem. In this code, you're referencing user.payload, which does not exist.
export const addUser = (user) => {
return {
type: ADD_USER,
payload: {
// id: id,
firstName: user.payload.firstName,
lastName: user.payload.lastName,
position: user.payload.position,
email: user.payload.email
}
};
}
It should be user.firstName, user.lastName, etc.
Thanks guys. Changing the action creator to
export const addUser = (user) => {
return {
type: ADD_USER,
payload: {
firstName: user.firstName,
lastName: user.lastName,
position: user.position,
email: user.email
}
};
}
and the reducer to
const INITIAL_STATE = [];
export default function UserReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case ADD_USER:
return [action.payload, ...state];
default:
return state;
}
}
solved the error.

Resources