I am getting used to redux with react and i have worked through a few tutorials now. I'm left confused on how I should get data from my express api (where i get data from db) to the redux store.
Say i have a file in my express backend users.js where I connect to my mysql db and return all users in my database. So in my reducers folder i have an index.js file looking something like this
/reducers/index.js
// would i do a fetch(/users) here to populate users array with data from db?
const initialState = {
users: []
};
const rootReducer = (state = initialState, action) => {
switch(action.type) {
case FETCH_USERS:
return { ...state, users: [...state.users, action.payload] };
}
}
or would the proper way be to have this return the empty array and in my component on componentDidMount(fetch(/users)...) and set state.users to my results from that fetch?
I haven't found anything solid on best practices for react/redux with express api so any answers aswell as links to clear up my confusion would be much appreciated
EDIT** If you're reading this and have similar confusion on making api calls with react/redux I have found that the best practice is to have fetch calls in action creators.
Simply import and call your action creator in your component and pass it the params you would like. For this example I put a function in my actions file called fetchUsers() where I use axios to fetch data from server.
You should fetch users in your componentDidMount() and then in the return of promise, you can dispatch the users to your store.
componentDidMount(){
fetch(url).then((response)=>{
if(response.data){
this.props.dispatch({
type:"SET_USERS",
payload: response.data
})
}
})
And you should connect() your component to store to get the dispatch() in your props.
Related
I have a redux react web app booking court app and I want to manage/store redux state based on the user unique id that is coming from the server when the user initials the booking. Currently my initial state looks like this:
const initialState = {
payload: [],
loading: false,
}
I have a reducer like
const courtReducer = (state =initialState, action: any) => {
switch(action.type) {
case MAKE_BOOKING:
return {
...state,
loading,
payload: action.data
};
other action...
}
}
I am not sure if I am making sense.
If you are trying to figure out how to store state, maybe the "Sending Data with Thunks" section of the Redux documentation will help. It's part of a tutorial which should leave you with a pretty good idea of how to integrate async API calls with reducers.
After some researches, I found some questions on stackoverflow about what I am trying to achieve, however, I don't feel that these questions and their answers gives me the "answers" or the "directions" i am looking for..
Note: I am pretty new to react even if I already made 2 projects and implemented redux into one of them. However, I ain't new at all in C# or in Go, even less in C. Based on my experience, I am just used to some architectures and I would like to reproduce one of them.
Here is a pretyy good schema from a similar question of mine:
Situation:
So let say I have pages that contains Components. I want these pages/compoments to display some stuff. One of my functionnality is to discover a map and for that, when the client moves, he gets new parts from my API. However, I don't wanna ask the server to give me the new parts and the ones I discovered already.
My idea about it would be to use a service MapService.js. This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones (concat).
However, I have to be logged for this, so I would like an ApiService.js that would store my authentication data and automatically put them in each of my requests.
Based on what I said, we would have something as:
Page -> Component -> Service -> API
From this, the API response would be gotten by my service, handled, then returned to the component. Handled means (data added to the previous then all returned)
I saw on internet one question that was referring "MVCS" (Model View Controller Service) pattern and I think I am looking for something as but I am not sure about how to implement it in ReactJs.
Redux seems to be something that you put all around and everywhere in your solution. What I would like is to use it as a "repository" let say, to be able to manage it from a service and not from the component itself. However, a service should be a single instance shared across the app and I don't know if something such as dependency injection could be the solution in ReactJS
Feel free to ask any edit if you need more details :)
Thanks for your help !
Here is a minimal example of Redux middleware usage. Usually, redux devs are using libraries (that give you a middleware) to have access to more appropriate APIs.
Redux middleware are chained, so each middleware can call the next middleware. The first middleware of the chain is called every time dispatch function (you can have it from react-redux connect) is called. In a middleware, if there is no next middleware it is the reducers that will be called. The next middleware can be call asynchronously after receiving an action. (Redux docs will still be better than my explainations).
In my example there is a catService that provide function that call rest API. Your services can be anything (a Class instance or a singleton for example). Usually in React/Redux stack, devs don't use object oriented development.
If a component dispatch getCat(123), the catMiddleware will be called (synchronously). Then requestGetCat will be called with the id 123. When the promise returned by requestGetCat will be resolved a setCat action will be send through the reducers to update the redux state. Once the redux state is done, the component listening for cats items object will be update too (triggering a rerender).
That can look very complexe, but in fact, it is very scalable and convenient.
// catService.js
// return a promise that return a cat object
const requestGetCat = id =>
fetch(`www.catcat.com/api/cat/${id}`)
.then(response => response.json())
// catTypes.js
export const GET_CAT = 'GET_CAT'
export const SET_CAT = 'SET_CAT'
// catActions.js
export const getCat = id => ({
type: GET_CAT,
id
})
export const setCat = (cat, id) => ({
type: SET_CAT,
id,
cat
})
// catReducer.js
const initialState = {
items: {}
}
const catReducer = (state = initialState, action) => {
if (action.type === SET_CAT) {
return {
items: {
...state.items,
[action.id]: action.cat
}
}
}
}
// catMiddleware.js
const handleGetCat = (next, action) => {
requestGetCat(action.id)
.then(cat => next(setCat(cat, action.id)))
// after retrieving the cat send an action to the reducers (or next middleware if it exist)
}
const actionHandlers = {
[GET_CAT]: handleGetCat
}
// receive every actions passing by redux (if not blocked)
// store: { dispatch, getState }
// next: next middleware or reducers (that set redux state)
// action: a redux action (dispatched) with at least type property
const catMiddleware = store => next => action => {
const handler = actionHandlers[action.type]
if (handler) {
handler(next, action)
} else {
// passing the action to the next middleware (or reducer - when there is no next middleware)
next(action)
}
}
// you have to apply your middleware
// and your reducer (see redux doc)
This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones
This is something I've wanted to do in the past, but never implemented a solution for.
The issue is that you essentially want to "cross the streams"..
In Redux there are two separate streams, ie dispatch an action to update the store, and read data from the store. Each of these are executed separately from a component. Combined, they can be used in a cycle by calling an action to load data into the store which triggers an update of the component which then reads from the store.
Basically you can't have non-component code that reads from the store, and if the data is missing, fires an action to load the data, then returns the data.
Thinking about it now, I'm wondering if the way to do this without adding logic to your view component is to wrap it in a component (HOC) that provides the logic.
The HOC will check the state for the location specified in the props. If it doesn't find it, it will dispatch an action to fetch it and render a loading display. When the state is updated with the new location it will update and render the wrapped component.
You could optionally always render the wrapped component and have it cope with the missing location until it is updated with the location set..
untested brain-dump below
loader HOC:
import React, { useEffect } from "react";
import actions from "./actions";
function withLocationLoader(Component) {
const Wrapper = function ({ location, locations, loadLocation, ...props }) {
useEffect(() => {
if (!locations[location]) {
loadLocation(location);
}
}, [locations]);
if (locations[location]) {
return <Component locations={locations} {...props} />;
}
return <div>Loading...</div>;
}
const mapStateToProps = (state, ownProps) => {
return { locations: state.locations };
};
const mapActionsToProps = {
loadLocation: actions.loadLocation,
};
return connect(
mapStateToProps,
mapActionsToProps
)(Wrapper);
}
export { withLoader };
component:
function MyBareComponent({ locations }) {
return <div>{JSON.stringify(locations)}</div>;
}
const MyComponent = withLocationLoader(MyBareComponent);
export { MyComponent };
actions: (utilising redux-thunk middleware)
function setLocation(location, data) {
return { type: "SET_LOCATION", payload: { location, data } };
}
export function loadLocation(location) {
return dispatch =>
Promise.resolve({ geoData: "" }) // mock api request
.then(data => dispatch(setLocation(location, data)));
}
I just need a little help with regards to fetching of data through an api
currently I have this code in my container
fetchAll: () => {
Promise.resolve(token.getToken()).then( (response) => {
let obj = {
token_type: response.data.token_type,
access_token: response.data.access_token
}
apiFetch("http://dev.apiserver.com/api/all",{
"method": "GET",
"headers": {
"Authorization": `${obj.token_type} ${obj.access_token}`,
"userId": 1
}
})
.then( (res) => {
return res.json();
}).then( (json) => {
dispatch({
type: 'FETCH_ALL',
payload: json
})
}).catch(function(err) {
console.log(err)
})
})
}
I'm calling the above function from componentWillMount(), I can successfully log the result in my console
here is my reducer code
const allReducer = (state: Immut = initialState, action: { type: string, payload: any }) => {
switch (action.type) {
case FETCH_ALL:
let newState = state
let payload = action.payload
newState = Immutable.fromJS(payload)
console.log(newState)
return newState
}
}
the problem is that it doesn't update the state or re-render the components
Since your reducer is getting called, the issue is likely not in the AJAX call or the update part of the Redux cycle, but in how your component connects to the state. Changes in the state should reactively trigger a re-render of the component, but for this to happen, the props of the component need to change. First, a few steps on how you might debug this issue, before some suggestions of possible causes, and suggested best practices.
Redux Dev Tools
Install Redux Dev Tools on Chrome. DevTools allow you to view a history of actions, and the changes each action induced to the Redux state.
First, use Redux DevTools to ensure that the shape of your Redux state is what you expect, even before triggering any action / AJAX call.
Then, select your AJAX fetch success (FETCH_ALL) action in Redux DevTools and, looking at the diff, see that the state changes as you expect.
Manual debugging
Try putting a debug value in the initialState of your reducer, and ensure it renders on the component.
See that the props of the component are updated when state changes. Put a console.log for this.props and nextProps in the life-cycle method componentWillReceiveProps(nextProps).
Issues to look for
Your component may be looking for the data in a wrong place of the state. The structure of your store follows the composition of your reducers. For example, the following puts the output of myDataReducer under app.db:
const rootReducer = combineReducers({
app: combineReducers({ db: myDataReducer }),
});
You seem to be using Immutable.js. Vanilla Redux treats the state as a POJO. You should either A) use a JavaScript object as the root of your state, or B) use a special combineReducers from redux-immutable.
Best practices
Trigger data fetching from componentDidMount instead of componentWillMount.
Trigger all side effects with an Redux action, instead of chaining promises directly in component code. Effect those changes using Redux middleware, like redux-saga. In your case, you could use redux-promise, create an action like
const action = ({type: FETCH_DATA, payload: axios.get(ENDPOINT, params)});
The redux-promise middleware handles the resolving, so that your reducer will only see the resolved value in the payload.
You're dispatching type 'FETCH_ALL' and catching case FETCH_ALL. One is a variable and one is a string.
Basic setup:
1) App is React and Redux,
2) App is served by a front facing NGINX serving static files like html, pictures and of course the app itself. It also forwards all relevant requests (web sockets and/or AJAX) to the back end (phoenix/elixir).
3) Users are required to authenticate. I'm using redux-oidc library, which is client side only and it works fine.
4) After user logs on is when I get hazy on what to do next.
Question(s):
1) I can't send the state along with the first request because I don't know who the user is and, thus, don't know which state to send. Meanwhile application is already booted (empty store created, login component displayed),
2) After user logs on I can't show anything (like user specific nav bar, timeline , mailbox) I have to saturate the store and let react do its job. What approach should I take?
3) Server rendering is out because a) I'm not using Node and rendering react components using chosen framework is messy and complicated at best and b) I won't be able to export the app to NGinx since it only serves static assets and there is no server logic run there. I could, theoretically, get rid of NGinx, have server based login on the API server and send down HTML along with JSON state which could be used to render the app on the client. However, NGinx does not only serve static assets but also load balances few instances and, thus, getting rid of it is not something I want to do.
Any advice would be appreciated.
Hydrating the state after the store was created, can be achieved by creating a main reducer that can bypass the top level reducers, and replace the whole state.
Reducers are functions that get the current state, combine it with the payload of an action, and return a new state. Usually the main reducer is a combination of all top reducers using combineReducers, and the state is the combination of state pieces returned by the top level reducers.
However, the main reducer can react to actions directly. If the main reducer receives a certain action (hydrate), instead of calling the combined reducers, it returns the action's payload (the saved state). Other actions are passed to the combined reducers.
const mainReducer = (state = {}, action) =>
action.type === 'hydrate' ?
action.payload // hydrate the state
:
reducers(state, action); // create new state by using combined reducers
Working example:
const { combineReducers, createStore } = Redux;
const people = (state = [], action) => action.type === 'people' ? [...state, action.payload] : state;
const items = (state = [], action) => action.type === 'items' ? [...state, action.payload] : state;
const reducers = combineReducers({
people,
items
});
const mainReducer = (state = {}, action) => action.type === 'hydrate' ? action.payload : reducers(state, action);
const store = createStore(mainReducer);
store.subscribe(() => console.log(store.getState()));
store.dispatch({ type: 'people', payload: 5 });
store.dispatch({ type: 'items', payload: 'green' });
store.dispatch({ type: 'hydrate', payload: {
people: [20, 30, 50, 100],
items: ['green', 'yellow', 'red']
}});
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script>
Even though the accepted answer might do the trick, I think that what you are chasing is an anti-pattern, after all.
If you are doing client rendering and handling authentication client side, the only thing you should send to your client, in my opinion, is a <Spinner/>, with zero preloaded state.
Once you are on the client, initialise your store, do your authentication, and decide if you are going to fetch data and render the authenticated version of the page, or if user is not authenticated yet, show them the login form. Everything from this point on, should be handled client side.
Here is what Redux has to say about it:
https://redux.js.org/usage/server-rendering
So, if during the initial render you got "Schrödinger" user (might be authenticated or not), the only thing you can safely assume to show is a spinner.
ANOTHER OPTION
If you really need to get new preloaded data from the server on multiple requests (that's kind of what happens in NextJS apps).
If you are going to pre-render every page (and get new state that on every page change), you can do what NextJS suggests you to do:
They basically re-initialise the store and merge the new data every time it receives new preloaded data from the server.
Here is the example:
https://github.com/vercel/next.js/tree/canary/examples/with-redux-thunk
And the main part of their code:
import { useMemo } from 'react'
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunkMiddleware from 'redux-thunk'
import reducers from './reducers'
let store
function initStore(initialState) {
return createStore(
reducers,
initialState,
composeWithDevTools(applyMiddleware(thunkMiddleware))
)
}
export const initializeStore = (preloadedState) => {
let _store = store ?? initStore(preloadedState)
// After navigating to a page with an initial Redux state, merge that state
// with the current state in the store, and create a new store
if (preloadedState && store) {
_store = initStore({
...store.getState(),
...preloadedState,
})
// Reset the current store
store = undefined
}
// For SSG and SSR always create a new store
if (typeof window === 'undefined') return _store
// Create the store once in the client
if (!store) store = _store
return _store
}
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState])
return store
}
From the discussion here it seems that the state of Redux reducers should be persisted in a database.
How does something like user authentication works in this instance?
Wouldn't a new state object be created to replace the previous state in the database for every user (and their application state) created and edited?
Would using all of this data on the front end and constantly updating the state in the database be performant?
Edit: I've created an example Redux auth project that also happens to exemplify universal Redux, and realtime updating with Redux, Socket.io and RethinkDB.
From the discussion here it seems that the state of Redux reducers should be persisted in a database.
To persist the state or not, it's likely not a concern of Redux at all. It's more up to application logic.
If something happens in an application, like data upload to server, obviously you need to save state (or a slice of the state to a server).
Since network calls are asynchronous, but Redux is synchronous - you need to introduce additional middleware, as redux-thunk or redux-promise.
As sign-up example, you likely need that actions,
export function creatingAccount() {
return { type: 'CREATING_ACCOUNT' };
}
export function accountCreated(account) {
return { type: 'ACCOUNT_CREATED', payload: account };
}
export function accountCreatingFailed(error) {
return { type: 'ACCOUNT_CREATING_FAILED', payload: error };
}
export function createAccount(data, redirectParam) {
return (dispatch) => {
dispatch(creatingAccount());
const url = config.apiUrl + '/auth/signup';
fetch(url).post({ body: data })
.then(account => {
dispatch(accountCreated(account));
})
.catch(err => {
dispatch(accountCreatingFailed(err));
});
};
}
Some portion of state, e.g. user object after authorization, might be stored to localStore and re-hydrated on next application run.
Those are valid concerns. Using localStorage to persist state on the frontend might be a better strategy. You can implement this using middleware, for example:
import {createStore, compose, applyMiddleware} from 'redux';
const localStorageMiddleware = ({getState}) => {
return (next) => (action) => {
const result = next(action);
localStorage.setItem('applicationState', JSON.stringify(
getState()
));
return result;
};
};
const store = compose(
applyMiddleware(
localStorageMiddleware
)
)(createStore)(
reducer,
JSON.parse(localStorage.getItem('applicationState'))
)
If you're concerned about the enemy accessing the user's laptop and stealing credentials from it you could persist state to the backend when the user leaves the page (Navigator.sendBeacon() might be helpful here) & store it in the session.