How to convert this create store to a promise based? - reactjs

My app is originally loading data from local-storage, Now i am trying to use firebase.
Firebase always tend to return a promise. So I am trying to convert the store into firebase return one.
here is the original one
export const loadState = () => {
const state: AppState = getDefaultState();
VALID_LABS.forEach(labId => {
state.labs[labId] = getDefaultLabState();
STORAGE_CONFIG.forEach(storageField => {
const { statePath, storageKey, defaultValueFn } = storageField;
const loadedValue = getFromLocalStorage(
labId,
storageKey,
defaultValueFn()
);
cachedValues[`${labId}:${storageKey}`] = loadedValue;
set(state, `labs.${labId}.${statePath}`, loadedValue);
});
});
return state as AppState;
};
const store = createStore(rootReducer, loadState(), applyMiddleware(thunk));
store.subscribe(
throttle(() => {
saveState(store.getState());
}, 500)
);
as you can see i am making process working fine. But the problem arise when i start using firebase.
my loadState become something like this.
export const loadState = (): AppState => {
if (firebase.auth().currentUser.uid) {
let userId = firebase.auth().currentUser.uid;
return firebase
.database()
.ref('/users/' + userId)
.once('value')
.then(function(snapshot) {
return snapshot.val() as AppState;
}).catch((err) => {
console.error(err)
})
}
};
So I also need to convert the store to accept the promise returning from the new loadState.
I don't know how to convert it since I am also using applyMiddleWare(thunk)
let saveState: (state: AppState) => void
 ;
let loadState: () => AppState;
if(firebase.auth().currentUser){
loadState = loadStateFirebase;
saveState = saveStateFirebase;
}else{
loadState = loadStateLocalStorage;
saveState = saveStateLocalStorage;
}
// call loadstate then data,pass it in as second para to appstate store
const store = createStore(rootReducer, loadState(), applyMiddleware(thunk));
store.subscribe(
throttle(() => {
saveState(store.getState());
}, 500)
);
Can someone help me

