Insert data into a dynamic array in Vue / Vuex - arrays

I have the following situation: when executing a change on select I am firing a function that goes to an API and performs a search. The result of this search is a JSON. After executing the search I am trying to get some specific ids, however in my component I am not able to access them.
I'm a beginner, I apologize for mistakes and lack of standards.
Here's my code where I run the #change on page:
<select v-if="users.items" v-model="usuarioId" #change="getById(usuarioId)">
<option value="" disabled selected>Escolha um Usuário</option>
<option v-for="user in users.items" :key="user.id" :value="user.id">{{user.nome}}</option>
</select>
GetById in Module:
import { usuarioSistemaService } from '../_services';
const state = {
all: {}
};
const actions = {
getById({ commit }, id){
commit('getByIdRequest', id);
usuarioSistemaService.getById(id)
.then(
usuarioSistemas => commit('getByIdSuccess', usuarioSistemas),
error => commit('getByIdFailure', error)
);
}
};
const mutations = {
getByIdRequest(state) {
state.all = { loading: true };
},
getByIdSuccess(state, usuarioSistemas) {
state.all = { items: usuarioSistemas };
},
getByIdFailure(state, error) {
state.all = { error };
}
};
export const usuarioSistemas = {
namespaced: true,
state,
actions,
mutations
};
GetById in Service:
function getById(id) {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
return fetch(`${config.apiUrl}/usuariosistema/${id}`, requestOptions).then(handleResponse);
}
function handleResponse(response) {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
if (response.status === 401) {
// auto logout if 401 response returned from api
logout();
location.reload(true);
}
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
}
With the result I want to insert data (usuarioSistema.sistemaId) into this array -> systemId:
<script>
import { mapState, mapActions } from 'vuex'
export default {
data () {
return {
usuarioId: '',
sistemaId: [],
}
}
}
I tried to create a javascript function in "methods:" for this, but the object always comes empty. I also tried to create something invisible on the page to feed this array, but it did not work.
Could you help me, please?
Thanks

You can access store data in a component via the computed object and Vuex's mapGetters helper function:
https://vuex.vuejs.org/guide/getters.html#the-mapgetters-helper
Assuming that you have already performed the API call, you can do it like this in your component:
computed: {
...mapGetters({
'usuarioSistemas': 'usuarioSistemas/all'
})
}
In the code, you should able to access it via this.usuarioSistemas (it is an alias for usuarioSistemas/all).

Related

Is there a way to check nested resources in react-admin's matchSuggestion?

The goal is to make an AutocompleteInput check for the filter value not only in the suggestion list directly, but also in the suggestions' references to different resources.
Specifically, say a Quote has a reference to a Contact and to an Address, and the user enters 'abc' in the input. Now, a Quote whose address contains 'abc' should also be displayed in the suggestion list.
The most elegant way would be to use the useGetOne hook like in the following code snippet but you can't call that hook from outside a React component.
const matchAnyNested = (filter, value) => {
if (matchAnyField(filter, value)) return true;
const { data: contact } = useGetOne('contacts', value.contact_id);
if (matchAnyField(filter, contact)) return true;
const { data: account } = useGetOne('accounts', contact.account_id);
if (matchAnyField(filter, account)) return true;
for (let item of value.part_items) {
const part = useGetOne('parts', item.part_id);
if (matchAnyField(filter, part)) return true;
}
return false;
};
[...]
<AutocompleteInput ... matchSuggestion={matchAnyNested} />
Is there a way to fetch records from within the matchSuggestion function or some other way to validate suggestions based on nested records ? Thanks for any help
Because of the React rules of hooks, this doesn't seem to be possible. I ended up implementing this filtering functionality in the backend.
The useGetOne hook, just like other dataProvider hooks, accepts an enabled option. The example from the react-admin documentation shows its usage:
// fetch posts
const { ids, data: posts, loading: isLoading } = useGetList(
'posts',
{ page: 1, perPage: 20 },
{ field: 'name', order: 'ASC' },
{}
);
// then fetch categories for these posts
const { data: categories, loading: isLoadingCategories } = useGetMany(
'categories',
ids.map(id=> posts[id].category_id),
// run only if the first query returns non-empty result
{ enabled: ids.length > 0 }
);
It applies to your case:
const matchAnyNested = (filter, value) => {
const { data: contact } = useGetOne(
'contacts',
value.contact_id,
{ enabled: !matchAnyField(filter, value) }
);
const { data: account } = useGetOne(
'accounts',
contact.account_id,
{ enabled: !matchAnyField(filter, contact) }
);
// ...
};
This won't solve your problem in the loop, though, because of the rules of hooks.
If you do need that loop, your best bet is to use the useDataProvider hook to call the dataProvider directly:
const matchAnyNested = async (filter, value) => {
const dataProvider = useDataProvider();
if (matchAnyField(filter, value)) return true;
const { data: contact } = await dataProvider.getOne('contacts', { id: value.contact_id });
if (matchAnyField(filter, contact)) return true;
const { data: account } = await dataProvider.getOne('accounts', { id: contact.account_id });
if (matchAnyField(filter, account)) return true;
for (let item of value.part_items) {
const part = await dataProvider.getOne('parts', { id: item.part_id });
if (matchAnyField(filter, part)) return true;
}
return false;
};

