Rerendering on async fetch with react-mobx - asynchronous

I'm trying to use mobx-rest with mobx-rest-axios-adapter and mobx-react, and I have trouble making the component rerender upon async data retrieval.
Here's my data model, in state/user.js:
import { Model } from 'mobx-rest';
class User extends Model {
url() {
return '/me';
}
}
export default new User();
This is the React component, in App.js:
import React from 'react';
import { inject, observer } from 'mobx-react';
import { apiClient } from 'mobx-rest';
import createAdapter from 'mobx-rest-axios-adapter';
import axios from 'axios';
import { compose, lifecycle, withProps } from 'recompose';
const accessToken = '...';
const API_URL = '...';
const App = ({ user }) => (
<div>
<strong>email:</strong>
{user.has('email') && user.get('email')}
</div>
);
const withInitialise = lifecycle({
async componentDidMount() {
const { user } = this.props;
const axiosAdapter = createAdapter(axios);
apiClient(axiosAdapter, {
apiPath: API_URL,
commonOptions: {
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
});
await user.fetch();
console.log('email', user.get('email'));
},
});
export default compose(
inject('user'),
observer,
withInitialise,
)(App);
It uses recompose to get the user asynchronously from an API in componentDidMount(), and once available the component is supposed to show the user email. componentDidMount() prints the email once available.
Finally this is index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import createBrowserHistory from 'history/createBrowserHistory';
import { Provider } from 'mobx-react';
import { RouterStore, syncHistoryWithStore } from 'mobx-react-router';
import { Router } from 'react-router';
import App from './App';
import { user } from './state/user';
const documentElement = document.getElementById('ReactApp');
if (!documentElement) {
throw Error('React document element not found');
}
const browserHistory = createBrowserHistory();
const routingStore = new RouterStore();
const stores = { user };
const history = syncHistoryWithStore(browserHistory, routingStore);
ReactDOM.render(
<Provider {...stores}>
<Router history={history}>
<App />
</Router>
</Provider>,
documentElement,
);
My problem is that the component doesn't rerender once the user is retrieved and the email is available, although the console log shows that it is returned ok in the async request. I've tried playing around with mobx-react's computed, but no luck. Any ideas?

I think it will work if you change your compose order of App.js:
export default compose(
inject('user'),
withInitialise,
observer,
)(App);
According to the MobX official document,
Tip: when observer needs to be combined with other decorators or
higher-order-components, make sure that observer is the innermost
(first applied) decorator; otherwise it might do nothing at all.

Related

Is it possible to implement expo-auth-session in rtk query?

I'm trying to move this logic to redux toolkit query in order to reduce logic from the screens and allow business logic to be available in redux side. However I'm having hard time to figure out how to move this to redux. Any suggestion would be appreciated
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { ResponseType } from 'expo-auth-session';
import * as Google from 'expo-auth-session/providers/google';
import { initializeApp } from 'firebase/app';
import { getAuth, GoogleAuthProvider, signInWithCredential } from 'firebase/auth';
import { Button } from 'react-native';
// Initialize Firebase
initializeApp({
/* Config */
});
WebBrowser.maybeCompleteAuthSession();
export default function App() {
const [request, response, promptAsync] = Google.useIdTokenAuthRequest(
{
clientId: 'Your-Web-Client-ID.apps.googleusercontent.com',
},
);
React.useEffect(() => {
if (response?.type === 'success') {
const { id_token } = response.params;
const auth = getAuth();
const credential = GoogleAuthProvider.credential(id_token);
signInWithCredential(auth, credential);
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}

I have successfully implemented the redux-persist with next-redux-wrapper in next js

Im getting data from the external api and storing it in the reducer.And im using redux-persist to persist the state while navigating from one page to another.But i have made left the whiteList as an empty array but all the state are being persisted?Need help
import "../assets/css/style.scss";
import "owl.carousel/dist/assets/owl.carousel.css";
import "owl.carousel/dist/assets/owl.theme.default.css";
import Layout from "../component/Layout/Layout";
import { wrapper } from "../redux/store";
import { useEffect } from "react";
import { useStore } from "react-redux";
function MyApp({ Component, pageProps }) {
const store = useStore((store) => store);
useEffect(() => {
{
typeof document !== undefined
? require("bootstrap/dist/js/bootstrap.bundle")
: null;
}
}, []);
return (
<Layout>
<Component {...pageProps} />;
</Layout>
);
}
export default wrapper.withRedux(MyApp);
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import rootReducer from "./index";
import { createWrapper, HYDRATE } from "next-redux-wrapper";
const middleware = [thunk];
let initialState={}
// BINDING MIDDLEWARE
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== "production") {
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const makeStore = ({ isServer }) => {
if (isServer) {
//If it's on server side, create a store
return createStore(rootReducer,initialState, bindMiddleware(middleware));
} else {
//If it's on client side, create a store which will persis
const persistConfig = {
key: "root",
storage: storage,
whiteList: [],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer,initialState, bindMiddleware(middleware));
store.__persisitor = persistStore(store); // This creates a persistor object & push that
persisted object to .__persistor, so that we can avail the persistability feature
return store;
}
};
// export an assembled wrapper
export const wrapper = createWrapper(makeStore);
If you keep the whitelist an empty array then nothing will be persisted.
You have to put inside that string array the redux reducers values you want to be persisted.
Example:
const persistConfig = {
key: 'root',
storage: storage,
whiteList: ['cart', 'form', 'user'],
};

Need help for my first steps with React-Redux

I am struggeling on getting my first steps with Redux. All of these "Todo-App" Tutorials are nice, the "Increment the button" tutorials as well. I thought of getting my own example to teach myself the logic of Redux, but something doesnt work. At the moment, I am not sure where the state comes from, so I tried a lot of different variations to have Redux "started" without getting initialization errors, and I found a working solution! First, I just setted up the state in the reducer, but the button-describtion didnt appear. Then, I setted up the state in the store additionally, and at least the button has the decribtion test123 and the console.log worked. But how to get the state from the reducer (I checked the documentation and it was recommended to pass state by reducers, not by the store itself). At the moment, I get the following error:
Error: Objects are not valid as a React child (found: object with keys {0, 1, 2, 3}). If you meant to render a collection of children, use an array instead.
Here is my absolutely basic code which should help me understand the logic of redux:
Action type:
export const CLICK = 'CLICK'
Action Creator:
import { CLICK } from './types';
export function clicked() {
return({
type: CLICK,
payload: 'switch the describtion of the button'
})
}
the clickReducer:
import { CLICK } from '../actions/types';
const initialState = {
name: 'test'
}
export default function (state = initialState, action) {
console.log('click-test', action)
switch(action.type) {
case CLICK: {
return Object.assign({}, state)
}
default:
return state
}
}
the rootReducer:
import { combineReducers } from 'redux';
import clickReducer from './clickReducer';
export default combineReducers({
name: clickReducer
})
the store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {
name: 'test123'
};
const middleWare = [thunk];
const store = createStore(rootReducer, initialState, applyMiddleware(...middleWare));
export default store;
and the button-component:
import React, { Component } from 'react'
import { connect } from 'react-redux';
import { clicked } from '../actions/clickAction';
class Button extends Component {
render() {
return (
<div>
<button onClick={this.props.clicked}>{this.props.name}</button>
</div>
)
}
}
const mapStateToProps = state => ({
name: state.name
});
export default connect(mapStateToProps, { clicked })(Button);
It would be very nice to get some help with this issue to be able to take further steps in Redux.
Thank you!
You don't need parentheses, do this instead:
import { CLICK } from './types';
export clicked = () => {
return {
type: CLICK,
payload: 'switch the describtion of the button'
}
}
Your "CLICK" type in the switch statement isn't updating the name, you're just returning the state. Do this, instead:
import { CLICK } from '../actions/types';
const initialState = {
name: 'test'
}
export default (state = initialState, action) => {
switch(action.type) {
case CLICK:
return {
...state,
name: action.payload
}
default:
return state
}
}
Your store has too much information, do this instead:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
export default store;
Call the object property:
import React, { Component } from 'react'
import { connect } from 'react-redux';
import { clicked } from '../actions/clickAction';
class Button extends Component {
render() {
return (
<div>
<button onClick={this.props.clicked}>{this.props.name.name}</button>
</div>
)
}
}
const mapStateToProps = state => ({
name: state.name
});
export default connect(mapStateToProps, { clicked })(Button);
this solution, regarding the reducer, still leads to the following error:
Error: Objects are not valid as a React child (found: object with keys {0, 1, 2, 3}). If you meant to render a collection of children, use an array instead.
in button (at button.js:9)
in div (at button.js:8)
in Button (created by ConnectFunction)
in ConnectFunction (at App.js:12)
in div (at App.js:11)
in Provider (at App.js:10)
in App (at src/index.js:6) react-dom.development.js:57
React 15
dispatchInteractiveEvent self-hosted:1029
I really cant imagine why it is like this, because my solution looks like okay and this is a very very primitive app to change the button description :(((

Why am i getting "Error: Actions must be plain objects. Use custom middleware for async actions." error?

I am continuously getting " Actions must be plain objects. Use custom middleware for async actions." error and I am totally stuck here. What am I doing wrong here please help me figure out and help me get out of this error.
This is my index.js file where I have integrated redux store to the app.
import "babel-polyfill";
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux'
import { composeWithDevTools } from 'redux-devtools-extension';
import { createStore, applyMiddleware, combineReducers, compose} from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootSaga from './sagas'
import { postsReducer } from './reducers/posts'
import Routes from './routes';
import './styles/style.css'
const rootReducer = combineReducers({ postsReducer })
const sagaMiddleware = createSagaMiddleware()
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)))
sagaMiddleware.run(rootSaga)
ReactDOM.render((
<Provider store={store}>
<Router><Routes /></Router>
</Provider>
), document.getElementById('root'))
this is my saga.js
import { take, put, call, fork, select, takeEvery, all, takeLatest } from 'redux-saga/effects'
import PostApi from './api/postApi';
import { gotPosts } from './actions/celebrity';
import { POSTS } from '../types'
export function* getAllPosts () {
const posts = yield call(PostApi.getPosts, {})
console.log('postssss', posts)
yield put(gotPosts(posts.data))
}
export function* watchGetPosts () {
yield takeLatest(POSTS, getAllPosts)
}
export default function* root() {
yield all([ fork(watchGetPosts) ])
}
this is my action.js
import { POSTS } from '../../types';
export const gotPosts = (data) => {
return {
type: POSTS,
data,
}
}
export const getPosts = () => dispatch => {
dispatch(gotPosts);
}
this is component page where i dispatched action.
import React, { Component } from 'react';
import { Card, Row, Col } from 'antd';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux'
import { getPosts } from '../actions/celebrity';
const { Meta } = Card;
class MainPage extends Component {
componentDidMount () {
console.log(this.props)
this.props.getPosts();
}
render() {
return <Row type="flex" className="main" justify="center" align="between">
......
</Row>
}
}
const mapStateToProps = state => {
return {
posts: state.postsReducer
}
}
const mapDispatchToProps = dispatch => ({
getPosts: () => {
dispatch(getPosts());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(MainPage);
postsReducer
export const postsReducer = (state = [], action) => {
console.log(action)
switch(action.type){
case POSTS:
return action.data;
default:
return state;
}
}
You can't dispatch function w/o middleware support.
Problem originates from mapDispatchToProps:
{
getPosts: () => { dispatch(getPosts()); }
}
tracing down to your actions.js, getPosts() returns dispatch => dispatch(gotPosts), which is actually a function not an action(plan javascript object), redux dispatch by default doesn't recognize functions, unless you use middleware to enhance it, redux thunk for example.
Since you already have redux saga for async flow, simply dispatch an action from mapDispatchToProps should be fine, also consider create separate actions to differentiate POSTS_REQUEST, POSTS_RECEIVE, POSTS_FAILURE if possible.
import {POST} from '....../actionTypes'
...
{
getPosts: () => { dispatch({ type: POST }); }
}

No reducer provided for key "dashboard"

when i tried user store in my test
import React from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { mount } from 'enzyme';
import chai from 'chai';
import App from '../layouts/App';
import store from '../redux/configureStore';
const expect = chai.expect;
// let store;
let app;
describe('login', () => {
beforeEach(() => {
app = mount (
<Provider store={store}>
<App />
</Provider>
)
})
but i got No reducer provided for key "dashboard"
here is my configStore main code
const reducer = {
dashboard,
PageLogin,
};
const store = createStore(
reducer,
composeEnhancers(applyMiddleware(sagaMiddleware))
);
I got PageLogin, but can't got dashboard
and there is dashboard main code
export {
snackbarActions,
dialogActions,
userConfigActions,
authActions,
progressActions,
UserProfileActions,
// ...
};
You need to use combineReducers to combine your reducers
import { combineReducers } from 'redux'
const reducer = combineReducers({
dashboard,
PageLogin,
})

Resources