What I'm trying to achieve is that I want a user to be able to submit a registration only when they follow the correct guidelines, such as having a minimum of 8 characters, etc. I have a button set up with a Link router:
//CSS
import "./Register.css"
//LOCAL LIBRARIES
import { VoidFunc, ChangeEventHandlerType,
FormEventHandlerType } from "./index"
import useSubmit from "../../Hooks/Register/useSubmit";
//EXTERNAL LIBRARIES
import { Link } from "react-router-dom";
const Register = () => {
{...}
//Manage Admin
const [username, password, confirmPassword, handleChange,
handleSubmit, placeholder, error, registerUser] = useSubmit()
return (
<div className="div-register">
{...}
<form className="form-register"
onSubmit={handleSubmit as FormEventHandlerType}>
{...}
<Link className="link-register" to={registerUser as string}>
<button className="btn-submit-register">Submit</button>
</Link>
{...}
</form>
</div>
);
};
export default Register;
My attempt was to use a useState hook to manage the url destination of the component so that when the input fields are not filled in properly, the Links "to" property is set to "/register" (keeping the user on the same page they are already on), and when they are, it is set to "/admin" which is the main logged in destination.
I have another .tsx file where I refactored a few elements into one arrow function. The bits I cut out just had conditions like "If the user didnt fill out a field, add an error text to the placeholder for that input field":
//LOCAL
import RegisterUser from "../../Components/Register/RegisterUser";
//EXTERNAL
import { useState } from "react";
import {iState, StateType,
ChangeEventType, FormEventType} from
"../../Pages/Register/index"
const useSubmit = () =>{
//Number Check
let hasNumber = /\d/;
//Error
const [error, setError] = useState<string>("")
//Is Password valid
const [registerUser, setRegisterUser] = useState<string>("/register")
//Placeholder
const [placeholder, setPlaceholder] = useState<string>("")
//Manage Admin
const [state, setState] = useState<StateType>(iState)
const {username, password, confirmPassword} = state
const handleChange = (event: ChangeEventType) => {
//unfinished code
event.preventDefault()
const {name, value} = event.target
setState({...state, [name]: value})
console.log(state)
}
const handleSubmit = (event: FormEventType) => {
event.preventDefault()
{...FAILURE CONDITIONS}
//SUCCESS BLOCK
else {
setPlaceholder("")
setError("")
setRegisterUser("/admin")
}
}
return [username, password,
confirmPassword, handleChange, handleSubmit, placeholder,
error, registerUser]
}
export default useSubmit;
Any help is appreciated!
It seems you are really just wanting to navigate to "/admin" from the submit handler. For this you should use an imperative navigation instead of the Link. (1) the Link's click handler won't wait for the form element's onSubmit handler to complete, and (2) it certainly can't wait for the registerUser state to update.
Use the useHistory hook to access the history object if you are using react-router-dom#5 or the useNavigate hook to access the navigate function if you are using react-router-dom#6.
useSubmit
Import and use either useHistory or useNavigate hooks, depending on installed version, and invoke in the submit handler on the success logic branch. Remove the registerUser state. Return an object instead of an array so destructuring assignment doesn't depend on array indices.
import { useHistory } from 'react-router-dom'; // RRDv5
import { useNavigate } from 'react-router-dom'; // RRDv6
const useSubmit = () => {
const history = useHistory(); // RRDv5
const navigate = useNavigate(); // RRDv6
// Number Check
let hasNumber = /\d/;
// Error
const [error, setError] = useState<string>("");
// Placeholder
const [placeholder, setPlaceholder] = useState<string>("");
// Manage Admin
const [state, setState] = useState<StateType>(iState);
const {username, password, confirmPassword} = state;
const handleChange = (event: ChangeEventType) => {
// unfinished code
event.preventDefault()
const { name, value } = event.target;
setState(state => ({
...state,
[name]: value
}));
}
const handleSubmit = (event: FormEventType) => {
event.preventDefault()
{...FAILURE CONDITIONS}
// SUCCESS BLOCK
else {
history.replace("/admin"); // RRDv5
navigate("/admin", { replace: true }); // RRDv6
}
}
return {
username,
password,
confirmPassword,
handleChange,
handleSubmit,
placeholder,
error,
};
};
Register
Remove the Link component wrapping the submit button, unnecessary. Use object destructuring assignment from the useSubmit hook.
const Register = () => {
...
// Manage Admin
const {
username,
password,
confirmPassword,
handleChange,
handleSubmit,
placeholder,
error,
} = useSubmit();
return (
<div className="div-register">
...
<form
className="form-register"
onSubmit={handleSubmit as FormEventHandlerType}
>
...
<button className="btn-submit-register">Submit</button>
...
</form>
</div>
);
};
I think I came up with a solution. Instead of:
<Link className="link-register" to={"/admin"}>
<button className="btn-submit-register">Submit</button>
</Link>
I just added the following to my success block in useSubmit:
window.location.href="/admin"
Related
I don't understand what should I expect for navigate button in the below code. can any one help me with this. Thank you.
code:
import react from 'react';
const HomeButton = (props) => {
const history = props.history;
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick} data-testid="goToHome">
Go home
</button>
);
}
export default HomeButton;
This is the test code I have been trying for the above component
import React from 'react';
import { render, screen, fireEvent } from '#testing-library/react';
import GoToHome from '../GoToHome';
describe('Read only text', () => {
const history = createMemoryHistory();
it('text came from props', () => {
const { container } = render(<GoToHome history={history} />);
const goToHome = screen.getByTestId('goToHome')
fireEvent.click(goToHome, jest.fn())
expect(container).
});
});
you can check my passing the history as a prop with push property assigned to jest.fn as mock function and check if it is getting called when you press to navigate button
describe('Read only text', () => {
const mockPush = jest.fn()
const history = {
push: mockPush()
}
it('text came from props', () => {
const { container } = render(<GoToHome history={history} />);
const goToHome = screen.getByTestId('goToHome')
fireEvent.click(goToHome)
expect(mockPush ).toBeCalled()
});
});
this my updated version of intergrating redux and NextJS. Just to elobarate what I have done so far...
STEP 1. I've created a store.js file to set up my global store in reference to github's explanation from nextJS developers.
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import thunkMiddleware from 'redux-thunk';
import { customerListReducer } from './customerReducers';
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const combinedReducer = combineReducers({
customerList: customerListReducer,
});
const reducer = (state, action) => {
console.log('Just Displaying the Store', state);
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
if (state.count) nextState.count = state.count; // preserve count value on client side navigation
return nextState;
} else {
return combinedReducer(state, action);
}
};
// create a makeStore function
const store = () =>
createStore(
reducer,
bindMiddleware([thunkMiddleware])
);
// export an assembled wrapper
export const wrapper = createWrapper(store);
STEP 2: Imported the wrapper above in my _app file to make the wrapper available across all pages in my application
import Nav from '../components/Nav';
import {wrapper} from '../reducers/store';
function MyApp({ Component, pageProps }) {
return (
<>
<Nav />
<Component {...pageProps} />
</>
);
}
export default wrapper.withRedux(MyApp);
STEP 3: CONFIGURATIONS
A) My Action that calls external API
import axios from 'axios';
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
export const listCustomers = () => async (dispatch) => {
try {
dispatch({
type: CUSTOMER_LIST_REQUEST,
});
const { data } = await axios.get(
'https://byronochara.tech/gassystem/api/v1/customers'
);
const result = data.results;
dispatch({
type: CUSTOMER_LIST_SUCCESS,
payload: result,
});
} catch (error) {
dispatch({
type: CUSTOMER_LIST_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
B)My Action Reducer
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
import { HYDRATE } from 'next-redux-wrapper';
export const customerListReducer = (state = { customers: [] }, action) => {
switch (action.type) {
case HYDRATE:
return { loading: true, customers: [] };
case CUSTOMER_LIST_REQUEST:
return { loading: true, customers: [] };
case CUSTOMER_LIST_SUCCESS:
return {
loading: false,
customers: action.payload,
};
case CUSTOMER_LIST_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
C)The finally bringing it all together in my index.js page to display the results:
import React, { useEffect } from 'react';
import Head from 'next/head';
import { useSelector} from 'react-redux';
import { listCustomers } from './../actions/customerActions';
import { wrapper } from '../reducers/store';
import styles from '../styles/Home.module.css';
const Home = () => {
//Select the loaded customers' list from central state
const customerList = useSelector((state) => {
console.log(state);
return state.customerList;
});
const { loading, error, customers } = customerList;
//displaying the customers data from the external API
console.log('Fetched Customers Data', customers);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{/* {loading && <h6>Loading...</h6>} */}
{/* {error && <h6>Error Occured...</h6>} */}
{/* {customers.map((customer) => (
<h3>{customer.customerName}</h3>
))} */}
{/* <ArticleList customers={customers} /> */}
</div>
);
};
// getStaticProp at build time
// getServerSideProp at every request slower
// getStaticPath to dynamically generate paths based on the data we are fetching
export const getStaticProps = wrapper.getServerSideProps(async ({ store }) => {
// console.log('STORE', store);
store.dispatch(listCustomers());
});
export default Home;
COMMENT ON THE PROBLEM I'M FACING FROM THE ABOVE CODE: once everything has been set up if you follow the code above, the code seems to run well the store is successfully created when I log the result on the console ``{ customerList: { loading: true, customers: [] } }. But then I guess this is the result from the HYDRATE action type since it will always be dispatch since am using getStaticProps``` that creates a new store instance in the server.
MAIN QUIZ: My challenge is how do I bypass the HYDRATED action and reconcile the server side state with the client side store and persist it and at least to finally be able to view the list from the external API. Thanks in advance. :)
I totally recommend you to use reduxjs/toolkit. It's very simple , less code, no wrappers, clean. And no matter your project on nextjs or created via CRA. Also you dont need to configure redux-thunk and redux-devtools cause they are enabled by default. Read documentation for more information ( how to persist state without any npm package and so on )
Here is a little example.
store.js
import { combineReducers, configureStore } from "#reduxjs/toolkit";
import userSlice from './user.slice.js';
//reducers
const rootReducer = combineReducers({
user: userSlice
});
const store = configureStore({
reducer: rootReducer,
});
export default store;
Wrap with Provider (in your case _app.js)
<Provider store={store}>
<Component {...pageProps} />
</Provider>
user.slice.js ( action + reducer )
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
id: '',
email: '',
roles: []
};
// export async action
export const signIn = createAsyncThunk('user/signIn', async (data) => {
try {
const payload = await api.auth.signin(data).then((res) => res.data);
// do some stuff if you want
return payload ;
} catch (err) {
console.log(err.response);
}
});
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
removeUser(state, payload) {
//cant be an async method
return initialState;
},
extraReducers: (builder) => {
builder.addCase(signIn.fulfilled, (state, { payload }) => {
// payload from the async method above (asyncThunk)
return payload;
});
},
},
});
// export actions
export const { removeUser } = userSlice.actions;
// export reducer
export default userSlice.reducer;
Thats it. Last step to call actions from any component e.g.
import { useDispatch, useSelector } from 'react-redux';
import { signIn, removeUser } from '../actions/userSlice';
// in function component
// call hooks
const dispatch = useDispatch();
// read the store
const { user } = useSelector((state) => state);
// dispatch any action , example below
dispatch(signIn(userCredentials));
// or
dispatch(removeUser());
I has an Issue with setting Redux with NextJS and this is my final answer after some insight from mirik999 too.
A. my store.
import { configureStore } from '#reduxjs/toolkit';
//importing the slice file with sliced reducers
import customerListReducer from '../slices/customerSlice';
// const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware));
const store = configureStore({
reducer: {
customerList: customerListReducer,
},
});
export default store;
B. The store is provided in my app component
function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<Nav />
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
C. The Slice file that automatically creates action creators and the reducer
import { createSlice } from '#reduxjs/toolkit';
//creating and action that calls API from a REST API backend
export const customersFetchedList = createAsyncThunk(
'customersList/customersListSuccess',
async () => {
try {
const { data } = await axios.get(
'https://example.com/api/your/endpoint'
);
const result = data.results;
//the payload
const payload = result;
return payload;
} catch (error) {
console.log(error.response);
const payload =
error.response && error.response.data.message
? error.response.data.message
: error.message;
return payload;
}
}
);
const initialState = {
loading: true,
customers: [],
error: false,
};
const customerListSlice = createSlice({
name: 'customersList',
initialState,
reducers: {
//reducer functions we've provided
customersRequest(state, action) {
if (state.loading == true) {
return state;
}
},
},
extraReducers: (builder) => {
initialState,
builder.addCase(customersFetchedList.fulfilled, (state, action) => {
state.loading = false;
state.customers = action.payload;
state.error = false;
return state;
});
},
});
export const {
customersRequest,
customersLoadingError,
} = customerListSlice.actions;
export default customerListSlice.reducer;
D. Then finally fired this action above in my component using the useEffect()
import React, { useEffect } from 'react';
import Head from 'next/head';
const Home = () => {
//method to fire the action
const dispatch = useDispatch();
//Select the loaded customers' list from central state
const customerList = useSelector((state) => state);
// const { loading, error, customers } = customerList;
useEffect(() => {
dispatch(listCustomers());
}, []);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{loading && <h6>Loading...</h6>}
{error && <h6>Error Occured...</h6>}
{customers.map((customer) => (
<h3>{customer.customerName}</h3>
))}
</div>
);
};
Thanks so much for your contribution. :)
I'm new to React and Redux. I've been trying to create a counter to learn how to work with Redux, but I cannot get the view to update, or mapStateToProps to be called. I've gone over the Redux troubleshooting page, and looked at a number of similar threads posted here. Could somebody please help me identify what is wrong with this code?
import React from 'react'
import { render } from 'react-dom'
import { Provider, connect } from 'react-redux'
import { createStore } from 'redux'
import './index.scss';
let Counter = props => {
console.log('>>> counter', props)
return (
<div>
<h1>counter (Redux Version)</h1>
<p>count: {props.count}</p>
</div>
);
}
let increment = () => {
return {
type: 'INC'
}
}
const counterReducer = (state = {count: 0}, action) => {
switch (action.type) {
case 'INC':
console.log('>>> inc');
return { count: state.count + 1 };
default:
return state;
}
}
const mapStateToProps = state => {
console.log('>>> map state to props', state);
return { count: state.counterReducer.count };
};
const store = createStore(counterReducer);
connect(mapStateToProps, null)(Counter);
render(
<Provider store={store}>
<Counter count={0}/>
</Provider>,
document.getElementById('root')
);
window.store = store;
window.increment = increment;
Using the Chrome console, I am able to see the sate of the store, and then dispatch the increment event. When I do this, I see that the state in the store has updated correctly but the value shown in the paragraph tag still says 0.
store.getState()
{count: 0}
store.dispatch(increment());
VM1382:1 >>> inc
{type: "INC"}
store.getState()
{count: 1}
Counter isn't updating on dispatch because Counter in render isn't actually a connected component.
According to the docs on connect (https://react-redux.js.org/api/connect):
It does not modify the component class passed to it; instead, it returns a new, connected component class that wraps the component you passed in.
This means that simply calling connect(mapStateToProps, null)(Counter); will not mutate Counter, but it'll instead return a connected version of it instead. This returned component is what you want.
A quick fix to this would be to save connect's return value:
const ConnectedCounter = connect(mapStateToProps)(Counter);
and use ConnectedCounter instead of Counter in render:
<Provider store={store}>
<ConnectedCounter count={0}/>
</Provider>
When connecting components to the store, I suggest implementing the component in its own jsx file and export the connected component as default.
In Counter.jsx, we can have...
import { connect } from 'react-redux';
const Counter = props => {
console.log('>>> counter', props)
return (
<div>
<h1>counter (Redux Version)</h1>
<p>count: {props.count}</p>
</div>
);
}
const mapStateToProps = state => {
console.log('>>> map state to props', state);
return { count: state.counterReducer.count };
};
export default connect(mapStateToProps)(Counter);
...so whenever Counter is imported somewhere else in the project (import Counter from '<path>/Counter'), we're sure that Counter is already a connected component.
I am having a very weird bug. I have reproduced the simplest test case scenario here: https://codesandbox.io/s/PNyPwyWP2
I have also uploaded a screencast explaining, the screencast is on youtube here - https://youtu.be/iILiFieO-gk
My goal is that I have a form with a single field, a button "Reset" and a button "Save". Clicking "Save" saves the form values into a reducer in my store called save. Clicking "Reset" should reset the form values to the last "pristine" values (the values in initialValues).
However my issue is, after saving the form, the "Reset" button should reset it to the "pristine" value (the newly saved value, the value in initialValues) but it is reseting it to the "old pristine value"
Here is my full app code:
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, combineReducers } from 'redux'
import { connect } from 'react-redux'
import { Field, reduxForm, reducer as form } from 'redux-form'
// ACTION & ACTION CREATOR
const SAVE_FORM = 'SAVE_FORM';
function saveForm(values) {
return {
type: SAVE_FORM,
values
}
}
// REDUCER - save
const INITIAL = { url:'hiiii' };
function save(state=INITIAL, action) {
switch(action.type) {
case SAVE_FORM: return action.values;
default: return state;
}
}
// STORE
const reducers = combineReducers({ form, save });
const store = createStore(reducers);
// MY FORM COMPONENT
class MyFormDumb extends React.Component {
handleReset = e => {
e.preventDefault();
this.props.reset();
}
render() {
console.log('MyFormDumb :: pristine:', this.props.pristine, 'initialValues:', this.props.initialValues);
return (
<form onSubmit={this.props.handleSubmit}>
<label htmlFor="url">URL</label>
<Field name="url" component="input" type="text" />
<button onClick={this.handleReset}>Reset</button>
<button type="submit">Save</button>
</form>
)
}
}
const MyFormControlled = reduxForm({ form:'my-form' });
const MyFormSmart = connect(
function(state) {
return {
initialValues: state.save
}
}
);
const MyForm = MyFormSmart(MyFormControlled(MyFormDumb));
// MY APP COMPONENT
class App extends React.PureComponent {
submitHandler = (values, dispatch, formProps) => {
dispatch(saveForm(values));
}
render() {
return (
<Provider store={store}>
<div className="app">
<MyForm onSubmit={this.submitHandler} />
</div>
</Provider>
)
}
}
// RENDER
ReactDOM.render(<App />, document.getElementById('app'))
Please use enableReinitialize: true flag on your reduxForm component, as per the docs.
For all I know, I have to write request in action create. How to use a promise in action for submitting a request? I am getting data in action. Then new state is created in reducer. Bind action and reducer in connect. But I don't know how to use promise for request.
Action
import $ from 'jquery';
export const GET_BOOK = 'GET_BOOK';
export default function getBook() {
return {
type: GET_BOOK,
data: $.ajax({
method: "GET",
url: "/api/data",
dataType: "json"
}).success(function(data){
return data;
})
};
}
Reducer
import {GET_BOOK} from '../actions/books';
const booksReducer = (state = initialState, action) => {
switch (action.type) {
case GET_BOOK:
return state;
default:
return state;
}
};
export default booksReducer;
Container
How display data in container?
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import getBook from '../actions/books';
import Radium from 'radium';
import {Link} from 'react-router';
function mapStateToProps(state) {
return {
books: state.data.books,
};
}
function mapDispatchToProps(dispatch) {
return {
getBooks: () => dispatch(getBook()),
};
}
#Radium
#connect(mapStateToProps, mapDispatchToProps)
class booksPage extends Component {
static propTypes = {
getBooks: PropTypes.func.isRequired,
books: PropTypes.array.isRequired,
};
render() {
const {books} = this.props;
return (
<div>
<Link to={`/authors`}><MUIButton style="flat">All Authors</MUIButton></Link>
<ul>
{books.map((book, index) =>
<li key={index}>
<Link to={`/book/${book.name}`}><MUIButton style="flat"><div class="mui--text-black mui--text-display4">
"{book.name}"</div></MUIButton></Link>
<Link to={`/author/${book.author}`}><MUIButton style="flat"><div class="mui--text-black mui--text-display4">
{book.author}</div></MUIButton></Link>
</li>
)}
</ul>
</div>
);
}
}
export default booksPage;
Since you are already using redux you can apply redux-thunk middleware which allows you to define async actions.
Installation & usage: Redux-thunk
export function fetchBook(id) {
return dispatch => {
dispatch(setLoadingBookState()); // Show a loading spinner
fetch(`/book/${id}`, (response) => {
dispatch(doneFetchingBook()); // Hide loading spinner
if(response.status == 200){
dispatch(setBook(response.json)); // Use a normal function to set the received state
}else {
dispatch(someError)
}
})
}
}
function setBook(data) {
return { type: 'SET_BOOK', data: data };
}
You should use Async Actions described in Redux Documentation
Here an example of reducer for async action.
const booksReducer = (state = {}, action) => {
switch (action.type) {
case 'RESOLVED_GET_BOOK':
return action.data;
default:
return state;
}
};
export default booksReducer;
and then you create your Async Action.
export const getBook() {
return fetch('/api/data')
.then(response => response.json())
.then(json => dispatch(resolvedGetBook(json)))
}
export const resolvedGetBook(data) {
return {
type: 'RESOLVED_GET_BOOK',
data: data
}
}
Several Notes:
We could return Promise (instead of Object) in action by using redux-thunk middleware.
Don't use jQuery ajax library. Use other library specifically for doing that (e.g. fetch()). I use axios http client.
Remember, in redux you only use pure function in reducer. Don't make ajax call inside reducer.
Read the complete guide from redux docs.
You should be able to use dispatch inside the callback (if you pass it as an argument):
export default function getBook(dispatch) {
$.ajax({
method: "GET",
url: "/api/data",
dataType: "json"
}).success(function(data){
return dispatch({type:'GET_BOOK', data: data});
});
}
Then, pass dispatch to the action:
function mapDispatchToProps(dispatch) {
return {
getBooks: () => getBook(dispatch),
};
}
Now, you should have access to the action.data property in the reducer:
const booksReducer = (state = initialState, action) => {
switch (action.type) {
case GET_BOOK:
//action.data <--- here
return state;
default:
return state;
}
};
You might want to separate concerns, to keep action creators "pure".
Solution; write some middleware. Take this for example (using superagent).
import Request from 'superagent';
const successHandler = (store,action,data) => {
const options = action.agent;
const dispatchObject = {};
dispatchObject.type = action.type + '_SUCCESS';
dispatchObject[options.resourceName || 'data'] = data;
store.dispatch(dispatchObject);
};
const errorHandler = (store,action,err) => {
store.dispatch({
type: action.type + '_ERROR',
error: err
});
};
const request = (store,action) => {
const options = action.agent;
const { user } = store.getState().auth;
let method = Request[options.method];
method = method.call(undefined, options.url)
if (user && user.get('token')) {
// This example uses jwt token
method = method.set('Authorization', 'Bearer ' + user.get('token'));
}
method.send(options.params)
.end( (err,response) => {
if (err) {
return errorHandler(store,action,err);
}
successHandler(store,action,response.body);
});
};
export const reduxAgentMiddleware = store => next => action => {
const { agent } = action;
if (agent) {
request(store, action);
}
return next(action);
};
Put all this in a module.
Now, you might have an action creator called 'auth':
export const auth = (username,password) => {
return {
type: 'AUTHENTICATE',
agent: {
url: '/auth',
method: 'post',
resourceName: 'user',
params: {
username,
password
}
}
};
};
The property 'agent' will be picked up by the middleware, which sends the constructed request over the network, then dispatches the incoming result to your store.
Your reducer handles all this, after you define the hooks:
import { Record } from 'immutable';
const initialState = Record({
user: null,
error: null
})();
export default function auth(state = initialState, action) {
switch (action.type) {
case 'AUTHENTICATE':
return state;
case 'AUTHENTICATE_SUCCESS':
return state.merge({ user: action.user, error: null });
case 'AUTHENTICATE_ERROR':
return state.merge({ user: null, error: action.error });
default:
return state;
}
};
Now inject all this into your view logic. I'm using react as an example.
import React from 'react';
import ReactDOM from 'react-dom';
/* Redux + React utils */
import { createStore, applyMiddleware, bindActionCreators } from 'redux';
import { Provider, connect } from 'react-redux';
// thunk is needed for returning functions instead
// of plain objects in your actions.
import thunkMiddleware from 'redux-thunk';
// the logger middleware is useful for inspecting data flow
import createLogger from 'redux-logger';
// Here, your new vital middleware is imported
import { myNetMiddleware } from '<your written middleware>';
/* vanilla index component */
import _Index from './components';
/* Redux reducers */
import reducers from './reducers';
/* Redux actions*/
import actionCreators from './actions/auth';
/* create store */
const store = createStore(
reducers,
applyMiddleware(
thunkMiddleware,
myNetMiddleware
)
);
/* Taint that component with store and actions */
/* If all goes well props should have 'auth', after we are done */
const Index = connect( (state) => {
const { auth } = state;
return {
auth
};
}, (dispatch) => {
return bindActionCreators(actionCreators, dispatch);
})(_Index);
const provider = (
<Provider store={store}>
<Index />
</Provider>
);
const entryElement = document.getElementById('app');
ReactDOM.render(provider, entryElement);
All of this implies you already set up a pipeline using webpack,rollup or something, to transpile from es2015 and react, to vanilla js.
Consider using the new thunk API
export const load = createAsyncThunk(
'example/api',
async (arg, thunkApi) => {
const response = await fetch('http://example.api.com/api')
if (response.status === 200) {
const json = await response.json()
return json
},
)
Also, in the new redux template application, actions are part of the reducer/slice, and you can use extraReducers to response to events related to the async action status. It is much simpler using redux this way.
See documentation of async thunk here: https://redux.js.org/usage/writing-logic-thunks