Context:
The database structure of my application starts at the Company
level, a user may be part of more than one company, and he may switch Companies
at any point, every other Resource resides inside Company
Every time the user switches company, the whole application changes, because the data changes, it's like a Global Filter, by companyId, a resource that needs to be loaded first and all other depend on it.
So for example, to get the Projects
of a Company
, the endpoint is /{companyId}/projects
The way I've tried to do this is using a React Context in the Layout, because I need it to encompass the Sidebar as well.
It's not working too well because it is querying userCompanies
4 times on startup, so I'm looking either for a fix or a more elegant solution
Another challenge is also to relay the current companyId to all child Resources, for now I'm using the filter parameter, but I don't know how I will do it in Show/Create/Edit
companyContext.js
const CompanyContext = createContext()
const companyReducer = (state, update) => {
return {...state, ...update}
}
const CompanyContextProvider = (props) => {
const dataProvider = useDataProvider();
const [state, dispatch] = useReducer(companyReducer, {
loading : true,
companies : [],
firstLoad : false
})
useEffect(() => {
if(!state.firstLoad) { //If I remove this it goes on a infinite loop
//This is being called 3 times on startup
console.log('querying user companies')
dataProvider.getList('userCompanies')
.then(({data}) =>{
dispatch({
companies: data,
selected: data[0].id, //Selecting first as default
loading: false,
firstLoad: true
})
})
.catch(error => {
dispatch({
error: error,
loading: false,
firstLoad: true
})
})
}
})
return (
<CompanyContext.Provider value={[state, dispatch]}>
{props.children}
</CompanyContext.Provider>
)
}
const useCompanyContext = () => {
const context = useContext(CompanyContext);
return context
}
layout.js
const CompanySelect = ({companies, loading, selected, callback}) => {
const changeCompany = (companyId) => callback(companyId)
if (loading) return <div>Loading...</div>
if (!companies || companies.length < 1) return <div>You are not part of a company</div>
return (
<select value={selected} onChange={(evt) => changeCompany(evt.target.value)}>
{companies.map(company => <option value={company.id} key={company.id}>{company.name}</option>)}
</select>
)
}
const CompanySidebar = (props) => {
const [companyContext, dispatch] = useCompanyContext();
const {companies, selected, loading, error} = companyContext;
const changeCompany = (companyId) => {
dispatch({
selected : companyId
})
}
return (
<div>
<CompanySelect companies={companies} selected={selected} loading={loading} callback={changeCompany}/>
<Sidebar {...props}>
{props.children}
</Sidebar>
</div>
)
}
export const MyLayout = (props) => {
return (
<CompanyContextProvider>
<Layout {...props} sidebar={CompanySidebar}/>
</CompanyContextProvider>
)
};
app.js
const ProjectList = (props) => {
const [companyContext, dispatch] = useCompanyContext();
const {selected, loading} = companyContext;
if(loading) return <Loading />;
//The filter is how I'm passing the companyId
return (
<List {...props} filter={{companyId: selected}}>
<Datagrid rowClick="show">
<TextField sortable={false} source="name" />
<DateField sortable={false} source="createdAt" />
</Datagrid>
</List>
);
};
const Admin = () => {
return (
<Admin
authProvider={authProvider}
dataProvider={dataProvider}
layout={MyLayout}
>
<Resource name="projects" list={ProjectList}/>
</Admin>
);
};