Confused with REDUX actions and reducers - javascript

so I am trying to refactor some code from my previous question:
React: How to update one component, when something happens on another component
So I started digging deep into the existing code template to see how it was implemented.
I found a reducers.js where I added a new reducer: ActiveTenant
import Auth from './auth/reducer';
import App from './app/reducer';
import ThemeSwitcher from './themeSwitcher/reducer';
import LanguageSwitcher from './languageSwitcher/reducer';
import ActiveTenant from './activetenant/reducer';
export default {
Auth,
App,
LanguageSwitcher,
ThemeSwitcher,
ActiveTenant
};
That new reducer is like this:
import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';
const initState = new Map({
tenantId: ''
});
export default function(state = initState, action) {
switch (action.type) {
case actions.SET_TENANT_ACTIVE:
{
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
.then(response =>{
if(response.status === 200){
console.log("Tenant activated");
}else{
throw "error";
}
})
.catch(error => {
console.error(error);
});
return state.set('tenant', state.Name);
}
default:
return state;
}
}
and actions for that reducer
const actions = {
SET_TENANT_ACTIVE: 'SET_TENANT_ACTIVE',
setTenantActive: () => ({
type: actions.SET_TENANT_ACTIVE
}),
};
export default actions;
Then from the component itself, I need to call the action when a row is selected on the front end, so I have refactored the commented code, into one line.
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import actions from '../../redux/activetenant/actions';
const { setTenantActive } = actions;
class ListTenants extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.id,
TestSiteCollectionUrl: row.TestSiteCollectionUrl,
TenantName: row.TenantName,
Email: row.Email
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
const columns = [
{
title: 'TenantName',
dataIndex: 'TenantName',
key: 'TenantName',
},
{
title: 'TestSiteCollectionUrl',
dataIndex: 'TestSiteCollectionUrl',
key: 'TestSiteCollectionUrl',
},
{
title: 'Email',
dataIndex: 'Email',
key: 'Email',
}
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
if(selectedRows[0].TenantName != undefined){
console.log(selectedRows[0].TenantName);
const options = {
method: 'post'
};
setTenantActive(selectedRows[0].TenantName);
/* adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+selectedRows[0].TenantName.toString(), options)
.then(response =>{
if(response.status === 200){
Notification(
'success',
'Tenant set to active',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Tenant not activated',
error
);
console.error(error);
}); */
}
},
getCheckboxProps: record => ({
type: Radio
}),
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListTenants;
However, its not clear to me the relationship between the action and the reducer, if I check the debugger the action is executed, and none parameter is received, but the reducer is never executed.
DO i have to put a dispatch somewhere?, what I am missing in this puzzle?

