How to pass variable when dispatching fetch function in react component? - javascript

I have react-redux app which fetching data from my node server, each time i need to fetch something i have to create same action where i change value in fetch, here is questions: How i can pass a variable in order to avoid duplicate code in such situation?
export function fetchProducts() {
return dispatch => {
dispatch(fetchProductsBegin());
return fetch("/api/followers")
.then(handleErrors)
.then(res => res.json().then(console.log(res)))
.then(json => {
dispatch(fetchProductsSuccess(json));
return json;
})
.catch(error => dispatch(fetchProductsError(error)));
};
}
Then i call fetchProduct:
class ProductList extends React.Component {
componentDidMount() {
this.props.dispatch(fetchProducts());
}
I want to have a result that where i call fetchProducts and put a variable, then each time using same action.

You can use following:
// in component:
this.props.dispatch(fetchProducts(id));
// in function
export function fetchProducts(id) {
Moreover, you can use
class ProductList extends React.Component {
componentDidMount() {
this.props. fetch(id);
}
const mapDispatchToProps = (dispatch) => ({
fetch: (id) => dispatch(fetchProducts(id))
});
export default connect(null, mapDispatchToProps)(ProductList)

About your code struct, you should use Axios library to call API, use
promise for wait API call instead of dispatch fetchProductsBegin.
in this case, you can rewrite the code as below:
export function fetchProducts(id) {
return dispatch => {
dispatch(fetchProductsBegin(id));
return fetch(`/api/followers/${id}`)
//...
}
call function
componentDidMount() {
this.props.dispatch(fetchProducts(id));
}

Related

Fetch in React making call continuosly

I am fetching the data from json file but the issue is that when i checked network tab. It's being call continuosly what can i do to resolve the problem.
: Network Tab Image
class Banner extends Component {
constructor(props) {
super(props);
this.state = {
buttonLink:"",
bannerImg:""
};
}
render() {
const a = fetch("./logo.json").then(response => response.json())
.then((temp1)=> {this.setState({buttonLink:temp1.buttonLink,bannerImg:temp1.bannerImg});
});
return(....
<img src={this.state.bannerImg}></img>
....
)
in class component you can use some lifecycle methods like componentDidMount
so put your fetch method inside of a componentDidMount not in render method because whenever you call setState() method, it will call render method again,and inside of your render method you call api and setState again, that makes an inifinite loop, do something like this:
componentDidMount(){
const a = fetch("./logo.json").then(response => response.json())
.then((temp1)=> {this.setState({buttonLink:temp1.buttonLink,bannerImg:temp1.bannerImg});
}
You can't call fetch inside render function.
You can use functional component to easily fetch the data.
import React, { useEffect, useState } from 'react';
const Banner = () => {
const [image, setImage] = useState({ buttonLink: '', bannerImg: '' });
useEffect(() => {
fetch('./logo.json')
.then((response) => response.json())
.then((temp1) => {
setImage({ buttonLink: temp1.buttonLink, bannerImg: temp1.bannerImg });
});
}, []);
return <img src={image.bannerImg}></img>;
};
export default Banner;

Pass jest.fn() function to mapDispatchToProps in enzyme shallow render

Having very simple component:
import PropTypes from 'prop-types'
import React from 'react'
import { connect } from 'react-redux'
class MyComponent extends React.Component {
componentWillMount() {
if (this.props.shouldDoSth) {
this.props.doSth()
}
}
render () {
return null
}
}
MyComponent.propTypes = {
doSth: PropTypes.func.isRequired,
shouldDoSth: PropTypes.bool.isRequired
}
const mapStateToProps = (state) => {
return {
shouldDoSth: state.shouldDoSth,
}
}
const mapDispatchToProps = (dispatch) => ({
doSth: () => console.log('you should not see me')
})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
I want to test if doSth is called when shouldDoSth is equal true.
I've written a test:
describe('call doSth when shouldDoSth', () => {
it('calls doSth', () => {
const doSthMock = jest.fn()
const store = mockStore({shouldDoSth: true})
shallow(<MyComponent doSth={doSthMock}/>, { context: { store } }).dive()
expect(doSthMock).toHaveBeenCalled()
})
})
but it seems that although I pass doSth as props it gets overridden by mapDispatchToProps as console.log('im not a mock') is executed.
How to properly pass/override/assign doSth function to make component use mock instead of function from mapDispatchToProps. Or maybe I'm doing something which should not be allowed at all and there is 'proper' way of testing my case. Shall I just mock dispatch instead and check if it is called with proper arguments?
I think one thing you need to figure out is whether you want doSth to be a prop, or a redux action connected in mapDispatchToProps.
If it's a prop, then you would connect it to redux in a parent (container). Remove it from this component's mapDispatchToProps. This would make the component more testable.
If you want it to be a redux action connected in this component, then it would make sense to move the action out of this component, somewhere like actions.js, import it in this component, and then mock it in the test jest.mock('actions.js', () => ({doSth: jest.mock()}))
Export the unconnected component and use it in the test and you will be able to override the mapDispatchToProps action.
export class MyComponent extends React.Component {
componentWillMount() {
if (this.props.shouldDoSth) {
this.props.doSth()
}
}
render () {
return null
}
}
MyComponent.propTypes = {
doSth: PropTypes.func.isRequired,
shouldDoSth: PropTypes.bool.isRequired
}
const mapStateToProps = (state) => {
return {
shouldDoSth: state.shouldDoSth,
}
}
const mapDispatchToProps = (dispatch) => ({
doSth: () => console.log('you should not see me')
})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
import {MyComponent} from '../MyComponent'
describe('call doSth when shouldDoSth', () => {
it('calls doSth', () => {
const doSthMock = jest.fn()
const store = mockStore({shouldDoSth: true})
shallow(<MyComponent doSth={doSthMock}/>, { context: { store } }).dive()
expect(doSthMock).toHaveBeenCalled()
})
})
I think that you should ask yourself if you want to test the unconnected MyComponent or the connected one.
Here you have two discussions that may help you: Am I testing connected components correclty? and Can't reference containers wrapped in a Provider or by connect with Enzyme
If you are not testing the action nor state properly said, you might forget about mapStateToProps and mapDispatchToProps (those processes are already tested by redux) and pass values through props.
Check the following example:
describe('MyComponent', () => {
let wrapper;
const doSthMock = jest.fn();
beforeEach(() => {
const componentProps = {
doSth: true,
};
wrapper = mount(
<MyComponent
{... componentProps}
doSth={doSthMock}
/>
);
});
it('+++ render the component', () => {
expect(wrapper.length).toEqual(1);
});
it('+++ call doSth when shouldDoSth', () => {
expect(doSthMock).toHaveBeenCalled();
});
})

How to send asynchronous state fetch to component?

I am currently working on a little app for fun. I ran into an issue with using axios and returning the response to my App component as updated state.
I then try to allow another component to use that piece of state, but I am not able to actually access the data. I can console.log(props) from within the List component, but I am not sure how to output the actual data as I am only able to output the promise results. I want to be able to output props.currentUser and have it be the googleId (using google Oauth2.0)..I am sure the solution is simple but, here is my code:
App.js ->
import React from 'react';
import helpers from '../helpers';
import List from './List';
class App extends React.Component{
state = {
currentUser: null
}
componentDidMount() {
this.setState(prevState => ({
currentUser: helpers.fetchUser()
}));
}
render() {
return (
<div>
<List currentUser={this.state.currentUser}/>
</div>
);
}
};
export default App;
helpers.js ->
import axios from 'axios';
var helpers = {
fetchUser: async () => {
const user = await axios.get('/api/current_user');
return user.data;
}
};
export default helpers;
List Component ->
import React from 'react';
const List = (props) => {
const renderContent = () => {
console.log(props);
return <li>{props.currentUser}</li>
}
renderContent();
return (
<div>
<h1>Grocery List</h1>
<ul>
</ul>
</div>
);
}
export default List;
Output ->
{currentUser: null}
{currentUser: Promise}
currentUser: Promise__proto__: Promise[[PromiseStatus]]: "resolved"
Because fetchUser is an async function, it returns a promise. Thus, in the App component, you have to call setState inside the .then of that promise, like so:
componentDidMount() {
helpers.fetchUser()
.then(data => {
this.setState(prevState => ({
currentUser: data
}));
});
}
Okay all you need to change is :
componentDidMount() {
this.setState(prevState => ({
currentUser: helpers.fetchUser()
}));
}
to
componentDidMount() {
helpers.fetchUser().then(data => {
this.setState(prevState => ({
currentUser: data
}));
})
}
WORKING DEMO (checkout the console)
NOTE : async await always returns the promise it just make
synchronousonus behaviour inside the async function but end ot the
function it will always returns the promise.

Redux action not present

I have set up correctly according to doc redux boilerplate.
I have a folder of various action files which contains actions.
In this folder I have created an index.js where I am exporting all these files. I import it as named export.
When I console log the function itself it is there, but when I console log out this.props its not present.
I have tried to console log out the function outside the class its undefined.
If I import the action directly from the file it is defined in it works.
actions/formActions.js
export const formInput = (key, val) => (
{
type: FORM_INPUT,
payload: { key, val }
}
);
actions/index.js
export * from './formActions';
FormComp.js
import { formInput } from './actions'; // <-- this.props.formInput = undefined
or
import { formInput } from './actions/formActions'; // <-- this.props.formInput = func
connect:
class InputField extends Component { ... }
const FormComp = connect(({ forms }) => ({ forms }), { formInput })(InputField);
export { FormComp };
edit1: If I inside componentWillMount() console.log(formInput) //without this.props its there.
edit2 (solution?):
I was able to map actions to props with bindActionCreators. How ever I don't understand why I need to use bindActionCreators and why I can't just export connect as it is with actions as second param.
const FormComp = connect(
({ forms }) => ({ forms }),
dispatch => bindActionCreators({ formInput }, dispatch)
)(InputField);
The reason this.props.formInput is not defined but formInput is defined is because the redux connect is not properly setting the function formInput against the variable(property) formInput within the component props.
formInput is the function you are importing,
this.props.formInput is the property that should "point" to the function you import.
Try (I have separated out for clarity)
function mapStateToProps(state) {
return {
forms: state.forms
}
}
function mapDispatchToProps(dispatch) {
return {
formInput: (key, val) => dispatch(formInput(key val))
};
}
export connect(
mapStateToProps,
mapDispatchToProps
)(InputField);
Then anywhere in your code you call
this.props.formInput(key, val);
This will call the action via props, and therefore will dispatch. properly
You need to return a function which has dispatch and getState as parameter.
export const formInput = (key, val) => (dispatch, getState) => {
// maybe additional stuff
dispatch({
type: FORM_INPUT,
payload: { key, val }
});
};
See title Action Creators from the documents: https://redux.js.org/docs/basics/Actions.html

React Redux - Actions must be plain objects. Use custom middleware for async actions

I try to deal with ajax data using axom in my learning react,redux project and I have no idea how to dispatch an action and set the state inside a component
In component will mount
componentWillMount(){
this.props.actions.addPerson();
}
Store
import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers";
import thunk from "redux-thunk";
export default function configureStore() {
return createStore(rootReducer, applyMiddleware(thunk));
}
In Action :
import * as types from "./action-types";
import axios from "axios";
export const addPerson = person => {
var response = [];
axios
.get(`&&&&&&&&&&&`)
.then(res => {
response = res.data;
return {
type: types.ADD_PERSON,
response
};
});
};
In reducer
import * as types from "../actions/action-types";
export default (state = [], action) => {
console.log("action======>", action);
switch (action.type) {
case types.ADD_PERSON:
console.log("here in action", action);
return [...state, action.person];
default:
return state;
}
};
I am getting Actions must be plain objects. Use custom middleware for async actions.
You should use dispatch for async function. Take a look of the redux-thunk's documentation: https://github.com/gaearon/redux-thunk
In Action:
import * as types from "./action-types";
import axios from "axios";
export const startAddPerson = person => {
return (dispatch) => {
return axios
.get(`https://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => {
dispatch(addPersons(res.data));
});
}
};
export const addPersons = personList => {
return {
type: types.ADD_PERSON,
personList
};
}
In PersonComponent:
class Person extends Component {
constructor(props){
super(props);
}
componentWillMount() {
this.props.dispatch(startAddPerson())
}
render() {
return (
<div>
<h1>Person List</h1>
</div>
);
}
}
export default Redux.connect()(Person);
You need two actions here: postPerson and addPerson.
postPerson will perform the API request and addPerson will update the store:
const addPerson = person => {
return {
type: types.ADD_PERSON,
person,
}
}
const postPerson = () => {
return (dispatch, getState) => {
return axios.get(`http://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => dispatch(addPerson(res.data)))
}
}
in your component, call postPerson()
You use the redux-thunk library which gives you access to the "getState" and "dispatch" methods. I see that that has been added by Chenxi to your question. Run your async operation first within your action and then call "dispatch" with your simple action action creator, which will return the simple object that redux is looking for.
Here is what your async action creator and your simple action creator(broken out into two action creators) will look like:
export const addPersonAsync = (person) => {
return (dispatch) => {
var response = [];
axios
.get(`http://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => {
response = res.data;
dispatch(addPerson(response));
});
};
};
export const addPerson = (response) => ({
type: types.ADD_PERSON,
response
});
From your component, you'll now call the "addPersonAsync" action creator.

Categories

Resources