fetching data from Sanity returns undefined (Next.js + Sanity)

I have page where I want to add some actualities. These actualities will be first set in the Sanity and then fetched via Next.js .
My Sanity schema
export default{
name:"actuality",
title:"Aktuality",
type:"document",
fields:[
{
name:"headline",
title:"Nadpis",
type:"string"
},
{
name:"publishedAt",
title:"Datum zveřejnění",
type:"datetime"
},
{
name:"body",
title:"Text",
type:"blockContent"
}
],
preview:{
select:{
title:"headline",
}
}
}
Problem is in fetching the data.
If I do this it will work, but will return only first actuality in the Sanity
export const getServerSideProps = async (pageContext: any) => {
const query = `*[ _type == "actuality"][0]`;
const recipe = await client.fetch(query);
console.log(recipe);
if (!recipe) return { props: null, notFound: true };
else
return {
props: {
headline: recipe.headline,
publishedAt: recipe.publishedAt,
body: recipe.body,
},
};
};
But if I remove the [0] it will throw error: "Reason: undefined cannot be serialized as JSON. Please use null or omit this value."
What do I need to change in order to get an array of Actualities?
Wrap the response in a data object to serialize and call {data} in your page props like this:
export const getServerSideProps = async (pageContext: any) => {
const query = `*[ _type == "actuality"]`;
const recipe = await client.fetch(query);
console.log(recipe);
if (!recipe) return { props: null, notFound: true };
else
return {
props: {
data: {
headline: recipe.headline,
publishedAt: recipe.publishedAt,
body: recipe.body,
},
},
};
};
Few things:
it returns an array if you remove [0], you can do return just the data, regardless an array or not.
props: {
data: recipe
}
if you want to return single data with obj vallue as multiple props
props: {...recipe}

Local storage handling in react in another class