So the first thing to understand is the Redux Cycle:
Action Creator-->Action-->dispatch-->Reducers-->State
Action Creator: An action creator is a function that is going to create or return a plain JavaScript object knowns as an Action with a type property and payload property which describes some change you want to make on your data.
The payload property describes some context around the change we want to make.
The purpose of an Action is to describe some change to the data inside our application.
The Action Creator is to create the Action.
The dispatch function is going to take in an Action and make copies of that object and pass it off to a bunch of different places inside our application which leads us to the Reducers.
In Redux, a reducer is a function responsible for taking in an Action. Its going to process that Action, make some change to the data and return it so it can be centralized in some location.
In Redux, the State property is a central repository of all information produced by our reducers. All the information gets consolidated inside the State object so our React application can easily reach into our Redux side of the app and get access to all the data inside the application.
So this way the app does not have to go around to each separate reducer and ask for the current State.
So digest that for a couple of minutes and then look at your architecture.
Let's skip over to reducers.
Reducers are called with an Action that was created by an Action Creator. The reducer will take a look at that Action and decide whether it needs to modify some data based on that Action.
So in other words, the job of a reducer is not to execute API requests but to process actions sent to it by the action creator.
So instead of this:
import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';
const initState = new Map({
tenantId: ''
});
export default function(state = initState, action) {
switch (action.type) {
case actions.SET_TENANT_ACTIVE:
{
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
.then(response =>{
if(response.status === 200){
console.log("Tenant activated");
}else{
throw "error";
}
})
.catch(error => {
console.error(error);
});
return state.set('tenant', state.Name);
}
default:
return state;
}
}
Your reducer should look something like this:
import { SET_TENANT_ACTIVE } from "../actions/types";
const initialState = {
tenantId: ''
};
export default (state = initialState, action) {
switch (action.type) {
case SET_TENANT_ACTIVE:
return {...state, [action.payload.id]: action.payload };
default:
return state;
}
}
Then inside your action creators file, you should have an action creator that looks something like this:
import axios from 'axios';
import { SET_TENANT_ACTIVE } from "../actions/types";
export const setTenant = id => async (dispatch) => {
const response = await axios.post(`/tenants/${id}`);
dispatch({ type: SET_TENANT_ACTIVE, payload: response.data });
};
You also need to learn about Redux project structure because after the above refactor, you are missing how to wire all this up to your component. In your component file there is no connect() function which also requires the Provider tag and you have none of that.
So for this I recommend first of all your set up your folder and file structure like so:
/src
/actions
/components
/reducers
index.js
So inside your index.js file it should look something like this:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import reduxThunk from "redux-thunk";
import App from "./components/App";
import reducers from "./reducers";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducers,
composeEnhancers(applyMiddleware(reduxThunk))
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
So your goal here is to ensure that you get that Provider tag at the very top of your component hierarchy and ensure that you pass it a reference to your Redux store that gets all the reducers loaded up into it.
So above I have created the store and passed it our set of reducers and it will return back to you all your applications State.
Lastly, what you see above is I created an instance of <Provider> and wrapped the <App /> component with it and then you want to pass the <Provider> component is a single prop called store. The store is the result of calling createStore() and calling the reducers.
The <Provider> is what interacts with the Redux store on our behalf.
Notice, I also have wired up Redux-Thunk that J. Hesters mentioned, you are making an ajax request as far as I can see from your code which is why I offered an asynchronous action creator for you, which means you will need Redux-Thunk or some middleware like that, let me not offend the Redux-Saga fans, so you have those two choice at least. You seem relatively new to Redux, just go with Redux-Thunk.
Now you can use the connect() component inside your component file to finish wiring up those action creators and reducers to your component or your React side of the application.
import React, { Component } from 'react';
import { connect } from "react-redux";
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import actions from '../../redux/activetenant/actions';
After importing connect, you create an instance of it below:
export default connect()(ListTenants);
Please don't argue with me on the above syntax (actually had a former student report me to administrators for using this syntax as evidence of not knowing what I was doing).
Then you need to configure this connect() React component by adding mapStateToProps if you are going to need it, but definitely pass in actions as the second argument to connect(). If you realize you don't need mapStateToProps, then just pass in null as the first argument, but you can't leave it empty.
Hope all this was helpful and welcome to the wonderful world of React-Redux.

