I have a project in react and redux with immutablejs. It looks like this:
The Page looks like this:
function mapStateToProps(state) {
return {
content: state.content
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(thunks, dispatch)
};
}
class ConnectedConnect extends Component {
componentDidMount() {
const { actions } = this.props
actions.retrieveContent();
console.log(this.props.content)
}
render() {
return (
<div>
<Header heading="Demo Heading" colour="orange" disableAnimation={false} />
</div >
);
}
}
`const Content = connect(mapStateToProps, mapDispatchToProps)`(ConnectedConnect);
export default Content
The api:
export function viewContent() {
return fetch("http://localhost:8080/watchlist/content",
{
method: "GET",
}).then(function (response) {
if (!response.ok) {
throw Error(response.statusText);
}
// Read the response as json.)
return response.json();
})
.catch(function (error) {
console.log('Looks like there was a problem: \n', error);
})
}
The actions:
export function contentSuccess(retrieveContentWasSuccessful) {
return { type: types.RETRIEVE_CONTENT_SUCCESS, contentSuccess };
}
export function retrieveContent() {
// make async call to api, handle promise, dispatch action when promise is resolved
return function (dispatch) {
return viewContent().then(retrieveContentWasSuccessful => {
dispatch(contentSuccess(retrieveContentWasSuccessful));
}).catch(error => {
throw (error);
});
};
}
The reducer:
export function contentReducer(state = fromJS({}), action) {
switch (action.type) {
case types.RETRIEVE_CONTENT_SUCCESS:
return state.merge(action.content)
default:
return state;
}
};
The store itself is like this:
const history = createBrowserHistory()
const store = createStore(
connectRouter(history)(rootReducer),
applyMiddleware(
routerMiddleware(history),
thunkMiddleware
)
);
export { store, history };
The api is successfully called. However I can't seem to actually access the response in the store! Grateful for any help with this!
Thanks!
Think you want to pass retrieveContentWasSuccessful here instead of contentSuccess:
export function contentSuccess(retrieveContentWasSuccessful) {
return { type: types.RETRIEVE_CONTENT_SUCCESS, retrieveContentWasSuccessful };
}
Related
Hello I have the following error when trying to consume my api
TypeError: api.get is not a function
api.js
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:8000', });
export default api;
action fetch:
const api = require('../../services/api');
export function productsError(bool) {
return {
type: 'PRODUCTS_HAS_ERRORED',
hasErrored: bool
};
}
export function productsIsLoading(bool) {
return {
type: 'PRODUCTS_IS_LOADING',
isLoading: bool
};
}
export function productsFetchSuccess(products) {
return {
type: 'PRODUCTS_SUCCESS',
products
};
}
export function errorAfterFiveSeconds() {
// We return a function instead of an action object
return (dispatch) => {
setTimeout(() => {
// This function is able to dispatch other action creators
dispatch(productsError(true));
}, 5000);
};
}
export function ProductsFetchData() {
return (dispatch) => {
dispatch(productsIsLoading(true));
api.get('/products')
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(productsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((products) => dispatch(productsFetchSuccess(products)))
.catch(() => dispatch(productsError(true)));
};
}
reducer fetch
export function ProductsHasErrored(state = false, action) {
switch (action.type) {
case 'PRODUCTS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
export function ProductsIsLoading(state = false, action) {
switch (action.type) {
case 'PRODUCTS_IS_LOADING':
return action.isLoading;
default:
return state;
}
}
return action.products;
default:
return state;
}
} return action.products;
default:
return state;
}
}export function Products(state = [], action) {
return action.products;
default:
return state;
}
} return action.products;
default:
return state;
}
}
my store :
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
}
in my app:
import React, { Component } from 'react'
import {connect} from 'react-redux'
import { bindActionCreators } from 'redux';
import { ProductsFetchData } from '../../store/actions/productsFetch';
class index extends Component {
componentDidMount() {
this.props.fetchData('/products');
}
render() {
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
return <p>Loading…</p>;
}
return (
<div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
products: state.products,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: () => dispatch(ProductsFetchData())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(index);
basically I have error in this function:
export function ProductsFetchData() {
return (dispatch) => {
dispatch(productsIsLoading(true));
api.get('/products')
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(productsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((products) => dispatch(productsFetchSuccess(products)))
.catch(() => dispatch(productsError(true)));
};
}
I don't know why or where I went wrong to get this error
in action fetch, you should be change:
const api = require('../../services/api');
to:
const api = require('../../services/api').default;
or
import api from '../../services/api')
You should just export the baseURL as as const, then in your actions:
import axios from 'axios'
//othercode here
export function ProductsFetchData() {
return (dispatch) => {
dispatch(productsIsLoading(true));
api.get(`${}/products`)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(productsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((products) => dispatch(productsFetchSuccess(products)))
.catch(() => dispatch(productsError(true)));
};
}
When you use export default, This file will create an object with key is default and export them.
const a = 2;
export default a;
import with require:
const a = require(...)
console.log(a)
// a here will be an object
>> Object {default: 2}
So when you want to use require from export default, you have to access to .default: console.log(a.default).
Or you can use import in ES6 like this:
import a from '...';
// console.log(a)
>> 2
My problem is that mapStateToProps returns undefined. Maybe I have some problems with dispatching in the state or maybe app rendering before data comes from the server? I can't understand. So app works right without redux with just componentDidMount, but I have some problems with redux
So I have a top-level component App:
const App = () => {
return (
<Provider store={store}>
<Screen />
</Provider>
)
}
I have store with thunk meddleware:
const store = createStore(reducer, applyMiddleware(ReduxThunk));
Two types of action:
export const fetchData = (newPhotos) => async (dispatch) => {
function onSuccess(success) {
dispatch({
type: FETCH_DATA,
payload: success})
return success
}
function onError(error) {
dispatch({type: FETCH_FAILED, error})
}
try {
const URL = 'https://api.unsplash.com/photos/?client_id=cf49c08b444ff4cb9e4d126b7e9f7513ba1ee58de7906e4360afc1a33d1bf4c0';
const res = await fetch(URL);
const success = await res.json();
console.log(success);
return onSuccess(success);
} catch (error) {
return onError(error)
}
};
reducer:
const initialState = {
data: []
}
export default dataReducer = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case FETCH_DATA:
return {
data: action.payload
}
case FETCH_FAILED:
return {
state
}
default: return state;
}
}
combine reducers:
export default combineReducers({
fetchedData: dataReducer
});
and my rendering component:
class HomeScreen extends Component {
render() {
console.log(this.props)
const {navigation, data} = this.props;
return (
<ScrollView>
<Header />
<ImageList navigation={navigation} data={data}/>
</ScrollView>
)
}
}
const mapStateToProps = state => {
return {
data: state.fetchedData.data
}
}
export default connect(mapStateToProps, {fetchData})(HomeScreen);
fetchData action will not be called on its own. You need to call that explicitly(in componentDidMount and, probably, componentDidUpdate) like
class HomeScreen extends Component {
componentDidMount() {
this.props.fetchData(/* I don't know where you are going to take newPhotos argument */);
}
render() {
//...
}
}
I am in need of guidance with getting through this error. The code is supposed to get the results from WebAPI while going through actions and services. In the actions is a dispatch where the error is. On my actions page it should call the service for WebAPI and depend on the response dispatch to the reducers for actions. The code does not pass the first dispatch in the jobActions.getjobs()
The error received from this is:
Unhandled Rejection (TypeError): _actions_job_actions__WEBPACK_IMPORTED_MODULE_1__.jobActions.getJobs(...).then is not a function
Page Load
import React from 'react';
import { jobActions } from '../../actions/job.actions';
class LoadTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
this.props.getJobs()
.then((res) => {
this.setState({ data: res.response || [] })
});
}
render() {
return ();
}
const mapDispatchToProps => dispatch => ({ getJobs: () => dispatch(jobActions.getJobs()) });
export default connect(mapDispatchToProps)( LoadTable );
===============================================
Actions
import { jobConstants } from '../constants/job.constants';
import { jobService } from '../services/job.service';
export const jobActions = {
getJobs
};
let user = JSON.parse(localStorage.getItem('user'));
function getJobs() {
return dispatch => {
dispatch(request());
return jobService.getJobs()
.then(
results => {
dispatch(success(user));
return { results };
},
error => {
dispatch(failure(error));
}
);
};
function request() { return { type: jobConstants.JOB_REQUEST }; }
function success(user) { return { type: jobConstants.JOB_SUCCESS, user }; }
function failure(error) { return { type: jobConstants.JOB_FAILURE, error }; }
}
=======================================================
services
export const jobService = {
getJobs
};
const handleResponseToJson = res => res.json();
function getJobs() {
return fetch('http://localhost:53986/api/jobs/getoutput')
.then(handleResponseToJson)
.then(response => {
if (response) {
return { response };
}
}).catch(function (error) {
return Promise.reject(error);
});
}
The result should be table data from the services page, actions page dispatching depending on the stage.
I assume you are using some sort of a middleware, like redux-thunk? If not, then your action creator returns a function, which is not supported by pure redux
I guess you do, because the error says that the action creator returned undefined after it was called
function getJobs() {
console.log("test -1");
return dispatch => {
console.log("test-2");
dispatch(request());
jobService.getJobs() // <==== here comes the promise, that you don't return
// return jobService.getJobs() <== this is the solution
.then(
results => {
console.log("test -3");
dispatch(success(user));
return { results };
},
error => {
dispatch(failure(error));
}
);
};
Update: you also need to map your action in mapDispatchToProps
Page Load
import React from 'react';
import { jobActions } from '../../actions/job.actions';
class LoadTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
this.props.getJobs() // as the name of mapDispatchToProps says, you mapped your action dispatch
// to a getJobs prop, so now you just need call it
.then((res) => {
this.setState({
data: res.response || []
})
}));
}
render() {
return ();
}
const mapStateToProps = state => ({});
const mapDispatchToProps = dispatch => ({
// this function will dispatch your action, but it also mapps it to a new prop - getJobs
getJobs: () => dispatch(jobActions.getJobs())
});
export default connect(mapStateToProps, mapDispatchToProps)( LoadTable );
I have a question about passing value (item.id) from one component to another component's saga, where I could add additional field in POST body and make a request.
I have two components: 1st Form component, where is two input fields. 2st component is Item, which are GET'ed from API. So there is a itemId value, which I need to give when making POST request with form.
My soliution right now is to pass itemId to localstorage and then take it in saga, but it causes some bugs when user opens two browser windows. What would be better solution for this task?
My Item component:
export class FindClientItem extends React.PureComponent {
constructor() {
super();
this.state = {
modalIsOpen: false,
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
this.setState({ modalIsOpen: true });
}
closeModal() {
this.setState({ modalIsOpen: false });
localStorage.removeItem('itemId');
}
render() {
const { item } = this.props;
if(this.state.modalIsOpen){
localStorage.setItem('itemId',item.itemId);
}
// Put together the content of the repository
const content = (
<Wrapper>
<h3>{item.title}</h3>
Details: {item.description}...<button onClick={this.openModal}>
More
</button>
<Modal
isOpen={this.state.modalIsOpen}
onRequestClose={this.closeModal}
style={customStyles}
contentLabel="Modal"
>
<h3>{item.title}</h3>
Details: {item.description} <br />
<button onClick={this.openBidModal}>Submit</button>{' '}
</Modal>
</Wrapper>
);
// Render the content into a list item
return <ListItem key={`items-${item.itemId}`} item={content} />;
}
}
And then my other 1st Form component's saga:
export function* submitForm() {
try {
const formType = 'item';
const body = yield select(makeSelectModifiedData());
body.itemId = localStorage.getItem('itemId');
let requestURL;
switch (formType) {
case 'item':
requestURL = 'http://localhost:1234/item';
break;
default:
}
const response = yield call(request, requestURL, { method: 'POST', body });
} catch (error) {
Alert.error('Error message...', {
html: false,
});
}
}
Not sure if this is the "Best" way to do this, however, works well for me. Have you tried creating a shared js file (imported into both components) which GETS / SETS a variable? for example.
shared.js
let data = null;
setData(d){
data = d;
}
getData(){
return data;
}
addChangeListner(eventName, callback){
this.on(eventname, callback);
}
dispatcherCallback(action){
switch(action.actionType){
case 'SET_DATA':
this.getData();
}
}
Whenever you require your component to update, you can add an change listener to then return the new data once set so the components aren't out of sync. Just remember to remove the listener afterwords!
Component
componentDidMount(){
shared.addChangeListner('SET_DATA', this.onUpdate)
}
// use x to pass to your saga...
onUpdate(){
var x = shared.getData();
}
Hope this helps!
index.js
import {handleSave, loadData } from './action';
import Modal from './Modal',
export class GetFormData extends React.PureComponent {
componentDidMount() {
this.props.loadData();
}
saveData = (data) => {
this.props.handleSave(data)
}
render() {
return (
<div>
<Modal
isOpen={this.state.modalIsOpen}
onRequestClose={this.closeModal}
style={customStyles}
contentLabel="Modal"
data={this.props.getdata}
handlePost={this.saveData}
/>
</div>
)
}
}
const mapStateToProps = state => ({
getdata: state.formData,
});
const mapDispatchToProps = dispatch => ({
loadData: bindActionCreators(loadData, dispatch),
handleSave: bindActionCreators(handleSave, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(GetFormData);
actions.js
import {
LOAD_DATA,
LOAD_DATA_SUCCESS,
LOAD_DATA_FAILED
HANDLE_SAVE,
HANDLE_SAVE_SUCCESS,
HANDLE_SAVE_FAILED
} from './constants';
export function loadData() {
return {
type: LOAD_DATA,
};
}
export function loadDataSuccess(formData) {
return {
type: LOAD_DATA_SUCCESS,
formData
};
}
export function loadDataFailed(error) {
return {
type: LOAD_DATA_FAILED,
error
};
}
export function handleSave(data) {
return {
type: HANDLE_SAVE,
data
};
}
export function handleSaveSuccess() {
return {
type: HANDLE_SAVE_SUCCESS
};
}
export function handleSaveFailed(error) {
return {
type: HANDLE_SAVE_FAILED,
error
};
}
reducers.js
import { fromJS } from 'immutable';
import {
LOAD_DATA, LOAD_DATA_SUCCESS, LOAD_DATA_FAILED,
HANDLE_SAVE,
HANDLE_SAVE_SUCCESS,
HANDLE_SAVE_FAILED
} from './constants';
const initialState = fromJS({
formData: undefined,
});
function formDataReducer(state = initialState, action) {
switch (action.type) {
case LOAD_DATA:
return state;
case LOAD_DATA_SUCCESS:
return state.set('formData', action.formData);
case LOAD_DATA_FAILED:
return state.set('errormsg', fromJS(action.errormsg));
case HANDLE_SAVE:
return state.set('data', action.data);
case HANDLE_SAVE_SUCCESS:
return state.set('message', action.message);
case HANDLE_SAVE_FAILED:
return state.set('errormsg', fromJS(action.errormsg));
default:
return state;
}
}
saga.js
import { takeEvery, call, put } from 'redux-saga/effects';
import {
LOAD_DATA,
LOAD_DATA_SUCCESS,
LOAD_DATA_FAILED,
HANDLE_SAVE,
HANDLE_SAVE_SUCCESS,
HANDLE_SAVE_FAILED
} from './constants';
export function* getFormDataWorker() {
try {
const formData = yield call(api);
if (formData) {
yield put({ type: LOAD_DATA_SUCCESS, formData });
}
} catch (errormsg) {
yield put({ type: LOAD_DATA_FAILED, errormsg });
}
}
// watcher
export function* formDataWatcher() {
yield takeEvery(LOAD_DATA, getFormDataWorker);
}
export function* saveDataWorker(action) {
try {
const message = yield call(savedata, action.data);
if (message) {
yield put({ type: HANDLE_SAVE_SUCCESS, message });
}
} catch (errormsg) {
yield put({ type: HANDLE_SAVE_FAILED, errormsg });
}
}
// watcher
export function* saveDataWatcher() {
yield takeEvery(HANDLE_SAVE, saveDataWorker);
}
// All sagas to be loaded
export default [
saveDataWatcher,
formDataWatcher,
];
Modal.js
const Modal = ({data, handlePost}) => (
{ data ? data.map(item => (
<input type="text" value={item.id} />
)
}
<Button type="submit" onClick={handlePost}/ >
)
Hope this helps!
I would suggest the following:
Remove the usage of localstorage
On componentDidUpdate dispatch an action that sets the itemId in the Redux store.
componentDidUpdate() {
this.props.setItemId({itemId: this.props.item.itemId})
}
On form submit, dispatch the same action as you are currently using to trigger the saga.
Change your makeSelectModifiedData selector to return the itemId you are storing in Redux now.
My goal is to basically do a basic GET request in react-redux. I know how to do it with POST but not with GET because there is no event that is triggering the action.
Heres' the code for action
export function getCourses() {
return (dispatch) => {
return fetch('/courses', {
method: 'get',
headers: { 'Content-Type': 'application/json' },
}).then((response) => {
if (response.ok) {
return response.json().then((json) => {
dispatch({
type: 'GET_COURSES',
courses: json.courses
});
})
}
});
}
}
Where do i trigger this to get the data? in component?
import React from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { getCourses } from '../actions/course';
class Course extends React.Component {
componentDidMount() {
this.props.onGetCourses();
}
allCourses() {
console.log(this.props.onGetCourses());
return this.props.courses.map((course) => {
return(
<li>{ course.name }</li>
);
});
return this.props
}
render() {
return (
<div>
<ul>
{ this.allCourses() }
</ul>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
courses: state.course.courses
}
}
const mapDispatchToProps = (dispatch) => {
return {
onGetCourses: () => dispatch(getCourses)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Course);
I tried this but it doesn't work.
Course Reducer
const initialState = {
courses: []
};
export default function course(state= initialState, action) {
switch (action.type) {
case 'GET_COURSES':
return Object.assign({}, state, {
courses: action.courses
})
default:
return state;
}
}
First, onGetCourses: () => dispatch(getCourses) should be changed to onGetCourses: () => dispatch(getCourses()) (you need to actually invoke the action creator).
When it comes to where you should call the action, it is absolutely fine to do it in componentDidMount, as you have done.
In case you did not notice, you have two return's in your allCourses().
I have similar code in my codebase, but I don't use return in front of fetch and response.json() because the function should return action object.