React Admin, Next.js, Redux configuration - redux

I added react-admin to an existing project using redux, following these instructions
https://marmelab.com/blog/2022/02/02/bootstrap-your-react-admin-project-with-nextjs.html
But unfortunately the /admin page did not work because I already had redux built into the project. Then I started to follow this instruction:
https://marmelab.com/react-admin/CustomApp.html
I created a store for the admin
import { applyMiddleware, combineReducers, compose, createStore } from "redux";
import { routerMiddleware, connectRouter } from "connected-react-router";
import createSagaMiddleware from "redux-saga";
import { all, fork } from "redux-saga/effects";
import { adminReducer, adminSaga, USER_LOGOUT } from "react-admin";
import { reducerMain, defaultState } from "./store";
export default ({ authProvider, dataProvider, history }) => {
const reducer = combineReducers({
admin: adminReducer,
router: connectRouter(history),
state: reducerMain, // my reducer
});
const resettableAppReducer = (state, action) =>
reducer(action.type !== USER_LOGOUT ? state : undefined, action);
const saga = function* rootSaga() {
yield all(
[
adminSaga(dataProvider, authProvider),
// add your own sagas here
].map(fork)
);
};
const sagaMiddleware = createSagaMiddleware();
const composeEnhancers =
(process.env.NODE_ENV === "development" &&
typeof window !== "undefined" &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
trace: true,
traceLimit: 25,
})) ||
compose;
const store = createStore(
resettableAppReducer,
{
state: defaultState, //my state
},
composeEnhancers(
applyMiddleware(
sagaMiddleware,
routerMiddleware(history)
// add your own middlewares here
)
// add your own enhancers here
)
);
sagaMiddleware.run(saga);
return store;
};
Rewrite _app.js file
import * as React from 'react';
import Head from 'next/head';
import { ThemeProvider } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import theme from '../theme/theme';
import {Provider} from 'react-redux';
// import store from '../redux/adminStore.js'
import '../styles/style.scss'
//import 'animate.css';
import createAdminStore from '../redux/adminStore'
import {createBrowserHistory as createHistory} from 'history'
import jsonServerProvider from 'ra-data-json-server';
const authProvider = () => Promise.resolve();
const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');
function App({ Component, pageProps }) {
const history = createHistory()
React.useEffect(() => {
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<ThemeProvider theme={theme}>
<Provider store={createAdminStore({
authProvider,
dataProvider,
history,
})}>
<Head>
<title>Test</title>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<CssBaseline />
<Component {...pageProps} />
</Provider>
</ThemeProvider>
);
}
App.getInitialProps = async ({ Component, ctx }) => ({
pageProps: {
...(Component.getInitialProps ? await Component.getInitialProps(ctx) : {}),
pathname: ctx.pathname,
},
});
export default App;
admin/App.js file:
import * as React from "react";
import { Admin, Resource, ListGuesser } from "react-admin";
import jsonServerProvider from "ra-data-json-server";
import {createBrowserHistory as createHistory} from 'history'
const dataProvider = jsonServerProvider("https://jsonplaceholder.typicode.com");
const App = () => {
const history = createHistory()
return (
<Admin dataProvider={dataProvider} title="My Admin" history={history}>
<Resource name="users" list={ListGuesser} />
<Resource name="posts" list={ListGuesser} />
</Admin>
);
};
export default App;
The project builds, but there is a problem with the history method, if you put it outside the component as shown in the instructions, the project stops building with the same error
Error: Invariant failed
at invariant (...\node_modules\tiny-invariant\dist\tiny-invariant.cjs.js:10:15)
at createBrowserHistory (...\node_modules\history\cjs\history.min.js:1:3677)
at App (...\.next\server\pages\_app.js:154:78)
at d (...\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:33:498)
at bb (...\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:36:16)
at a.b.render (...\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:42:43)
at a.b.read (...\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:41:83)
at Object.exports.renderToString (...\next-admin\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:52:138)
at renderPage (...\next-admin\node_modules\next\dist\server\render.js:768:45)
at Object.ctx.renderPage (...\next-admin\.next\server\pages\_document.js:50:26)
package.json
"dependencies": {
"#emotion/cache": "^11.7.1",
"#emotion/css": "^11.7.1",
"#emotion/react": "^11.8.2",
"#emotion/server": "^11.4.0",
"#emotion/styled": "^11.8.1",
"#mui/icons-material": "^5.0.0-rc.1",
"#mui/material": "^5.0.0-rc.1",
"#mui/styled-engine-sc": "^5.5.2",
"#mui/styles": "^5.5.1",
"#reduxjs/toolkit": "^1.8.0",
"#svgr/webpack": "^6.2.1",
"animate.css": "^4.1.1",
"babel-plugin-transform-imports": "^2.0.0",
"babel-preset-next": "^1.4.0",
"classnames": "^2.3.1",
"connected-react-router": "^6.9.2",
"framer-motion": "^6.2.8",
"next": "^12.1.0",
"next-redux-wrapper": "^7.0.5",
"prop-types": "^15.8.1",
"ra-data-json-server": "^3.19.10",
"react": "^17.0.2",
"react-admin": "^3.19.10",
"react-animated-css": "^1.2.1",
"react-animations": "^1.0.0",
"react-countup": "^6.1.1",
"react-customizable-progressbar": "^1.0.3",
"react-dom": "^17.0.2",
"react-hook-form": "^7.28.0",
"react-redux": "^7.2.6",
"redux": "^4.1.2",
"redux-saga": "^1.1.3",
"sass": "^1.49.9",
"styled-components": "^5.3.3"
},
"devDependencies": {
"babel-plugin-import": "^1.13.3",
"babel-plugin-inline-react-svg": "^2.0.1",
"babel-preset-env": "^1.7.0"
}

Related

How to view the data stored in redux orm by a reducer in another file?

I have redux and redux-orm integrated to my async store of react-native, expo app.
I have defined my model like this.
model.js
import { Model, attr, many, ORM, fk } from "redux-orm";
export class Zuser extends Model {
static modelName = "Zuser";
static fields = {
id: attr(),
firstName: attr(),
lastName: attr(),
};
}
Regestiring it in
orm.js
import { ORM } from "redux-orm";
import { Zuser } from "./model";
const orm = new ORM();
orm.register(Zuser);
export { orm };
Configured the orm in
store.js
import { configureStore } from "#reduxjs/toolkit";
import thunk from "redux-thunk";
import AsyncStorage from "#react-native-async-storage/async-storage";
import appReducer from "../slices/app.slice";
import ormReducer from "../slices/orm.slice";
import { persistReducer } from "redux-persist";
/*
the configureStore from "#reduxjs/toolkit" to create the store and persist the store.
It will automatically apply the middleware and other enhancers, so you don't need to call applyMiddleware separately.
*/
const persistConfig = {
key: "root",
storage: AsyncStorage,
whitelist: ["app", "orm"],
};
const rootReducer = {
app: persistReducer(persistConfig, appReducer),
orm: persistReducer(persistConfig, ormReducer),
};
const store = configureStore({
reducer: rootReducer,
middleware: [thunk],
});
export default store;
Making the store persist in
persistor.js
import { persistStore } from "redux-persist";
import store from "./store";
export const persistor = persistStore(store);
I have configured the above in
app.js
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Navigator linking={linking} notificationChange={notificationChange} />
</PersistGate>
</Provider>
I have created reducer and selector for storing and retriving zuser data in
orm.slice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import { orm } from "../../common/orm";
import { createSelector } from "#reduxjs/toolkit";
export const fetchZuser = createAsyncThunk("items/fetchZuser", async (uid) => {
const response = await fetch(
`https://XYZ/zen/get_zuser/${uid}`
);
const resp = await response.json();
if ((await resp.success) === false)
console.log("Error at: fetchZuser(), Problem while fetching zuser");
return await resp.data[0];
// Gives {id, firstName, lastName}
});
const ormSlice = createSlice({
name: "items",
initialState: { orm: orm.getEmptyState() },
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchZuser.fulfilled, (state, action) => {
const session = orm.mutableSession(state.orm);
const Zuser = session.Zuser;
const { objectId, firstName, lastName } = action.payload;
console.log("**Objects to store** ", action.payload);
Zuser.create({ id: objectId, firstName, lastName });
console.log("**Stored objects** ", Zuser.all().toModelArray());
state.orm = session.state;
});
},
});
const selectOrm = (state) => state.orm;
export const selectZuser = createSelector([selectOrm], (ormState) => {
const session = orm.session(ormState);
const Zuser = session.Zuser;
console.log("**Selector Zuser Session** ", Zuser);
let zobjects = Zuser.all().toRefArray()
console.log("**Selector zuser objects** ", zobjects);
});
export const {} = ormSlice.actions;
export default ormSlice.reducer;
I am calling the dispatch reducer thunk in
login.js
var res = dispatch(fetchZuser("1ZkDGFs8sX"));
console.log("Login res ", res);
It says the data is stored.
console
LOG **Objects to store** {"createdAt": "2022-12-13T07:01:01.591Z", "firstName": "oonga", "lastName": "boonga", "objectId": "NVlGiiw8Po", "updatedAt": "2023-02-05T08:54:58.723Z", "userPointer": {"__type": "Pointer", "className": "_User", "objectId": "1ZkDGFs8sX"}}
LOG **Stored objects** [{"_fields": {"firstName": "oonga", "id": "NVlGiiw8Po", "lastName": "boonga"}}]
Next when I try to use the selectZuser selector in
home.js
const zusers = useSelector(selectZuser);
console.log("Home zusers selector ", zusers);
I cant find any data, infact it gives me error in the selector.
console
LOG Selector Zuser Session [Function SessionBoundModel]
ERROR TypeError: undefined is not an object (evaluating 'branch[this.arrName]')
Package.json
{
"name": "timely-reflection",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject"
},
"dependencies": {
"#expo/metro-config": "^0.3.22",
"#parse/react-native": "^0.0.1-alpha.17",
"#react-native-async-storage/async-storage": "~1.15.0",
"#react-navigation/bottom-tabs": "^5.11.2",
"#react-navigation/native": "^5.8.10",
"#react-navigation/native-stack": "^6.9.1",
"#react-navigation/stack": "^5.12.8",
"#reduxjs/toolkit": "^1.8.6",
"accordion-collapse-react-native": "^1.1.1",
"deep-equal": "^2.0.5",
"expo": "^47.0.0",
"expo-app-loading": "^2.1.0",
"expo-auth-session": "~3.6.1",
"expo-device": "^4.3.0",
"expo-notifications": "^0.16.1",
"expo-random": "~12.2.0",
"expo-splash-screen": "^0.16.2",
"expo-web-browser": "~10.0.3",
"inline-css": "^4.0.1",
"inline-scripts": "^1.7.4",
"moment": "^2.29.4",
"moment-timezone": "^0.5.37",
"parse": "3.4.0",
"prop-types": "^15.8.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-native": "0.68.2",
"react-native-collapsible-tab-view": "^4.5.2",
"react-native-dropdown-picker": "^5.4.2",
"react-native-gesture-handler": "~2.8.0",
"react-native-reanimated": "^2.10.0",
"react-native-safe-area-context": "^4.3.4",
"react-native-screens": "^3.17.0",
"react-native-sha1": "^1.2.3",
"react-native-uuid": "^2.0.1",
"react-native-web": "0.17.7",
"react-native-webview": "^11.23.0",
"react-redux": "^8.0.4",
"redux-orm": "^0.16.2",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.4.1"
},
"devDependencies": {
"#babel/core": "^7.19.3"
},
"private": true
}
I tried persisting the store.
I am trying mutable session instead after normal session not working.
I am expecting to see the data stored when login happens in the home page.

