React-admin: How to create user profile form in react-admin v3.2.*? - reactjs

I've noticed that the official article on how to create user settings (profile) in React-Admin is outdated (https://marmelab.com/blog/2019/03/07/react-admin-advanced-recipes-user-profile.html).
I followed the example and tried to use a new DataProvider, but couldn't get Edit view working (it just showed blank Card component with no fields even though I've set them in a way that's described in the example).
I was searching for several days on how to implement it in a simplest/clean way, but there's a very small amount of information about it.
Does somebody know how to do it in react-admin 3.2.*?
It might be helpful for others who have the same issue.
Any help will be very appreciated! Thanks!

I had the same problem. Looking at the props passed toreact-admin's Edit, I saw the record prop was undefined. It was because the id field inside the record returned by the data provider's getOne method was different than the id prop hardcoded on the Edit component. Once that was set to match, both reading/editing works.
My working code:
// remove staticContext to avoid React 'unknown prop on DOM element' error
export const PrincipalEdit = ({ staticContext, ...props }: { staticContext: any; props: any }) => {
return (
// `id` has to match with `id` field on record returned by data provider's `getOne`
// `basePath` is used for re - direction
// but we specify no redirection since there is no list component
<Edit {...props} title="My Profile" id="my-profile" resource={b.PRINCIPAL} basePath="my-profile" redirect={false}>
<SimpleForm>
<TextInput source="firstName" validate={required()} />
</SimpleForm>
</Edit>
);
};

