Upgrading meteor (from 1.4 to 1.7) and react (from 15.3.2 to 16.8.6).
"react-redux": "^4.4.10"
"redux": "3.5.2"
I found my codes were unable to update/store using Store.dispatch(), the Store just not updated.
My ACTIONS file as below:
actions/config.js
...
export default {
load({Meteor, Store}) {
return new Promise((resolve, reject) => {
Meteor.call('variables.load', null, (err, data) => {
if (err) {
reject({_error: err.reason });
return;
}
console.log("************ Store (A) = "+JSON.stringify(Store.getState()))
Store.dispatch({
type: LOAD_CONFIG,
data
});
resolve();
console.log("************ Store (B) = "+JSON.stringify(Store.getState()))
});
});
},
...
Both the console.log() were having the following:
Store (A) = {"router":{"locationBeforeTransitions":{"pathname":"/settings/config","search":"","hash":"","action":"PUSH","key":"zif4ls","basename":"/crm","query":{}}},"form":{"config":{"syncErrors":{"reportLimit":"Required"}}},"loadingBar":{}}
Store (B) = {"router":{"locationBeforeTransitions":{"pathname":"/settings/config","search":"","hash":"","action":"PUSH","key":"zif4ls","basename":"/crm","query":{}}},"form":{"config":{"syncErrors":{"reportLimit":"Required"}}},"loadingBar":{}}
Which I do expect it will have something like "reportLimit":6 , which was confirmed to have loaded into the data variable. Instead, I was getting the following error in browser console:
Uncaught TypeError: Cannot read property 'data' of undefined
Is there anything wrong/breaking changes you could think of? As these codes had been working before the upgrade.
EDIT:
I've further narrowed down the problem. It may seems to be my Routes is not calling the Reducer.
I've since change the code in my Routes to remove the need to use require.ensure .
routes.jsx (prior)
{
path: 'config',
getComponent(nextState, cb) {
require.ensure([], (require) => {
Store.injectReducer('config', require('./reducers').config)
cb(null, require('./containers/config.js'))
}, 'config')
}
},
routes.jsx (latest, to get rid of require.ensure)
{
path: 'config',
getComponent(nextState, cb) {
import('./containers/config.js')
.then(mod => {Store.injectReducer('config', require('./reducers').config);
cb(null, mod);});
}
},
Then I notice that in the reducer:
reducer/config.js
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[LOAD_CONFIG]: (state, action) => ({
...state,
data: action.data
})
};
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
data: null
};
export default function configReducer(state = initialState, action) {
console.log("************ Reducer")
const handler = ACTION_HANDLERS[action.type];
return handler ? handler(state, action) : state;
}
As per logged, function configReducer doesn't seem to have been called elsewhere.
After much trial-and-error, confirmed the problem is with the routing part, final changes:
routes.jsx (final)
{
path: 'config',
getComponent(nextState, cb) {
import('./containers/config').then(mod => {
Store.injectReducer('config', require('./reducers/config').default);
cb(null, mod.default);
});
}
}
Key points:
1) This is how the to migrate from require.ensure (used by webpack) to without relying on webpack (which was my case as am fully using Meteor Atmosphere to run)
2) mod and require(...).xxx had changed to mod.default and require(...).default if reducer function is exported as export default, otherwise said reducer will not be called.
Really took me weeks to figure this out!
Try this:
Store.dispatch({
type: LOAD_CONFIG,
data: data
});
Related
I'm new for the RTK Query for redux.
What's the different for auto generated hook in below two ways.
The first way look like correct from the docs but it return 304 network status.
Second way, return 200. working perfectly
1.
const ProjectsList = () => {
const {
data: projects,
isLoading,
isSuccess,
isError,
error,
} = useGetProjectsQuery("projectList") // -- return 304 network status
}
worked fine. but cannot retrieve the object from the store. return.
const {
data: projects,
isLoading,
isSuccess,
isError,
error,
} = useGetProjectsQuery() // -- return 200 network status
Third, the memoized return uninitialize. It seem didn't correct.
// ApiSlice status return uninitialize
import { createSelector, createEntityAdapter } from "#reduxjs/toolkit"
import { apiSlice } from "#/app/api/apiSlice"
const projectsAdapter = createEntityAdapter({})
export const projectsApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
getProjects: builder.query({
query: () => "/api/projects",
validateStatus: (response, result) => {
return response.status === 200 && !result.isError
},
transformResponse: (responseData) => {
const loadedProjects = responseData.map((project) => {
project.id = project._id
return project
})
return projectsAdapter.setAll(initialState, loadedProjects)
},
providesTags: (result, error, arg) => {
if (result?.ids) {
return [
{ type: "Project", id: "LIST" },
...result.ids.map((id) => ({ type: "Project", id })),
]
} else return [{ type: "Project", id: "LIST" }]
},
}),
}),
})
export const {
useGetProjectsQuery,
} = projectsApiSlice
export const selectProjectsResult =
projectsApiSlice.endpoints.getProjects.select()
// creates memoized selector
const selectProjectsData = createSelector(
selectProjectsResult,
(projectsResult) => {
console.log("projectsResult: ", projectsResult) // -> { isUninitialized: true, status: "uninitialize" }
return projectsResult.data
}
)
export const {
selectAll: selectAllProjects,
selectById: selectProjectById,
selectIds: selectProjectIds,
} = projectsAdapter.getSelectors(
(state) => selectProjectsData(state) ?? initialState
)
Since your query function is just query: () => "/api/projects" (so, not using the argument in any way), both will make exactly the same request for the same resource.
There is no difference between them and every difference you see is probably something random happening on the server and not bound to either invocation.
As for retrieving from the store, there is a difference however.
Your code
export const selectProjectsResult =
projectsApiSlice.endpoints.getProjects.select()
creates a selector for the cache entry that is created calling useGetProjectsQuery() - if you wanted the cache entry for useGetProjectsQuery("foo"), that would need to be projectsApiSlice.endpoints.getProjects.select("foo").
Please note that there should almost never be any reason to use those selectors with React components - those are an escape hatch if you are not working with React. If you are working with React, use the useGetProjectsQuery hook with selectFromResult.
I am seeing people use select in this fashion quite often recently and I assume this traces back to a tutorial that misunderstood the feature - did you learn that in a tutorial and could you share that tutorial? Maybe I can convince the author to change that part.
What I want
I want to change pages without next thinking I am trying to open another page.
The Problem
I have this weird routing problem.
First, my folder structure
pages
[app]
[object]
index.js
index.js
manager.js
feed.js
I am at this path /[app] and navigate to /[app]/manager and then I want to navigate to /[app]/feed and I get this Unhandled Runtime Error.
TypeError: Cannot read property "title" of undefined
This error comes from [object] index.js. Stacktrace is below. Of course, it makes sense it cannot read title because I am trying to open another page. And yet it thinks I am trying to open [object].
This error happens from time to time, but it doesn't matter in what order I try to open the pages, it can be manager to feed or feed to manager, or whatever else I have there.
My getStaticPaths and getStaticProps are the same on all these pages, I will share the one for manager.js.
export const getStaticPaths = async () => {
const paths = appRoutes.map((appRoute) => {
const slug = appRoute.slug;
return {
params: {
app: slug,
manager: 'manager',
},
};
});
return {
fallback: false,
paths,
};
};
export const getStaticProps = async ({ locale }) => {
return {
props: {
...(await serverSideTranslations(locale, ['manager', 'common'])),
},
};
};
And the same again, but for [object]:
export const getStaticPaths = async () => {
const allObjects = await loadObjectData({ id: 'all' });
const paths = allObjects.flatMap((object) => {
return appRoutes.map((appRoute) => {
return {
params: {
object: object.type,
app: appRoute.slug,
},
};
});
});
return {
fallback: false,
paths,
};
};
export const getStaticProps = async ({ params, locale }) => {
const object = await loadObjectData({ type: params.object });
const app = appRoutes.find((appRoute) => appRoute?.slug === params.app);
if (!object) {
throw new Error(
`${object} is not a valid Object. Try checking out your parameters: ${params.object}`
);
}
if (!app) {
throw new Error(`${app} is not a valid App.`);
}
return {
props: {
...(await serverSideTranslation(locale, ['common'])),
object,
app,
},
};
};
This error is hard to reproduce because it happens only from time to time.
New Edits
This is the full file of [object]/index.js
import appRoutes from '../../../routes/appRoutes';
import loadObjectData from '../../../utils/loadObjects';
import { serverSideTranslation } from 'next-i18next/serverSideTranslations';
export default function ObjectPage({ object }) {
return <h1> {object.title} </h1>;
}
export const getStaticPaths = async () => {
const allObjects = await loadObjectData({ id: 'all' });
const paths = allObjects.flatMap((object) => {
return appRoutes.map((appRoute) => {
return {
params: {
object: object.type,
app: appRoute.slug,
},
};
});
});
return {
fallback: false,
paths,
};
};
export const getStaticProps = async ({ params, locale }) => {
const object = await loadObjectData({ type: params.object });
const app = appRoutes.find((appRoute) => appRoute?.slug === params.app);
if (!object) {
throw new Error(
`${object} is not a valid Object. Try checking out your parameters: ${params.object}`
);
}
if (!app) {
throw new Error(`${app} is not a valid App.`);
}
return {
props: {
...(await serverSideTranslation(locale, ['common'])),
object,
app,
},
};
};
Stacktrace:
ObjectPage: index.js:6 Uncaught TypeError: Cannot read property 'title' of undefined
at ObjectPage (http://localhost:3000/_next/static/chunks/pages/%5Bapp%5D/%5Bobject%5D.js:3733:21)
at div
at Grid (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:13654:35)
at WithStyles (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:179881:31)
at div
at StyledComponent (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:179652:28)
at div
at ProjectSelectionStore (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:234820:77)
at Layout (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:278:23)
at TaskStore (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:235454:77)
at UserDocumentStore (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:235663:77)
at StoneStore (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:235119:77)
at StoreMall (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:409:23)
at ThemeProvider (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:178584:24)
at App (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:234333:24)
at I18nextProvider (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624290251377:224427:19)
at AppWithTranslation
at ErrorBoundary (http://localhost:3000/_next/static/chunks/main.js?ts=1624290251377:146:47)
at ReactDevOverlay (http://localhost:3000/_next/static/chunks/main.js?ts=1624290251377:250:23)
at Container (http://localhost:3000/_next/static/chunks/main.js?ts=1624290251377:8662:5)
at AppContainer (http://localhost:3000/_next/static/chunks/main.js?ts=1624290251377:9151:24)
at Root (http://localhost:3000/_next/static/chunks/main.js?ts=1624290251377:9282:24)
25.06.2021
So I consoled logged the router from the ObjectPage and for each NavigationItem. I noticed something strange.
This is the href I am passing to teh <Link>:
{
pathname: "/[app]/[menuItem]"
query: {
app: "content"
menuItem: "files"
}
}
And this is the full router I am getting back on ObjectPage.
{
asPath: "/content/editor" // this the path i want to open
back: ƒ ()
basePath: ""
beforePopState: ƒ ()
components: {
"/[app]/[object]": {styleSheets: Array(0), __N_SSG: true, __N_SSP: undefined, props: {…}, Component: ƒ}
"/[app]/editor": {initial: true, props: {…}, err: undefined, __N_SSG: true, Component: ƒ, …}
"/_app": {styleSheets: Array(0), Component: ƒ}
}
defaultLocale: "de"
events: {on: ƒ, off: ƒ, emit: ƒ}
isFallback: false
isLocaleDomain: false
isPreview: false
isReady: true
locale: "de"
locales: ["de"]
pathname: "/[app]/[object]" // [object] is being loaded
prefetch: ƒ ()
push: ƒ ()
query: {app: "content", menuItem: "editor", object: "editor"} // this is interesting
reload: ƒ ()
replace: ƒ ()
route: "/[app]/[object]" // same as pathname
}
In the query you can see object was injected. But I cannot tell from where and why.
I had this code:
{
pathname: "/[app]/[menuItem]"
query: {
app: "content"
menuItem: "files"
}
}
This was incorrect because there is no dynamic path to [menuItem]. So instead I wrote:
{
pathname: "/[app]/files"
query: {
app: "content"
}
}
Which fixed the issue I had.
I have misunderstood the docs for parameters.
When I do a mutation with useMutation hook, I can console.log my data but I cannot save the data object in a localState. I don't know what am I missing. Any help is appreciated.
My code is as follows:
//Schema file
startTest(identifier: String): TestResponse
the mutation payload is as follows:
type TestResponse {
errorCode: Int
errorMessage: String
success: Boolean
transactionId: ID
version: Int
}
In the resolver I hardcoded the following data return:
startTest(_, { identifier }) {
console.log("hello from server");
return {
errorCode: 0,
errorMessage: null,
success: true,
transactionId: "d2984911-bbc4-4e6a-9103-96ca934f6ed3",
version: 0,
};
},
In component folder I've triggered the mutation with a button click.
const [startTestt, { loading, error, data }] = useMutation(
START_FIRE_DAMPER_TEST,
{
onCompleted: ({ startFireDampersTest }) =>
console.log(startFireDampersTest.success),
useState(startFireDampersTest.success)
},
{
onError: () => console.log("error!"), // never gets called
}
);
console.log(result); // undefined
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
if (data && data.startFireDampersTest)
return (
<div>
<p>Success!</p>
<pre>{JSON.stringify(data, null, 2)}</pre> // I can see string object but how to render the element of the object inside the component??
</div>
);
const triggerTest = () => {
startTest({ variables: { identifier: "0000000" } });
};
return (
<>
<Banner />
<button onClick={triggerTest}>Click to Delete Buck</button>
</>
);
I need to access returned data returned from mutation for later use. the test response/payload is not related to any other queries or types to update. It just contains status of the test and other key values that I need to save in a local state or maybe render it inside a component.
You already have a useState inside your onCompleted, you'd just need to write properly. Assuming the useState is a call to the a React's useState hook, then you're probably just need to move the initialization of your state to your component.
const [isSuccess, setIsSuccess] = useState(false);
const [startTestt, { loading, error, data }] = useMutation(
START_FIRE_DAMPER_TEST,
{
onCompleted: ({ startFireDampersTest }) =>
console.log(startFireDampersTest.success),
setIsSuccess(startFireDampersTest.success)
},
{
onError: () => console.log("error!"), // never gets called
}
);
I am trying to pass values from API to state but always give this error.
TypeError: Cannot read property 'ids' of undefined
selectIds
I am using the 'reduxjs/toolkit' I try everything but still continue that error could you please help me
this is a code from the Slic file
export const getListNamesDictionary = createAsyncThunk('dictionary/names/getNames', async () => {
try {
const response = await axios.get('http://localhost:6005/api/lookup/list-name');
const data = await response.data;
// dispatch(getNames(data));
debugger;
console.log(data);
return data;
} catch (error) {
return console.error(error.message);
}
});
const namesAdapter = createEntityAdapter({});
and the Slic :
const namesDictionarySlice = createSlice({
name: 'names',
initialState: {
names: []
},
reducers: {
},
extractors: {
[getListNamesDictionary.fulfilled]: (state, action) => {
state.entities.push(action.payload);
}
}
});
export const { selectAll: selectNamesDictionary } = namesAdapter.getSelectors(state => state.data);
and this code from component where I need to dispatch the action
const names = useSelector(selectNamesDictionary);
useEffect(() => {
// dispatch(getListNamesDictionary()).then(() => setLoading(false));
dispatch(getListNamesDictionary()).then(() => setLoading(false));
}, [dispatch]);
any suggesting why that error? and thanks
You are not using the entity adapter properly. It expects to manage a state in the form:
{
ids: [1, 2],
entities: {
1: {/*...*/},
2: {/*...*/}
}
}
Your names slice doesn't match that shape. But that's an easy fix as the namesAdapter provides all of the needed tools. Quick rundown of errors to fix:
property name extractors should be extraReducers
state.entities.push needs to be replaced with an adapter function
initialState needs to have properties ids and entities
selectors need to target the correct location
const namesAdapter = createEntityAdapter({});
const namesDictionarySlice = createSlice({
name: "names",
initialState: namesAdapter.getInitialState(),
reducers: {},
extraReducers: {
[getListNamesDictionary.fulfilled]: namesAdapter.upsertMany
}
});
This fixes the first three bullets. Regarding the reducer, it might make more sense if you write it out like this, but it does the same thing.
[getListNamesDictionary.fulfilled]: (state, action) => {
namesAdapter.upsertMany(state, action)
}
The last bullet point is the cause of the specific error message the you posted:
TypeError: Cannot read property 'ids' of undefined
It actually seems like state.data is undefined. Is this namesDictionarySlice being used to control the data property of your root state? If it is something else, like state.names, then you need to change your selectors to namesAdapter.getSelectors(state => state.names).
If your store looks like this:
const store = configureStore({
reducer: {
names: namesReducer
}
});
You would want:
export const { selectAll: selectNamesDictionary } = namesAdapter.getSelectors(
(state) => state.names // select the entity adapter data from the root state
);
in Slic function, I make a mistake in writing, I most write 'extraReducer' but I wrote "extractors" :D
Below are my action and reducer files - In my component state I am only seeing this.props.mainData - but others subdataOneData etc., are not being loaded in to the state - till reducer i see the right actions are being dispatched and I also see the data for sub - calls - but they are not reaching my component - I have mapStatetoprops - where I am doing
New issue: as per the updated code - when i print out payload in reducer - I see maindata with the api data but SubData [{}, {}, {}] ..?
Updated code:
import { GET_DATA_AND_SUBDATA } from '../constants/types';
export function getMainData() {
return async function getMainData(dispatch) {
const { data } = await getMainDataAPI();
const subData = data.map((item) => {
const endpoint = 'build with item.name';
return Request.get(endpoint);
});
console.log('subddd' + subData); prints -> **[object Promise],[object Promise],[object Promise]**
dispatch({
type: GET_DATA_AND_SUBDATA,
payload: { data, subData }
});
};
}
async function getMainDataAPI() {
const endpoint = 'url';
return Request.get(endpoint);
}
The problem lies on the way you dispatch the actions.
You are not providing data for mainData and subdataOneData at the same time.
export function getData() {
return async function getData(dispatch) {
const { data } = await getDataAPI();
// This will cause first re-render
dispatch({ type: GET_DATA, payload: data });
Object.keys(data).map((keyName, keyIndex) => {
const endpoint = 'ENDPOINT';
Request.get(endpoint).then((response) => {
// This will cause second re-render
dispatch({
type: GET_subdata + keyIndex,
payload: response.data });
});
return keyIndex;
});
};
}
At first render your subdataOneData is not availble yet.
You are not even specifying a default value in the reducer, therefore it will be undefined.
You can change your action thunk like this
export function getData() {
return async function getData(dispatch) {
const { data } = await getDataAPI();
const subDataResponse = await Promise.all(
Object.keys(data).map( () => {
const endpoint = 'ENDPOINT';
return Request.get(endpoint)
})
)
const subData = subDataResponse.map( response => response.data )
dispatch({
type: GET_DATA_AND_SUBDATA
payload: { data, subData }
});
};
}
And change your reducer accordingly in order to set all data at once.
export default function myReducer(state = {}, action) {
switch (action.type) {
case GET_DATA_AND_SUBDATA:
return {
...state,
mainData: action.payload.data,
subdataOneData: action.payload.subData[0],
subdataTwoData: action.payload.subData[1]
};
default:
return state;
}
}
Note: it's also a good practice to set your initial state in the reducer.
const initialState = {
mainData: // SET YOUR INITIAL DATA
subdataOneData: // SET YOUR INITIAL DATA
subdataTwoData: // SET YOUR INITIAL DATA
}
export default function myReducer(initialState, action) {