Use Auth0's hook to get token and set header in Apollo client

Hello I'm trying to get auth0 token and set it in the request with next.js, however I haven't been able to set it.
I think the cause is that the function "authLink" in apollo-client.tsx is not called.
I would be very grateful if someone could point me in the right direction.
apollo-client.tsx
import { createHttpLink,ApolloClient, InMemoryCache} from "#apollo/client";
import { setContext } from "#apollo/link-context";
import { Auth0ContextInterface } from "#auth0/auth0-react";
function createClient(
getAccessTokenSilently: Auth0ContextInterface["getAccessTokenSilently"],
){
const endpointUri ="http://localhost:8080/v1/graphql"
console.log("hoge")
const authLink = setContext(async (_, { headers }) => {
const accessToken = await getAccessTokenSilently();
console.log(accessToken)
return {
headers: {
...headers,
authorization: `Bearer ${accessToken}`,
},
};
});
const httpLink = createHttpLink({
uri: endpointUri,
});
return new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
}
export {createClient};
_app.tsx
import '../styles/globals.css'
import Head from 'next/head'
import type { AppProps } from 'next/app'
import { ApolloProvider } from '#apollo/client'
import { createClient } from './api/apollo-client'
import { ThemeProvider } from '#mui/material/styles'
import { CacheProvider, EmotionCache } from '#emotion/react';
import theme from '../src/theme'
import createEmotionCache from '../src/createEmotionCache'
import { Auth0Provider,useAuth0 } from '#auth0/auth0-react'
import { FC,ReactNode } from 'react'
const clientSideEmotionCache = createEmotionCache();
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}
const AuthApolloProvider: FC<{ children: ReactNode }> = ({ children }) => {
const { getAccessTokenSilently } = useAuth0();
const client = createClient(getAccessTokenSilently);
return <ApolloProvider client={client}>{children}</ApolloProvider>;
};
function MyApp({ Component, emotionCache = clientSideEmotionCache, pageProps }: MyAppProps) {
return (
<Auth0Provider
domain={process.env.NEXT_PUBLIC_AUTH0_DOMAIN||""}
clientId={process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID||""}
redirectUri={`${process.env.NEXT_PUBLIC_APP_URL}/`}
audience={process.env.NEXT_PUBLIC_AUTH0_AUDIENCE||""}
>
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<AuthApolloProvider>
<Component {...pageProps} />
</AuthApolloProvider>
</ThemeProvider>
</CacheProvider>
</Auth0Provider>
)
}
export default MyApp
I wanted to call the function authLink to get toke and set it in the request header but it didn't work.
Issue is that your code is not waiting for the function getAccessTokenSilently to return token as parent function's callback is async but you are not awaiting for it.
Try below:
apollo-client.tsx
import { createHttpLink,ApolloClient, InMemoryCache} from "#apollo/client";
import { setContext } from "#apollo/link-context";
import { Auth0ContextInterface } from "#auth0/auth0-react";
// Made it async
async function createClient(
getAccessTokenSilently: Auth0ContextInterface["getAccessTokenSilently"],
){
const endpointUri ="http://localhost:8080/v1/graphql"
console.log("hoge");
// Moved it out of set context
const accessToken = await getAccessTokenSilently();
// removed async from setContext as its no more needed
const authLink = setContext((_, { headers }) => {
console.log(accessToken)
return {
headers: {
...headers,
authorization: `Bearer ${accessToken}`,
},
};
});
const httpLink = createHttpLink({
uri: endpointUri,
});
return new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
}
export {createClient};
_app.tsx
import '../styles/globals.css'
import Head from 'next/head'
import type { AppProps } from 'next/app'
import { ApolloProvider } from '#apollo/client'
import { createClient } from './api/apollo-client'
import { ThemeProvider } from '#mui/material/styles'
import { CacheProvider, EmotionCache } from '#emotion/react';
import theme from '../src/theme'
import createEmotionCache from '../src/createEmotionCache'
import { Auth0Provider,useAuth0 } from '#auth0/auth0-react'
import { FC,ReactNode } from 'react'
const clientSideEmotionCache = createEmotionCache();
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}
const AuthApolloProvider: FC<{ children: ReactNode }> = ({ children }) => {
const { getAccessTokenSilently } = useAuth0();
// You cannot directly await in JSX function so use useState and useEffect
// useState to store client
const [client, setClient] = React.useState();
// UseEffect to load client
React.useEffect(()=>{
// useEffect will not allow await, so create a function and use it to await
const generateClient = async () => {
const generatedClient = await createClient(getAccessTokenSilently);
setClient(generatedClient);
}
generateClient();
},[])
return <ApolloProvider client={client}>{children}</ApolloProvider>;
};
function MyApp({ Component, emotionCache = clientSideEmotionCache, pageProps }: MyAppProps) {
return (
<Auth0Provider
domain={process.env.NEXT_PUBLIC_AUTH0_DOMAIN||""}
clientId={process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID||""}
redirectUri={`${process.env.NEXT_PUBLIC_APP_URL}/`}
audience={process.env.NEXT_PUBLIC_AUTH0_AUDIENCE||""}
>
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<AuthApolloProvider>
<Component {...pageProps} />
</AuthApolloProvider>
</ThemeProvider>
</CacheProvider>
</Auth0Provider>
)
}
export default MyApp;

