How to store coming data persistently? - react-native

I use react-native.
And this is the function that I want to use.
The most important point here is editCommentMutation.
In order to execute this mutation, it requires two variables which are id and payload.
const onUpdateComment = React.useCallback((commentId) => {
setEdit(true);
console.log(commentId, comments);
editCommentMutation({
variables: {
id: parseInt(commentId),
payload: comments,
},
});
}, []);
I get these two variables correctly.
For id , I get it from component screen. So It comes thru argument.
For payload, I get it on the same screen.
Problem here is when I press button1, it sends commentId(id) data from component screen to this page.
And when I press button2, it sends comments(payload) data on this page.
On this App, I press button1 and write comments then button2 in order.
So Each data comes not together, but one by one.
So I execute console.log(commentId, comments),
press button1 : 386 undefined
press button2 : undefined Object { "comments": "뭐야", }
It has undefined for sure..
But in order to execute mutation, I need two data together.
For this, I need to save coming data in somewhere, I guess.
I also tried
const [data, setData] = useState("").
After I save coming commentId to here as:
setData(commentId).
But it isn't saved, just disappears. :)
Can you help me?

Here is a minimal verifiable example. Run the code below and
click ✏️ to edit one of the comments
click ✅ to save your edit and view the updated comment
function App() {
const [edit, setEdit] = React.useState(null)
const [comments, setComments] = React.useState([ "hello", "안녕하세요" ])
const onEdit = editable => event => { setEdit(editable) }
const onUpdate = newComment => event => {
/* execute your graphql mutation here */
setComments([
...comments.slice(0, edit.index),
newComment,
...comments.slice(edit.index + 1)
])
setEdit(null)
}
return <div>
{comments.map((comment, index) =>
<p>{comment}
<button
children="✏️"
onClick={onEdit({comment, index})}
disabled={Boolean(edit)}
/>
</p>
)}
{edit ? <EditComment text={edit.comment} onUpdate={onUpdate} /> : null}
</div>
}
function EditComment({ text, onUpdate }) {
const [value, setValue] = React.useState(text)
return <div>
<input value={value} onChange={e => setValue(e.target.value)} />
<button onClick={onUpdate(value)} children="✅" />
</div>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
button[disabled] { opacity: 0.5; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
In your App you will execute your mutation in onUpdate. A cancel button ❌ can easily be defined using const onCancel = event => { setEdit(null) } and pass it to EditComment.

Related

Vee-Validate 4 - Disable Validation for Save as Draft

I have a form that has a normal save method which validates and saves with validation errors blocking submissions. But, I need a submission that is Save as Draft that bypasses the validation and submits with errors. The normal save and validate is working great, but I can't seem to find a way to easily turn off validation dynamically.
This is the stripped down version of what I have right now:
<template>
<form #submit.prevent="saveForm">
<!-- a bunch of custom components built around useField -->
<button type="submit" #click="item.draft = true">Save as Draft</button>
<button type="submit" #click="item.draft = false">Submit</button>
</form>
</template>
<script setup lang="ts">
import { useForm } from "vee-validate";
import { reactive } from "vue";
const { handleSubmit, isSubmitting, ...formStuff } = useForm();
const item = reactive({
draft: false
});
const saveForm = handleSubmit(async () => {
// do stuff to save here
})
</script>
Being "that guy" who answers their own question is better than "that guy" who just says they solved it I guess...
After lots of digging and some minor refactoring I was able to get this working. The key was changing from field based rules and validation to form based since the validationSchema is reactive. Then I can just change the schema to false to make the form skip validation when it's a draft.
My setup script now looks like the following:
import { useForm } from "vee-validate";
import { computed, reactive } from "vue";
const submissionValidation = {
'consult_patient_name': 'required|min:5',
'item_name': 'required|min:2',
'consult_question': 'required|min:5',
'consult_history': 'required|min:5',
};
// if it's a draft set it to false, so it can bypass validation
// otherwise set it to the actual validation schema
const validationSchema = computed(() =>
item.draft === true
? false
: submissionValidation
);
const { handleSubmit, isSubmitting } = useForm({ validationSchema });
const item = reactive({
draft: false
});
const saveForm = handleSubmit(async () => {
// do stuff to save here
})

Vue 3 display fetch data v-for

So, I'm creating a Pokemon application and I would like to display the pokemon names using the api : https://pokeapi.co/api/v2/pokemon/.
I'm doing a fetch request on the api and then display the pokemon names in my template. I have 0 problem when I try to display only 1 pokemon but I have this error when I try to display all my pokemons using v-for.
Do you have any idea why I meet this error ?
<template>
<p class="dark:text-white"> {{pokemons[0].name}} </p> //working
<div v-for="(pokemon, index) in pokemons" :key="'poke'+index"> //not working...
{{ pokemon.name }}
</div>
</template>
<script>
const apiURL = "https://pokeapi.co/api/v2/pokemon/"
export default {
data(){
return{
nextURL:"",
pokemons: [],
};
},
created(){
this.fetchPokemons();
},
methods:{
fetchPokemons(){
fetch(apiURL)
.then( (resp) => {
if(resp.status === 200){
return resp.json();
}
})
.then( (data) => {
console.log(data.results)
// data.results.forEach(pokemon => {
// this.pokemons.push(pokemon)
// });
// this.nextURL = data.next;
this.pokemons = data.results;
console.log(this.pokemons);
})
.catch( (error) => {
console.log(error);
})
}
}
}
</script>
<style>
</style>
I've just pasted your code into a Code Pen and removed the working/not working comments and the code runs and shows the names.
Maybe the problem is in the parent component where this component is mounted, or the assignment of the :key attribute
try :key="'poke'+index.toString()", but I'm pretty sure js handels string integer concats quiet well.
Which version of vuejs do you use?
Edit from comments:
The parent component with the name PokemonListVue imported the posted component as PokemonListVue which resulted in a naming conflict. Renaming either one of those solves the issue.
In the error message posted, in line 3 it says at formatComponentName this is a good hint.

Component can't access action defined in test store (Vue Test Utils)

Following Vue Test Utils's docs I created a test store (replicating my real store) in my test:
In it there's this action:
actions: {
updatePowerSelectedAction(
{ commit }: ActionContext<State, RootState>,
val: number
) {
commit('UPDATE_POWER_SELECTED', val);
},
},
... which the tested component calls when a button is clicked:
Template:
<button v-for="(power, i) in powers" :key="i" #click="comparePower(power)" />
JS:
function comparePower(val: string) {
updatePowerSelectedAction(val);
...
}
I can see that the component is properly loaded, however it fails to call the action when a button is clicked via await wrapper.findAll('.button')[0].trigger('click');.
There's no error message, the action just isn't called. Why not?
In your BattlePowers add a class named powerbutton to make that buttons unique :
<button
v-for="(power, i) in powers" :key="i"
#click="comparePower(power)"
class="button powerbutton"
>
{{ power }}
</button>
then inside the test file battle-powers.spec.ts import the global store instead of creating new one inside the test file since this global store will be affected when you mock the button click :
import { shallowMount } from '#vue/test-utils';
import BattlePowers from '#/views/battle/battle-powers.vue';
import store from '#/store'
describe('BattlePowers', () => {
it('updates store variable powerSelected with power clicked by user', async () => {
const wrapper = shallowMount(BattlePowers, {});
await wrapper.findAll('.powerbutton')[1].trigger('click');
expect(store.getters['powerSelected']).toBe("strength");
});
});

Vuex: How to grab latest user data after user profile updated?

I am using Vuex and having trouble getting a user's data to be "reactive" after his profile has been updated. Here's my scenario:
My App.vue checks a user's properties during the created() hook
like so:
async created() {
await this.$store.dispatch('getSSOUser') // gets user from auth server
await this.$store.dispatch('fetchUserProfiles') // queries known user table to see if user has a profile
// set for global user state throughout app
await this.setUser()
// then loads the UI
this.isBusy = false
methods: {
setUser() {
const user = this.getUserProfileBySSOID(this.ssoUser.data.user_id)
this.$store.commit('SET_USER', user)
}
So now I have the user's profile (user object) to use throughout the app. Works good....but...when a user edits his profile in the app (for example, updates his phone number, etc) and clicks submit, I can't seem to get the state to refresh/see that there has been a change unless the user manually refreshes the page.
What is the recommended way to handle this issue? Do I need to run a dispatch to the user state on every route change? The user's profile is located at path: '/userEdit/:uid'
This is my app structure:
<div id="app">
<Banner />
<section class="container-fluid">
<loading-spinner v-if="isBusy"></loading-spinner>
<div v-else>
<AuthName class="text-right" />
<MainNav />
<main id="routerView">
<transition name="component-fade" mode="out-in">
<RouterView :key="$route.fullPath" />
</transition>
</main>
</div>
</section>
</div>
User profile update function:
ApiService.putUserProfile(this.user)
.then(() => {
this.loading = false
this.$router.push('/admin/users')
}
})
.catch(err => {
if (err.response) {
this.errors = err.response.data
} else {
if (err.request) {
this.errors = err.request
} else {
this.errors = err.message
}
}
this.loading = false
console.error('Error from update', err)
})
after you update the detail of the user you can fire an event which may will fetch the data from the server behind the scene.
methods:{
loadData(){
await this.$store.dispatch('getSSOUser') ;
await this.$store.dispatch('fetchUserProfiles');
await this.setUser()
this.isBusy = false
},
updateInfo() {
this.form.put('api/profile').then((response) => {
let userData = response.data.userdata;
Fire.$emit('ProfileEvent');
}
},
},
created(){
this.loadData();
Fire.$on('ProfileEvent', () => {
this.loadData();
});
}
may be firing an event after the there is any changes saved in the profile page and executing the function that is called when the component is created after the fired event may resolve your problem. Hope you will get the idea from above code example.

REACT - defaultChecked don't render check attribute on second load

I got my component who won't check the radio when i go to the /view/:id for the second time. I started in my list component with react-router at the index of the site, i click on the view button of an element, the radio is checked, i return in my list and go to another or the same element and it's not checked anymore. When i inspect the component in the React developer tool, the radio has the defaultChecked=true property.
import React from 'react';
import { connect } from 'react-redux';
class LicenseRadios extends React.Component {
buildRadios() {
let { licenses, activeValue } = this.props;
return licenses.map(license => {
let checked = false;
if(activeValue !== undefined && activeValue === license.id){
checked = true;
}
return (
<div key={license.id} className="col l2">
<p>
<input name="license" type="radio" id={'licenseRdo_' + license.id} value={license.id} defaultChecked={checked} />
<label htmlFor={'licenseRdo_' + license.id}>{license.label}</label>
</p>
</div>
);
});
}
render() {
return (
<div className="row">
{this.buildRadios()}
</div>
);
}
}
export default LicenseRadios;
I tried to change the defaultChecked for the checked attribute, but it require an onChange event. I don't understand this problem. Can anybody help me please?
Thank you
The defaultChecked prop is only used during initial render. If you need to update the value in a subsequent render, you will need to use an onChange function to handle value changes.
Check out controlled components in the docs to better understand the problem you're having.
use "undefined" for initial value for defaultChecked and re-render by setting it to true or false
const Example = () => {
[checked,setChecked] = useState(undefined);
useEffect(()=>{
// fetch data
setChecked(true);
});
return (
<input type="checkbox" defaultChecked={checked} onClick={(e)=> changeValue(e)}/>
);
}