First things first, there is a small bug, you should probably throw err in the end of your catch block, if you don't, error will be considered handled and whatever is returned from catch will be considered the value of the promise. In this case it is undefined, so if an error happens, loadState will resolve to undefined instead of AppState object, which can cause some issues, but that's up to you, maybe you have other plans how to handle this
Anyway, the problem with promises is that as soon as one single function starts using it, everything else must as well. The easier option would be to create store in an asynchronous function:
function createStore() {
return loadState()
.then(state => {
const store = createStore(rootReducer, state, applyMiddleware(thunk))
// Either subscribe to store here
return store
})
}
const storePromise = createStore()
// or here
/* storePromise.then(store => store.subscribe(...))
or using async/await
async function createStore() {
const state = await loadState()
return createStore(rootReducer, state, applyMiddleware(thunk))
}
const storePromise = createStore()
It's hard to tell if this will suit your application, judging by tags, you're using react, so you may need to add extra logic to load this store for example write a StoreProvider and wrap your App with it:
function StoreProvider({ children }: { children?: ReactNode }) {
const [store, setStore] = useState<Store<AppState> | null>(null)
useEffect(() => {
createStore().then(setStore)
}, [])
if(!store) return <Loading /> // or whatever
return <Provider store={store}>{children}</Provider>
}
This may be fine for you or may not. Usually this is not very good, because your app cannot render, while firebase is loading its stuff. Another solution would be to initialize store with empty state in the beginning, render the parts of your application that don't need firebase staff and let others wait for it to load, you could use thunks for this or create three actions LOAD_STORE/REQUEST, LOAD_STORE/SUCCESS, LOAD_STORE/ERROR and make your app render in the appropriate way. You could maybe create a system that would queue some actions that need store while it is loading and executes them automatically as soon as it finished, there is a lot of space for creativity and frustration here, designing asynchronous stores is quite a challenge and you need to decide how you want to do it depending on your application needs

Related

Return data from Async function React Native Redux

I am having trouble with accessing the data after fetching it with SecureStore in Expo for react-native.
Here is the simple code:
const infoofuser = SecureStore.getItemAsync('userInfo').then(value =>
console.log(`this is the vlaue from infouser: ${value}`),
);
console.log(`infoouser: ${JSON.stringify(infoofuser)}`);
the first infoofuser constant definition returns the object of the intended data.
console.log(`infoouser: ${JSON.stringify(infoofuser)}`);
however returns {"_U":0,"_V":0,"_W":null,"_X":null} which U understand is a promise. I would like to simply get the data that comes from the SecureStore call and use it to set my initialState in redux.
const infoofuser = SecureStore.getItemAsync('userInfo').then(value =>
value
);
this does not work either to access the data
You can use async method using async/await. Try this:
const userInfo = useSelector(state => state.userInfo);
const getData = async () => {
try {
const infoofuser = await SecureStore.getItemAsync('userInfo');
console.log('infoofuser:', infoofuser)
/// strore on redux
} catch (err) {
// handle error
}
}
useEffect(() => {
getData()
}, [])
if (!userInfo) return null
//render something else
You can check the Expo Secure Store docs for reference.

Testing in React with preloadedState: redux store gets out of sync when having a manual import of store in a function

I'm writing tests for React using react-testing-library and jest and are having some problems figuring out how to set a preloadedState for my redux store, when another file imports the store.
I Have a function to set up my store like this
store.ts
export const history = createBrowserHistory()
export const makeStore = (initalState?: any) => configureStore({
reducer: createRootReducer(history),
preloadedState: initalState
})
export const store = makeStore()
and another js file like this
utils.ts
import store from 'state/store'
const userIsDefined = () => {
const state = store.getState()
if (state.user === undefined) return false
...
return true
}
I then have a test that looks something like this:
utils.test.tsx (the renderWithProvider is basically a render function that also wraps my component in a Render component, see: https://redux.js.org/recipes/writing-tests#connected-components)
describe("Test", () => {
it("Runs the function when user is defined", async () => {
const store = makeStore({ user: { id_token: '1' } })
const { container } = renderWithProvider(
<SomeComponent></SomeComponent>,
store
);
})
})
And the <SomeComponent> in turn calls the function in utils.ts
SomeComponent.tsx
const SomeComponent = () => {
if (userIsDefined() === false) return (<Forbidden/>)
...
}
Now.. What happens when the test is run seem to be like this.
utils.ts is read and reads the line import store from 'state/store', this creates and saves a store variable where the user has not yet been defined.
the utils.test.tsx is called and runs the code that calls const store = makeStore({ user: { id_token: '1' } }).
The renderWithProvider() renderes SomeComponent which in turn calls the userIsDefined function.
The if (state.user === undefined) returns false because state.user is still undefined, I think that's because utils.ts has imported the store as it were before I called my own makeStore({ user: { id_token: '1' } })?
The answer I want:
I want to make sure that when call makeStore() again it updates the previously imported version of store that is being used in utils.ts. Is there a way to to this without having to use useSelector() and pass the user value from the component to my utils function?
e.g I could do something like this, but I'd rather not since I have a lot more of these files and functions, and rely much on import store from 'state/store':
SomeComponent.tsx
const SomeComponent = () => {
const user = useSelector((state: IState) => state.user)
if (userIsDefined(user) === false) return (<Forbidden/>)
...
}
utils.ts
//import store from 'state/store'
const userIsDefined = (user) => {
//const state = store.getState()
if (user === undefined) return false
...
return true
}
As I said above I'd prefer not to do it this way.
(btw I can't seem to create a fiddle for this as I don't know how to do that for tests and with this use case)
Thank you for any help or a push in the right direction.
This is just uncovering a bug in your application: you are using direct access to store inside a react component, which is something you should never do. Your component will not rerender when that state changes and get out of sync.
If you really want a helper like that, make a custom hook out of it:
import store from 'state/store'
const useUserIsDefined = () => {
const user = useSelector(state => state.user)
if (user === undefined) return false
...
return true
}
That way your helper does not need direct access to store and the component will rerender correctly when that store value changes.

React Redux Server Side Rendering with code splitting and injecting

Sorry for bad English.
I'm struggling with ssr(pure react) + redux + code splitting(#loadable) + injecting redux. (highly effected by react-boilerplate)
currently my code working great without preload data.
I don't know how can I handle ssr preload data before inject reducer.
here is example to help my problem is.
store = {
global: { // default
key: 'value' // this is done. ssr working great using this value.
},
injected: { // dynamically injected. using replaceReducer per page. (same with react-boilerplate)
key: 'value' // I want to put this value on ssr preload. (not working)
}
}
When it done, it said
Unexpected property "injected" found in previous state received by the reducer. Expected to find one of the known reducer property names instead: "global". Unexpected properties will be ignored.
I know why this error comes(because initial store does not has 'injected' store.), but I don't know How can I fix it properly.
Is there any usage example?
Here is my thought, but it seemed not proper answer.
insert key for preload data on 'global'.
put preload data on 'global' in server.
Move global to injected store(in this case, 'injected') when injecting is done.
voila!
reducerInjector.js
export const injectState = (reducers, preloadedState = {}) =>
Object.keys(reducers).reduce((result, key) => {
const finalReducers = result;
if (typeof reducers[key] === 'function') {
finalReducers[key] = (state = preloadedState[key], action) => reducers[key](state, action);
}
return finalReducers;
}, {});
export const createInitialState = (reducers, preloadedState = {}) =>
Object.keys(preloadedState).reduce((r, key) => {
if (!reducers[key]) return r;
return { ...r, [key]: preloadedState[key] };
}, {});
export const createReducer = (staticReducers, asyncReducers, preloadedState) =>
combineReducers(injectState({
...staticReducers,
...asyncReducers,
}, preloadedState));
export default function reducerInjector(store, staticReducers, preloadedState) {
// Add a dictionary to keep track of the registered async reducers
store.asyncReducers = {};
// Create an inject reducer function
// This function adds the async reducer, and creates a new combined reducer
store.injectReducer = (key, asyncReducer) => {
store.asyncReducers[key] = asyncReducer;
store.replaceReducer(createReducer(staticReducers, store.asyncReducers, preloadedState));
};
return store;
}
...
import reducerInjector, { createReducer, createInitialState } from './reducerInjector';
const configureStore = (initialState, ssr) => {
const sagaMiddleware = createSagaMiddleware({});
const createStoreWithMiddleware = compose(applyMiddleware(thunkMiddleware, sagaMiddleware));
const reducer = createReducer(rootReducer, {}, initialState);
const initialData = createInitialState(rootReducer, initialState);
let store = createStoreWithMiddleware(createStore)(reducer, initialData);
store = reducerInjector(store, rootReducer, initialState);
return store;
}

next.js mapStateToProps, mapDispatchToProps and getInitialProps

i am currently still trying to wrap my head around redux when using next.js and i am not sure what is the best way to use redux with next. I am used to using mapDispatchToProps for my actions and mapStateToProps for my props. After some research i am now using next-redux-wrapper in my _app.js like recommended but now i am fighting with how to best get my props and dispatch my actions. I had look at a few examples and practices and now have a counter component based on one of these examples.
class Counter extends Component {
increment = () => {
const {dispatch} = this.props
dispatch(incrementCount())
}
decrement = () => {
const {dispatch} = this.props
dispatch(decrementCount())
}
reset = () => {
const {dispatch} = this.props
dispatch(resetCount())
}
render () {
const { count } = this.props
return (
<div>
<h1>Count: <span>{count}</span></h1>
<button onClick={this.increment}>+1</button>
<button onClick={this.decrement}>-1</button>
<button onClick={this.reset}>Reset</button>
</div>
)
}
}
function mapStateToProps (state) {
const {count} = state.counter;
return {count};
}
export default connect(mapStateToProps)(Counter)
Most examples i have seen so far do something similar to this or only dispatch actions in getInitialProps. Is there a reason to do it this way and not use mapDispatchToProps?
Cause this work perfectly fine as well:
export default connect(null, {authenticate})(Signin);
Dispatching actions in getIntialProps seems to have some drawback (or i made some mistakes), cause they do not get executed again when the props change. In my user-profile component i get the current user based on a token from the redux store like this:
const Whoami = ({isAuthenticated, user}) => (
<Layout title="Who Am I">
{(isAuthenticated && user && <h3 className="title is-3">You are logged in as <strong className="is-size-2 has-text-primary">{user}</strong>.</h3>) ||
<h3 className="title is-3 has-text-danger ">You are not authenticated.</h3>}
</Layout>
);
Whoami.getInitialProps = async function (ctx) {
initialize(ctx);
const token = ctx.store.getState().auth.token;
if(token) {
const response = await axios.get(`${API}/user`, {headers: {
authorization: token
}});
const user = response.data.user;
return {
user
};
}
}
const mapStateToProps = (state) => (
{isAuthenticated: !!state.auth.token}
);
export default connect(mapStateToProps)(Whoami);
This works perfectly fine for the initial page-load or when navigating there one the client, but when the token expires or i logout the page does not reflect that without reload or navigating there again without my mapStateToProps. But it seems super clunky to split the concern over 2 seperate functions. But i cant find a cleaner way to do it.
Thanks in advance
About mapDispatchToProps:
It is better to use mapDispatchToProps at least because it is easier to test: you can just pass a mock function to your component. With using this.props.dispatch to dispatch some imported actions it can be much harder.
About getInitialProps:
This answer may be helpful:
GetInitialProps: is provided by Next.js and it is NOT always triggered, so be careful with that, it happen when you wrap 1 component inside another. If the parent Component has GetInitialProps, the child's GetInitialProps will never be triggered, see this thread for more info.
I found some answers to my questions after playing around with next a bit more. For pages where the data does not change after intial load, i could get rid of mapStateToProps by rewriting my thunks a bit to return the dispatches and only use getInitialProps like this:
export function fetchShow(id) {
return (dispatch) => {
dispatch({ type: actionTypes.FETCH_SHOW_REQUESTED,id});
// we need to return the fetch so we can await it
return fetch(`http://api.tvmaze.com/shows/${id}`)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
//dispatch(itemsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((data) => dispatch({type: actionTypes.FETCH_SHOW_SUCEEDED,id, show: data, time: Date.now() }))
.catch(() => dispatch({ type: actionTypes.FETCH_SHOW_ERROR,id }));
};
}
Post.getInitialProps = async function ({store, isServer, pathname, query}) {
const { id } = query;
const {show} = await store.dispatch(fetchShow(id));
return {show};
}
For pages where the data should update upon store changes i am not sure yet. My current idea is to try and write a helper function that will be called from both getInitialProps and mapStateToProps to reduce code duplication but i am not sure yet.

Handling Auth State using Redux

I have a chat-app that uses React, Redux and Firebase. I'm also using thunkmiddleware to do the async updates of the state with Firebase.
I successfully get everything I need, except that everything depends of a previously hard-coded variable.
The question is, how can I call inside my ActionCreators the getState() method in order to retrieve a piece of state value that I need in order to fill the rest of my states?
I currently have my auth: { uid = 'XXXZZZYYYY' }... I just need to call that like
getState().auth.uid
however that doesn't work at all.
I tried a lot of different questions, using mapDispatchToProps, etc. I can show my repo if needed.
Worth to mention that I tried following this other question without success.
Accessing Redux state in an action creator?
This is my relevant current code:
const store = createStore(
rootReducer,
defaultState,
applyMiddleware(thunkMiddleware));
And
function mapDispatchToProps(dispatch) {
watchFirebase(dispatch); // to dispatch async Firebase calls
return bindActionCreators(actionCreator, dispatch);
}
const App = connect(mapStateToProps, mapDispatchToProps)(AppWrapper);
Which I am exporting correctly as many other not pure functions work correctly.
For instance, this works correctly:
export function fillLoggedUser() {
return (dispatch, getState) => {
dispatch({
type: C.LOGGED_IN,
});
}
}
However as suggested below, this doesn't do a thing:
const logState = () => ( dispatch, getState ) => {
console.log(getState());
};
In general your thunked action creator should look something like the below (I have used a post id as an example parameter):
const getPost = ( postId ) => ( dispatch, getState ) => {
const state = getState();
const authToken = state.reducerName.authToken;
Api.getPost(postId, authToken)
.then(result => {
// where postRetrieved returns an action
dispatch(postRetrieved(result));
});
};
If this is similar to what you have then I would log your state out and see what is going on with a simple thunk.
const logState = () => ( dispatch, getState ) => {
console.log(getState());
};

Resources