Can't connect custom action with the custom reducer react-admin - javascript

8.4 of react-admin. I've been trying to implement a custom action that connects with the custom reducer but so far nothing has worked.
I've Implemented this part of the guide in the official documentation for the action side https://marmelab.com/react-admin/doc/3.8/Actions.html#querying-the-api-with-fetch and this for the reducer https://marmelab.com/react-admin/doc/3.8/Admin.html#customreducers. The problem stems from that I can only use useUpdate method which sends update request, instead of a get without connecting to the reducer and there is no clear explanation of how I can chain those two things together. I also tried using an older way of dispatching actions, but still didn't work. Please help I've been trying this for 2 weeks now. Nothing gets updates and the redux store stays the same.
component
const { data, loading, error } = useQueryWithStore({
type: 'getList',
resource: 'goals',
action: "GET_USER_GOALS",
payload: { pagination: { page: 1, perPage: 10 }, sort: { field: "a-z", order: "ABC" }, filter: {} }
});
reducer
export default (previousState = 0, { type, payload }) => {
console.log(type)
if (type === 'GET_USER_GOALS') {
return payload.rate;
}
return previousState;
}
I even wrote a custom action
but it says that "Cannot read property 'update' of undefined" which isn't supported in the newer version I guess.
import { UPDATE } from 'react-admin';
export const UPDATE_PAGE = 'GET_USER_GOALS';
export const setGoals = (id, data) => {
return {
type: UPDATE_PAGE,
payload: { id, data: { ...data, is_updated: true } },
meta: { fetch: UPDATE, resource: 'goals' },
}
};
admin
<Admin
locale="en"
customReducers={{ userGoals: userGaolsReducer }}
loginPage={LoginPage}
authProvider={authProvider}
dataProvider={testProvider}
i18nProvider={i18nProvider}
history={history}
dashboard={Dashboard}
customSagas={[userGoalsSaga]}
>

I had to include it in the store.js as well
const reducer = combineReducers({
admin: adminReducer,
router: connectRouter(history),
userDashboardSettings: userGaolsReducer
});

Related

useEffect/useCallback missing dependency warnings, but redux state does not let me fix it