You are using reducers wrong. Reducers are supposed to be pure. Yours has side-effects showing that you haven't understood Redux, yet.
Instead of writing down a solution for you (which would take forever anyways since one would have to explain Redux in total), I suggest you invest the 3 hours and go through the Redux docs and follow the tutorials (they are great).
Afterwards you might want to look into Redux Thunk. But, you might not need thunks.
PS: (Small thing to bring up, but I haven't seen anyone using Maps in Redux. Is there a reason you do that? You might want to use plain objects instead.)

Your action is not correct you should pass an active tenant name as parameter.
Ref. https://redux-starter-kit.js.org/api/createaction
We could have written the action types as inline strings in both places.
The action creators are good, but they're not required to use Redux - a component could skip supplying a mapDispatch argument to connect, and just call this.props.dispatch({type : "CREATE_POST", payload : {id : 123, title : "Hello World"}}) itself.
Ref. https://redux-starter-kit.js.org/usage/usage-guide

Related

How can I assign data from Http response in const Component?

My goal is to ultimately create add a shopping cart functionality using context API. In order to do so I need to get the products from my database and store it in an array.
Currently, the challenge I'm facing is how to retrieve the data from the axios response and store it in a variable that will be passed on within a const component. Apparently, the issue is that the variable gets passed to the child before the Axios response is complete.
I tried using the await keyword, but got an error regarding not being in an async function. Hence, I tried plugging the async keyword but that didn't work as it yielded errors.
I was able to retrieve data from axios within class components with success, however, I am unable to do so in these const.
Here is my code:
Context.js
import { createContext, useContext, useReducer } from "react";
import React from "react";
import ProductService from "../services/ProductService";
import { cartReducer } from "./Reducers";
const Cart = createContext();
const Context = ({ children }) => {
let products = [];
console.log("part1", products);
ProductService.getAllProducts().then((res) => {
products = res.data; //Also tried setState({ products : res.data})
console.log("response: ", products);
});
const [state, dispatch] = useReducer(cartReducer, {
products: products,
cart: [],
});
return <Cart.Provider value={{ state, dispatch }}>{children}</Cart.Provider>;
};
export const CartState = () => {
return useContext(Cart);
};
export default Context;
ProductService.js
import axios from "axios";
const PRODUCT_BASE_URL = "http://localhost:8080/api/v1/";
class ProductService {
getAllProducts() {
return axios.get(PRODUCT_BASE_URL + "products");
}
getProductsByCategory(category) {
return axios.get(PRODUCT_BASE_URL + "products/" + category);
}
getProductById(id) {
return axios.get(PRODUCT_BASE_URL + "product/" + id);
}
}
export default new ProductService();
Reducers.js
export const cartReducer = (state, action) => {
switch (action.type) {
default:
return state;
}
};
HomeComponenet.jsx
import React from "react";
import SlideShowComponent from "./SlideShowComponent";
import HomeCategoriesComponent from "./HomeCategoriesComponent";
import FeaturedProductsComponent from "./FeaturedProductsComponent";
import { CartState } from "../context/Context";
// class HomeComponent extends React.Component
function HomeComponent() {
const { state } = CartState();
console.log("Cart Inside the Home Component: ", state);
return (
<>
<SlideShowComponent />
<div>
<HomeCategoriesComponent />
</div>
<div>
<FeaturedProductsComponent />
</div>
</>
);
}
export default HomeComponent;
First of all, I've created a fixed example here: https://stackblitz.com/edit/react-jqtcia?file=src/Context.js
You are correct, you need to await the result from axios. In React, we use the useEffect hook for things with side effects or that should not be done as part of the render. Renders in react should be non blocking, that is they should not be dependent on things like data fetching.
A simple example of this would be if we needed it in local state. This example renders without the data, then re-renders once the data is available.
const [products, setProducts] = useState([]);
useEffect(async () => {
const { data } = await ProductService.getAllProducts();
setProducts(data);
}, []);
return <div>{products.length > 0 ? `${products.length} products` : 'Loading...'</div>
NOTE: the , []); means that this will fire once, when the first render happens.
This fixes the first part of your problem, getting the result out of the request/axios.
However, the second part and most important part is that you weren't using this value. You were attempting to insert the result as part of the initial state, but this was empty by the time it was created. As you are using reducers (useReducer), this means you need to dispatch an action for each event and handle all the relevant events to data fetching in the reducer. That means, you should need to be able to handle:
Some data is in a loading state (e.g., pagination or first load)
The data failed to load
The data is partially loaded
The data has fully loaded
I've created a minimal happy example (there is no pagination and data fetching always succeeds):
Context.js
useEffect(async () => {
const { data: loadedProducts } = await ProductService.getAllProducts();
console.log('response: ', JSON.stringify(loadedProducts));
dispatch({ type: 'PRODUCTS_LOADED', products: loadedProducts });
console.log(state);
}, []);
Reducers.js
export const cartReducer = (state, action) => {
console.log(action);
switch (action.type) {
case 'PRODUCTS_LOADED':
const newState = { ...state, products: action.products };
console.log(newState);
return newState;
default:
return state;
}
};
In fact, if you'd done this with your original code, it would've worked:
ProductService.getAllProducts().then((res) => {
dispatch({ type: 'PRODUCTS_LOADED', products : res.data});
console.log("response: ", products);
});
However, this has a different bug: It will refetch the data and then dispatch the event each time Context.js is re-rendered.
Since you've asked for more information in your comment, I'll provide it here (comments were not big enough).
I've linked the relevant API documentation for the hooks above, these provide pretty good information. React does a pretty good job of explaining the what, and why of these hooks (and the library itself). Seriously, if you haven't read their documentation, you should do so.
Additional resources:
What, when and how to use useEffect - In short, if you have something to do that isn't rendering such as data fetching, it's a side-effect and should be in a useEffect.
What is a reducer in JavaScript/React/Redux - Reducers are a pattern to make shared state easier to manage and test. The basic idea is that you define a reducer, which takes an initial state and an event/action, and produces a new state. The key idea is that there must be no side-effects, the same action and state will always produce the same result, no matter the date/time, network state, etc, etc. This makes testing and reasoning about things easier, but at the cost of a more complex state management.
However, something important that I ignored in your original question is that you are kind of reinventing the wheel here. There is already a library that will centralise your state and make it available via context, it's the library that originally invented reducers: redux. I'm guessing you have read something like this article about using context instead of redux, however the advantage of redux for you is that there is a litany of documentation about how to use it and solve these problems.
My recommendation for you is to make sure you need/want redux/reducers. It has it's value and I personally love the pattern but if you are just getting started on React, you would be better off just using useState in my opinion.

Updating Redux state and getting updated data into component with mapStateToProps ERROR

I am learning and trying to understand Redux with React
I have following test code in index.js and App.js
Where it reads API URL I make a request to API to get some data.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import './index.css';
import App from './components/App';
import { createStore } from 'redux';
const initialState = {
events: [],
isViewingEvent : false,
eventIndex : 0,
eventProducts : []
}
//STORE
async function reducer(state = initialState, action) {
switch(action.type) {
case "GETEVENTS":
{
const response = await fetch("API URL", {
method: 'get',
headers: {'Content-Type':'application/json','Accept': 'application/json'}
})
.then(function(response) {
return response.json();
})
console.log(response);
return state.events = response;
};
default:
return state;
}
}
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Now App.js
import { Component } from 'react';
import { connect } from 'react-redux'
class App extends Component {
handleOnClick() {
this.props.dispatch({ type : "GETEVENTS" })
}
render() {
return <div>
<header>
<button onClick={() => this.handleOnClick()}>TEST</button>
</header>
</div>
}
}
const mapStateToProps = (state) => {
console.log(state.events);
return {
events: state.events
}
}
export default connect(mapStateToProps)(App);
I simply wish to test if I can update state with Clicking a button, by calling the dispatch function.
dispatch function makes a API get request and should update state with its response.
I have console.log output in dispatch function to see what the response is, and it will show it correctly. I will get valid data from API.
Another console.log is at mapStateToProps function in App.js, where I'd like to get that events data as a prop from state. This will always show me undefined when I click the button. I can see I get valid response from API but I can never get that data as prop into App.js
What am I doing wrong here? I can think of that mapStateToProps happens before I get async response from API. So its mapping props before state update? But then if I click it the second time I should see data. But I dont, so Im missing something here which I do not understand.
this is my console.log output
You are doing an API request in the reducer here, which is not only conceptually something you should not do in a reducer (side effects are not allowed to happen there), but in this case also something that is just plain impossible, because it turns the whole state slice into a promise.
You need to handle API requests & co outside the reducer in a middleware, for example a thunk. I recommend looking into the Async Logic and Data Fetching chapter of the official redux tutorial.
You are changing the state in your reducer function directly which is the wrong way to do it as it is immutable. Read about it here: Immutable patterns redux
async function reducer(state = initialState, action) {
...
console.log(response);
return state.events = response; // here is the problem
};
...
}
Try one of the suggested approaches on the link provided.

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)