I had a component that each time something was added to state was added to local storage as well. It was deleted from local storage on componentWillUnmnout. I was told to prepare an indirect abstract layer for local storage handling in order to follow single responsibility principle.
I am confused how this could be done, can someone give an example of such layer, class?
componentWillUnmount() {
localStorage.removeItem('currentUser');
}
static getDerivedStateFromProps(nextProps) {
const currUser = JSON.parse(
localStorage.getItem('currentUser')
);
if (
currUser && nextProps.users.some(
(user) => user.id === currUser.id
)
) {
return {
user: currUser,
};
}
return null;
}
const onSelect = (
user
) => {
this.setState({
user,
});
localStorage.setItem('currentUser', JSON.stringify(user));
}
private onRemove = () => {
this.setState({
user: null,
});
localStorage.removeItem('currentUser');
}
Applying single responsibility principle here might be over-programming, since Javascripts is not OOP. But if you need, there are some concerns with using localStorage directly that can be separated:
Your component doesn't need to know where you store persistent data. In this case, it doesn't need to know about the usage of localStorage.
Your component doesn't need to know how you store the data. In this case, it doesn't need to handle JSON.stringify to pass to localStorage, and JSON.parse to retrieve.
With those ideas, an interface for localStorage can be implemented like so
const Storage = {
isReady: function() {
return !!window && !!window.localStorage;
},
setCurrentUser: function(user) {
if (!this.isReady()) throw new Error("Cannot find localStorage");
localStorage.setItem('currentUser', JSON.stringify(user));
return true;
},
getCurrentUser: function() {
if (!this.isReady()) throw new Error("Cannot find localStorage");
if (localStorage.hasOwnProperty('currentUser'))
{
return JSON.parse(localStorage.getItem('currentUser'));
}
return null;
},
removeCurrentUser: function() {
if (!this.isReady()) throw new Error("Cannot find localStorage");
localStorage.removeItem('currentUser');
return true;
}
}
By importing Storage object, you can rewrite your component:
componentWillUnmount() {
Storage.removeCurrentUser();
}
static getDerivedStateFromProps(nextProps) {
const currUser = Storage.getCurrentUser();
if (
currUser && nextProps.users.some(
(user) => user.id === currUser.id
)
) {
return {
user: currUser,
};
}
return null;
}
const onSelect = (
user
) => {
this.setState({
user,
});
Storage.setCurrentUser(user);
}
private onRemove = () => {
this.setState({
user: null,
});
Storage.removeCurrentUser();
}

how to get the result from recursive promises in a redux action

I've searched the net, and I can't find out a solution. My final goal is to pull all the data from a dynamodb table. The problem is when a table is bigger than 1MB, in the response I'll get one chunk of data and a LastEvaluatedKey parameter (which provides the index I can use in the next call to get the next chunk). The scan operation is documented here if needed.
I'm using reactjs, redux and redux-thunk in my app.
I have used promises moderately in the single or chained formats, but this one is more challenging that I could resolve so far. What puzzles me is the fact that the new calls can not be made without receiving the previous response, so the calls can not be done simultaneously in my opinion. In another hand since the scan operation is a promise (as far as I understand) if I try to return a promise from my own method the action does not receive the results.
I'm very confused and I really like to understand how I can get this to work.
action:
function getDynamodbTableRecords(tableName) {
return dispatch => {
dispatch(request());
var recordsSet = [];
var data = myAwsService.getTableRecords(tableName, null) || {Items:[]};
if (data.Items.length > 0){
data.Items.map(record => {
recordsSet.push(record);
});
dispatch(success(recordsSet));
} else {
dispatch(failure("No Records Found!"));
}
};
function request() { return { type: DATA_LOADING, selectedTable: tableName } }
function success(tableRecords) { return { type: DATA_LOAD_SUCCESS, tableRecords } }
function failure(error) { return { type: DATA_LOAD_FAILED, errors: error } }
}
myAwsService:
function getTableRecords(tableName, lastEvaluatedKey = null) {
getRecordsBatch(tableName, lastEvaluatedKey)
.then(
data => {
if (data.LastEvaluatedKey) {
return getTableRecords(tableName, data.LastEvaluatedKey)
.then(
nextData => {
data.Items = data.Items.concat(nextData.Items);
}
)
}
return data;
}
)
}
function getRecordsBatch(tableName, lastEvaluatedKey = null) {
var awsDynamodb = new DynamoDB();
let params = { TableName: tableName };
if (lastEvaluatedKey) {
params['ExclusiveStartKey'] = lastEvaluatedKey;
}
return new Promise((resolve, reject) => {
awsDynamodb.scan(params, function(err, data) {
if (err) {
reject(err);
}
return resolve(data);
});
});
}
Not sure if your recursive promise is working but I'd do it like this:
function getTableRecords(
tableName,
lastEvaluatedKey = null,
result = { Items: [] }
) {
return getRecordsBatch(tableName, lastEvaluatedKey).then(
data => {
if (data.LastEvaluatedKey) {
return getTableRecords(
tableName,
data.LastEvaluatedKey,
{
...data,
Items: result.Items.concat(data.Items),
}
);
}
return {
...data,
Items: result.Items.concat(data.Items),
};
}
);
}
The action should also dispatch the data.Items and not the promise that getTabelRecords returns and you probably want to dispatch failure action if something goes wrong:
function getDynamodbTableRecords(tableName) {
return async dispatch => {
dispatch(request());
//you probably want the data, not a promise of data
try {
var data = await myAwsService.getTableRecords(
tableName,
null
);
if (data.Items.length > 0) {
//no reason to have the temporary recordSet variable
dispatch(success(data.Items.map(record => record)));
} else {
dispatch(failure('No Records Found!'));
}
} catch (e) {
dispatch(failure(e.message));
}
};
function request() {
return { type: DATA_LOADING, selectedTable: tableName };
}
function success(tableRecords) {
return { type: DATA_LOAD_SUCCESS, tableRecords };
}
function failure(error) {
return { type: DATA_LOAD_FAILED, errors: error };
}
}