I'm trying to clean up my warnings, but im facing those dependency warnings.
This is an example, but a lot of useEffect() is facing a similar problem.
Im trying to laod my page calling my fetch api inside useCallback (got samething inside useEffect), but the filter param there is actually a redux state
useEffect(() => {
if (checkValidRoute(env.activeSelector.menu, "indicacoes")) {
dispatch(
indicationsAction.getIndications(config.page, config.rowsPerPage, config.order, {
environmentId: env[router.query.ambiente].envId,
loginId: user.login?.id,
selectorID: env.activeSelector?.selectorID,
token: user.login.token,
details: false,
filter: {
status: config.status,
dateInit: dateFormat(beforeMonth),
dateEnd: dateFormat(today),
name: config.name,
indicatorName: config.indicatorName
}
})
)
} else {
router.push(`/${router.query.ambiente}`)
}
}, [env, config.status, config.order, dispatch, beforeMonth, config.indicatorName, config.name, config.page, config.rowsPerPage, router, today, user.login?.id, user.login.token])
Those filters has it value associated to an input, i do not want to re-fetch after change my config state, because i need to wait for the user fill all the filter fields, but i need to reload my page if my env change.
I thought about this solution, but it does not work
const filterParams = {
page: config.page,
rowsPerPage: config.rowsPerPage,
order: config.order,
details: false,
filter: {
status: config.status,
dateInit: dateFormat(beforeMonth),
dateEnd: dateFormat(today),
name: config.name,
indicatorName: config.indicatorName
}
}
const loadPage = useCallback(() => {
if (checkValidRoute(env.activeSelector.menu, "indicacoes")) {
dispatch(
indicationsAction.getIndications({
environmentId: env[router.query.ambiente].envId,
loginId: user.login?.id,
selectorID: env.activeSelector?.selectorID,
token: user.login.token,
}, filterParams)
)
} else {
router.push(`/${router.query.ambiente}`)
}
}, [dispatch, env, router, user.login?.id, user.login.token, filterParams])
useEffect(() => {
loadPage()
}, [loadPage])
Now I got the following warning:
The 'filterParams' object makes the dependencies of useCallback Hook (at line 112) change on every render. Move it inside the useCallback callback. Alternatively, wrap the initialization of 'filterParams' in its own useMemo() Hook.eslintreact-hooks/exhaustive-deps
if add filterParams to useMemo() dependencies samething will happend
// eslint-disable-next-line react-hooks/exhaustive-deps sounds not good ...
There's any solution for this ? I think that I have to change my form to useForm() to get the onChange values then after submit() i set my redux state... but i dont know yet
EDIT: In that case i did understand that we need differente states to control my input state and my request state, they cant be equals. If someone find another solution, i would appreciate (:
EDIT2: Solved by that way:
const [ filtersState ] = useState(
{
page: config.page,
rowsPerPage: config.rowsPerPage,
order: config.order,
data: {
environmentId: env[router.query.ambiente].envId,
loginId: user.login?.id,
selectorID: env.activeSelector?.selectorID,
token: user.login.token,
details: false,
filter: {
status: config.status,
dateInit: dateFormat(config.dateInit),
dateEnd: dateFormat(config.dateEnd),
name: config.name,
indicatorName: config.indicatorName
}
}
}
);
const handleLoadPage = useCallback(() => {
if (checkValidRoute(env.activeSelector.menu, "indicacoes")) {
dispatch(indicationsAction.getIndications({
...filtersState,
filters: {
...filtersState.filters,
selectorID: env.activeSelector?.selectorID,
}
}))
} else {
router.push(`/${router.query.ambiente}`)
}
}, [env.activeSelector, filtersState, dispatch, router]
)
useEffect(() => {
handleLoadPage()
}, [handleLoadPage])
Any other alternatives is appreciate
The thing here is, if you memoize something, it dependencies(if are in local scope) must be memoized too.
I recommend you read this amazing article about useMemo and useCallback hooks.
To solve your problem you need to wrap filterParams within useMemo hook. And if one of it dependencies are in local scope, for example the dateFormat function, you'll need to wrap it as well.

NextJS: Failed when fallback set to true

I am using vercel for NextJS and this is my setup in getStaticPaths
const paths = posts.map((post) => ({
params: { player: post.player, id: post.id },
}))
return { paths, fallback: true }
When I set the fallback to true, I have got this error in vercel:
21:55:01.736 info - Generating static pages (1752/1752)
21:55:01.736 > Build error occurred 21:55:01.739 Error: Export
encountered errors on following paths: 21:55:01.739
/clip/[player]/[id]
It is ok when fallback is set to false but I really like to set fallback set to true so that pages can be updated frequently. Any help will be greatly appreciated...
Inside your /clip/[player]/[id].js file, you need to handle the fallback state when that page is being requested on-demand.
// pages/posts/[id].js
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
return {
// Only `/posts/1` and `/posts/2` are generated at build time
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
// Enable statically generating additional pages
// For example: `/posts/3`
fallback: true,
}
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return {
props: { post },
// Re-generate the post at most once per second
// if a request comes in
revalidate: 1,
}
}
export default Post
https://nextjs.org/docs/basic-features/data-fetching#fallback-true
What I did was conditionally render my component. So, my component receives the object data and if I need to use a value from data, such as "title", I will do...
data?.title
Also, for my entire return component I will conditionally render it. For example...
{data !== undefined ? (
<div className ='main-content'>
<p> This is the content that I want rendered if data is defined </p>
</div>
) : (
<div className = 'fallback-content'>
<p> This shows if data == undefined </p>
</div>
)

How to keep data synchronized in ember using ember-apollo-client?

I have an app built using Ember and ember-apollo-client.
// templates/collaborators.hbs
// opens an ember-bootstrap modal
{{#bs-button type="success" onClick=(action (mut createCollaborator) true)}}Create collaborator{{/bs-button}}
// submit button in modal triggers "createCollaborator" in controller
{{#each model.collaborators as |collaborator|}}
{{collaborator.firstName}} {{collaborator.lastName}}
{{/each}}
// routes/collaborators.js
import Route from '#ember/routing/route';
import { RouteQueryManager } from 'ember-apollo-client';
import query from '../gql/collaborators/queries/listing';
export default Route.extend(RouteQueryManager, {
model() {
return this.get('apollo').watchQuery({ query });
}
});
// controllers/collaborator.js
export default Controller.extend({
apollo: service(),
actions: {
createCollaborator() {
let variables = {
firstName: this.firstName,
lastName: this.lastName,
hireDate: this.hireDate
}
return this.get('apollo').mutate({ mutation, variables }, 'createCollaborator')
.then(() => {
this.set('firstName', '');
this.set('lastName', '');
this.set('hireDate', '');
});
}
}
});
Currently, after creating a collaborator the data is stale and needs a browser refresh in order to update. I'd like the changes to be visible on the collaborators list right away.
From what I understood, in order to use GraphQL with Ember, I should use either Ember Data with ember-graphql-adapter OR just ember-apollo-client. I went on with apollo because of its better documentation.
I dont think I quite understood how to do that. Should I somehow use the store combined with watchQuery from apollo? Or is it something else?
LATER EDIT
Adi almost nailed it.
mutationResult actually needs to be the mutation itself.
second param in store.writeQuery should be either data: { cachedData } or data as below.
Leaving this here as it might help others.
return this.get('apollo').mutate({
mutation: createCollaborator,
variables,
update: (store, { data: { createCollaborator } }) => {
const data = store.readQuery({ query })
data.collaborators.push(createCollaborator);
store.writeQuery({ query, data });
}
}, createCollaborator');
You can use the apollo imperative store API similar to this:
return this.get('apollo').mutate(
{
mutation,
variables,
update: (store, { data: {mutationResult} }) => {
const cachedData = store.readyQuery({query: allCollaborators})
const newCollaborator = mutationResult; //this is the result of your mutation
store.writeQuery({query: allCollaborators, cachedData.push(newCollaborator)})
}
}, 'createCollaborator')

Is it possible to use startUndoable with custom action in react-admin?

I wondered if passing a custom action with a custom fetch and type (which is not update) to startUndoable is feasible.
Or is it possible that somehow define a pattern with values in meta and based on this pattern the view would be re-rendered?
In this case the IMPORT is updating only one property in the database with a fixed value.
This is the action:
export const importParcel = ({ id }) => ({
type: IMPORT_PARCEL,
payload: {
id
},
meta: {
resource: 'parcels',
fetch: IMPORT,
refresh: true,
onSuccess: {
notification: {
body: 'Parcel Imported',
level: 'info'
}
},
onFailure: {
notification: {
body: 'Error: Import failed',
level: 'warning'
}
}
}
});
This is the handler:
fetchUtils
.fetchJson(`/${resource}/import/${params.id}`, {
method: 'PUT',
headers: getAuthenticationHeaders()
})
.then(res => ({ data: res.json }));
Thanks for your help! :)
Sure, as explained in the Optimistic Rendering and Undo documentation you can create whatever action you want with startUndoable:
import { startUndoable as startUndoableAction } from 'ra-core';
class App extends Component {
handleImport = () => {
this.props.startUndoable(importParcel());
};
render() {
return <Button onClick={this.handleImport}>Import Parcel</Button>;
}
}
export default connect(null, { startUndoable: startUndoableAction })(App);
You action must have a onSuccess notification in order to display the undo button.
The rest should be implemented in your data provider.

Redux reducer not being called

Using redux-api-middleware I'm encountering a problem wherein one and only one of my reducer functions is not triggering.
I would like to handle failed api requests in a similar way to how I handle the successful api requests. Yet as far as I can tell, the SEARCH_FAILURE FSA never gets handled, though it is treated (AFAICT) identically to the SEARCH_SUCCESS FSA. It does seem to be created and dispatched, based on what I see in the devtools.
I have this
import { CALL_API } from 'redux-api-middleware'
import { handleActions } from 'redux-actions'
const searchReducer = handleActions({
//... other handlers elided
SEARCH_SUCCESS: (state = defaultState, action) => {
return {
...state,
search_results: ({...action.payload}),
api: {
requestPending: false,
searchPending: false
},
}
},
SEARCH_FAILURE: function(state = defaultState, action) {
console.log("Handling SEARCH_FAILURE given state, action: ", state, action)
return {
...state,
search_results: {Total: 0},
api: {
requestPending: false,
error: action.payload
},
errors: [action.payload, ...state.errors]
}
},
})
the SEARCH_SUCCESS FSA gets handled by searchReducer, but when the server gives a 400 response, the SEARCH_FAILURE handler never gets called--at least I don't see the log output I would expect, and the state sure doesn't end up looking right. I do see a SEARCH_FAILURE entry in the redux devtools panel, however.
Serving to confuse me further, here is the declaration I have at the moment for creating the RSAA
export function doSearch( selected_filters, page ){
let qs = SearchPage.constructQueryString(selected_filters, page)
return {
[CALL_API]: {
endpoint: `/api/songs/search?${qs}`,
method: 'GET',
types: [
{type: SEARCH_REQUEST},
{type: SEARCH_SUCCESS},
{
type: SEARCH_FAILURE,
payload: (action, state, res) => {
if (400 === res.status)
{
console.log(`${SEARCH_FAILURE} payload: `, action, state, res)
}
return res
}
},
],
headers: { 'Content-Type': 'application/json' },
credentials: 'include'
}
}
}
the payload function is being called and logging more or less what I'd expect. So what am I messing up here? As far as I can discern from multiple readings of the docs for redux-api-middleware, this setup should yield the behavior I want, but it does not. The successes succeed, but the failures fail...
I ran into a similar issue when I was defining one of my types with an object rather than the just the action as you're doing for SEARCH_FAILURE. I ended up fixing by updating my package.json with:
"redux-api-middleware": "^1.0.0-beta3",
I think the problem is that the docs describe the version 1.0.0 API, but when you:
npm install redux-api-middleware --save
You end up getting an earlier version of the package.

Categories