how to use selectors with a combineReducers when one of the child reducers relies on adapter? - ngrx-entity

students.reducer.ts
export const StudentsFeatureKey = 'students';
export interface StudentState {
studentProfile: fromStudProfReducer.StudentProfileState;
studentAdmin: fromStudAdminReducer.StudentAdminState;
}
export const reducer = combineReducers({
studentProfile: fromStudProfReducer.Reducer,
studentAdmin: fromStudAdminReducer.Reducer
});
student-profile.reducer.ts
export interface StudentProfileState extends EntityState<StudentProfileViewModel> {}
export const adapter = createEntityAdapter<StudentProfileViewModel>();
export const initialState: StudentProfileState = adapter.getInitialState();
export const Reducer = createReducer(
initialState
);
export const {
selectIds,
selectEntities,
selectAll,
selectTotal,
} = adapter.getSelectors();
student-profile.selectors.ts
export const getStudentsFeatureState =
createFeatureSelector<fromStudentsReducer.StudentState>(
fromStudentsReducer.StudentsFeatureKey
);
THIS SELECTOR IS FAILING --> TypeError: Cannot read property 'map' of undefined
export const selectAllStudentProfiles = createSelector(
getStudentsFeatureState,
fromStudProfReducer.selectAll,
(_, profiles) => profiles
);
student-facade.service.ts
private _testValue = this.store.pipe(
select(selectAllStudentProfiles),
tap(profiles => console.log('profiles: ', profiles))
).subscribe();
Thanks for helping
EDIT
selecting the parent feature slice is working just fine, i.e. when I use getStudentsFeatureState.
private _testValue = this.store.pipe(
select(getStudentsFeatureState),
tap(state => console.log('state: ', state))
).subscribe();
I'am getting the

Related

I work with github api on redux-toolkit(rtk-query) and have a problem

