Options not displaying in react-select dropdown, but can see the array looks good in console.log - react-select

I have an async api call (using axios) to fetch all my 'options' from the server. I can see the response coming in even modified the API to return the options with the "label" and "value" in the array.
I have tried using the component which also did not work. I am not even trying to set the value yet, just want the list of options to display.
Have stripped the component to its bare minimum
Here is my complete component:
import Api from "../API/Api";
import Select from "react-select/async";
// so I have access to the AccessToken for any requests I need to send..
// Might want to 'catch' a 401 error and retry the submission, but lets first
// see if the form works properly
import { useCookies } from "react-cookie";
const GetSuppliers = () => {
const [cookies] = useCookies("accessToken");
const [input, setInput] = useState("");
const [suppliers, setSuppliers] = useState([]);
const askApi = async searchInput => {
await Api.get("supplier", {
headers: {
Authorization: "Bearer ".concat(cookies.accessToken)
},
params: {
searchString: ""
}
}).then(response => {
setSuppliers(response.data);
return response.data;
});
};
useEffect(() => {
askApi();
}, []);
if (suppliers.length == 0) {
return <div>Loading ... </div>;
} else {
console.log(suppliers);
return (
<div className="DropdownField">
<Select cacheOptions options={suppliers} defaultOptions />
</div>
);
}
};
export default GetSuppliers;
The console.log(suppliers); returns :
1: {value: 5609, label: "AAE02-01-AP", name: "Supplier name 2"}
2: {value: 6197, label: "AAG01-01-AP", name: "Supplier name 3"}
3: {value: 6402, label: "AAL01-01-AP", name: "Supplier name 4"}
4: {value: 6486, label: "AAN01-02-AP", name: "Supplier name 5"}
So I am expecting it to work. Where am I missing the plot?

This was a rookie mistake (at least I am a rookie, so nobody can blame me).
During my original struggles I attempted the Async select, so the import was still reference the "Async" select.
Changed
import Select from "react-select/async";
to:
import Select from "react-select";
And voila, options showing!

Related

Asynchronous problem when dispatch the hook with Context API

