This shows the typical file structure in the client folder.
1234567
client
├── middleware # contains all about middleware on data transport. Code running before main execution of transport
├── actions # contains all function regarding on functions that modifies the state/store by dispatching a change of state (reducer)
├── reducers # contains all function regarding on functions that modifies the state/store (called by action)
├── store # contains all about middleware on data transport. Code running before main execution of transport (creates store with reducer and middlewares)
│
├── feathersClient.js # This can be embedded in `app.js`, but this contains connection parameters to integrate Feathers with redux
// ======= reducers/index.j s=======// This is where you will combine both custom and feathers reducersimport{combineReducers}from'redux';exportdefaultfunction(reduxifiedServices){returncombineReducers({users:reduxifiedServices.users.reducer,todo:reduxifiedServices.todo.reducer});}// ======= middleware/index.js =======// This is a configuration for all the middleware for ReduximportreduxThunkfrom'redux-thunk';importreduxPromiseMiddlewarefrom'redux-promise-middleware';// import { routerMiddleware } from 'react-router-redux';// import { browserHistory } from 'react-router';importloggerfrom'./logger';exportdefault[reduxThunk,// Thunk middleware for ReduxreduxPromiseMiddleware,// Resolve, reject promises with conditional optimistic updates// routerMiddleware(browserHistory), // !! IMPORTANT for location.href changeslogger,// A basic middleware logger];// ======= store/index.js =======// This is where both middleware and reducers are combined together to be createdimport{createStore,applyMiddleware}from'redux';importrootReducerfrom'../reducers';importmiddlewaresfrom'../middleware';exportdefaultfunctionconfigureStore(reduxifiedServices,initialState){// Engage the Chrome extension "Redux DevTools" if it is installed on the browser.// This extension watches reducers and logs their invocations, actions and changing state.// It caches activity so you can 'time travel' through state changes.// It runs in an extension reducing the size of your app bundle.// This interface can be left in prod bundles and the extension activated in the field as needed.// https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?utm_source=chrome-app-launcher-info-dialogconstcreateStoreWithDevTools=(typeofwindow!=='undefined'&&window.devToolsExtension)?window.devToolsExtension()(createStore):createStoreconstcreateStoreWithMiddlewares=applyMiddleware(...middlewares)(createStoreWithDevTools);returncreateStoreWithMiddlewares(rootReducer(reduxifiedServices),initialState);}// ======= feathersClient.js =======// This is where feathers connection is establish// This is also where the store is createdimportconfigureStorefrom"../store"importiofrom"socket.io-client"importfeathersfrom'@feathersjs/feathers';importsocketiofrom'@feathersjs/socketio-client';importRealtimefrom"feathers-offline-realtime"importreduxifyServices,{getServicesStatus}from"feathers-redux"// Configure Socket and Feathers Connectionexportconstsocket=io(process.env.REACT_APP_BACKEND_URL});exportconstfeathersClient=feathers().configure(socketio(socket));// Configure Reduxexportconstservices=reduxifyServices(feathersClient,['users','message']);// Replace the array with the services nameconststore=configureStore(services);exportdefaultstore;// Configure realtime & connect it to servicesconstmessages=feathersClient.service('/messages');// Replace this with a specific service to configure realtime connectionconstmessagesRealtime=newRealtime(messages,{sort:Realtime.sort('text')});messagesRealtime.on('events',(records,last)=>{store.dispatch(services.messages.store({connected:messagesRealtime.connected,last,records}));});// Enable realtime. It will start with a snapshot.messagesRealtime.connect().then(()=>console.log('Realtime replication started'));
With Feathers, it could connect with various methods such as sockets. With sockets, it enables the update of the store in realtime.
See below for the configuration. More information can be seen in the feathers-redux documentation.
// feathersClient.js// Configure Redux with FeathersexportconstserviceNames=['users','course-evaluation']exportconstrawServices=reduxifyServices(feathersClient,serviceNames,{idField:"_id",// This is to ensure that realtime update matching uses that attribute});// Realtime Feathers Update ConfgurationserviceNames.forEach(serviceName=>{constcurrentSelectedService=feathersClient.service(`/${serviceName}`)currentSelectedService.on('created',(data)=>{store.dispatch(rawServices[serviceName].onCreated(data));})currentSelectedService.on('updated',(data)=>{store.dispatch(rawServices[serviceName].onUpdated(data));})currentSelectedService.on('patched',(data)=>{store.dispatch(rawServices[serviceName].onPatched(data));})currentSelectedService.on('removed',(data)=>{store.dispatch(rawServices[serviceName].onRemoved(data));})})
Pay attention, that this configuration allows realtime update for each of the services. If you don't need for each services, you can change serviceNames to a list of services that you would want to receive update.
There are two types of services that can be fetched from feathersClient.js:
- rawServices this are services that are not binded to the state which means that calls made from here will not affect the state
- services (binded with dispatch)
An example of a custom action and reducer is authentication. There are mainly 3 parts that you need to do to create a custom action and reducer for state management, see below as follow.
In the AuthGuard component, the following useEffect code is used to prevent accessing of page without Authentication.
1 2 3 4 5 6 7 8 910
// Store Actions and Reduximport{useDispatch}from"react-redux"import{signIn}from"actions/auth"...useEffect(()=>{// Authentication Setupdispatch(signIn())},[])
useDispatch is a function that takes a function (signIn is a function that returns a function with setup action dispatch)
1. signIn Action
This will outline actions towards the state. Note that any action that calls a reducer, all of its parameters are passed to the action object. Hence action.type, action.user, and action.error. The correct names that follows from the action should be followed.
3. Combine Reducer
An example is getting the current user that has login
Getting User Name
12345678
import{useSelector}from"react-redux"...constuser=useSelector(state=>state.auth.user)console.log(user.name)// This will print out the name of the user or it will error (if user is null)
Accessing the user can be accessed from the reducer auth. The auth reducer has access to its state which just happens to store the current user login.