How do I update my apollo data after subscribing to a query using client.subscribe?

I'm trying to subscribe to different queries than I am performing as my root query. This is because subscriptions cannot watch the connections of a child node on my graphql server. So instead I subscribe to each child connection I need, and would like to update the view with the data I recieve. Here's what I have so far:
client.subscribe({
query: queries[queryName],
variables,
updateQuery: (previousResult, { subscriptionData }) => {
console.log('this never happens');
//this would be where I make my modifications if this function was ever called
return {};
},
}).subscribe({
next(resp) {
//this function is called however I still don't know how to update the view with the response.
that.newData(resp,queryName);
},
error,
complete,
})
Here's some relevant sample code:
subscribe(fromID, toID, updateQueryViaSubscription) {
const IM_SUBSCRIPTION_QUERY = gql`
subscription getIMsViaSubscription($fromID: String!, $toID: String!){
IMAdded(fromID:$fromID, toID: $toID){
id,
fromID,
toID,
msgText
}
}
`;
this.subscriptionObserver = this.props.client.subscribe({
query: IM_SUBSCRIPTION_QUERY,
variables: { fromID: this.fromID, toID: this.toID },
}).subscribe({
next(data) {
const newMsg = data.IMAdded;
updateQueryViaSubscription((previousResult) => {
// if it's our own mutation, we might get the subscription result
// after the mutation result.
if (isDuplicateIM(newMsg, previousResult.instant_message)) {
return previousResult;
}
// update returns a new "immutable" list with the new comment
// added to the front.
return update(
previousResult,
{
instant_message: {
$push: [newMsg],
},
}
);
});
},
error(err) {
console.error('err', err); },
});
}
You'll notice that updateQueryViaSubscription gets passed to subscribe as a parameter. Here's where it comes from in my app:
//NOTE: NAME OF 2ND PROPERTY IN DATA OBJECT ("instant_message" in this example) MUST BE IDENTICAL TO NAME OF RESOLVER
//OTHERWISE DATA WILL NOT LOAD
const CreateIMPageWithDataAndMutations = graphql(GETIMS_QUERY, {
options({ toID }) {
const fromID = Meteor.userId();
return {
variables: { fromID: `${fromID}`, toID: `${toID}`}
};
}
,
props({ data: { loading, instant_message, updateQuery } }) {
//debugger;
return { loading, instant_message, updateQueryViaSubscription: updateQuery };
},
});
export default compose(
CreateIMPageWithMutations,
CreateIMPageWithDataAndMutations,
withApollo
)(CreateIM);
export { GETIMS_QUERY };
Notice that the function updateQuery gets passed into the component from Apollo, and renamed by my code to updateQueryViaSubscription prior to being added to the component's props.
My code calls subscribe in componentDidMount:
componentDidMount() {
const userIsLoggedIn = Meteor.userId() ? true : false;
const {toID, ApolloClientWithSubscribeEnabled} = this.props;
if (userIsLoggedIn && toID){
this.fromID = Meteor.userId();
this.toID = toID;
this.subscribe(this.fromID, this.toID, this.props.updateQueryViaSubscription);
}
}
...and unsubscribes in componentWillUnmount:
componentWillUnmount() {
if (this.subscriptionObserver) {
this.subscriptionObserver.unsubscribe();
}
}
I hope this info is helpful.

Resources