Local storage handling in react in another class - reactjs

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

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

Reusing the result of an http call

I have the following use case:
Two visual grids are using two methods to load the data to display. These methods are automatically called by the grids and this part cannot be changed, it's by design:
loadDataForGrid1 = (params: any): any => {
return this.httpService.getData().then((response) => {
return response.dataGrid1;
}, (err) => {
});
}
loadDataForGrid2 = (params: any): any => {
return this.httpService.getData().then((response) => {
return response.dataGrid2;
}, (err) => {
});
}
Everything is working fine but my problem is performance. Since the getData method does an http request that is quite huge, calling it twice like Im doing right now is not acceptable. It there a way to solve this problem by doing only one call? Like caching the data so that they are reusable by the second call?
Im using typescript and angularjs
Edit:
Something like this would not work since the result would not be available when the grids load the data:
result: any;
// called at the beginning, for example contructor
loadData = (params: any): any => {
return this.httpService.getData().then(result => {
this.result = result;
});
}
loadDataForGrid1 = (params: any): any => {
return this.result.gridGrid1;
}
loadDataForGrid2 = (params: any): any => {
return this.result.gridGrid2;
}}
Using the answer suggested by #georgeawg generates the following javascript (which does 2 calls)
this.loadDataForGrid1 = function (params) {
_this.promiseCache = _this.promiseCache || _this.httpService.getData();
return _this.promiseCache.then(function (response) {
return response.gridGrid1;
}, function (err) {
});
};
this.loadDataForGrid2 = function (params) {
_this.promiseCache = _this.promiseCache || _this.httpService.getData();
return _this.promiseCache.then(function (response) {
return response.gridGrid2;
}, function (err) {
});
};
You can always store the the data array in a variable on the page for SPA. If you want to use the data over different pages, you can use localStorage to 'cache' the data on the client-side.
localStorage.set("mydata", response.dataGrid1);
localStorage.get("mydata");
FYI, i does not seem you are using typescript, but rather native javascript :-)
--
Why don't you do something like this, or am i missing something?
$scope.gridData = {};
loadDataForGrid1 = (params: any): any => {
return this.httpService.getData.then((response) => {
$scope.gridData = response;
}, (err) => {
}).finally(function(){
console.log($scope.gridData.gridData1);
console.log($scope.gridData.gridData2);
});
}
What you can do is store the returned variable into a service variable and then do a check if it already exists.
dataGrid;
loadDataForGrid1 = (params: any): any => {
if(!this.dataGrid) {
return this.httpService.getData.then((response) => {
this.dataGrid = response;
return this.dataGrid.dataGrid1;
}, (err) => {
});
}
return this.dataGrid.dataGrid1;
}
loadDataForGrid2 = (params: any): any => {
if(!this.dataGrid) {
return this.httpService.getData().then((response) => {
this.dataGrid = response;
return this.dataGrid.dataGrid2;
}, (err) => {
});
}
return this.dataGrid.dataGrid2;
}
Something like this should work. Every time you call loadDataForGrid1 or loadDataForGrid2 you will first check if the data is already there - therefore you make an API call only once.
The solution is to cache the promise and re-use it:
var promiseCache;
this.loadDataForGrid1 = (params) => {
promiseCache = promiseCache || this.httpService.getData();
return promiseCache.then(result => {
return result.gridGrid1;
});
}
this.loadDataForGrid2 = (params) => {
promiseCache = promiseCache || this.httpService.getData();
return promiseCache.then(result => {
return result.gridGrid2;
});
}
Since the service immediately returns a promise, it avoids the race condition where the
second XHR is started before the first XHR returns data from the server.
You mean that would be a javascript solution? But how to do it with typescript then?
JavaScript supports private variables.1
function MyClass() {
var myPrivateVar = 3;
this.doSomething = function() {
return myPrivateVar++;
}
}
In TypeScript this would be expressed like so:
class MyClass {
doSomething: () => number;
constructor() {
var myPrivateVar = 3;
this.doSomething = function () {
return myPrivateVar++;
}
}
}
So, after many hours I came to the following solution. It's a bit a hack but it works.
In the initialization (constructor or so) Im loading the data:
this.httpService.getData().then((response) => {
this.data1 = response.dataGrid1;
this.data2 = response.dataGrid2;
// other properties here...
this.isReady= true;
}, (err) => {
});
then I wrote an ugly wait method
wait(): void {
if (this.isReady) {
return;
} else {
setTimeout(this.wait, 250);
}
}
Finally, my two methods look like this
loadDataForGrid1 = (params: any): any => {
this.wait();
return this.$q.resolve(this.data1);
}
loadDataForGrid2 = (params: any): any => {
this.wait();
return this.$q.resolve(this.data2);
}

