React/Redux how to access the state in the networkservice - javascript

I have created a Network service component which deals with the API call. I want to retrieve state from other components which update the store.
Im having trouble getting the state so I started using Redux, but I havent used Redux before and still trying to find a way to pass the state to the NetworkService. Any help would be great, thanks!
Here is my NetworkService.js
import RequestService from './RequestService';
import store from '../store';
const BASE_URL = 'api.example.com/';
const REGION_ID = //Trying to find a way to get the state here
// My attempt to get the state, but this unsubscribes and
// doesnt return the value as it is async
let Updated = store.subscribe(() => {
let REGION_ID = store.getState().regionId;
})
class NetworkService {
getForecast48Regional(){
let url =`${BASE_URL}/${REGION_ID }`;
return RequestService.getRequest(url)
}
}
export default new NetworkService();
store.js
import {createStore} from 'redux';
const initialState = {
regionId: 0
};
const reducer = (state = initialState, action) => {
if(action.type === "REGIONAL_ID") {
return {
regionId: action.regionId
};
}
return state;
}
const store = createStore(reducer);
export default store;
My folder heirarchy looks like this:
-App
----Components
----NetworkService
----Store

Do not import store directly. Use thunks/sagas/whatever for these reasons.
NetworkService should not know about anything below.
Thunks know only about NetworkService and plain redux actions.
Components know only about thunks and store (not store itself, but Redux's selectors, mapStateToProps, mapDispatchToProps).
Store knows about plain redux actions only.
Knows - e.g. import's.
//////////// NetworkService.js
const networkCall = (...args) => fetch(...) // say, returns promise
//////////// thunks/core/whatever.js
import { networkCall } from 'NetworkService'
const thunk = (...args) => (dispatch, getState) => {
dispatch(startFetch(...args))
const componentData = args
// I'd suggest using selectors here to pick only required data from store's state
// instead of passing WHOLE state to network layer, since it's a leaking abstraction
const storeData = getState()
networkCall(componentData, storeData)
.then(resp => dispatch(fetchOk(resp)))
.catch(err => dispatch(fetchFail(err)))
}
//////////// Component.js
import { thunk } from 'thunks/core/whatever'
const mapDispatchToProps = {
doSomeFetch: thunk,
}
const Component = ({ doSomeFetch }) =>
<button onClick={doSomeFetch}>Do some fetch</button>
// store.subscribe via `connect` from `react-redux`
const ConnectedComponent = connect(..., mapDispatchToProps)(Component)

Related

I can't render the API I fetched with createAsyncThunk when I refresh the page

When I first open the app it gets the data from api but when I refresh the page it says Cannot read properties of undefined (reading 'memes'). When I console.log the store item It shows an empty object but I can see the api with Redux Devtools extension. I got stuck with this problem and can't figure it out for two days.
slice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const fetchJson = createAsyncThunk(
"json/fetchJson",
async () => {
const data = await fetch("https://api.imgflip.com/get_memes");
const json = await data.json();
return json;
}
);
export const loadJsonSlice = createSlice({
name: "loadJson",
initialState: {
isLoading: false,
hasError: false,
memes:{}
},
extraReducers: (builder) => {
builder
.addCase(fetchJson.pending, (state) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(fetchJson.fulfilled, (state, action) => {
state.isLoading = false;
state.memes = action.payload;
})
.addCase(fetchJson.rejected, (state, action) => {
state.isLoading = false;
state.hasError = true;
state.memes = {};
});
}
});
export default loadJsonSlice.reducer;
export const selectAllJson = (state) => state.loadJsonReducer.memes;
export const isLoading = (state) => state.loadJsonReducer.isLoading;
display.js
import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { selectAllJson, isLoading, fetchJson } from "./jsonSlice";
const DisplayJson = () => {
const allMemes = useSelector(selectAllJson);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchJson());
console.log(allMemes.data.memes[0].id); //Here is where the code gives error.
}, [dispatch]);
if (useSelector(isLoading)) {
return <h1 style={{ fontSize: "48px" }}>WAIT PUST</h1>;
}
return <div>{allMemes.data.memes[0].id}</div>; //Here is where the code gives error.
};
export default DisplayJson;
store.js
import { configureStore } from "#reduxjs/toolkit";
import loadJsonSlice from "./jsonSlice";
const store = configureStore({
reducer: {
loadJsonReducer: loadJsonSlice
}
});
export default store;
I believe you have a small miss-understanding of how asynchronicity works in JavaScript.
Your code in the slide.js is correct and will work.
However, your display.js has to wait for the asynchronous action to complete before it can access the state.
useEffect(() => {
// This dispatch will return a Promise, only after the promise resolves you can access the data
dispatch(fetchJson());
console.log(allMemes.data.memes[0].id); //Here is where the code gives error.
}, [dispatch]);
Make sure you check whether allMemes is already populated when you access it:
const allMemes = useSelector(selectAllJson);
console.log('memes: ', allMemes); // initially 'undefined' but eventually populated via your thunk
// When using memes, make sure it is populated
return allMemes ? allMemes.data.memes[0].id : 'Loading...';
I think this solution works, but the reason for the error is not the async type of your fetch request. The reason for this error is the current closure of your effect-function. Quoted by Eric Elliott:
"A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time."
See also this link: https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-closure-b2f0d2152b36
Depending on this, allMemes in the effect-function will be an empty object (like your initialization) also by dispatching an action-object and not a thunk.

