Repository: guillotinaweb/guillotina_react (language: python)

Issue:
List of proposals for improvement
After a review, I add a list of proposals for improvement:

- [x] Provide directly the **`useTraversal`** hook. It is not recommended that the libraries directly expose the context and users have to write `useContext(TraversalContext)`.
- [ ] **Consistency between the Form elements**. Input, TextArea, FileUpload etc each with its own properties which are different from the standard HTML5. Or they are the same as HTML5 or they should have their own consistent version across all elements. For example: `onChange` now sometimes receives the value and other times the event.
- [ ] **Add source-maps** to make it easier to debug for those who use the library.
- [ ] **Deprecate `Ctx.filterTabs`** and instead use TabPanel props. Or keep both, but I would make the TabPanel have this controlled by default.

Instead of:

```js
const calculated = ctx.filterTabs(tabs, tabsPermissions);
return <TabsPanel tabs={calculated} {...props} />
````

Do:

```js
return <TabsPanel tabs={tabs} tabsPermissions={tabsPermissions} {...props} />
````
- [ ] Change the **`currentTab`** prop of the TabPanel **behavior**. If the `currentTab` doesn't match with any tab that the user has permissions, then the first tab should be selected.
- [ ] **Add types** to every configuration and context content. We don't need to convert the project to TypeScript but this way, those who use our API from the IDE itself can know, for example, when `doAction` what actions are available without having to consult our documentation. 
- [x] **Add a formatter like Prettier** to keep the code in a certain order.
- [x] **Add missing files** like `CONTRIBUTING.md`, `LICENSE`...
- [x] Remove `linkstate` dependency (only used in 1 component) 
- [x] Replace `react-use` to own hooks: It's importing 60kb for 3 simple hooks...
- [x] **Add an example** in the repo of a simple admin. This will be useful for two things: 
    - Make it easier for users to get started
    - Allow us to test the basic functionalities, either with cypress or in the development environment
- [ ] _... Anything else you would like to add?_


Discussion / hints:
Before commenting about the list of proposal I'd like to ask this question: 

What's the purpose of this project? It's clear to me that this project is (at least) a management interface (aka Admin UI) but I also think this project could be lib/client for Guillotina. That's what someone could think looking at the repo name `guillotina_react` (like me :P). Also, I'm not sure if we want the AdminUI to do a lot of things or something very simple and small that's just a foundation to build things on top of it.

If we want AdminUI to do a lot of things OOTB, maybe it's interesting to move to `antd` (39.4MB) but if we want something minimal, I'd stay with `bulma` (2.33 MB). Also if we consider this project as a lib/client/sdk, maybe there is a way to "split" the project in two layers: `core` and `UI`. Anyway, I'd like to discuss and define what we want and then we can discuss some improvements.

But right now, I'm ok with adding a CONTRIB, LICENSE, Prettier and adding consistency to the Form elements.
I agre with @masipcat opinion. The main idea behind guillotina_react is to provide a minimal package that can provide whatever needed to work with a guillotina, with as less as possible dependecies.

Another idea on my head that I will try to fix soon, is to bundle guillotina_react as an es module, that we can import from regular htmls.


It will be interesting to isolate client in a new context and hook to allow use client in a project without traversal, or in a isolated pages outside traversal? 
@rboixaderg sounds interesting :)

Fix patch:
diff --git a/README.md b/README.md
index 1e2801a6..6307e064 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,3 @@
-
 # 🔌 Guillotina Management Interface
 
 It's build around the idea of a framework to roll you own GMI.
@@ -16,7 +15,6 @@ layer that could be extended from outside.
 
 Alpha version. The app is usable, but still needs some love.
 
-
 ## Roll your own guillotina
 
 ### With create react app
@@ -31,22 +29,25 @@ yarn add @guillotinaweb/react-gmi
 ```
 
 App.js
+
 ```jsx
-import React from 'react';
-import {Layout} from '@guillotinaweb/react-gmi'
-import {Auth} from '@guillotinaweb/react-gmi'
-import {Guillotina} from '@guillotinaweb/react-gmi'
-import {Login} from '@guillotinaweb/react-gmi'
-import {useState} from 'react'
+import React from 'react'
+import { Layout } from '@guillotinaweb/react-gmi'
+import { Auth } from '@guillotinaweb/react-gmi'
+import { Guillotina } from '@guillotinaweb/react-gmi'
+import { Login } from '@guillotinaweb/react-gmi'
+import { getClient } from '@guillotinaweb/react-gmi'
+import { ClientProvider } from '@guillotinaweb/react-gmi'
+import { useState } from 'react'
 import '@guillotinaweb/react-gmi/dist/css/style.css'
 
 // guillotina url
 let url = 'http://localhost:8080/'
 
 const auth = new Auth(url)
