Access store from service (or where I should be accessing the store)? - react-redux

I'm only a few days in (transitioning from Ember) so please pardon my ignorance.
I have an array of object (profileaccounts) in my store.
I have many different components which are connected to the store and have code like below, sometimes the user is the person logged in, sometimes its a user that is being passed into the component (when someone looks at someone else's profile)
componentDidMount() {
let user = this.props.user;
let account = this.props.profiles.find(function (prof) { return prof.profile.id === user.id });
if (account == null) {
this.props.dispatch(userActions.getProfile(user.id));
}
}
This completely works but I don't want this code replicated over and over again. My gut feeling is that I should always call .getProfile(user.id) and its the job of the actions to determine if the data exist in the local cache (store) or does it needs to be added. If it needs to be added, add it, then either way return it.
Alternatively, maybe the user service (which represents the API and is called by the actions to populate the profiles) is supposed to look locally before it calls the API. Regardless, I don't know how (or if I should) to access the store from the actions or service, only from the connected component.
I haven't seen this scenario in any of the guides I've read about redux/react, so if anyone can include a resource to where I should be looking I'd appreciate it. If I'm totally going about this the wrong way, I'd be happy to know that too.

You can use redux-thunk to access state inside the action
link to thunk: https://github.com/reduxjs/redux-thunk
The code would be like this:
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
Using thunk you'll be able to access state inside the action.
Your approach sounds good...You'll start with dispatching an action "dispatch(getProfile(userId))" then inside the action you'll do whatever you want and when you finally have the data you want to put in the store dispatch another action "dispatch(storeUserProfile(profile))" which will put the data in the store via reducer.

Related

Cookie-less Laravel sessions

We have a small quiz type functionality built in Laravel to be embedded in a site via an iframe served from a separate domain (to work around CMS limitations).
It uses sessions to keep track of the user's progress in the quiz. This doesn't work in Safari (Mac/iOS), I believe because Apple disable cookies issued from within an iframe.
Assuming that limitation is one we're stuck with, has anyone had any success making Laravel sessions cookie-less? I found this code on Github, which looks promising but is old enough (and incompatible with current Laravel) that I can't tell if it's going to be a solution.
In case it helps someone else, or anyone can see any silly errors in my code, this is what I did (an adaption of the Github code, to work in Laravel 9).
I extended StartSession and SessionServiceProvider (to use my new StartSession). I created an override for handleStatefulRequest in Start Session, and where it adds a cookie to the reponse (it calls addCookieToResponse) did this:
if ($request->cookies->get($session->getName())) {
$this->addCookieToResponse($response, $session);
}
else {
// Add session ID to header
$this->addIdentifierToResponse($response, $session);
}
That new function looks like this:
protected function addIdentifierToResponse(Response $response, Session $session)
{
if ($this->sessionIsPersistent($config = $this->manager->getSessionConfig())) {
$response->headers->set("X-Session-Token", $session->getId());
}
}
I also changed the getSession method to get the session ID from that newly set header (when no cookie found):
public function getSession(Request $request)
{
return tap($this->manager->driver(), function ($session) use ($request) {
if ($request->cookies->get($session->getName())) {
Log::debug('1. Set session ID from cookie');
$session->setId($request->cookies->get($session->getName()));
}
else if ($request->headers->get("X-Session-Token", $request->input("sess_id"))) {
$sessionToken = $request->headers->get("X-Session-Token", $request->input("sess_id"));
$session->setId($sessionToken);
}
});
}
I created a Github repo containing the whole thing.

NGXS State documentation

I'm new to NGXS and I'm trying to fully understand the docs so I can start using it knowing what I'm doing.
There is one thing I don't understand in this code snippet from here.
export class ZooState {
constructor(private animalService: AnimalService) {}
#Action(FeedAnimals)
feedAnimals(ctx: StateContext<ZooStateModel>, action: FeedAnimals) {
return this.animalService.feed(action.animalsToFeed).pipe(tap((animalsToFeedResult) => {
const state = ctx.getState();
ctx.setState({
...state,
feedAnimals: [
...state.feedAnimals,
animalsToFeedResult,
]
});
}));
}
}
Just below this code, it says:
You might notice I returned the Observable and just did a tap. If we
return the Observable, the framework will automatically subscribe to
it for us, so we don't have to deal with that ourselves. Additionally,
if we want the stores dispatch function to be able to complete only
once the operation is completed, we need to return that so it knows
that.
The framework will subscribe to this.animalService.feed, but why?
The action, FeedAnimals, uses the injected service, AnimalService to feed the animals passed in the action's payload. Presumably the service is operates asynchronously and returns an Observable. The value of that Observable is accessed via the tap function and is used to update the ZooState state context based on completing successfully.
In order to use NGXS specifically and Angular in general, you really have to understand RxJS... here's my goto doc page for it

KeystoneJS: How to set a field to receive randomly generated value?

I'm creating a model that I will use to authenticate users for API access, and I have a secret field where I want to store a Base64 encoded uuid/v4 generated value.
I went through the different field types and options, but still not seeing how I could achieve this.
Is there a way to hook in model instance creation, and set the value of my secret field ?
Yes, you can use the pre hooks.
In your situation, the basics would be:
AuthenticationModel.schema.pre("save", function(next) {
const secretValue = generateSecretValue();
this.secret = secretValue;
next();
});
That would go before your final AuthenticationModel.register(); in your model.js file.
This is how I set it up, also with the pre-save hook. My problem before was that I was getting the same random number again until I restarted the server.
Store.schema.pre('save', function (next) {
if (!this.updateId && this.isNew) {
// generates a random ID when the item is created
this.updateId = Math.random().toString(36).slice(-8);
}
next();
});
Using this.isNew was also useful in my case.

The proper way to skip the dashboard for non-master users in a User Frosting site

I want any user who isn't the master user to be redirected to a separate page at /profiles.
I've achieved this by editing the admin sprinkle, adding this simple statement to the pageDashboard function in AdminController.php:
if(!$currentUser->isMaster()){
header("Location: /profiles");
exit;
}
I want to move this to my own sprinkle, but I'm not clear on how best to do this. Would I create my own controller that extends AdminController, and just replace the function? Or is there a neater way of doing it? What I have now works but obviously isn't future-proof as this file will be overwritten in future updates.
You can change where the users are redirected after login using the determineRedirectOnLogin service. See : https://learn.userfrosting.com/services/default-services#determineredirectonlogin. In your sprinkle ServicesProvider, simply overwrite the default service with something similar:
$container['determineRedirectOnLogin'] = function ($c) {
return function ($response) use ($c)
{
if (!$c->currentUser->isMaster()) {
return $response->withHeader('UF-Redirect', '/dashboard');
} else {
return $response->withHeader('UF-Redirect', '/profiles');
}
};
};
You can then use the permission system to remove access to the dashboard for the non root users if you wish so.
Side note, like you pointed out, you shouldn't edit any core sprinkles and move that code to your own sprinkle.

Updating a session and using it within my view

I have a wish list, that is throughout the shopping pages. I need to know if this makes sense/the proper way of structuring.
Store the wish list as a session, when a user adds/deletes a new item it updates the session by an ajax call that just returns true/false if successful. On the partial view of the wish list component, I check for the session and cast it to my viewModel (which the session is based on) or serialize it for my knockout.
Let me know if this makes sense, otherwise I can post some code samples
It's hard to say without having a look at your basic structure, and not knowing you exact needs.
I don't know if you know this, but you can actually access the Session directly in Views:
#{
var wishlist = (WishList)HttpContext.Current.Session["Wishlist"];
}
It's fine to use Ajax to update it server side; and then you can return a partial view from the controller, to use however you like in the Ajax success call.
I hope this makes sense.
To begin with, if the wishlist is only supposed to exist for the duration of their visit then storing it in a session would be the best thing to do. However if the wishlist is supposed to live longer than a single visit and should be available to the user upon their return then I would suggest storing it in the database against the user's credentials/account (this is presuming they have an account).
As for the session itself, whilst you can access session data from a view I would not suggest it as you start to have a dependency on the session and before long you'll have code such as this scattered throughout your views.
var wishlist = (WishList)HttpContext.Current.Session["Wishlist"];
What happens when you want to change the way the wishlist works and instead have it database driven as you'd now like to persist the wishlist? You'll have to go through all of your views updating the references to the session.
Instead I would opt for registering your session with your IoC container of choice and injecting it using dependency injection, here is a simple example of how to register the session with StructureMap:
public class WebsiteRegistry : Registry
{
public WebsiteRegistry()
{
this.For<IUserWishlist>().HybridHttpOrThreadLocalScoped().Use(() => GetUserWishlistFromSession());
}
public static IUserWishlist GetUserWishlistFromSession()
{
var session = HttpContext.Current.Session;
if (session["WishList"] != null)
{
return session["WishList"] as IUserWishlist;
}
/* Create new empty session object */
session["WishList"] = new UserWishlist();
return session["WishList"] as IUserWishlist;
}
}
Now you're able to inject your wishlist into your controller and pass the data to your view via a view model. And as you're now programming against an interface instead of an implementation you could easily change how the wishlist is persisted without needing to change any code that references the wishlist.
public class WishlistController : Controller {
private readonly IUserWishlist userWishlist;
public void WishlistController(IUserWishlist userWishlist) {
this.userWishlist= userWishlist;
}
public ActionResult ViewProfile()
{
...
var viewModel = new UserWishlistViewModel {
wishlist = this.userWishlist.GetWishList()
}
...
}
}
I've written a more detailed example up in a blog post that might be of interest which can be read here. I hope this helps!

Resources