Redux saga worker not being called

I don't know why, one of saga workers is not being called.
In sagas/login.js is login worker, where I call another action getProfile in actions/profile.js.
yield put({ type: ActionTypes.LOGIN_SUCCEEDED, address: account.address }); is called and getProfile action is called as well, but getProfile in sagas/profile.js is not being called.
Home.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import login from 'actions/login';
class Home extends Component {
static propTypes = {
login: PropTypes.func,
};
static defaultProps = {
login: () => null,
};
submit = (e) => {
e.preventDefault();
this.props.login(this.key.value);
};
render() {
return (
<div>
<form onSubmit={this.submit}>
<div className="input-group">
<input
type="password"
className="form-control"
ref={el => (this.key = el)}
/>
<button type="submit" className="btn btn-primary">
Login
</button>
</div>
</form>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
login: key => dispatch(login(key)),
});
export default connect(
null,
mapDispatchToProps,
)(Home);
actions/login.js
import * as ActionTypes from '../constants/actionTypes';
const login = key => ({
type: ActionTypes.LOGIN_REQUESTED,
key,
});
export default login;
actions/profile.js
import * as ActionTypes from '../constants/actionTypes';
const getProfile = id => ({
type: ActionTypes.PROFILE_REQUESTED,
id,
});
export default getProfile;
sagas/index.js
import { all, fork } from 'redux-saga/effects';
import watchLogin from './login';
import watchProfile from './balance';
export default function* rootSaga() {
yield all([
fork(watchLogin),
fork(watchProfile),
]);
}
sagas/login.js
import { fork, put, takeLatest } from 'redux-saga/effects';
import { wallet } from '#cityofzion/neon-js';
import getProfile from 'actions/profile';
import * as ActionTypes from '../constants/actionTypes';
function* login(action) {
const { key } = action;
try {
const account = new wallet.Account(key);
yield call(getProfile, account.id);
yield put({ type: ActionTypes.LOGIN_SUCCEEDED, address: account.address });
} catch (error) {
yield put({ type: ActionTypes.LOGIN_FAILED, message: error.message });
}
}
export default function* watchLogin() {
yield takeLatest(ActionTypes.LOGIN_REQUESTED, login);
}
sagas/profile.js
import { call, put, takeLatest } from 'redux-saga/effects';
import { api, wallet } from '#cityofzion/neon-js';
import * as ActionTypes from '../constants/actionTypes';
function* getProfile(action) {
const { id } = action;
try {
const profile = yield call(
get,
id,
);
yield put({ type: ActionTypes.PROFILE_SUCCEEDED, profile });
} catch (error) {
yield put({ type: ActionTypes.PROFILE_FAILED, message: error.message });
}
}
export default function* watchProfile() {
yield takeLatest(ActionTypes.PROFILE_REQUESTED, getProfile);
}
index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import createSagaMiddleware from 'redux-saga';
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';
import App from 'components/App';
import reducers from 'state/index';
import sagas from 'sagas/index';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
combineReducers({
...reducers,
}),
composeWithDevTools(applyMiddleware(
sagaMiddleware,
)),
);
sagaMiddleware.run(sagas);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app'),
);
package.json
"dependencies": {
"#cityofzion/neon-js": "^3.8.1",
"axios": "^0.18.0",
"react": "^16.3.2",
"react-dom": "^16.3.2",
"react-redux": "^5.0.7",
"react-slidedown": "^1.3.0",
"redux": "^4.0.0",
"redux-saga": "^0.16.0"
},
You need to have your *watch sagas in while(true)
export default function* watchProfile() {
while(true) {
yield takeLatest(ActionTypes.PROFILE_REQUESTED, getProfile);
}
}
Instead of yield call I needed to use yield put.
I changed yield call(getProfile, account.id); to yield put(getProfile, account.id); and now it works.

