react-admin: what is authParams for? - reactjs

I am using react-admin and I am looking at the documentation page for creating page with permissions
The example shows:
const MyPageWithPermissions = ({ location, match }) => (
<WithPermissions
authParams={{ key: match.path, params: route.params }}
// location is not required but it will trigger a new permissions check if specified when it changes
location={location}
render={({ permissions }) => <MyPage permissions={permissions} /> }
/>
);
export default MyPageWithPermissions;
First of all:
route is not defined, what is the real value?
I would like to know:
What is authParams used for? Is it required or optional?
Could authParams be skipped?
If it's required, why is it not set automatically directly in WithPermissions Component?

Those are for custom params you'd like to check in your authProvider. They will be passed to it when WithPermissions calls it.
They are optional though as you can see in the next example for a custom menu

Related

React router provides javascript object instead of original type on url address bar reload

I am explining my problem with just the relevant code, as the full example is in this codesandbox link.
I am passing some props through a link to a component.
These props, have a firebase timestamp.
The props are passed correctly when the component is called through the link.
Link:
<Link to={{
pathname:path,
state: {
project
},
}} key={project.id}>
<ProjectSummary project={project} deleteCallback={projectDelete}/>
</Link>
Route:
<Route
path='/project/:id'
render={({ location }: {location: Location<{project: IFirebaseProject}>}) => {
const { state } = location;
const returnedComponent = state ? <ProjectDetails project={state.project} /> :
<ProjectDetails project={undefined}/>;
return returnedComponent;
}}
/>
and received by the ProjectList component, like this:
<div>{moment(stateProject.createdAt.toDate()).calendar()}</div>
My problem is that when the component is called through the link, props are passed and everything works fine, but, when I re-enter in the url adress bar, as the access to the component is not through the link, I would expect that the Route's render returned an undefined project (check route:
const returnedComponent = state ? <ProjectDetails project={state.project} /> : <ProjectDetails project={undefined}/>;) but, it returns the last passed project, with the timestamp as a plain Javascript object instead of a Timestamp type. So I get the error:
TypeError: stateProject.createdAt.toDate is not a function
Because the toDate() function is not available in the plain Javascript object returned, it is the Timestamp firebase type. Seems that for this specific case, the router is keeping it as a plain js object, instead of the original Timestamp instance. I would expect the route to return always the proyect undefined if not called from the link, as the props are not passed in (supposedly), but its not the case on the reload from the url address bar.
Curiously, in the codesandbox project, it does not reproduce, it fetches the data (you will be able to see the console.log('project fetched!!') when the project received is undefined).
However thrown from the dev server it happens. Might have something to do.
Find the git url if you wish to clone and check: https://github.com/LuisMerinoP/my-app.git
Remember that to reproduce you just need to enter to the link, and then put the focus in the explorer url address bar en press enter.
I case this might be the expected behaviour, maybe there is a more elegant way to way to deal with this specific case instead of checking the type returned on the reload. I wonder if it can be known if it is being called from the address bar instead of the link.
I know I can check the type in my component and fix this, creating a new timeStamp in the component from the js object returned, but I do not expect this behaviour from the router and would like to understand what is happenning.
Problem: Non-Serializable State
It returns the last passed project, with the timestamp as a plain Javascript object instead of a Timestamp type
I do not expect this behaviour from the router and would like to understand what is happening.
What's going on is that the state is being serialized and then deserialized, which means it's being converted to a JSON string representation and back. You will preserve any properties but the your methods.
The docs should probably be more explicit about this but you should not store anything that is not serializable. Under the hood React Router DOM uses the browser's History API and those docs make it more clear.
Suggestions
as in typescript is an assertion. It how you tell the compiler "use this type even though it's not really this type". When you have something that really is the type then do not use as. Instead apply a type to the variable: const project: IFirebaseProject = {
Your getProjectId function to get an id from a URL is not necessary because React Router can do this already! Use the useParams hook.
Don't duplicate props in state. You always want a "single source of truth".
Fetching Data
I played with your code a lot because at first I thought that you weren't loading the project at all when the page was accessed directly. I later realized that you were but by then I'd already rewritten everything!
Every URL on your site needs to be able to load on its own regardless of how it was accessed so you need some mechanism to load the appropriate project data from just an id. In order to minimize fetching you can store the projects in the state of the shared parent App, in a React context, or through a global state like Redux. Firestore has some built-in caching mechanisms that I am not too familiar with.
Since right now you are using dummy placeholder data, you want to build a way to access the data that you can later replace your real way. I am creating a hook useProject that takes the id and returns the project. Later on just replace that hook with a better one!
import { IFirebaseProject } from "../types";
import { projects } from "./sample-data";
/**
* hook to fetch a project by id
* might initially return undefined and then resolve to a project
* right now uses dummy data but can modify later
*/
const useProject_dummy = (id: string): IFirebaseProject | undefined => {
return projects.find((project) => project.id === id);
};
import { IFirebaseProject } from "../types";
import { useState, useEffect } from "react";
import db from "./db";
/**
* has the same signature so can be used interchangeably
*/
const useProject_firebase = (id: string): IFirebaseProject | undefined => {
const [project, setProject] = useState<IFirebaseProject | undefined>();
useEffect(() => {
// TODO: needs a cleanup function
const get = async () => {
try {
const doc = await db.collection("projects").doc(id).get();
const data = doc.data();
//is this this right type? Might need to manipulate the object
setProject(data as IFirebaseProject);
} catch (error) {
console.error(error);
}
};
get();
}, [id]);
return project;
};
You can separate the rendering of a single project page from the logic associated with getting a project from the URL.
const RenderProjectDetails = ({ project }: { project: IFirebaseProject }) => {
return (
<div className="container section project-details">
...
const ProjectDetailsScreen = () => {
// get the id from the URL
const { id } = useParams<{ id: string }>();
// get the project from the hook
const project = useProject(id ?? "");
if (project) {
return <RenderProjectDetails project={project} />;
} else {
return (
<div>
<p> Loading project... </p>
</div>
);
}
};
Code Sandbox Link

Equivalent of dynamic query params for React Native Navigation?

I want to dynamically show profiles based on userId in React Native. In react where I set up my routes, I'd add something like route="/users/:userId", and then use that userId to make various calls, display various data, etc. When I implement a link to the profile page, I route them to, say, /users/1.
How can I achieve something similar in React Native? The only thing seen around the web is to pass it in as params, but does that mean I've got to pass it in each time I call navigation.navigate? I'm not sure what the route-defining or the navigation.navigate syntax should look like.
I've checked a couple of SO threads, but none of them seem to answer this fundamental question. And all of the docs and articles about dynamic routing in React Navigation seem to mostly concern passing in dynamic title headers and stuff.
React Navigation primarily uses a params object which looks like { userId: 1 } rather than a string-based route definition like "/users/:userId".
Linking to a Profile
The navigation.navigate function takes two props: the name of the route and the params to pass. You don't need to include the second params argument at all if you are navigating to a route which doesn't take any parameters. navigation.navigate("Home") is fine. But when going to your "User" route you will always need to include a userId.
In order to go to a particular user profile, you would call:
onPress={() => navigation.navigate("User", { userId: 1 })}
Docs: Passing parameters to routes
Accessing Params
That userId param can then be accessed in the UserScreen component through the props which are injected by the navigator. Every screen receives props route and navigate. The params are a property of the route prop.
So you can define a UserRoute component like this, where we get the current userId from route.params.userId.
const UserScreen = ({route, navigation}) => (
<View>
<Text>Viewing profile for user #{route.params.userId}</Text>
<TouchableOpacity onPress={() => navigation.navigate("Home")}>
<Text>Back to Home</Text>
</TouchableOpacity>
</View>
);
(Typescript users: this component is React.FC<StackScreenProps<RootStackParamList, "User">> where RootStackParamList is your own app's param definitions)
Declaring Routes
You don't actually need to say anything about the params when you create your routing. This works just fine:
export const App = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="User" component={UserScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
There are some additional optional configurations that you can use. You can map the params to options or map the params to a unique screen id, for example.
<Stack.Screen
name="User"
component={UserScreen}
options={({ route }) => ({
title: `User Profile #${route.params.userId}`
})}
getId={({ params }) => params.userId.toString()}
/>
(Typescript users will want to define a type, typically called RootStackParamList, which maps each route to its params types. This is then used as the generic on StackScreenProps, StackNavigationProp, etc.)
String-Based Navigation
React Navigation does support linking to paths like "/user/1", but it requires additional configuration. Behind the scenes it still uses a params object, so you need to define a mapping from the path to the params. It can be a custom mapping, or you can use the same syntax that React Router does with : to define params. "users/:userId" will create a params object with the userId as the key.
Your Stack.Screen component stay the same. The configuration options are passed as a prop linking to the NavigationContainer component. If you set this up then you are able to use the experimental Link component like you would in React Router to link to a path like "/users/1".
export const App = () => {
return (
<NavigationContainer
linking={{
prefixes: ["https://yourdomain.com"],
config: {
screens: {
Home: "",
User: "users/:userId"
}
}
}}
>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="User" component={UserScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
CodeSandbox Demo with typescript types.

Update parent state variable from child React Reactive Form

I am trying to explore react library with next framework. Since I am an angular developer, I like to reuse some of my reactive-form to my new application. I found this library since it has almost the same implementation of reactive-form.
Now, I am using state variable on my parent form; however, whenever I try to update the value from child (which is the reactive form). I cannot accomplish it.
Here's my simple code to replicate this.
import React, { useState } from "react";
import { FieldGroup, FieldControl } from "react-reactive-form";
export default function App() {
const [isRegistered, setIsRegistered] = useState(false);
async function submitForm(e) {
e.preventDefault();
setIsRegistered(state => !state);
console.log(isRegistered);
//await function call .....
}
return (
<div>
<h1>Hello StackBlitz!</h1>
<FieldGroup
control={form}
render={({ get, invalid }) => (
<form onSubmit={submitForm}>
<FieldControl
name="email"
render={TextInput}
meta={{ label: "Email" }}
/>
<button type="submit">Submit</button>
<p>{isRegistered.toString()}</p>
{isRegistered ? <span>Registered Successfully</span> : null}
</form>
)}
/>
</div>
)
}
Just to keep it short, the form and TextInput is just a model and an element.
As you can see., I am updating my state variable on the submitForm function by putting it as an onSubmit function of my form; however, I am able to trigger the submitForm whenever I am trying to submit, but the state variable value doesn't change.
The thing is, when I try to call the submitForm function outside the child (FieldGroup), I am able to update the value.
I created a sample app so you can check as well.
It seems like you need to set strict prop to false for FieldGroup, like described here: https://github.com/bietkul/react-reactive-form/blob/master/docs/api/FieldGroup.md
strict: boolean;
Default value: true
If true then it'll only re-render the component only when any change happens in the form group control irrespective of the parent component(state and props) changes.
I don't know this library, but to me it just looks like the FormGroup is not re-render, because none of it's props are being changed.
The documentation says that passing strict={false} to the <FieldGroup /> component should allow it to re-render when the parent component updates as well. In your given example (thanks for making an example) that also does the trick.

useGetOne response doesn't rerender component

I am feeling quite frustrated at this simple example because either I am not getting something or it is not working the way I expect it and the way it says it should in the documentation.
So I am trying to extend the CloneButton component to actually retrieve the record on each row od the Datagrid and pass it to the create component:
import React from 'react';
import PropTypes from 'prop-types';
import { CloneButton, useGetOne } from 'react-admin';
import CircularProgress from '#material-ui/core/CircularProgress';
const CloneQueryButton = ({
label = "Duplicate",
basePath,
record,
...props
}) => {
const { data, loading, error } = useGetOne(basePath, record.id);
if (loading) { return <CircularProgress />; }
if (error) { return <p>ERROR {error.message}</p>; }
if (data) {
console.log("inside", data);
data.name = `Copy of ${data.name}`;
}
console.log("outside", data);
return <CloneButton
basePath={basePath}
record={data}
label={label}
{...props} />
};
What I get on the console is just outside null
Also when the button is pressed it redirects to the create page with empty record property.
I can see the network request and they all return valid jsons. I don't understand why the component obviusly doesn't rerender when the response is recieved. If someone can see what am I missing, I will be extremely grateful!
The console succeeds, this means that there is neither an error nor a loading.
After checking the implementation of useGetOne, if it doesn't basically find a resource in your admin state with the given name, it will return null.
So I presume that you don't have the resource being declared in the admin's state (aka <Resource name="your name" /> in an <Admin> tree.
onst useGetOne = (
resource: string,
id: Identifier,
options?: any
): UseGetOneHookValue =>
useQueryWithStore(
{ type: 'getOne', resource, payload: { id } },
options,
(state: ReduxState) =>
state.admin.resources[resource]
? state.admin.resources[resource].data[id]
: null
);
I faced the same issue. Here's what I found.
When an action has been dispatched, Redux checks if the state has changed. If the state has not changed then it doesn't have to update the components. When Redux cannot see that the state has changed, it will not update the React components with new data from the state. Reference
The returning data is the same JSON which is why the state is not updating.
Two ways to solve this.
If you have access to your API, then create a new resource with a different basePath that returns the same result. Temporary fix. Not the best solution.
Ex: <Resource name="cart" />, <Resource name="cartDetails" />
Clear State in React - You can follow this article for clearing state in hooks

Route handling a uniquely generated URL specific to a user in ReactJS and react-router-dom v4

In the app I am working on if a user forgot their password, they click on a link that brings them to a page where they enter the username. If the username is matched, it sends a uniquely generated URL to the email associated with the username. For example:
http://localhost:8000/auth/security_questions/0e51706361e7a74a550e995b415409fdab4c66f0d201c25cb4fa578959d11ffa
All of this works fine, but I am trying to figure out how to handle this on the front-end routing using React and react-router-dom v4. I made this route.
<Route exact path='/auth/security_questions/:key' component={SecurityQuestions} />
The correct component loads related to security questions, but that is not the behavior I am after. After all, it takes anything you put after security_questions/.
What it should be doing is matching :key against the database before it loads the component.
I'm not sure about a few things:
1) How to parse out the :key portion so that I can pass it as a value to verify against the database.
2) While I have a general idea of how to handle the verification, I am not sure how to tell React: "Ok, the key has been verified in the database. Finish loading the component."
I think it would in general look like:
// ./containers/security_questions.js
componentWillMount() {
this.props.verifyKey(:key);
}
And then actions:
// ./actions/authentication.index.js
export function verifyKey({ :key }) {
return function(dispatch) {
axios
.post(
`${ROOT_URL}/api/auth/security_questions/`,
{ :key }
)
.then(response => {
dispatch('Finish loading the rest of the component')
})
.catch(error => {
dispatch(authError(error.response.data.non_field_errors[0]));
});
}
}
Or maybe instead of it finishing loading the component, it should just route to a different URL that is a protected route.
You can grab the params from the path like so (https://reacttraining.com/react-router/web/api/Route):
<Route path="/user/:username" component={User}/>
const User = ({ match }) => <h1>Hello {match.params.username}!</h1>
You will want to conditionally render based upon some state set by verifyKey.
componentWillMount() {
this.props.verifyKey(this.props.match.params.key);
}
render() {
if (this.state.permitRender) {
return <Component>
} else {
return <LoadingComponent />
}
You can use the render method on the route to put in your verification logic and render the appropriate control. You would need to move the action to verify the key to the component which renders the route, rather than the SecurityQuestions component.
<Route exact
path='/auth/security_questions/:key'
render={(props)=>{
let finalComponent= <ComponentToRenderWhenKeyDoesNotMatch/>;
if(this.props.verifyKey(props.match.params.key)){
resultantComponent=<SecurityQuestions/>
}
return finalComponent;
}}
/>
Route params will be inside the match.params prop:
componentWillMount() {
this.props.verifyKey(this.props.match.params.key);
}
See https://reacttraining.com/react-router/web/example/url-params
--
Then on the success of your security questions I would set some state in your reducer and render based on that.

Resources