I'm currently working on a react-redux project with a rails API in the back end and am having some issues. So I currently have an update method in the back end for my edit functionality in the front end. For the most part, it seems to work fine, it updates the back end when edited but it won't render the updated object in the front end until manually refresh the page.
I'm thinking this has something to do with my async function but I can't figure it out.
Thank you!
Here are my actions:
return (dispatch) => {
fetch(`http://localhost:3000/haikus`)
.then(res => res.json())
.then(haikus => {
dispatch({type: "FETCH_HAIKUS", payload: haikus})
})
}
}
export function addHaiku(haiku){
return (dispatch) => {
const options = {
method: "POST",
headers: {
"Content-type": "application/json",
"accept": "application/json"
},
body: JSON.stringify({haiku})
}
fetch(`http://localhost:3000/haikus`, options)
.then(res => res.json())
.then(haiku => {
dispatch({type: "ADD_HAIKU", payload: haiku})
})
}
}
export function editHaiku(haiku){
// debugger
return (dispatch) => {
const options = {
method: "PATCH",
headers: {
"Content-type": "application/json",
"accept": "application/json"
},
body: JSON.stringify({haiku})
}
fetch(`http://localhost:3000/haikus/${haiku.id}`, options)
.then(res => res.json())
.then(haiku => {
dispatch({type: "EDIT_HAIKU", payload: haiku})
// dispatch({type: "EDIT_HAIKU", payload: haiku.data})
})
}
}
export function deleteHaiku(haikuId){
return (dispatch) => {
const options = {
method: "DELETE"
}
fetch(`http://localhost:3000/haikus/${haikuId}`, options)
.then(res => res.json())
.then(message => {
dispatch({type: "DELETE_LIST", payload: haikuId})
})
}
}```
Here is my reducer:
```export default function haikuReducer(state, action){
// debugger
switch(action.type){
case "FETCH_HAIKUS":
return {
haikus: action.payload
}
case "ADD_HAIKU":
return {
haikus: [...state.haikus, action.payload]
}
case "DELETE_HAIKU":
const newHaiku = state.haikus.filter(haiku => haiku.id !== action.payload)
return {
haikus: newHaiku
}
case "EDIT_HAIKU":
// debugger
// const editHaiku = state.haikus.map(haiku => haiku.id === action.payload.id ? action.payload : haiku)
const editHaiku = state.haikus.map(haiku => haiku.id === parseInt(action.payload.id) ? action.payload : haiku)
return {
haikus: editHaiku
}
default:
return state
}
}```
Here is my form component:
```import React, {Component} from 'react'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import { addHaiku } from './actions/haikuActions'
import { editHaiku } from './actions/haikuActions'
class HaikuForm extends Component {
// Normally do not set state directly to props
constructor(props){
super(props)
this.state = {
id: this.props.haiku ? this.props.haiku.id : "",
title: this.props.haiku ? this.props.haiku.title : "",
haiku: this.props.haiku ? this.props.haiku.haiku : "",
genre: this.props.haiku ? this.props.haiku.genre : ""
}
}
handleSubmit(event) {
event.preventDefault()
if(!this.props.haiku){
this.props.addHaiku(this.state)
} else {
this.props.editHaiku(this.state)
}
this.setState({ title: "", haiku: "", genre: "" ,id: "" })
this.props.history.push('/haikus')
}
handleChange(event){
this.setState({
[event.target.name]: event.target.value
})
}
redirectOrRenderForm = () => {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="text" onChange={(event) => this.handleChange(event)} value={this.state.title} name="title"/><br></br>
<input type="text" onChange={(event) => this.handleChange(event)} value={this.state.haiku} name="haiku"/><br></br>
<input type="text" onChange={(event) => this.handleChange(event)} value={this.state.genre} name="genre"/>
<input type="submit"/>
</form>
)
}
render(){
return (
<>
{this.redirectOrRenderForm()}
</>
)
}
}
export default withRouter(connect(null, { addHaiku, editHaiku })(HaikuForm))```
Related
Recently I've been working on filters in my service for booking hotel rooms in .NET + Vue3.
Backend method for filtering works fine, but I don't have clue how to force component to update its content using fetched data.
Im reciving data in format like this:
enter image description here
Here are my script and component files:
Filters component:
<template>
<div class="container">
<div class="d-flex align-items-center">
<label for="first_day" class="p-2">First day: </label>
<input type="date" name="first_day" v-model="filtersOptions.FirstDay" />
<label for="last_day" class="p-2">Last day: </label>
<input type="date" name="last_day" v-model="filtersOptions.LastDay"/>
<button type="submit" class="m-2 p-2" v-on:click="fetchFilteredRooms()">Search</button>
</div>
</div>
</template>
<script lang="ts">
import { useFilters } from '#/composables/useFilters';
export default {
setup(props: any, context: any) {
const { filtersOptions, fetchFilteredRooms } = useFilters();
return {
filtersOptions,
fetchFilteredRooms,
}
}
}
</script>
Filters script:
import { ref } from 'vue';
import Consts from "#/consts";
import { useRooms } from './useRooms';
class FiltersOptions {
FirstDay: any;
LastDay: any;
};
const { Rooms } = useRooms();
export const useFilters = () => {
const filtersOptions = ref<any>(new FiltersOptions());
async function fetchFilteredRooms() {
const filterRoomsAPI = Consts.API.concat(`rooms/search`)
const headers = {
'Content-type': 'application/json; charset=UTF-8',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Origin': `${filterRoomsAPI}`
}
fetch(filterRoomsAPI, {
method: 'POST',
mode: 'cors',
credentials: 'same-origin',
body: JSON.stringify(filtersOptions._value),
headers
})
.then(response => response.json())
.then((data) => (Rooms.value = data))
.catch(error => console.error(error));
}
return {
Rooms,
filtersOptions,
fetchFilteredRooms,
}
}
Rooms component:
import { ref } from 'vue';
import Consts from "#/consts";
import { useRooms } from './useRooms';
class FiltersOptions {
FirstDay: any;
LastDay: any;
};
const { Rooms } = useRooms();
export const useFilters = () => {
const filtersOptions = ref<any>(new FiltersOptions());
async function fetchFilteredRooms() {
const filterRoomsAPI = Consts.API.concat(`rooms/search`)
const headers = {
'Content-type': 'application/json; charset=UTF-8',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Origin': `${filterRoomsAPI}`
}
fetch(filterRoomsAPI, {
method: 'POST',
mode: 'cors',
credentials: 'same-origin',
body: JSON.stringify(filtersOptions._value),
headers
})
.then(response => response.json())
.then((data) => (Rooms.value = data))
.catch(error => console.error(error));
}
return {
Rooms,
filtersOptions,
fetchFilteredRooms,
}
}
Rooms script:
import { ref } from 'vue';
import Consts from "#/consts"
const headers = {
'Content-type': 'application/json; charset=UTF-8',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Origin': `${Consts.RoomsAPI}`
}
export function useRooms() {
const Rooms = ref([]);
async function fetchRooms() {
fetch(Consts.RoomsAPI, { headers })
.then(response => response.json())
.then((data) => (Rooms.value = data))
.catch(error => console.log(error));
}
return {
Rooms,
fetchRooms,
};
}
Any idea how to deal with it?
I'm get the error in tittle when a action is dispatched to redux in a next application and i can't find the solution: the first action is correctly dispatched but others raises the error: TypeError: Cannot perform 'get' on a proxy that has been revoked, redux-toolkit and nextJS, you can see the project in the follow link : https://github.com/cpereiramt/BACKBONE-TEST
Below I share the mainly snippets of code and the configuration in general :
configuring store:
import {
configureStore,
EnhancedStore,
getDefaultMiddleware
} from "#reduxjs/toolkit"
import { MakeStore } from "next-redux-wrapper"
import { Env } from "../constants"
import { rootReducer, RootState } from "./reducers"
import { createWrapper } from 'next-redux-wrapper';
/**
* #see https://redux-toolkit.js.org/usage/usage-with-typescript#correct-typings-for-the-dispatch-type
*/
const middlewares = [...getDefaultMiddleware<RootState>()]
const store = configureStore({
reducer: rootReducer,
middleware: middlewares,
devTools: Env.NODE_ENV === "development",
})
const makeStore: MakeStore = (_?: RootState): EnhancedStore => store
export const wrapper = createWrapper(makeStore);
combineReducers
import { combineReducers } from "redux"
import { contactReducer } from "./contact"
/**
* Combine reducers
* #see https://redux-toolkit.js.org/usage/usage-with-typescript
*/
export const rootReducer = combineReducers({
contacts: contactReducer,
})
export type RootState = ReturnType<typeof rootReducer>
actions
import { createAsyncThunk } from "#reduxjs/toolkit";
import { Contact } from "../../model";
import { FeatureKey } from "../featureKey";
/**
* Fetch all contact action
*/
export const fetchAllContactsAction = createAsyncThunk(
`${FeatureKey.CONTACT}/fetchAll`,
async (arg: { offset: number; limit: number }) => {
const { offset, limit } = arg
const url = `/api/contact?offset=${offset}&limit=${limit}`
const result: Contact[] = await fetch(url, {
method: "get",
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Fetch contact action
*/
export const fetchContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/fetch`,
async (arg: { id: number }) => {
const url = `/api/contact/${arg}`
const result: Contact = await fetch(url, {
method: "get",
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Add contact action
*/
export const addContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/add`,
async (arg: { contact: Contact }) => {
const url = `/api/contact`
const result: Contact = await fetch(url, {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(arg),
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Edit contact action
*/
export const editContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/edit`,
(arg: { contact: Contact }) => {
const { contact } = arg
const url = `/api/contact/${arg.id}`
const result: Contact = fetch(url, {
method: "put",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(contact),
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Delete contact action
*/
export const deleteContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/delete`,
async (arg: { id: number }) => {
const url = `/api/contact/${arg}`
await fetch(url, {
method: "delete",
})
}
)
reducers
import { ActionReducerMapBuilder, createReducer } from "#reduxjs/toolkit"
import {
addContactAction,
deleteContactAction,
editContactAction,
fetchAllContactsAction,
fetchContactAction
} from "./action"
import { adapter, ContactState, initialState } from "./state"
/**
* CONTACT reducer
*/
export const contactReducer = createReducer(
initialState,
(builder: ActionReducerMapBuilder<ContactState>) =>
builder
.addCase(fetchAllContactsAction.pending, (state) => {
return { ...state, isFetching: true }
})
.addCase(fetchAllContactsAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.setAll({ ...state, isFetching: false }, contacts)
})
.addCase(fetchAllContactsAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(fetchContactAction.pending, (state, action) => {
const { id } = action.meta.arg
return { ...state, isFetching: true, selectedId: id }
})
.addCase(fetchContactAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.upsertOne({ ...state, isFetching: false }, contacts)
})
.addCase(fetchContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(addContactAction.pending, (state, action) => {
const { contact } = action.meta.arg
return { ...state, isFetching: true, selectedId: contact?.id }
})
.addCase(addContactAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.addOne({ ...state, isFetching: false }, contacts)
})
.addCase(addContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(editContactAction.pending, (state, action) => {
const { contact } = action.meta.arg
return { ...state, isFetching: true, selectedId: contact?.id }
})
.addCase(editContactAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.updateOne(
{ ...state, isFetching: false },
{
id: contacts.id,
changes: contacts,
}
)
})
.addCase(editContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(deleteContactAction.pending, (state, action) => {
const { id } = action.meta.arg
return { ...state, isFetching: true, selectedId: id }
})
.addCase(deleteContactAction.fulfilled, (state, action) => {
const { id } = action.meta.arg
return adapter.removeOne({ ...state, isFetching: false }, id)
})
.addCase(deleteContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
)
selectors
import { createSelector } from "#reduxjs/toolkit"
import { RootState } from "../reducers"
import { adapter, ContactState } from "./state"
const { selectAll, selectEntities } = adapter.getSelectors()
const featureStateSelector = (state: RootState) => state.contacts
const entitiesSelector = createSelector(featureStateSelector, selectEntities)
/**
* isFetching selector
*/
export const isFetchingSelector = createSelector(
featureStateSelector,
(state: ContactState) => state?.isFetching
)
/**
* selectedId selector
*/
export const selectedIdSelector = createSelector(
featureStateSelector,
(state: ContactState) => state?.selectedId
)
/**
* all contact selector
*/
export const allContactSelector = createSelector(featureStateSelector, selectAll)
/**
* contact selector
*/
export const contactSelector = createSelector(
entitiesSelector,
selectedIdSelector,
(entities, id) => (id ? entities[id] || null : null)
)
states
import { createEntityAdapter, EntityState } from "#reduxjs/toolkit"
import { Contact } from "../../model"
export interface ContactState extends EntityState<Contact> {
isFetching: boolean
selectedId: number | null
}
export const adapter = createEntityAdapter<Contact>({
selectId: (contacts: Contact) => contacts.id,
})
export const initialState: ContactState = adapter.getInitialState({
isFetching: false,
selectedId: null,
})
And the _app file
import CssBaseline from "#material-ui/core/CssBaseline";
import { ThemeProvider } from "#material-ui/styles";
import { NextPageContext } from 'next';
import App from "next/app";
import React from "react";
import { MuiTheme } from "../components/MuiTheme";
import { Store } from '../redux/store';
import { wrapper } from "../store/configureStore";
import "../styles/main.css";
interface AppContext extends NextPageContext {
store: Store;
}
class MyApp extends App<AppContext> {
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector("#jss-server-side")
jssStyles?.parentNode?.removeChild(jssStyles)
}
render() {
const { Component, ...props } = this.props;
return (
<ThemeProvider theme={MuiTheme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...props} />
</ThemeProvider>
)
}
}
export default wrapper.withRedux(MyApp);
And in index file the action fetchAllContacts() work without problems.
import React, { useEffect } from "react";
import ContactTable from "../components/Table/";
import { useContact } from "../hooks";
import { Contact } from '../model/Contact';
import appStyles from "./indexStyles";
type Props = {}
function Index(props: Props) {
const { fetchAllContacts } = useContact();
const [contacts, setContacts] = React.useState<Contact[]>([])
useEffect(() => {
const results = fetchAllContacts();
results.then(data => console.log(data));
results.then(data => setContacts(data.contacts));
}, [])
const classes = appStyles(props)
return (
<div className={classes.indexBackground}>
<div className={classes.indexTabletDiv}>
<ContactTable contacts={contacts} />
</div>
</div>
);
}
export default Index
But when I try to use the action addContact() in another component the error is raised
page add
import { Button, createStyles, InputLabel, makeStyles, TextField, Theme } from "#material-ui/core";
import router from "next/router";
import React from "react";
import { useContact } from "../../hooks";
const useStyles = makeStyles((_: Theme) =>
createStyles({
root: {},
})
)
type Props = { }
const AddContact = (props: Props) => {
const { addContact } = useContact();
const newContact = {
id:'455666gghghttttytyty',
firstName: 'clayton',
lastName: 'pereira',
email: 'cpereiramt#gmail.com',
phone: '5565992188269',
}
const handleCreateContact = () => {
addContact(newContact);
}
const { } = props
return (
<div style={{margin: '10px', display: 'flex', justifyContent: 'space-between', wrap: 'wrap', flexDirection:'column'}}>
<>
<InputLabel>Name </InputLabel><TextField />
<InputLabel>Last Name </InputLabel><TextField />
<InputLabel>Email </InputLabel><TextField />
<div>
<Button variant="outlined" color="primary" onClick={() => handleCreateContact(newContact)} >
Create Contact</Button>
<Button variant="outlined" color="primary" onClick={() => router.push('/')} >
Back</Button>
</div>
</>
</div>
)
}
export default AddContact;
I think I see the issue.
First, the actual error message is Immer telling you that something is trying to modify the Proxy-wrapped state in a reducer, but long after the reducer has actually finished running. Normally that's impossible, because reducers are synchronous. So, there has to be some kind of async behavior going on.
The case reducers themselves seem basically okay, and mostly look like this:
.addCase(fetchAllContactsAction.pending, (state) => {
return { ...state, isFetching: true }
})
I'll point out that Immer lets you write state.isFetching = true instead, so you don't have to do object spreads :) But this code should run fine, and it's synchronous. So, what's the problem?
You didn't actually describe which actions are causing errors, so I'm having to guess. But, I think it's in one of the async thunks, and specifically, here:
export const editContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/edit`,
(arg: { contact: Contact }) => {
const { contact } = arg
const url = `/api/contact/${arg.id}`
// PROBLEM 1
const result: Contact = fetch(url, {
method: "put",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(contact),
}).then((response: Response) => response.json())
// PROBLEM 2
return { contacts: result }
}
Notice the line const result: Contact = fetch(). This doesn't have any await in there So, this is going to end up returning a Promise and saving it as result, and then that Promise ends up being returned as the contacts field.
I think that the object with the promise is being put into Redux state, wrapped by an Immer proxy, and then modified sometime later, and that's what's causing the error. But I'm not 100% sure because I don't know which actions are actually involved.
Why is my tmp object getting undefined values?
The fetch is getting the object right, maybe <FormStructure {...this.state.system} /> is the wrong syntax to give the Element the object?
export default class System extends React.Component<any, State> {
constructor(props: any) {
super(props);
this.state = {
system: new SystemClass(),
}
this.GetData();
}
public render() {
return (
<div>
<FormStructure {...this.state.system} />
</div>
);
}
private GetData = (): void => {
fetch('api/Admin/Get', {
headers: { 'Content-Type': 'application/json' },
method: 'GET'
})
.then(response => response.json())
.then(data => this.setState({ system: Helper.ConvertData(data, SystemClass) as SystemClass }))
.then(() => console.log(this.state.system))
};
}
const FormStructure = (system: SystemClass) => {
const onFill = () => {
var tmp = { name: system.name, street: system.street };
console.log(tmp);
console.log(system);
}
}
I have the following controller action
[HttpPost]
[Route("api/Tenant/SetTenantActive")]
public async Task<IHttpActionResult> SetTenantActive(string tenantid)
{
var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();
var allTenants = await tenantStore.Query().Where(x => x.TenantDomainUrl != null).ToListAsync();
foreach(Tenant ten in allTenants)
{
ten.Active = false;
await tenantStore.UpdateAsync(ten);
}
var tenant = await tenantStore.Query().FirstOrDefaultAsync(x => x.Id == tenantid);
if (tenant == null)
{
return NotFound();
}
tenant.Active = true;
var result = await tenantStore.UpdateAsync(tenant);
return Ok(result);
}
And my react code:
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
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.ClientId,
ClientId: row.ClientId,
ClientSecret: row.ClientSecret,
Id: row.Id,
SiteCollectionTestUrl: row.SiteCollectionTestUrl,
TenantDomainUrl: row.TenantDomainUrl
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
const columns = [
{
title: 'Client Id',
dataIndex: 'ClientId',
key: 'ClientId'
},
{
title: 'Site Collection TestUrl',
dataIndex: 'SiteCollectionTestUrl',
key: 'SiteCollectionTestUrl',
},
{
title: 'Tenant DomainUrl',
dataIndex: 'TenantDomainUrl',
key: 'TenantDomainUrl',
}
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
if(selectedRows[0].key != undefined){
console.log(selectedRows[0].key);
const options = {
method: 'post',
body: {tenantid:selectedRows[0].key},
};
adalApiFetch(fetch, "/Tenant/SetTenantActive", options)
.then(response =>{
if(response.status === 200){
Notification(
'success',
'Tenant created',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Tenant not created',
error
);
console.error(error);
});
}
},
getCheckboxProps: record => ({
type: Radio
}),
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListTenants;
focus only on the onchange event,
And the screenshot:
And it looks like the request gets to the webapi (I attached the debugger)
Update:
Basically If I dont put FromBody I need to send the parameter via querystring.
However if I put from Body and I send the parameter in the body, its received null on the webapi
Add [FromBody] before your input parameter in your action method like this:
public async Task<IHttpActionResult> SetTenantActive([FromBody] string tenantid)
Then, convert your selected row key into string
const options = {
method: 'post',
body: { tenantid : selectedRows[0].key.toString() }
};
I'm trying to pass some value from a component to a action creators which is doing a get request with axios. I'm trying to follow this pattern from Dan Abramov :
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return (dispatch, getState) => {
const {items} = getState().otherReducer;
dispatch(anotherAction(items));
}
}
However I can't make it work. I think I have trouble on two level : my component and my action creator. Would be great to have some helps.
my component :
const timeR = ({
selectedTimeRange,
timeRange = [],
onTimeChange }) => {
return (
<div>
<div>
Filters:
<div>
Year:
<select
defaultValue={selectedTimeRange}
onChange={onTimeChange}>
<option value="all" >All</option>
{timeRange.map((y, i) =>
<option key={i} value={y}>{y}</option>
)}
</select>
</div>
</div>
</div>
);
}
function mapStateToProps(state) {
var range = ['30daysAgo', '15daysAgo', '7daysAgo'];
return {
selectedTimeRange: state.timeReducer.timerange[0],
timeRange: range
};
};
const mapDispachToProps = (dispatch) => {
return {
onTimeChange: (e) => {dispatch (onSetTimeRange(e.target.value));},
};
};
const TimeRange = connect(mapStateToProps, mapDispachToProps)(timeR);
export default TimeRange;
This component give me a dropdown menu. When selecting a timerange, for example '30daysAgo', it update my redux store state so I can access the value from my reducer.
Here is the action associated to my dropdown menu :
export function onSetTimeRange(timerange) {
return {
type: 'SET_TIME_RANGE',
timerange
}
}
and here is the action dealing with axios.get :
export const fetchgadata = () => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null
});
var VIEW_ID = "ga:80820965";
return axios.get("http://localhost:3000/gadata", {
params: {
id: VIEW_ID
}
}).then(response => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching: false,
data: response.data.rows.map( ([x, y]) => ({ x, y }) )
});
})
.catch(err => {
dispatch({
type: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
};
My question :
How do I bring these two actions together. At the end I would like to be able, when doing onChange on my drop-down menu, to call a action with the value selected from my menu as a param for my axios.get request.
I feel like I need to nest two actions creators. I've tried this but doesn't work ("fetchgadata" is read-only error in my terminal)
export const SET_TIME_RANGE = 'SET_TIME_RANGE';
export function onSetTimeRange() {
return (dispatch, getState) => {
const {VIEW_ID} = getState().timerange;
dispatch(fetchgadata = (VIEW_ID) => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null,
id:VIEW_ID,
});
});
return axios.get("http://localhost:3000/gadata", {
params: {
id: VIEW_ID
}
}).then(response => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching: false,
data: response.data.rows.map( ([x, y]) => ({ x, y }) )
});
})
.catch(err => {
dispatch({
ype: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
}
}
Edit:
reducers for API call :
const initialState = {data:null,isFetching: false,error:null};
export const gaData = (state = initialState, action)=>{
switch (action.type) {
case 'FETCH_DATA_REQUEST':
case 'FETCH_DATA_FAILURE':
return { ...state, isFetching: action.isFetching, error: action.error };
case 'FETCH_DATA_SUCCESS':
return Object.assign({}, state, {data: action.data, isFetching: action.isFetching,
error: null });
default:return state;
}
};
reducers for Drop-down :
const items = [{timerange: '30daysAgo'},{timerange: '15daysAgo'},{timerange: '7daysAgo'}]
const timeReducer = (state = {
timerange: items
}, action) => {
switch (action.type) {
case 'SET_TIME_RANGE':
console.log(state,action);
return {
...state,
timerange: action.timerange,
};
default:
return state;
}
}
I see a little typo in the catch of your axios.get request, it reads ype: FETCH_DATA_FAILURE. Otherwise, can you add in your reducer for me, I don't see it up there? If I understand correctly, you want one action to update two different pieces of state, in which case you would simply dispatch an action and add it to both reducers. Really it's best to just demonstrate:
//axios call
axios.get("some route", { some params } )
.then(response => {
dispatch({
type: UPDATE_TWO_THINGS,
payload: some_value
})
}) .... catch, etc
//reducer 1
import { UPDATE_TWO_THINGS } from 'types';
const INITIAL_STATE = { userInfo: '' };
export default function (state = INITIAL_STATE, action) {
switch(action.type) {
case UPDATE_TWO_THINGS:
return {...state, userInfo: payload };
}
return state;
}
//reducer 2
import { UPDATE_TWO_THINGS } from 'types';
const INITIAL_STATE = { businessInfo: '' };
export default function (state = INITIAL_STATE, action) {
switch(action.type) {
case UPDATE_TWO_THINGS:
return {...state, businessInfo: payload };
}
return state;
}
Hopefully this helps, but let me know if not, I'll do my best to get this working with you! Thanks for asking!