isssue with redux and apollo 2.x.x

How should I use redux with apollo 2.x.x beside graphql ?
I have this error
"configureStore.js:11 Uncaught TypeError: Cannot read property 'reducer' of undefined
at ./src/store/configureStore.js.exports.default"
and it seems to be related to the cache instanse of apollo
import React from "react";
import ReactDOM from "react-dom";
import AppRouter from "./routers/AppRouter";
import registerServiceWorker from "./registerServiceWorker";
// 1
import { ApolloProvider } from "react-apollo";
import { ApolloClient } from "apollo-client";
import { HttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloLink } from 'apollo-client-preset';
import {AUTH_TOKEN} from './lib/constants';
import configureStore from './store/configureStore';
import "./styles/App.css";
const httpLink = new HttpLink({ uri: "http://localhost:3000/graphql" });
const middlewareAuthLink = new ApolloLink((operation, forward) => {
const token = localStorage.getItem(AUTH_TOKEN);
const authorizationHeader = token ? `Bearer ${token}` : null
operation.setContext({
headers: {
authorization: authorizationHeader
}
})
return forward(operation)
})
const httpLinkWithAuthToken = middlewareAuthLink.concat(httpLink)
console.log("httpLink",httpLink);
console.log("httpLinkWithAuthToken",httpLinkWithAuthToken);
const store =configureStore();
export const client = new ApolloClient({
link: httpLinkWithAuthToken,
cache: new InMemoryCache()
});
const jsx = (
<ApolloProvider store={store} client={client}>
<AppRouter />
</ApolloProvider>
);
ReactDOM.render(jsx, document.getElementById("app"));
registerServiceWorker();
and the store in configured in this way :
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import {client} from "../app";
import thunk from "redux-thunk";
import { ApolloClient } from "apollo-client";
export default ()=>{
const store =createStore(
combineReducers({
// classes:classes ,
apollo:client.reducer(),
}),
{}, //initial state
compose(
applyMiddleware(client.middleware()),
thunk.withExtraArgument(client),
// If you are using the devToolsExtension, you can add it here also
(typeof window.__REDUX_DEVTOOLS_EXTENSION__ !== 'undefined') ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f,
)
);
return srore;
}