useEffect rewrites the entire store

I have a redux store with 3 reducers:
let reducers = combineReducers({
config: configReducer,
data: dataReducer,
currentState: gameStateRecuder})
let store = createStore(reducers, applyMiddleware(thunkMiddleware));
In each of those reducers the initial store is empty, but once the App component mounts I use useEffect to replace each initial store inside a reducer with the one I receive with axios.get using redux-thunk. It looks like this in every reducer:
let initialState = [];
const SET_STATE = 'SET_STATE';
const configReducer = (state = initialState, action) => {
switch (action.type) {
case SET_STATE: {
return { ...action.state};
}
default:
return state;
}
const setState = (state) => ({ type: SET_STATE, state });
export const getConfigState = () => (dispatch) => {
getAPI.getConfig() //I import getAPI with all the REST API logic//
.then(response => {
dispatch(setState(response));
})
};
And the App trigger is:
const App = (props) => {
useEffect(() => {
props.getConfigState();
props.getDataState();
props.getGameState();
}, []);
return (
//JSX//
);
}
export default compose(connect(null, { getConfigState, getDataState, getGameState }))(App);
However, when the App mounts, I have this mess:
In the end, I get the state of each reducer replaced with the state of the one whose promise resolved the last one. I can try to wrap the app 2 more times with a HOC that does nothing but re-writes a state of the precise reducer, but I would still like to understand what causes a promise to affect other reducers besides the one he needs to effect.
A silly mistake, but maybe someone has the exact same problem - the solution is to give different case names for each reducer - SET_STATE need to become SET_GAME_STATE, SET_CONFIG_STATE, SET_DATA_STATE respectivly. I believe that's because of my misunderstanding on how the dispatch works.

Why is my redux-observable epic not being triggered?

trying to understand rxjs and rxjs within redux and redux observables by trying to do a simple fetch example
got my store set up like so:
import { applyMiddleware, createStore } from 'redux'
import { reducers } from 'redux/reducers'
import { createEpicMiddleware } from 'redux-observable'
import rootEpic from '../epics'
const epicMiddleware = createEpicMiddleware()
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
epicMiddleware.run(rootEpic)
export const store = createStore(reducers, composeEnhancers(applyMiddleware(epicMiddleware)))
and in my epic I've got
const getUserDataEpic = (action$, state$) =>
action$.pipe(
ofType('GET_USER_DATA'),
mergeMap(async (action) => {
const url = `my-url-is-here`
const data = await fetch(url).then((res) => res.json())
return Object.assign({}, action, { type: 'GET_DATA_SUCCESS', data })
}),
catchError((err) => Promise.resolve({ type: 'FETCH_ERROR', message: err.message })),
)
const epic2 = (action$, state$) => {}
export default combineEpics(getUserDataEpic)
I also have my action creator:
export const fetchData = () => ({ type: 'GET_USER_DATA' })
this gets fired in my component on mount. I've wrapped in mapDispatchToProps and I've verified it's definitely getting called. as is my reducer
I don't understand why my epic is not being triggered tho?? I was hoping it would see the GET_USER_DATA being fired and then fire it's own action to put the resolved API request into my state.
please advise where im going wrong
ok I figured it out
export const store = createStore(reducers, composeEnhancers(applyMiddleware(epicMiddleware)))
epicMiddleware.run(rootEpic)
I had to call these the other way around ^ :facepalm:

How can I use a provider value to useMutation and also dispatch the changes to state afterwards?

I would like to use a Context.Provider value to handle both mutating and dispatching similar changes. I have read about React-Apollo's onComplete method, but I'm not sure which approach will cover more cases where I need to both mutate and dispatch state. Here's what I have:
const CartContext = React.createContext<{
state: State
dispatch: Dispatch<AnyAction>
cartApi: any
}>({ state: initialState, dispatch: () => null, cartApi: mutateUserProductsAndUpdateCart })
function CartProvider({ children }: { children?: React.ReactNode }) {
const [state, dispatch] = useReducer<Reducer<State, AnyAction>>(reducer, initialState)
// feel like i need to do something with the hook here to avoid invariant violation side effects
const [updateUserProducts] = useUpdateUserProducts()
return (
<CartContext.Provider value={{ state, dispatch, cartApi: mutateUserProductsAndUpdateCart}}>
{children}
</CartContext.Provider>
)
}
export const useCartState = () => useContext(CartContext)
And here's what I would like to do with my mutateUserProductsAndUpdateCart:
const mutateUserProductsAndUpdateCart = async (_mutation: any, _mutationParams: any, _dispatchObject: AnyObject) => {
// apollo mutation
const updateUserProductsResult = await updateUserProducts(_mutationParams)
if (updateUserProductsResult.error) throw Error("wtf")
// useReducer dispatch
dispatch(_dispatchObject)
return
}
and here is how I would like to access this on another component:
const { cartApi } = useCartState()
const addProductToCart = async () => {
const result = await cartApi({
mutation,
mutationVariables,
dispatchObject})
}
I feel like this article is sort of the direction I should be taking, but I'm very lost on implementation here. Thanks for reading.
I'm not sure this directly answers your question, but have you considered just using Apollo Client? It looks like you are trying to do two things:
Save items added to the cart to the server
Update the cart locally in the cache
It seems like you could skip creating your own context altogether and just create a hook for mutating (saving your cart items) and then update your local cache for cart items. Something like this:
import gql from 'graphql-tag';
import useMutation from '#apollo/client';
export const mutation = gql`
mutation($items: [CartItem]!) {
saveCartItems(items: $items) {
id
_list_of_properties_for_cache_update_
}
}
`;
export const useSaveCartItems = mutationProps => {
const [saveCartItems, result] = useMutation(
mutation,
mutationProps
);
return [
items => {
saveCartItems({
update: (cache, { data }) => {
const query = getCartQuery; // Some query to get the cart items from the cache
const results = cache.readQuery({ query });
// We need to add new items only; existing items will auto-merge
// Get list of new items from results
const data = []; // List of new items; IMPLEMENT ME!!!
cache.writeQuery({ query, data });
},
variables: { items },
});
},
result,
];
};
Then in your useCartState hook you can just query the local cache for the items using the same query you used for the update and return that. By using the update function you can fix your local cache and anybody can access it from anywhere, just use the hook. I know that isn't exactly what you asked for, but I hope it helps.
Apollo client documentation on handling this may be found here.

react-admin: dispatch action to store meta data from dynamic request

Hi this question is a continue to this one!
I'm getting my routes dynamically via an ajax request (following this article in the official docs "Declaring resources at runtime"), I'm using an async function to return a list of resources from an ajax request.
What is the best way to dispatch an action to store meta data, which I got form ajax request in redux, for later access?
Also when user has not yet logged in, this function will not return anything, after logging in, user will have access to a couple of resources. What is the best way to reload resources?
The best option is to use redux-saga. https://redux-saga.js.org/docs/introduction/BeginnerTutorial.html
Then
export function* async() {
yield fetch(); //your Ajax call function
yield put({ type: 'INCREMENT' }) //call your action to update your app
}
Incase you can't use redux-saga, I like your solution with private variable. You should go ahead with that.
To get this to work, I added a private variable, which I store the data mentioned in the question, and I access it via another function, which I exported from that file.
This gives me what I need, but I don't know if it's the best way to go.
https://github.com/redux-utilities/redux-actions
redux-actions is really simple to setup. Configure the store and then you can setup each state value in a single file:
import { createAction, handleActions } from 'redux-actions'
let initialState = { myValue: '' }
export default handleActions({
SET_MY_VALUE: (state, action) => ({...state, myValue: action.payload})
})
export const setMyValue = createAction('SET_MY_VALUE')
export const doSomething = () => {
return dispatch => {
doFetch().then(result => {
if (result.ok) dispatch(setMyValue(result.data))
})
}
}
Then in your component you just connect and you can access the state value
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
class MyComponent extends React.Component {
render = () => (
<span>{this.props.myValue}</span>
)
}
MyComponent.propTypes = {
myValue: PropTypes.string.isRequired
}
const mapStateToProps = (state) => ({
myValue: state.myState.myValue
})
const mapDispatchToProps = () => ({})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

Categories