ReactJs Redux Data Fetch

I just started to work on React Js and Redux-Thunk. Currently, I am trying to fetch data from a url using redux-thunk. I got data successfully but the issue is that it renders undefined data twice, then it gives me the desired data in props.
Here is my code.
In Actions
index.js
function getData() {
return {
type: 'FETCH'
}
}
function getSuccess(data) {
return {
type: 'FETCH_SUCCESS',
payload: data
}
}
function getFailed(err) {
return {
type: 'FAILED',
payload: err
}
}
export function fetchData() {
const thunk = async function thunk(dispatch) {
try {
dispatch(getData());
const body = await fetch("http://jsonplaceholder.typicode.com/users")
const res = await body.json();
console.log("Thunk", res);
dispatch(getSuccess(res));
}
catch(err) {
dispatch(getFailed(err));
}
}
return thunk;
}
In Reducers fetch.js
const initialState = {
state: []
}
export default function(state=initialState , actions) {
switch(actions.type) {
case 'FETCH':
return {
...state
}
case 'FETCH_SUCCESS':
return {
...state,
data: actions.payload
}
case 'FAILED':
return {
...state
}
default:
return state;
}
}
Reducers Index.js
import fetch from './fetch';
import { combineReducers } from 'redux';
const rootReducer = combineReducers ({
fetch
});
export default rootReducer;
App.js
import React, { Component } from 'react';
import './App.css';
import Main from './component/Main';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
console.log("STore", store.getState());
class App extends Component {
render() {
return (
<Provider store={store}>
<Main/>
</Provider>
);
}
}
export default App;
Main.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
class Main extends Component {
componentWillMount() {
const { fetchData } = this.props
fetchData();
}
render() {
let mydata = this.props.data.data;
console.log("Data .....<>", mydata);
return(
<div>
Main
</div>
);
}
}
const mapStateToProps =(state)=> {
return {
data: state.fetch
}
}
export default connect(mapStateToProps, {fetchData: actions.fetchData})(Main);
Output Screen Shot
Please let me know what i am doing wrong. Any help will be appreciated.
Thanks
This behavior is correct. You're doing everything in the normal way, except calling async operations in componentWillMount method instead of componentDidMount.
Read more here about it.
You need to know, that when you are using componentDidMount - you are handle this in a safe way due to commit phase in component lifecycle and it means that your request will be guaranteed trigger once instead of possible several times, which can be in render phase.
See here the visual diagram to understand this more.
Regarding several renderings - it can be explained easily.
First time, when your component is mounting you are calling asynchronous operation and it needs more time to load data than for component mounting. Thats why you are accessing data property of your initialState (which is empty array), and getting undefined.
When you response is ready and actions is being dispatched, redux trigger re-render of connected components with new state.
If you want to make your async-await syntax works you should make lifecycle with async keyword as well and await your request inside.
NOTE: It's safe, but in several cases it might cause unexpected bugs, so keep in mind. Nevertheless, I don't recommend to use it in a such way. Read about this topic in the another thread at SO.
I advice you to create some isLoading boolean status in your reducer and keep track whether data is loaded or not.
Good luck! Hope it will helps!
There is nothing wrong with your code, but there is one unnecessary action.
Why do you see two times undefined and then you see your data?
First one is coming from the initial render. You are making an async dispatching in your componentWillMount so render does not wait for it, then try to log your data. At that time it is undefined.
Your fetchData dispatches two actions: getData and getSuccess. You see second undefined because of getData action since it returns current state which props.data.data undefined at that time again.
You see the data since getSuccess updates your state now.
If you want to test this comment out getData in your fetchData function, just use getSuccess and change your console.log in the render like that:
mydata && console.log("Data .....<>", mydata);
I think getData action is unnecessary here, it does not do anything beside returning the current state. Also, try to use componentDidMount instead of componentWillMount since it will be deprecated in version 17.