Upgrading from createContainer to withTracker with React Router V4

I upgraded meteor and I started receiving deprecation warning for createContainer(). As a result, I've tried to implement withTracker however now I'm getting Component(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.. I'm not sure what I'm missing here, can someone point out my error.
Path: App.jsx
import { Meteor } from 'meteor/meteor';
import React from 'react';
import PropTypes from 'prop-types';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { withTracker } from 'meteor/react-meteor-data';
// IsCandidate Spefic Routes
import TestContainer from '../../containers/candidate/TestContainer';
const App = appProps => (
<Router>
<ScrollToTop>
<div className="bgColor">
<NavBar {...appProps} />
<Grid className="main-page-container">
<Switch>
{/* candidate routes */}
<IsCandidate exact path="/candidate/testpage/:id" component={withTracker(TestContainer)} {...appProps} />
{/* IsPublic routes */}
<Route render={function () {
return <p>Page not found</p>;
}}
/>
</Switch>
</Grid>
</div>
</ScrollToTop>
</Router>
);
App.propTypes = {
loggingIn: PropTypes.bool,
isCandidate: PropTypes.bool
};
export default createContainer(() => {
const loggingIn = Meteor.loggingIn();
return {
loggingIn,
isCandidate: !loggingIn && !!Meteor.userId() && !!Roles.userIsInRole(Meteor.userId(), 'isCandidate'),
};
}, App);
Path: IsCandidate.jsx
import React from 'react';
import PropTypes from 'prop-types'; // ES6
import { Route, Redirect } from 'react-router-dom';
const IsCandidate = ({ loggingIn, isCandidate, component: Component, ...rest }) => (
<Route
{...rest}
render={(props) => {
if (loggingIn) return <div />;
return isCandidate ?
(<Component loggingIn={loggingIn} isCandidate={isCandidate} {...rest} {...props} />) :
(<Redirect to="/login" />);
}}
/>
);
IsCandidate.propTypes = {
loggingIn: PropTypes.bool,
isCandidate: PropTypes.bool,
component: PropTypes.func
};
export default IsCandidate;
Path: Testcontainer.jsx
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
import { Test } from '../../../api/test/test';
import TestPage from '../../pages/candidate/TestPage';
export default TestContainer = withTracker(({ match }) => {
const testHandle = Meteor.subscribe('test', match.params.id);
const loadingTest = !testHandle.ready();
const testCollection = Test.findOne(match.params.id);
const testExist = !loadingTest && !!testCollection;
return {
loadingTest,
testExist,
testCollection: testExist ? testCollection : {}
};
}, TestPage);
Update
export default withTracker(() => {
const loggingIn = Meteor.loggingIn();
return {
loggingIn,
isCandidate: !loggingIn && !!Meteor.userId() && !!Roles.userIsInRole(Meteor.userId(), 'isCandidate'),
isEmployer: !loggingIn && !!Meteor.userId() && !!Roles.userIsInRole(Meteor.userId(), 'isEmployer'),
isAdmin: !loggingIn && !!Meteor.userId() && !!Roles.userIsInRole(Meteor.userId(), 'isAdmin')
};
})(App);
In App.jsx you import withTracker but use createContainer.
withTracker takes only one argument (your reactive function) and wraps your child component where createContainer took 2 arguments (function and component).
createContainer(fn, C);
withTracker(fn)(C);
EDIT
Remove the withTracker call in App.js from this line:
<IsCandidate exact path="/candidate/testpage/:id" component={withTracker(TestContainer)} {...appProps} />
so it becomes
<IsCandidate exact path="/candidate/testpage/:id" component={TestContainer} {...appProps} />
How about it?

Resources