Uncaught Error: Warning: Middleware for RTK-Query API at reducerPath "github/api" has not been added to the store.
You must add the middleware for RTK-Query to function correctly!
import {configureStore} from "#reduxjs/toolkit";
import {githubApi} from "./github/github.api";
export const store = configureStore({
reducer: {
[githubApi.reducerPath]: githubApi.reducer
},
})
import {createApi, fetchBaseQuery} from "#reduxjs/toolkit/query/react";
export const githubApi = createApi({
reducerPath: 'github/api',
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.github.com/'
}),
endpoints: build => ({
searchUsers: build.query<any, string>({
query: (search: string) => ({
url: `search/users`,
params: {
q: search
}
})
})
})
})
export const {useSearchUsersQuery} = githubApi
import React from "react";
import {useSearchUsersQuery} from "../store/github/github.api";
export function HomePage() {
const {isLoading, isError, data} = useSearchUsersQuery('anyname')
return (
<div>Home</div>
)
}
It's literally what it says - in your configureStore call, you skipped adding the RTK Query middleware.
export const store = configureStore({
reducer: {
[githubApi.reducerPath]: githubApi.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(githubApi.middleware),
})

Uncaught (in promise) TypeError: hobbies is not iterable at createHobby when using Prisma and Postgresql

So I'm very new to Prisma, and actually also to React. My Postgresql database works, but I'm trying to show the stored data in my application. My very simple table in the schema file looks like this:
model Hobby {
id Int #id #default(autoincrement())
title String
}
I'm using useContext to distribute my createHobby functionality, this is what the context file looks like.
export async function getServerSideProps() {
const hobbies: Prisma.HobbyUncheckedCreateInput[] = await prisma.hobby.findMany();
return {
props: {initialHobbies: hobbies},
};
}
export const HobbyContext = createContext({})
function Provider({ children, initialHobbies }){
const [hobbies, setHobbies] = useState<Prisma.HobbyUncheckedCreateInput[]>(initialHobbies);
const createHobby = async (title) => {
const body: Prisma.HobbyCreateInput = {
title,
};
await fetcher("/api/create-hobby", {hobby : body});
console.log(hobbies);
const updatedHobbies = [
...hobbies,
body
];
setHobbies(updatedHobbies);
const contextData = {
hobbies,
createHobby,
}
return (
<HobbyContext.Provider value={contextData}>
{children}
</HobbyContext.Provider>
);
};
export default HobbyContext;
export {Provider};
Here I get the following error Uncaught (in promise) TypeError: hobbies is not iterable at createHobby. Which refers to the const updatedHobbies = [...hobbies, body];
For more context, I have a HobbyCreate.tsx which creates a little hobby card that renders the title of the hobby, which is submitted with a form.
function HobbyCreate({updateModalState}) {
const [title, setTitle] = useState('');
const {createHobby} = useHobbiesContext();
const handleChange = (event) => {
setTitle(event.target.value)
};
const handleSubmit = (event) => {
event.preventDefault();
createHobby(title);
};
return (
...
<form onSubmit={handleSubmit}></form>
...
)
I can't really figure out what is going wrong, I assume somewhere when creating the const [hobbies, setHobbies] and using the initialHobbies.
I don't think you're using the Context API correctly. I've written working code to try and show you how to use it.
Fully typed hobby provider implementation
This is a fully typed implementation of your Provider:
import { createContext, useState } from 'react';
import type { Prisma } from '#prisma/client';
import fetcher from 'path/to/fetcher';
export type HobbyContextData = {
hobbies: Prisma.HobbyCreateInput[]
createHobby: (title: string) => void
};
// you could provide a meaningful default value here (instead of {})
const HobbyContext = createContext<HobbyContextData>({} as any);
export type HobbyProviderProps = React.PropsWithChildren<{
initialHobbies: Prisma.HobbyCreateInput[]
}>;
function HobbyProvider({ initialHobbies, children }: HobbyProviderProps) {
const [hobbies, setHobbies] = useState<Prisma.HobbyCreateInput[]>(initialHobbies);
const createHobby = async (title: string) => {
const newHobby: Prisma.HobbyCreateInput = {
title,
};
await fetcher("/api/create-hobby", { hobby: newHobby });
console.log(hobbies);
setHobbies((hobbies) => ([
...hobbies,
newHobby,
]));
};
const contextData: HobbyContextData = {
hobbies,
createHobby,
};
return (
<HobbyContext.Provider value={contextData}>
{children}
</HobbyContext.Provider>
);
}
export default HobbyContext;
export { HobbyProvider };
Using HobbyProvider
You can use HobbyProvider to provide access to HobbyContext for every component wrapped inside it.
For example, to use it in every component on /pages/hobbies your implementation would look like:
// /pages/hobbies.tsx
import { useContext, useState } from 'react';
import HobbyContext, { HobbyProvider } from 'path/to/hobbycontext';
export default function HobbiesPage() {
// wrapping the entire page in the `HobbyProvider`
return (
<HobbyProvider initialHobbies={[{ title: 'example hobby' }]}>
<ExampleComponent />
{/* page content */}
</HobbyProvider>
);
}
function ExampleComponent() {
const { hobbies, createHobby } = useContext(HobbyContext);
const [title, setTitle] = useState('');
return (
<div>
hobbies: {JSON.stringify(hobbies)}
<div>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<button onClick={() => createHobby(title)}>Create hobby</button>
</div>
</div>
);
}
Similarly, to make the context available throughout your entire website, you can use HobbyProvider in
/pages/_app.tsx.
Using getServerSideProps
To retrieve the initialHobbies from the database, your getServerSideProps would look something like this:
// /pages/hobbies.tsx
import type { Hobby } from '#prisma/client';
export async function getServerSideProps() {
// note: there is no need to use `Hobby[]` as prisma will automatically give you the correct return
// type depending on your query
const initialHobbies: Hobby[] = await prisma.hobby.findMany();
return {
props: {
initialHobbies,
},
};
}
You would have to update your page component to receive the props from getServerSideProps and set initialHobbies on HobbyProvider:
// /pages/hobbies.tsx
import type { InferGetServerSidePropsType } from 'next';
export default function HobbiesPage({ initialHobbies }: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<HobbyProvider initialHobbies={initialHobbies}>
<ExampleComponent />
</HobbyProvider>
);
}
Note your page component and getServerSideProps function have to be exported from the same file

How to insert a draft-js custom component/block

I'm trying to insert my custom block to the editorState of draft-js's editor. I can't seem to find any detailed information on how to accomplish this.
Block Renderer:
const blockRendererFn = (contentBlock) => {
const type = contentBlock.getType();
if (type === 'CustomTestChipBlock') {
return {
component: CustomTestChipBlock,
editable: false,
props: {
foo: 'bar',
},
};
}
}
Block Render Map:
import { DefaultDraftBlockRenderMap } from "draft-js";
import { Map } from 'immutable';
const blockRenderMap = Map({
CustomTestChipBlock: {
element: 'div',
}
}).merge(DefaultDraftBlockRenderMap);
My custom block (material ui chip):
import { Chip } from "#mui/material";
const CustomTestChipBlock = (props) => {
const { block, contentState } = props;
const { foo } = props.blockProps;
const data = contentState.getEntity(block.getEntityAt(0)).getData();
console.log("foo: "+foo)
console.log("data: "+data)
return (
<Chip label="test" size="small"/>
)
}
Now my problem is when I try to insert my custom block. I assume my method of insertion must be wrong. I tried multiple insertion methods but due to lack of any detailed information on the subject, all of them ended up not even running the console.log inside my custom component.
Insertion:
const addChip = () => {
setEditorState(insertBlock("CustomTestChipBlock"));
}
const insertBlock = (type) => {
// This is where I can't find any detailed info at all
const newBlock = new ContentBlock({
key: genKey(),
type: type,
text: "",
characterList: List(),
});
const contentState = editorState.getCurrentContent();
const newBlockMap = contentState.getBlockMap().set(newBlock.key, newBlock);
const newEditorState = ContentState.createFromBlockArray(
newBlockMap.toArray()
)
.set("selectionBefore", contentState.getSelectionBefore())
.set("selectionAfter", contentState.getSelectionAfter());
return EditorState.push(editorState, newEditorState, "add-chip");
};

Any other way of adding prefixes?

How to add prefixes in another way? This is course.route.js I did not get the expected output.
const KoaRouter = require("koa-router");
const router = new KoaRouter({ prefix: "/course" });
const {
addCourse,
getCourses,
updateCourse,
deleteCourse,
} = require("../controller/course.controller");
router.post("/add", addCourse);
router.delete("/:courseId", deleteCourse);
router.put("/:courseId", updateCourse);
router.get("/", getCourses);
module.exports = router;

Babel plugin error: Don't use `path.replaceWith()` with a source string, use `path.replaceWithSourceString()`

EDIT / UPDATE:
I have taken loganfsmyth's advice and pulled out babel as the first argument to the sveltify function and I can access / console log babel.template.statement.ast however, if I try to call that function my app hangs indefinitely.
Thie details:
I am trying to use this with svelte to replace the import statement and I have a plugin as:
const sveltify = (babel) => ({
visitor: {
ImportDeclaration(path){
// import x from 'svelte/somewhere'
if (path.node.source.value.startsWith("svelte/")) {
const specifiers = path.node.specifiers.map(s => ` ${s.local.name}` );
const importee = path.node.source.value.replace('/', '.');
// this line works without babel.template.statement.ast but gives the error in the question title
// adding babel.template.statement.ast causes the app to hang indefinitely
const importNode = babel.template.statement.ast`const {${specifiers} } = ${importee};`;
path.replaceWith(importNode);
}
}
}
});
and my babel options:
const SVELTE_OPTIONS = {
presets: [
// [
// Babel.availablePresets['env'],
// {
// useBuiltIns: 'usage',
// corejs: 3,
// }
// ],
['es2017', { 'modules': false }],
],
plugins: [
sveltify,
'transform-modules-commonjs',
'transform-destructuring',
'proposal-object-rest-spread',
],
};
And finally I am using that in my code later on a call to transform like this:
// simplified
function transpile(moduleCode) {
const { code } = Babel.transform(moduleCode, SVELTE_OPTIONS);
return code;
}
The other question you linked is pulling babel out of the first param of the plugin, and you should be doing the same, so
const sveltify = () => ({
should be
const sveltify = (babel) => ({
then you can use babel.template from that object.
I think the problem was in the way I was calling ast. The following works:
const sveltify = (babel) => ({
visitor: {
ImportDeclaration(path){
// import x from 'svelte/somewhere'
if (path.node.source.value.startsWith("svelte/")) {
const specifiers = path.node.specifiers.map(s => ` ${s.local.name}` );
const importee = path.node.source.value.replace('/', '.');
const tmpNode = `const {${specifiers} } = ${importee};`;
const importNode = babel.template.statement.ast(tmpNode);
path.replaceWith(importNode);
}
}
}
});