Async with redux works? But still gives error, should I ignore it?

I am using a mix of laravel, react.
Attempting to implement the redux-thunk middleware.
I am having issues with async calls.
I would like to use jquery for ajax (which successfully retrieve the API data, but I am getting an error which reads,
"Error: dispatch is not a function", meaning I cannot make any changes to the store. From what I understand dispatch and GetState is passed through the thunk middle-ware. Correct?So why am I not able to use the function?
It also gives me an error which reads: "Error: Actions may not have an undefined "type" property. Have you misspelled a constant?"
Another issue that arises, after trying to deal with the above issue is: "Error: Actions must be plain objects. Use custom middleware for async actions."
I have read many similar questions but I still cannot seem to get it to work.
"Actions must be plain objects. Use custom middleware for async actions."
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import FilterBar from './SideBar/FilterBar';
import Store from '../redux/store/mainStore';
import { REMOVE_ATTRIBUTE_FILTER,ADD_ATTRIBUTE_TO_FILTER, removeAttribute } from '../redux/actions/actions';
Store.subscribe(()=>{
console.log("store changes", Store.getState())
})
console.log(Store.getState());
Store.dispatch({
type:ADD_ATTRIBUTE_TO_FILTER,
payload:{'make':23}
})
if (document.getElementById('InventoryDisplay')) {
ReactDOM.render(
<Provider store={Store}>
<FilterBar/>
</Provider>
,document.getElementById('FilterBar'));
}
mainstore.js
```
import { createStore,applyMiddleware,combineReducers,compose } from 'redux';
import thunk from 'redux-thunk';
import {inventoryFilter,availableAttributes} from '../reducers/reducer';
const Store = createStore(
///combine imported reducers
combineReducers({
activeFilter:inventoryFilter,
availableAttributes:availableAttributes
},
///initilize store
{},
applyMiddleware(thunk)
));
export default Store;
```
actions.js what is relevant
```
///first case
const getAttributes2 = (dispatch) => {
return(
$.ajax('/getFilteredAttributes/', {
type: 'GET',
dataType : 'json'
}).done(response => {
dispatch(addAttribute("make",32));
}).fail((xhr, status, error) => {
console.log("failed");
})
)
};
///second case
const getAttributes = (dispatch) => {
return ()=>{}
}
export {
ADD_ATTRIBUTE_TO_FILTER,addAttribute,
REMOVE_ATTRIBUTE_FILTER,removeAttribute,
GET_INVENTORY,getInventory,
GET_AVAILABLE_ATTRIBUTES,getAttributes,
}
```
component connect to store that dispatch action
```
import React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import * as ActionCreators from '../../../redux/actions/actions';
class DropDownList extends React.Component{
componentDidMount(){
this.props.addAttributes("make",32)
this.props.getAttributes()
this.props.removeAttributes("make",32)
}
render(){
return(
<div>
</div>
)
}
}
function mapStatesToProps(state){
return{
activeFilters:state.activeFilter,
availableAttributes:state.availableAttributes
}
};
const mapDispatchToProps = dispatch => {
return {
addAttributes: (type,value) => {
dispatch(ActionCreators.addAttribute(type,value))
},
removeAttributes: (type,value) => {
dispatch(ActionCreators.removeAttribute(type,value))
},
getAttributes: () => {
dispatch(ActionCreators.getAttributes())
}
}
}
DropDownList.propTypes = {
availableAttributes: PropTypes.object.isRequired,
activeFilters: PropTypes.object.isRequired,
}
export default connect(mapStatesToProps,mapDispatchToProps)(DropDownList)
```
My solution for case one for the second error was to just place the ajax function call into in object that contains the "type" property. Like such
return (
{
type: "Something",
$.ajax('/getFilteredAttributes/', {
type: 'GET',
dataType: 'json'
}).done(response => {
dispatch(addAttribute("make", 32));
}).fail((xhr, status, error) => {
console.log("failed");
})
})
The ajax call is made, but dispatch still isn't available, I am lost and looking for the best solution? Maybe I am overthinking something or missed a minor detail. I have tried other solutions but none has worked for me.
Please Help.
It's pretty difficult to know exactly what's going on, but I definitely wouldn't recommend ignoring / hacking round an error you're getting using a common feature of a popular library. It's important to get your implementation right for simplicity moving forward.
It looks to me like the way you are using thunk is a little odd.
The function that is returned by the action you dispatch has dispatch and getState as arguments:
In your case your thunk action might look like this
In your actions.js:
export function getAttributes2(){
return function(dispatch, getState){
// you could dispatch a fetching action here before requesting!
return $.ajax('/getFilteredAttributes/', {type: 'GET', dataType: 'json'})
.done(response => dispatch(saveTheResponseAction(response)))
.fail((xhr, status, error) => console.log("failed"))
}
Mapping that thunk function to your props:
import {getAttributes2} from '../../../redux/actions/actions';
const mapDispatchToProps = dispatch => {
return {
getAttributes2: () => dispatch(getAttributes2()),
}
}
This way you can dispatch an action from the .done part of your api call with the result in it, you can respond to errors, you can even dispatch an action before returning your api call to let redux know you've requested but not yet received the data, allowing you to do all kinds of loading states.
I hope this helps, let me know :)

Categories