+const client = getClient(url, auth)
 
 function App() {
-
   const [isLogged, setLogged] = useState(auth.isLogged)
 
   // You can do whatever you want on login, this includes,
@@ -60,10 +61,10 @@ function App() {
   auth.onLogout = onLogout
 
   return (
-
+    <ClientProvider client={client}>
       <Layout auth={auth} onLogout={onLogout}>
-        { isLogged && <Guillotina auth={auth} url={url} />}
-        { !isLogged && (
+        {isLogged && <Guillotina auth={auth} url={url} />}
+        {!isLogged && (
           <div className="columns is-centered">
             <div className="columns is-half">
               <Login onLogin={onLogin} auth={auth} />
@@ -71,16 +72,15 @@ function App() {
           </div>
         )}
       </Layout>
-  );
+    </ClientProvider>
+  )
 }
 
-
-export default App;
+export default App
 ```
 
 ### To add icons:
 
-
 Add the icons to the default public/index.html header
 
 ```diff
@@ -104,7 +104,6 @@ curl https://raw.githubusercontent.com/guillotinaweb/guillotina_react/master/pub
 - [Howto Extend Guillotina React form outside?](docs/extend.md)
 - [Narrative Docs](docs/api.md)
 
-
 ## Develop
 
 ```
@@ -120,7 +119,6 @@ yarn start
 ![](screenshots/screen1.png)
 ![](screenshots/screen3.png)
 
-
 ## Sponsors
 
 This project is sponsored by <a href="https://www.vinissimus.com">Vinissimus Wine Shop</a>
diff --git a/docs/api.md b/docs/api.md
index 4e7ea72f..051161fc 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -69,7 +69,6 @@ import { useTraversal } from '@guillotinaweb/react-gmi'
 // ...
 const Ctx = useTraversal()
 // ...
-Ctx.setPath('/db/guillotina/') // will navigate tracersal to the container
 Ctx.doAction // show an action
 Ctx.cancelAction // hide (terminate) the latest action. Used, when the process completes
 Ctx.flash // Show a flash message on the main screen
@@ -102,17 +101,10 @@ const [location, setLocation] = useLocation()
 ### Guillotina
 
 ```jsx
-import {
-  Guillotina,
-  GuillotinaClient,
-  RestClient,
-} from '@guillotinaweb/react-gmi'
-// ...
-const client = new GuillotinaClient(new RestClient(url, auth))
+import { Guillotina } from '@guillotinaweb/react-gmi'
 // ...
 return (
   <Guillotina
-    client={client}
     config={{
       icons: {
         Banners: 'fas fa-image',
diff --git a/src/App.js b/src/App.js
index 3b0b48d6..7deeb087 100644
--- a/src/App.js
+++ b/src/App.js
@@ -3,7 +3,10 @@ import { Layout } from './guillo-gmi'
 import { Auth } from './guillo-gmi'
 import { Guillotina } from './guillo-gmi'
 import { Login } from './guillo-gmi'
+import { getClient } from './guillo-gmi'
+import { ClientProvider } from './guillo-gmi'
 import { useState } from 'react'
+
 import './guillo-gmi/scss/styles.sass'
 
 /*
@@ -28,6 +31,7 @@ if (process.env.NODE_ENV === 'production') {
 }
 
 const auth = new Auth(url)
+const client = getClient(url, auth)
 
 function App() {
   const [isLogged, setLogged] = useState(auth.isLogged)
@@ -40,16 +44,18 @@ function App() {
   auth.onLogout = onLogout
 
   return (
-    <Layout auth={auth} onLogout={onLogout}>
-      {isLogged && <Guillotina auth={auth} url={url} />}
-      {!isLogged && (
-        <div className="columns is-centered">
-          <div className="columns is-half">
-            <Login onLogin={onLogin} auth={auth} />
+    <ClientProvider client={client}>
+      <Layout auth={auth} onLogout={onLogout}>
+        {isLogged && <Guillotina auth={auth} url={url} />}
+        {!isLogged && (
+          <div className="columns is-centered">
+            <div className="columns is-half">
+              <Login onLogin={onLogin} auth={auth} />
+            </div>
           </div>
-        </div>
-      )}
-    </Layout>
+        )}
+      </Layout>
+    </ClientProvider>
   )
 }
 
diff --git a/src/guillo-gmi/components/behaviors/iattachment.js b/src/guillo-gmi/components/behaviors/iattachment.js
index 12fd014e..570005a3 100644
--- a/src/guillo-gmi/components/behaviors/iattachment.js
+++ b/src/guillo-gmi/components/behaviors/iattachment.js
@@ -18,7 +18,7 @@ export function IAttachment(props) {
     ctx.refresh()
   }
 
-  const downloadFile = (file, content_type) => async (event) => {
+  const downloadFile = (file, content_type) => async () => {
     const endpoint = `${ctx.path}@download/file`
     const res = await ctx.client.download(endpoint)
     const text = await res.blob()
diff --git a/src/guillo-gmi/components/behaviors/imultiattachment.js b/src/guillo-gmi/components/behaviors/imultiattachment.js
index cf488ab3..ebe55729 100644
--- a/src/guillo-gmi/components/behaviors/imultiattachment.js
+++ b/src/guillo-gmi/components/behaviors/imultiattachment.js
@@ -34,7 +34,7 @@ export function IMultiAttachment(props) {
     Ctx.refresh()
   }
 
-  const downloadFile = (file, content_type, fileName) => async (event) => {
+  const downloadFile = (file, content_type, fileName) => async () => {
     const endpoint = `${Ctx.path}@download/files/${file}`
     const res = await Ctx.client.download(endpoint)
     const text = await res.blob()
diff --git a/src/guillo-gmi/components/context_toolbar.js b/src/guillo-gmi/components/context_toolbar.js
index 14c14bfb..d1d68202 100644
--- a/src/guillo-gmi/components/context_toolbar.js
+++ b/src/guillo-gmi/components/context_toolbar.js
@@ -12,7 +12,7 @@ import { useLocation } from '../hooks/useLocation'
 /* eslint jsx-a11y/anchor-is-valid: "off" */
 const initialState = { types: undefined }
 
-export function CreateButton(props) {
+export function CreateButton() {
   const [state, setState] = useSetState(initialState)
   const Ctx = useTraversal()
   const Config = useConfig()
@@ -34,7 +34,7 @@ export function CreateButton(props) {
     return (
       <Button
         className={'is-small is-success'}
-        onClick={(ev) => doAction(state.types[0])}
+        onClick={() => doAction(state.types[0])}
       >
         Add {state.types[0]}
       </Button>
@@ -75,13 +75,6 @@ export function ContextToolbar({ AddButton, ...props }) {
     ev.preventDefault()
   }
 
-  const setFocus = (ev) => {
-    ref.current.focus()
-    ev.preventDefault()
-  }
-
-  // useKey("/", setFocus)
-
   // cleanup form on state.search change
   React.useEffect(() => {
     if (!searchText || searchText === '') {
diff --git a/src/guillo-gmi/components/guillotina.js b/src/guillo-gmi/components/guillotina.js
index c41341c6..224ad7bc 100644
--- a/src/guillo-gmi/components/guillotina.js
+++ b/src/guillo-gmi/components/guillotina.js
@@ -1,10 +1,8 @@
 import React from 'react'
 import { useReducer } from 'react'
 import { useEffect } from 'react'
-import { useRef } from 'react'
-import { getClient } from '../lib/client'
 import { Flash } from './flash'
-import { TraversalProvider } from '../contexts'
+import { TraversalProvider, useGuillotinaClient } from '../contexts'
 import { useConfig } from '../hooks/useConfig'
 import { useRegistry } from '../hooks/useRegistry'
 import { useLocation } from '../hooks/useLocation'
@@ -15,14 +13,12 @@ import { Loading } from './ui/loading'
 export function Guillotina({ auth, ...props }) {
   const url = props.url || 'http://localhost:8080' // without trailing slash
   const config = props.config || {}
-
-  // Will hold client instance
-  const ref = useRef()
+  const client = useGuillotinaClient()
 
   const { Permissions } = useConfig(config)
   const registry = useRegistry(props.registry || {})
   // Location is cooked routing solution (only uses search params)
-  const [location, setRouterParam] = useLocation()
+  const [location] = useLocation()
 
   // if there is no path provided just go to root
   const searchPath = location.get('path') || '/'
@@ -32,15 +28,7 @@ export function Guillotina({ auth, ...props }) {
 
   const [state, dispatch] = useReducer(guillotinaReducer, initialState)
 
-  // we store the client on a ref (refs are stable across renders)
-  if (!ref.current) {
-    // TODO: Refactor, we should be able to provide just a client
-    // this way is easy composable and extensible from outside.
-    ref.current = props.client || getClient(url, auth)
-  }
-
   const { path, refresh } = state
-  const client = ref.current
 
   useEffect(() => {
     dispatch({ type: 'SET_PATH', payload: searchPath })
@@ -75,7 +63,6 @@ export function Guillotina({ auth, ...props }) {
     state,
     dispatch,
     registry,
-    setRouterParam,
     flash: config.flash,
   }
 
diff --git a/src/guillo-gmi/components/input/form_builder.js b/src/guillo-gmi/components/input/form_builder.js
index 52c6029e..6290dee7 100644
--- a/src/guillo-gmi/components/input/form_builder.js
+++ b/src/guillo-gmi/components/input/form_builder.js
@@ -66,6 +66,7 @@ export function FormBuilder({
       onChange: onUpdate(field),
       placeholder: properties[field].title || '',
       id: generateUID(),
+      testid: `${field}TestInput`,
     }
 
     if (required.includes(field)) {
@@ -86,7 +87,7 @@ export function FormBuilder({
 
   return (
     <Form onSubmit={changes} {...rest}>
-      {fields.map((field, i) => (
+      {fields.map((field) => (
         <GetTag field={field} key={field} />
       ))}
       {children_}
diff --git a/src/guillo-gmi/components/input/input.js b/src/guillo-gmi/components/input/input.js
index 97e90629..dfdc2b08 100644
--- a/src/guillo-gmi/components/input/input.js
+++ b/src/guillo-gmi/components/input/input.js
@@ -31,6 +31,7 @@ export const Input = React.forwardRef(
       onChange,
       validator = noop,
       errorMessage,
+      testid = 'input-test',
       ...rest
     },
     ref
@@ -81,6 +82,7 @@ export const Input = React.forwardRef(
             autoComplete={autoComplete}
             disabled={loading || rest.disabled}
             required={required}
+            data-test={testid}
             {...handlers}
             {...rest}
           />
diff --git a/src/guillo-gmi/components/input/search_input.js b/src/guillo-gmi/components/input/search_input.js
index 68a3e59e..ebe92d24 100644
--- a/src/guillo-gmi/components/input/search_input.js
+++ b/src/guillo-gmi/components/input/search_input.js
@@ -64,14 +64,14 @@ export const SearchInput = ({
     let searchTermQs = []
     let searchTermParsed = []
     if (value !== '') {
-      searchTermParsed = parser(`title__in=${value}`)
+      searchTermParsed = parser(`id__in=${value}`)
     }
 
     if (qs.length > 0 || searchTermParsed.length > 0) {
       searchTermQs = buildQs([...qs, ...searchTermParsed])
     }
 
-    const res = await client.search(
+    const data = await client.search(
       path,
       searchTermQs,
       false,
@@ -79,24 +79,15 @@ export const SearchInput = ({
       page * PageSize,
       PageSize
     )
-    if (res.status === 401) {
-      setOptions({
-        loading: false,
-        items: [],
-        page: page,
-      })
-    } else {
-      const data = await res.json()
-      const newItems =
-        options.items && concat ? [...options.items, ...data.items] : data.items
+    const newItems =
+      options.items && concat ? [...options.items, ...data.items] : data.items
 
-      setOptions({
-        items: newItems,
-        loading: false,
-        items_total: data.items_total,
-        page: page,
-      })
-    }
+    setOptions({
+      items: newItems,
+      loading: false,
+      items_total: data.items_total,
+      page: page,
+    })
   }
 
   React.useEffect(() => {
diff --git a/src/guillo-gmi/components/item.js b/src/guillo-gmi/components/item.js
index 8d45cae3..ded8a0fb 100644
--- a/src/guillo-gmi/components/item.js
+++ b/src/guillo-gmi/components/item.js
@@ -1,14 +1,15 @@
 import React from 'react'
 
 import { ItemModel } from '../models'
+import { useLocation } from '../hooks/useLocation'
 import { useTraversal } from '../contexts'
 import { Icon } from './ui/icon'
 import { ItemCheckbox } from './selected_items_actions'
 import { Delete } from './ui'
 
-export function Item({ item, setPath, icon }) {
-  const Ctx = useTraversal()
-  const link = () => Ctx.setPath(item.path)
+export function Item({ item, icon }) {
+  const [, navigate] = useLocation()
+  const link = () => navigate({ path: item.path }, true)
   return (
     <tr>
       <td onClick={link} style={{ width: '25px' }}>
@@ -24,9 +25,10 @@ const smallcss = {
 }
 
 export function RItem({ item, search, columns }) {
+  const [, navigate] = useLocation()
   const traversal = useTraversal()
   const model = new ItemModel(item, traversal.url, traversal.path)
-  const link = () => traversal.setPath(model.path)
+  const link = () => navigate({ path: model.path }, true)
 
   return (
     <tr key={item}>
diff --git a/src/guillo-gmi/components/panel/items.js b/src/guillo-gmi/components/panel/items.js
index 61ac728a..17afb388 100644
--- a/src/guillo-gmi/components/panel/items.js
+++ b/src/guillo-gmi/components/panel/items.js
@@ -23,7 +23,7 @@ const initialState = {
   total: 0,
 }
 
-export function PanelItems(props) {
+export function PanelItems() {
   const [location, setLocation] = useLocation()
   const { PageSize } = useConfig()
 
@@ -116,7 +116,6 @@ export function PanelItems(props) {
               items.map((item) => (
                 <RItem
                   item={item}
-                  setPath={Ctx.setPath}
                   key={item['@uid']}
                   search={search}
                   columns={columns}
diff --git a/src/guillo-gmi/components/panel/permissions.js b/src/guillo-gmi/components/panel/permissions.js
index 547d785b..3cbc6f8f 100644
--- a/src/guillo-gmi/components/panel/permissions.js
+++ b/src/guillo-gmi/components/panel/permissions.js
@@ -10,7 +10,7 @@ import { Table } from '../ui/table'
 import { useCrudContext } from '../../hooks/useCrudContext'
 import { useTraversal } from '../../contexts'
 
-export function PanelPermissions(props) {
+export function PanelPermissions() {
   const { get, result, loading } = useCrudContext()
   const ctx = useTraversal()
 
diff --git a/src/guillo-gmi/components/panel/permissions_prinrole.js b/src/guillo-gmi/components/panel/permissions_prinrole.js
index bfca8408..eacde5d5 100644
--- a/src/guillo-gmi/components/panel/permissions_prinrole.js
+++ b/src/guillo-gmi/components/panel/permissions_prinrole.js
@@ -23,7 +23,7 @@ export function PermissionPrinrole({ groups, roles, operations, refresh }) {
     setter({ [field]: values })
   }
 
-  const savePermission = async (ev) => {
+  const savePermission = async () => {
     if (!state.principal || !state.setting || state.roles.length === 0) {
       setState({ error: 'Invalid form' })
       return
diff --git a/src/guillo-gmi/components/panel/permissions_roleperm.js b/src/guillo-gmi/components/panel/permissions_roleperm.js
index a3c5534f..39432b65 100644
--- a/src/guillo-gmi/components/panel/permissions_roleperm.js
+++ b/src/guillo-gmi/components/panel/permissions_roleperm.js
@@ -28,7 +28,7 @@ export function PermissionRoleperm({
     setter({ [field]: values })
   }
 
-  const savePermission = async (ev) => {
+  const savePermission = async () => {
     if (!state.role || !state.setting || state.permission.length === 0) {
       console.log(state)
       setState({ error: 'Invalid form' })
diff --git a/src/guillo-gmi/components/panel/properties.js b/src/guillo-gmi/components/panel/properties.js
index 23130184..52a5aa27 100644
--- a/src/guillo-gmi/components/panel/properties.js
+++ b/src/guillo-gmi/components/panel/properties.js
@@ -12,7 +12,7 @@ import { useTraversal } from '../../contexts'
 const _showProperties = ['@id', '@name', '@uid', 'title']
 const _editable = ['title']
 
-export function PanelProperties(props) {
+export function PanelProperties() {
   const Ctx = useTraversal()
   const modifyContent = Ctx.hasPerm('guillotina.ModifyContent')
   const cfg = useConfig()
diff --git a/src/guillo-gmi/components/panel/requester.js b/src/guillo-gmi/components/panel/requester.js
index 4a0bc9ea..896279f8 100644
--- a/src/guillo-gmi/components/panel/requester.js
+++ b/src/guillo-gmi/components/panel/requester.js
@@ -25,7 +25,7 @@ const methods = [
 
 export function PanelRequester() {
   const [state, setState] = useSetState(initial)
-  const { get, post, del, patch, loading, isError, result } = useCrudContext()
+  const { get, post, del, patch, loading, result } = useCrudContext()
   const ref = React.useRef()
 
   React.useEffect(() => {
diff --git a/src/guillo-gmi/components/path.js b/src/guillo-gmi/components/path.js
index 260d6d25..127280d9 100644
--- a/src/guillo-gmi/components/path.js
+++ b/src/guillo-gmi/components/path.js
@@ -4,7 +4,7 @@ import { useLocation } from '../hooks/useLocation'
 
 /* eslint jsx-a11y/anchor-is-valid: "off" */
 
-export function Path(props) {
+export function Path() {
   const ctx = useTraversal()
   const [, navigate] = useLocation()
 
@@ -14,7 +14,7 @@ export function Path(props) {
   if (segments.length === 1) {
     return false
   }
-  //ctx.setPath(links[indx])
+
   return (
     <nav className="breadcrumb" aria-label="breadcrumbs">
       <ul>
diff --git a/src/guillo-gmi/components/properties_view.js b/src/guillo-gmi/components/properties_view.js
index 9ce65abd..b8a89567 100644
--- a/src/guillo-gmi/components/properties_view.js
+++ b/src/guillo-gmi/components/properties_view.js
@@ -1,7 +1,7 @@
 import React from 'react'
 import { useTraversal } from '../contexts'
 
-export function PropertiesButtonView(properties) {
+export function PropertiesButtonView() {
   const Ctx = useTraversal()
   const { getProperties } = Ctx.registry
 
@@ -14,7 +14,7 @@ export function PropertiesButtonView(properties) {
   return null
 }
 
-export function PropertiesView(props) {
+export function PropertiesView() {
   const Ctx = useTraversal()
   const { getProperties } = Ctx.registry
   const Props = getProperties(Ctx.context['@type'])
diff --git a/src/guillo-gmi/components/searchLabels.js b/src/guillo-gmi/components/searchLabels.js
index 097d75f2..6f36ac2d 100644
--- a/src/guillo-gmi/components/searchLabels.js
+++ b/src/guillo-gmi/components/searchLabels.js
@@ -2,7 +2,7 @@ import React from 'react'
 import { Tag } from './ui/tag'
 import { useLocation } from '../hooks/useLocation'
 
-export function SearchLabels(props) {
+export function SearchLabels() {
   const [location, , del] = useLocation()
   const search = location.get('q')
 
diff --git a/src/guillo-gmi/components/tabs.js b/src/guillo-gmi/components/tabs.js
index fa83ab4c..c6492a57 100644
--- a/src/guillo-gmi/components/tabs.js
+++ b/src/guillo-gmi/components/tabs.js
@@ -15,8 +15,6 @@ export function TabsPanel({
 }) {
   const [location, setLocation] = useLocation()
 
-  const defaultTab = currentTab
-
   if (location.get('tab')) {
     currentTab = location.get('tab')
   } else {
diff --git a/src/guillo-gmi/contexts/index.js b/src/guillo-gmi/contexts/index.js
index 37ccf645..bba2c91d 100644
--- a/src/guillo-gmi/contexts/index.js
+++ b/src/guillo-gmi/contexts/index.js
@@ -4,6 +4,8 @@ import { getContainerFromPath } from '../lib/client'
 
 export const AuthContext = createContext({})
 
+export const ClientContext = createContext({})
+
 export const TraversalContext = createContext({})
 
 class Traversal {
@@ -12,12 +14,6 @@ class Traversal {
     if (typeof flash === 'function') this.flash = flash
   }
 
-  setPath(path) {
-    // This is like black magic, document it
-    // router interactions are hard
-    this.setRouterParam({ path: path }, true)
-  }
-
   refresh() {
     this.dispatch({ type: 'REFRESH' })
   }
@@ -89,3 +85,13 @@ export function TraversalProvider({ children, ...props }) {
 export function useTraversal() {
   return React.useContext(TraversalContext)
 }
+
+export function ClientProvider({ children, client }) {
+  return (
+    <ClientContext.Provider value={client}>{children}</ClientContext.Provider>
+  )
+}
+
+export function useGuillotinaClient() {
+  return React.useContext(ClientContext)
+}
diff --git a/src/guillo-gmi/forms/base.js b/src/guillo-gmi/forms/base.js
index 72d2fa29..159812d7 100644
--- a/src/guillo-gmi/forms/base.js
+++ b/src/guillo-gmi/forms/base.js
@@ -9,7 +9,7 @@ export function BaseForm({ onSubmit, actionName, title }) {
   const [id, setId] = useState('')
   const [error, setError] = useState(undefined)
 
-  const submit = (values) => {
+  const submit = () => {
     if (name === '') {
       setError('This field is required')
       return
diff --git a/src/guillo-gmi/hooks/useInput.js b/src/guillo-gmi/hooks/useInput.js
index 896fc276..0fad2dd6 100644
--- a/src/guillo-gmi/hooks/useInput.js
+++ b/src/guillo-gmi/hooks/useInput.js
@@ -20,12 +20,12 @@ const useInput = (onChange, value, validator) => {
     if (onChange) onChange(value)
   }
 
-  const onBlur = (ev) => {
+  const onBlur = () => {
     const hasError = applyValidators(state.value, validator) === false
     if (hasError) setState({ value: state.value, hasError })
   }
 
-  const onFocus = (ev) => {
+  const onFocus = () => {
     if (state.hasError) {
       setState({ value: state.value, hasError: false })
     }
diff --git a/src/guillo-gmi/hooks/useLocation.js b/src/guillo-gmi/hooks/useLocation.js
index ba1b1d1c..0d7fb17d 100644
--- a/src/guillo-gmi/hooks/useLocation.js
+++ b/src/guillo-gmi/hooks/useLocation.js
@@ -1,4 +1,3 @@
-import React from 'react'
 import { useEffect, useRef, useState, useCallback } from 'react'
 
 // Mostly inspired from
@@ -17,7 +16,7 @@ const clean = (to) => {
   setURLParams(current)
 }
 
-export const useLocation = ({ base = '' } = {}) => {
+export const useLocation = () => {
   const [path, update] = useState(currentSearchParams())
   const prevPath = useRef(path)
 
diff --git a/src/guillo-gmi/lib/auth.js b/src/guillo-gmi/lib/auth.js
index 9732dfef..afc19897 100644
--- a/src/guillo-gmi/lib/auth.js
+++ b/src/guillo-gmi/lib/auth.js
@@ -1,7 +1,5 @@
 import jwt_decode from 'jwt-decode'
 
-const noop = () => {}
-
 export class Auth {
   maxRetry = 1
   retryRefresh = 0
diff --git a/src/guillo-gmi/lib/helpers.js b/src/guillo-gmi/lib/helpers.js
index 99da7a6c..79cecbea 100644
--- a/src/guillo-gmi/lib/helpers.js
+++ b/src/guillo-gmi/lib/helpers.js
@@ -47,7 +47,7 @@ export function stringToSlug(str) {
 }
 
 export function sleep(ms) {
-  return new Promise((resolve, reject) => {
+  return new Promise((resolve) => {
     setTimeout(function () {
       resolve()
     }, ms)
diff --git a/src/guillo-gmi/views/application.js b/src/guillo-gmi/views/application.js
index b0383307..914c6bc1 100644
--- a/src/guillo-gmi/views/application.js
+++ b/src/guillo-gmi/views/application.js
@@ -28,8 +28,7 @@ export function ApplicationCtx(props) {
   )
 }
 
-export function DatabaseCtx({ state, client, ...props }) {
-  const Ctx = useTraversal()
+export function DatabaseCtx({ state }) {
   const { containers } = state.context
   const { path } = state
   return (
@@ -43,7 +42,6 @@ export function DatabaseCtx({ state, client, ...props }) {
                 item={{ id: db, path: `${path}${db}/` }}
                 key={db}
                 icon={'fas fa-archive'}
-                setPath={Ctx.setPath}
               />
             ))}
           </tbody>
@@ -78,7 +76,7 @@ function ModalAddContainer({ isActive, setActive }) {
   const [idField, setId] = useState('')
   const [error, setError] = useState(undefined)
 
-  async function createContainer(ev) {
+  async function createContainer() {
     setLoading(true)
     const data = {
       '@type': 'Container',
diff --git a/src/guillo-gmi/views/users.js b/src/guillo-gmi/views/users.js
index 77d7e4b8..d4c2f2a8 100644
--- a/src/guillo-gmi/views/users.js
+++ b/src/guillo-gmi/views/users.js
@@ -41,8 +41,8 @@ export function UsersCtx(props) {
   )
 }
 
-export function UserCtx(props) {
-  const { Ctx, patch, loading, isError, errorMessage } = useCrudContext()
+export function UserCtx() {
+  const { Ctx, patch, loading } = useCrudContext()
 
   const [state, setState] = React.useState({ roles: [], gorups: [] })
 
@@ -60,12 +60,9 @@ export function UserCtx(props) {
         Ctx.client.getRoles(Ctx.path),
       ])
 
-      let groups = []
+      let groups = requestGetGroups
       let roles = []
 
-      if (requestGetGroups.ok) {
-        groups = await requestGetGroups.json()
-      }
       if (requestGetRoles.ok) {
         roles = await requestGetRoles.json()
       }


Test patch:
diff --git a/e2e/cypress/elements/user-form-selectors.js b/e2e/cypress/elements/user-form-selectors.js
new file mode 100644
index 00000000..df630225
--- /dev/null
+++ b/e2e/cypress/elements/user-form-selectors.js
@@ -0,0 +1,6 @@
+export const USER_FORM_SELECTORS = {
+  username: "[data-test='usernameTestInput']",
+  password: "[data-test='passwordTestInput']",
+  email: "[data-test='emailTestInput']",
+  name: "[data-test='nameTestInput']",
+}
diff --git a/e2e/cypress/integration/content.js b/e2e/cypress/integration/content.js
index 101135bf..70cb82c5 100644
--- a/e2e/cypress/integration/content.js
+++ b/e2e/cypress/integration/content.js
@@ -1,3 +1,5 @@
+import { USER_FORM_SELECTORS } from '../elements/user-form-selectors'
+
 describe('test content', function () {
   beforeEach('clear', function () {
     cy.clearLocalStorage()
@@ -50,27 +52,30 @@ describe('test content', function () {
     cy.get('.notification').should('contain', 'Items removed!')
   })
 
-  it.only('creates a User as Admin, modifies it and delete it', function () {
+  it('creates a User as Admin, modifies it and delete it', function () {
     cy.get('td:contains(UserManager)').click()
 
     // Create User
     cy.get('.level-right > .button').click()
-    cy.get('#id-0').type('Test User')
-    cy.get('#id-2').type('test-user@test.test')
-    cy.get('#id-4').type('Test Name')
-    cy.get('#id-6').type('TestPassword')
+    cy.get(USER_FORM_SELECTORS.username).type('test-user')
+    cy.get(USER_FORM_SELECTORS.email).type('test-user@test.test')
+    cy.get(USER_FORM_SELECTORS.name).type('Test Name')
+    cy.get(USER_FORM_SELECTORS.password).type('TestPassword')
     cy.get('p.control > .button').should('contain', 'Add User').click()
     cy.get('.notification').should('contain', 'Content created!')
 
     // Modify Item
-    cy.get('td:contains(Test User)').click()
-    cy.get('#id-81').clear().type('Test Modified User')
+    cy.get('td:contains(test-user)').click()
+    cy.get(USER_FORM_SELECTORS.username).type('Test Modified User', {
+      force: true,
+    })
+
     cy.get('p.control > .button').click()
     cy.get('.notification').should('contain', 'Data updated')
     cy.get('.breadcrumb a:contains(users)').click()
 
     // Delete Item
-    cy.get('tr:contains(Test User) .delete').click()
+    cy.get('tr:contains(test-user) .delete').click()
     cy.get('.level-right > .control > .button').click()
     cy.get('.notification').should('contain', 'Items removed!')
   })
diff --git a/e2e/cypress/integration/guillotina.js b/e2e/cypress/integration/guillotina.js
index 0b1adee8..ecd1bf17 100644
--- a/e2e/cypress/integration/guillotina.js
+++ b/e2e/cypress/integration/guillotina.js
@@ -10,17 +10,17 @@ describe('check guillotina', function () {
       method: 'GET',
       url: api_url,
       headers,
-    }).then((res) => {
+    }).then(() => {
       console.log('Container exists')
     })
   })
 
-  it('check root user is created', function () {
+  it('check default is created', function () {
     cy.request({
       method: 'GET',
-      url: `${api_url}/container/users/root`,
+      url: `${api_url}/container/users/default`,
       headers,
-    }).then((res) => {
+    }).then(() => {
       console.log('User exists')
     })
   })
diff --git a/e2e/cypress/support/guillotina.js b/e2e/cypress/support/guillotina.js
index 2ee095cf..55c7cd63 100644
--- a/e2e/cypress/support/guillotina.js
+++ b/e2e/cypress/support/guillotina.js
@@ -10,14 +10,14 @@ export function setupGuillotina() {
     url: api_url,
     headers,
     body: { '@type': 'Container', id: 'container' },
-  }).then((response) => console.log('container created'))
+  }).then(() => console.log('container created'))
 
   cy.request({
     method: 'POST',
     url: `${api_url}/container/@addons`,
     headers,
     body: { id: 'dbusers' },
-  }).then((response) => console.log('dbusers addon added'))
+  }).then(() => console.log('dbusers addon added'))
 
   cy.request({
     method: 'POST',
@@ -25,11 +25,11 @@ export function setupGuillotina() {
     headers,
     body: {
       '@type': 'User',
-      username: 'root',
-      password: 'root',
-      email: 'root@test.com',
+      username: 'default',
+      password: 'default',
+      email: 'default@test.com',
     },
-  }).then((response) => console.log('dbusers addon added'))
+  }).then(() => console.log('default user added'))
 }
 
 export function tearDownGuillotina() {
@@ -39,21 +39,27 @@ export function tearDownGuillotina() {
   }
   const api_url = 'http://localhost:8080/db'
 
+  cy.request({
+    method: 'DELETE',
+    url: `${api_url}/container/users/default`,
+    headers,
+  }).then(() => console.log('default user deleted'))
+
   cy.request({
     method: 'DELETE',
     url: `${api_url}/container/users`,
     headers,
-  }).then((response) => console.log('container deleted'))
+  }).then(() => console.log('users deleted'))
 
   cy.request({
     method: 'DELETE',
     url: `${api_url}/container/groups`,
     headers,
-  }).then((response) => console.log('container deleted'))
+  }).then(() => console.log('groups deleted'))
 
   cy.request({
     method: 'DELETE',
     url: `${api_url}/container`,
     headers,
-  }).then((response) => console.log('container deleted'))
+  }).then(() => console.log('container deleted'))
 }
diff --git a/e2e/example/components/guillotina.js b/e2e/example/components/guillotina.js
index fd2b467c..75427a3f 100644
--- a/e2e/example/components/guillotina.js
+++ b/e2e/example/components/guillotina.js
@@ -2,11 +2,14 @@ import React from 'react'
 import { Guillotina } from 'react-gmi'
 import { Auth } from 'react-gmi'
 import { Login } from 'react-gmi'
+import { getClient } from 'react-gmi'
+import { ClientProvider } from 'react-gmi'
 
 import '../node_modules/react-gmi/dist/css/style.css'
 
 const url = 'http://localhost:8080/'
 const auth = new Auth(url)
+const client = getClient(url, auth)
 
 export default function App() {
   const [isLogged, setLogged] = React.useState(auth.isLogged)
@@ -19,7 +22,7 @@ export default function App() {
   auth.onLogout = onLogout
 
   return (
-    <>
+    <ClientProvider client={client}>
       {isLogged && <Guillotina auth={auth} url={url} />}
       {!isLogged && (
         <div className="columns is-centered">
@@ -28,6 +31,6 @@ export default function App() {
           </div>
         </div>
       )}
-    </>
+    </ClientProvider>
   )
 }
diff --git a/e2e/example/pages/index.js b/e2e/example/pages/index.js
index 2496bc2e..60d9a6fc 100644
--- a/e2e/example/pages/index.js
+++ b/e2e/example/pages/index.js
@@ -1,6 +1,4 @@
 import React from 'react'
-import { useState } from 'react'
-import { useRef, useEffect } from 'react'
 import dynamic from 'next/dynamic'
 
 const DynamicComponentWithNoSSR = dynamic(
diff --git a/e2e/g_conf/config.yaml b/e2e/g_conf/config.yaml
index 84e66736..e0da6b34 100644
--- a/e2e/g_conf/config.yaml
+++ b/e2e/g_conf/config.yaml
@@ -8,7 +8,7 @@ databases:
     dsn: postgres://guillotina:@postgres/guillotina
     read_only: false
     cache_strategy: redis
-    autovacuum: false
+    autovacuum: true
     db_schema: guillotina
     pool_size: 8
 host: 0.0.0.0