I have been facing some issues with the native base checkbox and AsynStorage. In fact, AsynStorage only accepts strings by default BUT can store boolean variables also, I tried to use that method but I get a string stored every time.
While the checkbox does only accept boolean variables and throws a warning if I tried to use a string and it does not show the previous state of the checkbox (checked or not ).
So, I decided to make my own checkbox using TouchbleOpacity .. So do you guys have any idea how to make it ?
Here is the result i want to achieve:
So, the purpose is to make a checkbox settings page that controls the style of a text in another page and to get the checkbox as left the previous time, for an example : if I check it , I change the page and go back again to the settings page , I need to find it checked (indicating the previous state)
The code is
in the settings page :
toggleStatus() {
this.setState({
status: !this.state.status
});
AsyncStorage.setItem("myCheckbox",JSON.stringify(this.state.status));
}
// to get the previous status stored in the AsyncStorage
componentWillMount(){
AsyncStorage.getItem('myCheckbox').then((value) => {
this.setState({
status: value
});
if (this.state.status == "false") {
this.setState({
check: false
});
}
else if (this.state.status == "true") {
this.setState({
check: true
});
}
if (this.state.status == null) {
this.setState({
check: false
});
}
});
}
render {
return(
...
<CheckBox
onPress={() => { this.toggleStatus() }
checked={ this.state.check }/>
)}
In other page :
componentDidMount(){
AsyncStorage.getItem('myCheckbox').then((value) => {
JSON.parse(value)
this.setState({
status: value
});
});
}
This code change the status after TWO clicks and I don't know why and i get this weird output in the console, every time I click the checkbox
If you take a look at AsyncStorage documentation, you can see that, in fact, the method getItem() will always return a string (inside the promise).
For the problem with the AsyncStorage you should consider trying to parse this string returned to a boolean using this method explained here and then use this parsed value inside the native base checkbox.
But if you want to do your own component, try doing something like this:
export default class Checkbox extends Component {
constructor(){
super();
this.state = { checked: false }
}
render(){
return(
<TouchableOpacity
onPress={()=>{
this.setState({ checked : !this.state.checked });
}}
>
<Image source={this.state.checked ? require('checkedImagePath') : require('uncheckedImagePath')} />
</TouchableOpacity>
);
}
}
You will need to set some style to this image to configure it the way you want.
-- Based on your edition:
I can't see nothing wrong on your toggleStatus() method in settings page, but try changing your componentWillMount() to this:
componentWillMount(){
AsyncStorage.getItem('myCheckbox').then((value) => {
if (value != null){
this.setState({
check: (value == 'true')
});
}
});
}
However in the other page the line you do JSON.parse(value) is doing nothing, once you are not storing the result anywhere.
Related
In my first component I have set up a property like so:
linkEnabled: boolean = false;
and when this is set to false, certain routes will not be able to be accessed by users which I've set up in my html file like so:
<a class="nav-link" [routerLink]="linkEnabled ? ['/libraries']: null" [routerLinkActive]="linkEnabled ? 'active-route' : 'is-disabled'">
This is set to false until a project has been selected, this is done in another component
In the second component I've imported the first one like so:
import { NavSidebarComponent } from '../nav-sidebar/nav-sidebar.component';
and added it to the constructor:
constructor(private store: Store<AppState>,
..........
private navSidebarComponent: NavSidebarComponent
) { }
and in the ngOnit, where the project is set, I call the linkEnabled value and set to true for when project name is not null:
this.projectNameSub = this.store.pipe(select(ProjectSelectors.selectCurrentProjectName))
.subscribe(projectName => {
this.projectName = projectName;
if(this.projectName !=null) {
this.navSidebarComponent.linkEnabled = true;
}
});
The issue I am having, is that I am not sure how to get the first component to listen to the changes so that it know that linkEnabled has now been set to true? As at the moment it just sees it as false so I know I am missing a step but I'm just not sure what. Is there a way to subscribe to the value so that it can listen to it changing in the ngOnInit in the first component?
I had thought of creating a function like so within the first component:
public activateRoutes(): Observable<boolean> { console.log("activate routes called"); return of(this.linkEnabled = true); }
and then in the ngOnit do something like:
this.activateRoutes().subscribe((link) => { this.linkEnabled = link; })
and then in the ngOnit in the second component, instead of doing:
this.navSidebarComponent.linkEnabled = true;
I would do: this.navSidebarComponent.activateRoutes();
However all that is happening is that on page load, the linkEnabled is set to true and it's not working at all as I need it to
Solved this issue by creating new action, reducer and selectors file to store this in the store and could then do:
this.routeEnabledSub = this.store.pipe(select(RouteSelectors.selectRouteEnabled))
.subscribe(routeEnabled => {
this.routeEnabled = routeEnabled;
});
in the first component
and then in the second one, update it like so:
if(this.projectName !=null) {
this.store.dispatch(RouteActions.setRouteActive(
{ routeActive: true }));
} else {
this.store.dispatch(RouteActions.setRouteActive(
{ routeActive: false }));
and in html check for linkEnabled being set like so:
[routerLinkActive]="linkEnabled ? 'active-route' : 'is-disabled'"
I have created a simple web (stencil) component AuthGuard, not to be be confused with Angular's AuthGuard.
The purpose of this component is to check if the user is logged in.
If yes, render the slot html.
If not, render the Signup button.
The component code is as follows:
import { Component, Host, h } from '#stencil/core';
import { Build, State } from '#stencil/core';
import { AuthService } from 'auth/auth.service';
import { ConfigService } from 'common/config.service';
#Component({
tag : 'auth-guard',
styleUrl : 'auth-guard.css',
shadow : true,
})
export class AuthGuard {
#State() canRender : boolean = false;
componentWillLoad() {
if (Build.isBrowser) {
const timerId = setInterval(() => {
if (AuthService.isInitialized) {
AuthService.vol$.subscribe(_u => {
this.canRender= true;
});
clearInterval(timerId);
}
}, ConfigService.loadTime);
}
}
render() {
console.log('auth guard :: render', this.canRender, AuthService.me);
return (
<Host>
{
this.canRender ? (
AuthService.me && AuthService.me.id.length > 0 ? (
<slot></slot>
) : (
<ion-button
href="/signup"
routerDirection="forward"
color="danger">
Signup
</ion-button>
)
): null
}
</Host>
);
}
}
Now in the other file, I use the following code:
<auth-guard slot='end'>
<volunteer-mini volunteer={AuthService.me}></volunteer-mini>
</auth-guard>
With this what I am expecting is
Nothing to be rendered, till this.canRender becomes true.
Once this.canRender becomes true, If AuthService.me is valid, render the slot HTML,
If AuthService.me is null, render signup button.
But seems when this.canRender is false, it tried to render volunteer-mini the slot HTML, which is a problem. Since volunteer-mini internally depends on AuthService.me, which is not yet initialized.
But once this.canRender becomes true, other 2 scenarios are working fine.
It's in general a bad idea to write an auth-guard using stencil. The core problem is that your slot exists before your component has initialized.
Therefor, using your current code, you'd have to manually remove the slot after you decided that you don't have the rights.
Additionally, if you do not define a slot position, but still provide a slot-content in your parent, it will still be appended to your inner children.
To resolve this problem, you can refactor your component to an function, like <Host>, but this has other pits to consider.
I'm actually trying to toggle a class on an element when user clicked. But unfortunately, my code only set class for single element. It looks like the view is not refreshing for subsequent clicks, even the set class is not removing. But my store is properly updating.
Here is my code.
class MyInterests extends Component {
constructor(props) {
super(props);
this.state = {selected: []};
}
toggleChip(id, index, event){
const { interestChanged } = this.props;
let index = this.state.selected.indexOf(id);
if(index === -1){
this.state.selected.push(id);
}else{
this.state.selected.splice(index, 1);
}
interestChanged(this.state.selected);
}
render() {
const {classes, myinterests: { categories, userinterest } } = this.props;
const getClassNames = (id) => {
return classNames(classes.chip, {[classes.selected]: (userinterest.indexOf(id) !== -1)});
}
return (
/*..... Few element to support styles....*/
{categories.map((data, index) => {
return (
<Chip
key={data._id.$oid}
label={data.name}
onClick={this.toggleChip.bind(this, data._id.$oid, index)}
className={getClassNames(data._id.$oid)}
/>
);
})}
);
}
}
Can anyone tell me what is wrong in this or how can I achieve this?.
Since state is immutable, you cannot use .push on it.
By using this.state.selected.push(id) you're mutating the state thus not signaling react on the change making the change vulnerable to future state updates (remember that setState is asynchronous and changes are batched for single action).
Take a look at this for how to solve it.
In your case, a better way to update the state would be something like this:
// create a copy of the current array
var selected = this.state.selected.slice();
// push the new element to the copy
selected.push(id);
// replace the current array with the modified copy
this.setState({ selected: selected });
As far as my understanding goes, it's an anti-pattern to dispatch actions from within a store update handler. Correct?
How can I handle the following workflow then?
I have some company switcher on my page header
Clicking on a company dispatches some SELECTEDCOMPANY_UPDATE action
The active view reacts on the according change in the state store by forcing a data reload. E.g. by calling companyDataService.fetchOrders(companyName).
I'd like to show some loading animation during the data is being fetched and therefore have an dedicated action like FETCHINGDATA_UPDATE which updates the fetchingData section in my app state store to which all interested views can react by showing/hiding the load mask
Where do I actually dispatch the FETCHINGDATA_UPDATE action? If I directly do this from within companyDataService.fetchOrders(companyName) it would be called from within a store update handler (see OrdersView.onStoreUpdate in exemplary code below)...
Edit
To clarify my last sentence I'm adding some exemplary code which shows how my implementation would have looked like:
ActionCreator.js
// ...
export function setSelectedCompany(company) {
return { type: SELECTEDCOMPANY_UPDATE, company: company };
}
export function setFetchingData(isFetching) {
return { type: FETCHINGDATA_UPDATE, isFetching: isFetching };
}
// ...
CompanyDataService.js
// ...
export fetchOrders(companyName) {
this.stateStore.dispatch(actionCreator.setFetchingData(true));
fetchData(companyName)
.then((data) => {
this.stateStore.dispatch(actionCreator.setFetchingData(false));
// Apply the data...
})
.catch((err) => {
this.stateStore.dispatch(actionCreator.setFetchingData(false));
this.stateStore.dispatch(actionCreator.setFetchError(err));
})
}
// ...
CompanySwitcher.js
// ...
onCompanyClicked(company) {
this.stateStore.dispatch(actionCreator.setSelectedCompany(company));
}
// ...
OrdersView.js
// ...
constructor() {
this._curCompany = '';
this.stateStore.subscribe(this.onStoreUpdate);
}
// ...
onStoreUpdate() {
const stateCompany = this.stateStore.getState().company;
if (this._curCompany !== stateCompany) {
// We're inside a store update handler and `fetchOrders` dispatches another state change which is considered bad...
companyDataService.fetchOrders(stateCompany);
this._curCompany = stateComapny;
}
}
// ...
I agree with Davin, in the action creator is the place to do this, something like:
export function fetchOrders (company) {
return (dispatch) => {
dispatch ({ type: FETCHINGDATA_UPDATE });
return fetchOrderFunction ().then(
(result) => dispatch ({ type: FETCHING_COMPLETED, result }),
(error) => dispatch ({ type: FETCHING_FAILED, error })
);
};
}
Then in the reducer FETCHINGDATA_UPDATE can set your loading indicator to true and you can set it back to false I both SUCCESS and FAILED
React document states that the render function should be pure which mean it should not use this.setState in it .However, I believe when the state is depended on 'remote' i.e. result from ajax call.The only solution is setState() inside a render function
In my case.Our users can should be able to log in. After login, We also need check the user's access (ajax call )to decide how to display the page.The code is something like this
React.createClass({
render:function(){
if(this.state.user.login)
{
//do not call it twice
if(this.state.callAjax)
{
var self=this
$.ajax{
success:function(result)
{
if(result==a)
{self.setState({callAjax:false,hasAccess:a})}
if(result==b)
{self.setState({callAjax:false,hasAccess:b})}
}
}
}
if(this.state.hasAccess==a) return <Page />
else if(this.state.hasAccess==a) return <AnotherPage />
else return <LoadingPage />
}
else
{
return <div>
<button onClick:{
function(){this.setState({user.login:true})}
}>
LOGIN
</button>
</div>
}
}
})
The ajax call can not appear in componentDidMount because when user click LOGIN button the page is re-rendered and also need ajax call .So I suppose the only place to setState is inside the render function which breach the React principle
Any better solutions ? Thanks in advance
render should always remain pure. It's a very bad practice to do side-effecty things in there, and calling setState is a big red flag; in a simple example like this it can work out okay, but it's the road to highly unmaintainable components, plus it only works because the side effect is async.
Instead, think about the various states your component can be in — like you were modeling a state machine (which, it turns out, you are):
The initial state (user hasn't clicked button)
Pending authorization (user clicked login, but we don't know the result of the Ajax request yet)
User has access to something (we've got the result of the Ajax request)
Model this out with your component's state and you're good to go.
React.createClass({
getInitialState: function() {
return {
busy: false, // waiting for the ajax request
hasAccess: null, // what the user has access to
/**
* Our three states are modeled with this data:
*
* Pending: busy === true
* Has Access: hasAccess !== null
* Initial/Default: busy === false, hasAccess === null
*/
};
},
handleButtonClick: function() {
if (this.state.busy) return;
this.setState({ busy: true }); // we're waiting for ajax now
this._checkAuthorization();
},
_checkAuthorization: function() {
$.ajax({
// ...,
success: this._handleAjaxResult
});
},
_handleAjaxResult: function(result) {
if(result === a) {
this.setState({ hasAccess: a })
} else if(result ===b ) {
this.setState({ hasAccess: b })
}
},
render: function() {
// handle each of our possible states
if (this.state.busy) { // the pending state
return <LoadingPage />;
} else if (this.state.hasAccess) { // has access to something
return this._getPage(this.state.hasAccess);
} else {
return <button onClick={this.handleButtonClick}>LOGIN</button>;
}
},
_getPage: function(access) {
switch (access) {
case a:
return <Page />;
case b:
return <AnotherPage />;
default:
return <SomeDefaultPage />;
}
}
});