Insert data into a dynamic array in Vue / Vuex

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

Cannot see firebase data in application - react native

I have set up a call to fetch data from my firebase database using react native.
Database structure
Code inside FirebaseList.js
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentWillMount() {
firebase.database().ref('/signposts/items').on('value', snapshot => {
const dataArray = [];
const result = snapshot.val();
for (const data in result) {
dataArray.push(data);
}
this.setState({ data: dataArray });
console.log(this.state.data);
});
}
render() {
return (
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<Text>{item}</Text>
)}
keyExtractor={item => item}
/>
);
}
I believe the connection to firebase is successful as I can build and run the application. However, when the component renders, I do not see my two rows of data 'row1' and 'row2'.
You said your code is right then also check that this rules are set
{
"rules": {
"foo": {
".read": true,
".write": false
}
}
}
Note : - When you use the above rule your database is open for all Read more here. Make you use update the rules once you push to production.
If console.log(dataArray) shows an empty array (assuming that console.log() works...), try checking your connection:
componentDidMount() {
const ref = firebase.database().ref('/signposts');
const checkConnection = firebase.database().ref(`.info/connected`);
checkConnection.on('value', function(snapshot) {
if (snapshot.val() === true) { /* we're connected! */
firebase.database().ref('/signposts').on('value', snapshot => {
const dataArray = [];
const result = snapshot.val();
for (const data in result) {
dataArray.push(data);
}
if (dataArray.length === 0)
console.log("No data.")
else
this.setState({ listViewData: dataArray });
});
} else { /* we're disconnected! */
console.error("Check your internet connection.")
}
}
}

Ionic 2 inspect observable before returning it

I'm new to Ionic and Angular, and coming from years of .NET development. I'm trying a few examples online to build login prototype with Ionic 2.
I got WebAPI working in the background simply returning JSON true or false depending if credentials passed is correct or not.
I got authentication provider looking like this:
public login(credentials) {
if (credentials.email === null || credentials.password === null) {
return Observable.throw("Please insert credentials");
} else {
this.result = this.http.post(this.CONST.APIUrl, JSON.stringify(credentials), new RequestOptions({headers: this.contentHeader})).map(res => res.json())
if (this.result)
{
this.currentUser = new User('Simon', 'saimon#devdactic.com');
}
return this.result;
}}
and login page looking like this:
public login() {
this.showLoading()
this.auth.login(this.registerCredentials).subscribe(allowed => {
if (allowed) {
setTimeout(() => {
this.loading.dismiss();
this.nav.setRoot(HomePage)
});
} else {
this.showError("Access Denied");
}
},
error => {
this.showError(error);
});
}
At the moment it always logs person in. I understand that this is happening because this.result always has a value. But how would I check data returned from the API before allowing person to login?
You can use Observable.do to create side effects for an Observable.
In login function:
public login(credentials) {
if (credentials.email === null || credentials.password === null) {
return Observable.throw("Please insert credentials");
} else {
this.result = this.http.post(this.CONST.APIUrl,
JSON.stringify(credentials), new RequestOptions({headers: this.contentHeader}))
.map(res => res.json())
.do(userData=>{
//check userData and set current user
this.currentUser = new User('Simon', 'saimon#devdactic.com');
return userData;//make sure to return the value to subscribe
});
return result;
}}

Resources