The issue is how the data is stored by react-admin at the moment (haven't checked how it was before). Now each data entry is saved by its id in the store object (the reasons are obvious). I think the best approach is to modify the data provider.
if (resource === 'profile') {
const { id, ...p } = params; // removes the id from the params
if (p.data)
delete p.data.id; // when updates there is data object that is passed to the backend
return dataProvider(type, resource, { ...p, id: '' }) // set the id just empty string to hack undefined as http param
.then(({ data }) => {
return Promise.resolve({
data: {
...data,
id // return the data with the initial id
}
});
});
}
This way the backend endpoint could return just the object at the main endpoint of the entity /profile. If you do not set the id prop to '' it will try to fetch /profile/undefined because the id prop was deleted from the params object. If you do not delete the id prop from the data object when updating, depending on the backend sql query (assuming you are using some kind of db) for updating a record, it may try to set or search by this id.
In the Edit component you can pass whatever id you want but something must be passed.
Additional:
If you are using NestJS as backend with the Crud package, these #Crud params may be helpful
...
params: {
id: { // the routes won't have params, very helpful for me/profile controller
primary: true,
disabled: true,
},
},
routes: {
only: ["getOneBase", "updateOneBase"],
},
...

Related

How to get an object from firebase in react native

I'm trying to read object data from firebase, but since I use the code below, some error occurred.
This is my method to read data from firebase.
function getMenuData() {
let newMenu = [];
MENUref.onSnapshot(querySnapshot => {
querySnapshot.forEach(doc => {
const menu = {
id: doc.id,
Name: doc.data().name,
desc: doc.data().desc,
url: Images[doc.data().name],
people: doc.data().people,
type: doc.data().type,
time: doc.data().time,
ingre: doc.data().ingre,
season: doc.data().season,
like: doc.data().like,
}
newMenu.push(menu)
});
setMenu(newMenu)
setDisplayOfData(newMenu)
// console.log('food', Menu)
});
}
Then pop out "Objects are not valid as a React child." error message.
After I do some research shows it need to done through map() method, but can it performed in the "menu: {}" ?
The goal is to read the object data from firebase and make it like this.
data structure
This is my firebase data structure.
Or some available approaches can do the same thing with the object data like this?
Thanks a lot!
In the JavaScript SDK there is something known as useCollectionData than can be imported from react-firebase-hooks/firestore then you would use something such as const [data] = useCollectionData(query); where query could be a reference to the firebase collection you are pointing to with filtering capabilities ex. firestore.collection('your-collection').orderBy('createdAt). This is the official method of handling firebase data which you can then simply use with the .map(item => <Item data={item}>)
Check how your menu is being rendered to the View.
Since the menu is an array of data from firebase, to render it to the view, you have to use the map() js method because objects or array cannot be converted to a React Child element.
e.g;
{menu.map((item) => {
return <Text>{item.Name}</Text>
})}

Getting current table data from Ant Design Table

I'm using Ant design table for displaying some data. New data arrives every one second. When user clicks on button, I need to export current table data to XSL.
I'm getting current data in this way:
onChange={(pagination, filter, sorter, currentTable) => this.onTableChange(filter, sorter, currentTable)}.
This thing works and gets me good and filtered data, but when I get new data, I can't get those new data because this function is not triggered while none of the filters or sort settings didn't change. Is there any way to get current table data without dummy changing filter to trigger onChange function?
Title and footer return only current page data.
Code show here
{ <Table
dataSource={this.props.data}
rowKey={(record) => { return record.id}}
columns={this.state.Columns}
pagination={this.state.pageSizeOptions}
rowClassName={(record) => { return
this.getRowClassNames(record) }}
expandedRowKeys={ this.state.expandedRowKeys }
expandedRowRender={record => { return
this.getEventsRows(record) }}
onExpand={(expanded, record) => {
this.onExpandRow(expanded, record.id) }}
expandRowByClick={false}
onChange={(pagination, filter, sorter, currentTable)
=> this.onTableChange(filter, sorter, currentTable)}
/>
}
You can store pagination, sorter and filter data in your component state. Also you can pass these params to your redux state and store there. If you can share your code, i can provide more specific answers.
Below you can find my solution for permanent filter. I was sending filter params to API and get filtered data. If you want to filter it in the component, you can use component lifecycle or render method.
onTableChange(pagination, filter, sorter){
const { params, columns } = this.state;
/** You can do any custom thing you want here. Below i update sort type in my state and pass it to my redux function */
if(sorter.order != null)
params.ordering = (sorter.order === 'descend' ? "-" : "")+ sorter.columnKey.toString();
this.setState({params});
this.props.listUsers(this.state.params);
}
This is because you have taken either columns, datasource varibale as object instead of array.
write
this.state.someVariable=[]
instead of
this.state.someVariable = {}

Preserve internal state on page refresh in React.js

It must be pretty regular issue.
I'm passing props down to the children and I'm using it there to request to the endpoint. More detailed: I'm clicking on the list item, I'm checking which item was clicked, I'm passing it to the child component and there basing on prop I passed I'd like to request certain data. All works fine and I'm getting what I need, but only for the first time, ie. when refreshing page incoming props are gone and I cannot construct proper URL where as a query I'd like to use the prop value. Is there a way to preserve the prop so when the page will be refresh it will preserve last prop.
Thank you!
(You might want to take a look at: https://github.com/rt2zz/redux-persist, it is one of my favorites)
Just like a normal web application if the user reloads the page you're going to have your code reloaded. The solution is you need to store the critical data somewhere other than the React state if you want it to survive.
Here's a "template" in pseudo code. I just used a "LocalStorage" class that doesn't exist. You could pick whatever method you wanted.
class Persist extends React.Component {
constuctor(props) {
this.state = {
criticalData = null
}
}
componentDidMount() {
//pseudo code
let criticalData = LocalStorage.get('criticalData')
this.setState({
criticalData: criticalData
})
}
_handleCriticalUpdate(update) {
const merge = {
...LocalStorage.get('criticalData')
...update
}
LocalStorage.put('criticalData', merge)
this.setState({
criticalData: merge
})
}
render() {
<div>
...
<button
onClick={e => {
let update = ...my business logic
this._handleCriticalUpdate(update) //instead of set state
}}
>
....
</div>
}
}
By offloading your critical data to a cookie or the local storage you are injecting persistence into the lifecycle of the component. This means when a user refreshes the page you keep your state.
I hope that helps!

Apollo Optimistic UI does not work in Mutation Component?

I am using <Mutation /> component which has Render Prop API & trying to do Optimistic Response in the UI.
So far I have this chunk in an _onSubmit function -
createApp({
variables: { id: uuid(), name, link },
optimisticResponse: {
__typename: "Mutation",
createApp: {
__typename: "App",
id: negativeRandom(),
name,
link
}
}
});
And my <Mutation /> component looks like -
<Mutation
mutation={CREATE_APP}
update={(cache, { data: { createApp } }) => {
const data = cache.readQuery({ query: LIST_APPS });
if (typeof createApp.id == "number") {
data.listApps.items.push(createApp);
cache.writeQuery({
query: LIST_APPS,
data
});
}
}}
>
{/*
some code here
*/}
</Mutation>
I know that update function in <Mutation /> runs twice, once when optimisticResponse is ran & second time when server response comes back.
On the first time, I give them id as a number. Checkout createApp in optimisticResponse where id: negativeRandom()
That's why my update prop in <Mutation /> component has a check if createApp.id is a number then push it in the array. It means that if data returned from local then push it in local cache & if returned from server don't push it.
But what happens is the data is only showed when returned from the server. The function update runs twice but it does not push it in the array.
I think there might 3 problems -
Either the update function does not run when local state is pushed
I've tried making fetchPolicy equal to cache-and-network & cache-first but it didn't work too.
The __typename in optimisticResponse. Idk if Mutation is the correct value, so I tried AppConnection too but it still does not work.
The complete code can be found here. Whole code exist in one file for simplicity. Its a very simple app which has 2 inputs & 1 submit button. It looks like -
Note: Same thing works with React. Here's a link to React Repo - https://github.com/deadcoder0904/react-darkmodelist
Apparently this was a bug in Apollo or React Apollo package. Don't know which bug or was it just for React Native but I just updated my dependencies & solved it without changing any code
You can check out the full code at https://github.com/deadcoder0904/react-native-darkmode-list

What's the proper way to grab an object from my Entities dictionary in a Normalized state and pass it to a component?

I have a state that looks like:
entities: {
pageDict: {
['someId1']: { name: 'page1', id: 'someId1' },
['someId2']: { name: 'page2', id: 'someId2' },
['someId3']: { name: 'page3', id: 'someId3' }
}
}
lists: {
pageIdList: ['someId1', 'someId2', 'someId3']
}
And a Component that looks like:
const Pages = ( props ) => {
return (
<div>
props.pageIdList.map(( pageId, key ) => {
return (
<Page
key={ key }
pageObj={ somethingHere } // What to do here?
/>
);
})
</div>
);
}
To grab the object, I would need to do:
let pageObj = state.entities.pageDict[ pageId ];
I guess I can pass the state.entities.pageDict as props from the Containers.. but I'm trying to look at the selectors pattern and looking at this:
https://redux.js.org/docs/recipes/ComputingDerivedData.html
and I'm wondering if I'm doing this wrong, can someone give me some insight? I just want to make sure I'm doing this correctly.
EDIT: I got the idea of using Entities from the https://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html and the SoundCloud Redux project
EDIT2: I'm looking at things online like this https://github.com/reactjs/redux/issues/584 but I'm questioning if I'm using this incorrectly and I'm not sure how to apply this to my own project
EDIT3: I'm leaning on creating a selector that will get the pageIdList and return a list of the pageobjects from the pageDict.. that way I already have the object to pass into my Page component.
I think I follow what you're trying to do here. With Redux try thinking of your user interface as always displaying something immutable: rather than "passing something to it" it is "reading something from the state". That when when your state changes your user interface is updated (it isn't always this simple but it is a pretty good start).
If I read your answer correctly you have a Map of pages:
//data
{
id1: {...pageProperties} },
id2: {...pageProperties} },
id3: {...pageProperties} },
}
and your page list is the order these are displayed in:
ex:
[id2, id3, id1]
Your page object might look something like this:
//Page.js
class Page extends React.Component {
render() {
const { pageIdList, pageEntities } = this.props //note I'm using props because this is connected via redux
return (
<div>
{ pageIdList.map((pageId, index)=>{
return <Page key={index} pageEntity={pageEntities[pageId]} /> //I'm just pulling the object from the map here
}}
</div>
)
}
}
//connect is the Redux connect function
export default connect((store)=> { //most examples use "state" here, but it is the redux store
return {
pageEntities: store.pageEntities,
pageIdList: store.pageList
}
})(Page)
Now when we want to change something you update the state via a dispatch / action. That is reduced in the reducer to display the new state. There are a lot of example out there on how this works but the general idea is update the state and the components take care of displaying it.
The result of this code (and there might be some typeos) is that you should see:
Page id: 2, Page id: 3, Page id: 1 because the list of the pages in the state is 2, 3, 1 in the example I gave.
To answer your question specifically what the entity I'm pulling is the global Redux Store (not the internal component state). 'Map State to Props' is really 'Map Store to Props' as the 'state' is part of React and the 'store' is your Redux store.
Does that help? React+Redux is really nice once you figure it out, it took me a solid month to understand all the ins and outs but it really does simplify things in the long run.

Resources