I have my store using the Context API to create a user, where there are two screens for completing the registration.
The first screen has several input fields that I control with React Hook Form, and when I enter all the data, I navigate to the other screen and fill in 2 more inputs, one being a checkbox with several options.
On the first screen, I can get all the information from the user, and on the second screen, I can get only the values ​​of the checked boxes, and the location input that I have, it cannot insert into the state of my Context API.
src/store/CreatePersonalAccount/index.tsx
import { createContext, useReducer } from 'react'
import { reducer } from './reducer'
export const initialState: PersonalAccount = {
avatar: '',
name: '',
email: '',
password: '',
location: '',
occupationCategory: '',
occupationSubcategories: []
}
export const CreatePersonalAccountContext = createContext<
IContext<PersonalAccount>
>({
state: initialState,
dispatch: () => {}
})
export const CreatePersonalAccountContextProvider: React.FC = ({
children
}) => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<CreatePersonalAccountContext.Provider value={{ state, dispatch }}>
{children}
</CreatePersonalAccountContext.Provider>
)
}
CreatePersonalAccount/action.ts
export enum CreatePersonalAccountActions {
SET_CREATE_PERSONAL_ACCOUNT = 'SET_CREATE_PERSONAL_ACCOUNT',
}
export const setPersonalAccount = (payload: Partial<PersonalAccount>) => {
return {
type: CreatePersonalAccountActions.SET_CREATE_PERSONAL_ACCOUNT,
payload
}
}
CreatePersonalAccount/reducer.ts
import { initialState } from './index'
import { CreatePersonalAccountActions as ActionTypes } from './action'
export const reducer = (state: PersonalAccount, action: IAction) => {
switch (action.type) {
case ActionTypes.SET_CREATE_PERSONAL_ACCOUNT:
return {
...state,
...action.payload
}
case ActionTypes.RESET:
return {
...initialState
}
default:
return state
}
}
In my user creation routes, I have hooks that can be used both on the first screen and on the second screen.
import { useContext } from 'react'
import { CreatePersonalAccountContext } from 'store/CreatePersonalAccount'
import {
setPersonalAccount,
resetPersonalAccount
} from 'store/CreatePersonalAccount/action'
export function usePersonalAccount() {
const { state, dispatch } = useContext(CreatePersonalAccountContext)
const setPersonalInformation = (state: PersonalAccountInformation) =>
dispatch(setPersonalAccount(state))
const setPersonalLocation = (location: string) =>
dispatch(setPersonalAccount({ location }))
const setPersonalOccupation = (
params: Partial<
Pick<PersonalAccount, 'occupationCategory' | 'occupationSubcategories'>
>
) => dispatch(setPersonalAccount(params))
const clearPersonalAccount = () => dispatch(resetPersonalAccount())
const submit = () => {
console.log('Enviar pra API', state)
}
return {
state,
submit,
setPersonalLocation,
clearPersonalAccount,
setPersonalOccupation,
setPersonalInformation
}
}
The setPersonalInformation function is used on the first screen with the user information, and the other functions setPersonalLocation and setPersonalOccupation are used on the second screen of the user information, the problem is the setPersonalLocation function that cannot update the state with the new location property.
This function below is the function I use in the first info screen, and it works correctly, updating the avatar, name, document, email, password fields.
SignUp/PersonalAccount/PersonalInformation/index.tsx
...
function handleRegisterPersonal(data: PersonalAccountInformation) {
const personalInformationData = {
...data,
avatar: image
}
setPersonalInformation(personalInformationData)
navigation.navigate('PersonalLocationAndOccupation')
}
...
And on the screen where I would like to update the location property is the function below.
SignUp/PersonalAccount/PersonalLocationAndOccupation
function registerPersonalAccountForm({ location }: PersonalAccountLocation) {
setPersonalLocation(location)
navigation.navigate('Welcome')
}
And my final state is this, but without the location property assigned
Object {
"avatar": "file:///Users/vagnerwentz/Library/Developer/CoreSimulator/Devices/BCB82E0B-A81E-495A-9917-80C315CEE735/data/Containers/Data/Application/FB8A6B2E-3F8F-4EBE-9AC0-128BBC80A84E/Library/Caches/ExponentExperienceData/%2540anonymous%252FPlugApp0800-fe3f8358-2821-44a3-a206-d0e1909b03e0/ImagePicker/FF615EB9-E385-4397-9198-E49FE6CD8CE4.jpg",
"documentation": "1234567890",
"email": "email#email.com",
"location": "",
"name": "John Doe",
"occupationCategory": "Developer",
"occupationSubcategories": Array [
Object {
"id": "frontend",
"title": "Front-end",
},
Object {
"id": "backend",
"title": "Back-end",
},
],
"password": "1234567890",
}
I've already tried to put it in the property inside the useForm, assign the context field, with my Context, but without success, I also tried to put a setTimeout to simulate an asynchronous call, and I didn't succeed either.

How to fetch data with slot in Vue?

