How to update the state in pinia with Vue 3 - vuejs3

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/>

Related

Vue3 composition API and pinia | submit data then reload the data

I'm learning Vue 3 composition API and Pinia. I'm making a todo.
When I submit a todo data through Pinia, I can submit to the DB, but it won't re-render until reload the page.
Do I need to use 'watch' to watch the state todos:[] and execute fetchTodos()?
any good solution?
here both codes, hope someone can help me. Thank you in advance.
----- VUE -----
<script setup>
import { ref, onMounted } from 'vue'
import { storeToRefs } = from 'pinia'
import { useTodoStore } from '../store/todo'
const store = useTodoStore()
const { getTodos } = storeToRefs(store)
onMounted(() => {
store.fetchTodos()
})
const todo = ref('')
const initForm = () => {
todo.value = ''
}
// submit via Pinia
const onSubmitToPinia = () => {
const payload = {
todo: todo.value,
}
store.addTodoFromPinia(payload)
initForm()
store.fetchTodo()
}
</script>
<template>
<h4>TODO</h4>
<!-- form addTodo -->
<form class="row g-4">
<div class="col-auto">
<input
class="form-control"
v-model="newName"
type="text"
placeholder="todo">
</div>
<div>
<button
class="btn btn-primary"
type="button"
#click="onSubmitToPinia(payload)">
submit through pinia</button>
</div>
</form>
<!-- render data from pinia -->
<div class="todo"
v-for="getTodo in getTodoss.todo"
:key="getTodo.id">
<b class="ms-2">{{ getTodo.todo }}</b>
</div>
</template>
---- PINIA ----
import { defineStore } from 'pinia'
import axios from "axios"
export const useAboutStore = defineStore('todo',{
state: () => {
return {
todos: []
}
},
getters: {
getTodos(state) {
return state.todos
}
},
actions: {
async fetchTodos() {
try {
const data = await axios.get('http://localhost:5000/todo')
this.todos = data.data
}
catch (error) {
alert(error)
console.log(error)
}
},
addTodoFromPinia(payload) {
const path = 'http://localhost:5000/todo'
axios.post(path, payload)
}
},
})
You don't need to use storeToRefs to accomplish what you want nor do you need to watch the state of the store.
<template>
<div class="todo"
v-for="getTodo in store.todos"
:key="getTodo.id">
<b class="ms-2">{{ getTodo.todo }}</b>
</div>
</template>
If for any reason the vue complains that the array is empty put a v-if checking if the store.todos.length is != 0.
And also fix your typos.
If the problem persists show me your new code and I help you again.

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

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

Need help in my VueJS + VueX + Firebase project

I am trying to show the current userName and title which was already created by signin and now stored in Firebase Cloud Firestore database.
But I don't know how to handle this, I can't find an error in the code by my self.
Here is the code:
<template>
<div id="dashboard">
<section>
<div class="col1">
<div class="profile">
<h5>{{ userProfile.name }}</h5>
<p>{{ userProfile.title }}</p>
<div class="create-post">
<p>create a post</p>
<form #submit.prevent>
<textarea v-model.trim="post.content"></textarea>
<button #click="createPost"
class="button">post</button>
</form>
</div>
</div>
</div>
<div class="col2">
<div>
<p class="no-results">There are currently no posts</p>
</div>
</div>
</section>
</div>
</template>
<script>
const fb = require('../firebaseConfig.js')
import { mapState } from 'vuex'
import firebase from 'firebase'
const db = firebase.firestore()
export default {
data() {
return {
post: {
content: ''
}
}
},
computed: {
...mapState(['userProfile'])
},
methods: {
createPost() {
fb.postsCollection.add({
createdOn: new Date(),
content: this.post.content,
userId: this.currentUser.uid,
userName: this.userProfile.name,
comments: 0,
likes: 0
}).then(ref => {
this.post.content = ''
}).catch(err => {
console.log(err)
})
}
}
}
</script>
Store.js
import Vue from 'vue'
import Vuex from 'vuex'
const fb = require('./firebaseConfig.js')
Vue.use(Vuex)
// handle page reload
fb.auth.onAuthStateChanged(user => {
if (user) {
store.commit('setCurrentUser', user)
store.dispatch('fetchUserProfile')
}
})
export const store = new Vuex.Store({
state: {
currentUser: null,
userProfile: {}
},
actions: {
clearData ({ commit }) {
commit('setCurrentUser', null)
commit('setUserProfile', {})
},
fetchUserProfile ({
commit,
state
}) {
fb.usersCollection.doc(state.currentUser.uid).get().then(res => {
commit('setUserProfile', state.res.data())
}).catch(err => {
console.log(err)
})
console.log(state)
}
},
mutations: {
setCurrentUser (state, val) {
state.currentUser = val
},
setUserProfile (state, val) {
state.userProfile = val
}
}
})
Error output:
userProfile output as Object:

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).

Resources