I have a code block like this.
<template slot="name" slot-scope="row">{{row.value.first}} {{row.value.last}}</template>
Also I have a header.
{ isActive: true, age: 38, name: { first: 'Jami', last: 'Carney' } },
{ isActive: false, age: 27, name: { first: 'Essie', last: 'Dunlap' } },
{ isActive: true, age: 40, name: { first: 'Thor', last: 'Macdonald' } },
This code is running clearly but I want to show data from my API. Which terms do I need to know? I used Axios before in React. Where can I define Axios method? Do I need to change the template slot instead of :v-slot ?
Although you can make API calls directly from inside the component code, it does not mean that you should. It's better to decouple API calls into a separate module.
Here's a good way to do it which properly follows Separation of Concern (SoC) principle:
Create a directory services under src if it's not already there.
Under services, create new file named api.service.js.
api.service.js
import axios from 'axios';
const baseURL = 'http://localhost:8080/api'; // Change this according to your setup
export default axios.create({
baseURL,
});
Create another file peopleData.service.js
import api from './api.service';
import handleError from './errorHandler.service'; // all the error handling code will be in this file, you can replace it with console.log statement for now.
export default {
fetchPeopleData() {
return api.get('/people')
.catch((err) => handleError(err));
},
// All other API calls related to your people's (users'/customers'/whatever is appropriate in your case) data should be added here.
addPerson(data) {
return api.post('/people', data)
.catch((err) => handleError(err));
},
}
Now you can import this service into your component and call the function.
<template>
... Template code
</template>
<script>
import peopleDataService from '#/services/peopleData.service';
export default {
data() {
return {
rows: [],
};
},
mounted() {
peopleDataService.fetchPeopleData().then((res) => {
if (res && res.status == 200) {
this.rows = res.data;
}
});
},
}
</script>
You haven't given us any idea about your current setup. If you're using Vue-Router, it's better to fetch data in navigation guards, especially if your component is relying on the data: Data Fetching
Simply shift the code from mounted() into a navigation guard. this may not be available, so you will have to use next callback to set rows array, it's explained in the link above.
You can use Axios in methods or mounted.
mounted(){
this.loading = true;
axios
.get(`${this.backendURL}/api/v1/pages/layouts` , authHeader())
.then(response => (this.layouts = response.data.data))
.catch(handleAxiosError);
}
methods: {
/**
* Search the table data with search input
*/
uncheckSelectAll(){
this.selectedAll = false
},
onFiltered(filteredItems) {
// Trigger pagination to update the number of buttons/pages due to filtering
this.totalRows = filteredItems.length;
this.currentPage = 1;
},
handlePageChange(value) {
this.currentPage = value;
axios
.get(`${this.backendURL}/api/v1/pages?per_page=${this.perPage}&page=${this.currentPage}` , authHeader())
.then(response => (this.pagesData = convert(response.data.data),
this.pagesDataLength = response.data.pagination.total));
},
handlePerPageChange(value) {
this.perPage = value;
this.currentPage = 1;
axios
.get(`${this.backendURL}/api/v1/pages?per_page=${this.perPage}&page=${this.currentPage}` , authHeader())
.then(response => (this.pagesData = convert(response.data.data),
this.pagesDataLength = response.data.pagination.total));
},
deletePage(){
this.loading = true
this.$bvModal.hide("modal-delete-page");
window.console.log(this.pageIdentity);
if (!roleService.hasDeletePermission(this.pageIdentity)){
return;
}
axios
.delete(`${this.backendURL}/api/v1/pages/${this.page.id}` , authHeader())
.then(response => (
this.data = response.data.data.id,
axios
.get(`${this.backendURL}/api/v1/pages?per_page=${this.perPage}&page=${this.currentPage}` , authHeader())
.then(response => (this.pagesData = convert(response.data.data),
this.pagesDataLength =
response.data.pagination.total)),
alertBox(`Page deleted succesfully!`, true)
))
.catch(handleAxiosError)
.finally(() => {
this.loading = false
});
}

Vuelidate using Vue 2.6 and Composition API

I'm using the Composition API together with Vue 2 (by using #vue/composition-api) combined with the following two libraries (#vuelidate/core": "^2.0.0-alpha.18,
#vuelidate/validators": "^2.0.0-alpha.15).
I am trying to use sameAs to check whether the entered email and repeated email are a match and return an error if not. Although this is not working as smooth as expected.
This is my validation.js file
import { required, email, maxLength, sameAs } from "#vuelidate/validators";
export default {
email: {
required,
email,
maxLength: maxLength(100),
},
repeatEmail: {
required,
email,
maxLength: maxLength(100),
sameAsEmail: sameAs('email')
},
}
and this is my validation-errors.js file. (probably irrelevant for this question though)
export default {
email: [
{
key: "required",
message: "Email is required.",
id: "emailRequired",
},
{
key: "email",
message: "Wrong format on your email.",
id: "emailFormat",
},
{
key: "maxLength",
message: "Email can't be longer than 100 characters.",
id: "emailLength",
},
],
repeatEmail: [
{
key: "required",
message: "Email is required.",
id: "emailRequired",
},
{
key: "email",
message: "Wrong format on your email.",
id: "emailFormat",
},
{
key: "maxLength",
message: "Email can't be longer than 100 characters.",
id: "emailLength",
},
{
key: "sameAsEmail",
message: "Email isn't matching.",
id: "sameAsEmailFormat",
},
],
}
And this is how I try to use it in my component.
import validations from "#/validation";
import validationErrors from "#/validation-errors";
import { useVuelidate } from "#vuelidate/core";
import { reactive, toRefs } from '#vue/composition-api';
export default {
setup() {
const state = reactive({
email: "",
repeatEmail: "",
});
const $v = useVuelidate(validations, state);
return {
...toRefs(state)
}
}
}
So when I enter the same input in both the inputfield for email and repeatEmail correctly it gives me true as an invalid value.
Built-in validators such as sameAs don't have access to the state, so they aren't supposed to be workable when used like sameAs('email').
This way validators are supposed to be defined in setup function in order to access the state:
const emailRef = computed(() => state.email);
const validations = {
...
repeatEmail: {
...
sameAsEmail: sameAs(emailRef)
},
Otherwise this needs to be done with custom validators instead of built-ins that will access the state on component instance with getCurrentInstance.
if you can use the same validation in vuejs 3 with composition syntax because using compute function get update state
code password = computed(() => state.password);
const validations = {
...
confirm-password: {
...
sameAs: sameAs(emailRef)
},

How to test an Error of a Method in a Vue.js Component using jest

How can I test the method createUser() of my Vue component? I want to test if the createUser() method throws an Error if the firstname < 2 for example. How is this possible?
I'm not really familiar with testing VUE components. It's my first time, so I have no idea how to get access the VUE component and how to submit for a example a username to the component
<script>
import {ApiService} from '../ApiService.js';
import {User} from '../User.js';
//const API_URL = 'http://localhost:8080';
const apiService = new ApiService();
export default {
name: "CreateUser",
data() {
return {
input: {
username: "",
firstname: "",
lastname: "",
title: "",
password: "",
groupId: "",
groups: [],
},
}
},
/.../
methods: {
getAllGroups() {
apiService.getAllGroups().then((data) => {
this.input.groups = data;
});
},
createUser() {
if (this.input.firstname == null || this.input.firstname.length < 2 || this.input.firstname > 50) {
throw ("Firstname to short/long/empty");
} else {
let user = new User(this.input.username, this.input.lastname, this.input.title, this.input.firstname, this.input.password, this.input.groupId)
apiService.createUser(user).then(() => {
location.reload()
});
}
},
I tried the following, but something doesn't not work
import { shallowMount } from '#vue/test-utils';
import UserModal from "../src/views/UserModal";
describe('UsarModal', () => {
it('should throw error when first name is too short', () => {
const myItems = [
{
username: "Heinz",
firstname: "H",
lasname: "Müller"}
]
const wrapper = shallowMount(UserModal, {
input: {
myItems
}
})
expect(wrapper.vm.createUser()).toThrow("Firstname to short/long/empty")
})
})
since in the code, it is throwing an error, so we will need to add a catch block in our test case to test this scenario. PFB example for your case:
try {
wrapper.vm.createUser();
} catch (error) {
expect(error).toBe('Firstname to short/long/empty');
}
let me know if you face any issue.
What you did in your example is very close. You just need to wrap the function call that is going to throw the exception in a lambda, e.g.
expect(() => wrapper.vm.createUser()).toThrow("Firstname to short/long/empty")
As described in the docs: https://jestjs.io/docs/expect#tothrowerror
It's probably doing something like internally wrapping the lambda in a try/catch, but I think using this method is a bit nicer than wrapping in a try/catch in your own test.
Thanks for the tip! But still something is not working right. It seems for me, that it does not accept my mock-data for input. I want to test if a got an error when I use a username which is too short. But even when I put a username that's long enough, the try catch block catches my error as you can see in the attached picture.
import {shallowMount} from '#vue/test-utils'
import UserModal from "./UserModal";
describe('ParentComponent', () => {
test("displays 'Emitted!' when custom event is emitted", () => {
const wrapper = shallowMount(UserModal, {
input: {
username: "asfsf",
firstname: "thomas",
lastname: "bird",
title: "Dr.",
password: "asdasda",
groupId: "2",
groups: [],}
});
try {
wrapper.vm.createUser();
} catch (error) {
expect(error).toBe("username missing");
}
})
});
Error Log

Unit testing HTTP request with Vue, Axios, and Mocha

I'm really struggling trying to test a request in VueJS using Mocha/Chai-Sinon, with Axios as the request library and having tried a mixture of Moxios and axios-mock-adaptor. The below examples are with the latter.
What I'm trying to do is make a request when the component is created, which is simple enough.
But the tests either complain about the results variable being undefined or an async timout.
Am I doing it right by assigning the variable of the getData() function? Or should Ireturn` the values? Any help would be appreciated.
Component
// Third-party imports
import axios from 'axios'
// Component imports
import VideoCard from './components/VideoCard'
export default {
name: 'app',
components: {
VideoCard
},
data () {
return {
API: '/static/data.json',
results: null
}
},
created () {
this.getData()
},
methods: {
getData: function () {
// I've even tried return instead of assigning to a variable
this.results = axios.get(this.API)
.then(function (response) {
console.log('then()')
return response.data.data
})
.catch(function (error) {
console.log(error)
return error
})
}
}
}
Test
import Vue from 'vue'
import App from 'src/App'
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
let mock = new MockAdapter(axios)
describe('try and load some data from somewhere', () => {
it('should update the results variable with results', (done) => {
console.log('test top')
mock.onGet('/static/data.json').reply(200, {
data: {
data: [
{ id: 1, name: 'Mexican keyboard cat' },
{ id: 2, name: 'Will it blend?' }
]
}
})
const VM = new Vue(App).$mount
setTimeout(() => {
expect(VM.results).to.be.null
done()
}, 1000)
})
})
I am not sure about moxios mock adaptor, but I had a similar struggle. I ended up using axios, and moxios, with the vue-webpack template. My goal was to fake retreiving some blog posts, and assert they were assigned to a this.posts variable.
Your getData() method should return the axios promise like you said you tried - that way, we have some way to tell the test method the promise finished. Otherwise it will just keep going.
Then inside the success callback of getData(), you can assign your data. So it will look like
return axios.get('url').then((response) {
this.results = response
})
Now in your test something like
it('returns the api call', (done) => {
const vm = Vue.extend(VideoCard)
const videoCard = new vm()
videoCard.getData().then(() => {
// expect, assert, whatever
}).then(done, done)
)}
note the use of done(). That is just a guide, you will have to modify it depending on what you are doing exactly. Let me know if you need some more details. I recommend using moxios to mock axios calls.
Here is a good article about testing api calls that helped me.
https://wietse.loves.engineering/testing-promises-with-mocha-90df8b7d2e35#.yzcfju3qv
So massive kudos to xenetics post above, who helped in pointing me in the right direction.
In short, I was trying to access the data incorrectly, when I should have been using the $data property
I also dropped axios-mock-adaptor and went back to using moxios.
I did indeed have to return the promise in my component, like so;
getData: function () {
let self = this
return axios.get(this.API)
.then(function (response) {
self.results = response.data.data
})
.catch(function (error) {
self.results = error
})
}
(Using let self = this got around the axios scope "problem")
Then to test this, all I had to do was stub the request (after doing the moxios.install() and moxios.uninstall for the beforeEach() and afterEach() respectively.
it('should make the request and update the results variable', (done) => {
moxios.stubRequest('./static/data.json', {
status: 200,
responseText: {
data: [
{ id: 1, name: 'Mexican keyboard cat' },
{ id: 2, name: 'Will it blend?' }
]
}
})
const VM = new Vue(App)
expect(VM.$data.results).to.be.null
VM.getData().then(() => {
expect(VM.$data.results).to.be.an('array')
expect(VM.$data.results).to.have.length(2)
}).then(done, done)
})