I have functions.js file and it export one function that I want to use in many files.
functions.js
import { API_URL } from "./index";
export const getData = (skip = 0, params = "") => {
this.setState({
loading: true
});
fetch(`${API_URL}items?limit=5&skip=${skip}${params}`, {
method: "GET",
credentials: "include"
})
.then(res => res.json())
.then(res => {
if (res.result.length > 0) {
let array = [];
res.result.map(item => {
let obj = item.data;
obj = Object.assign({ id: item._id }, obj);
array.push(obj);
});
this.setState({
records: array,
loading: false
});
} else {
this.setState({
next: true,
loading: false,
records: []
});
}
})
.catch(err => {
this.setState({
loading: false
});
});
};
hear this is function.js file that gets data from API and set in the state,
now, I want to use this function in items.js
items.js
import { getData } from "./../../config/functions";
import React from "react";
class Customers extends React.Component {
constructor(props) {
super(props);
this.getData = getData.bind(this);
}
componentDidMount() {
this.getData();
}
...
}
Error
TypeError: Cannot read property 'setState' of undefined
I fount this answer How to use state of one component in another file in reactjs? but it did not work for me so help me to change app.js file state from my functions.js file.
You're trying to re-bind this on an arrow function, which you cannot do. Check out this other SO question/answer for more details, but that's your problem. I'm going to edit this post with a suggestion of a more idiomatic way to write this in React.
Edit: OK I wanted to get you an answer quickly so you could unblock yourself and learn a bit more about arrow functions and this binding.
But more than just fixing this, you could improve this code significantly if you separate your api requests from your component. Right now you're mixing them up by trying to set state in your function that fetches data.
import { API_URL } from "./index";
export const getData = (skip = 0, params = "") => {
this.setState({
loading: true
});
fetch(`${API_URL}items?limit=5&skip=${skip}${params}`, {
method: "GET",
credentials: "include"
})
.then(res => res.json())
.then(res => {
// no need to declare an array and then push to it,
// that's what map is for. It will return a new array.
return res.result.map(item => {
// can also be written as return { ...item, id: item._id }
return Object.assign({ id: item._id }, obj)
});
});
// no need to catch here, you can do error handling in your component
};
import { getData } from "./../../config/functions";
import React from "react";
class Customers extends React.Component {
constructor(props) {
super(props);
this.fetchData = this.fetchData.bind(this);
}
componentDidMount() {
this.fetchData();
}
fetchData() {
getData()
.then((results) => {
this.setState({
next: results.length === 0,
records: results,
loading: false
});
})
.catch((err) => {
this.setState({ loading: false })
});
}
...
}
Related
So, I'm trying to make this ApiCalls class, which was working if I just plugged in a url into the fetch statement, but I'm trying to make it so that I can change the url depending on which button I press on the site. I want to call ApiCalls in the SearchButtons.js class where in each click function I'll specify the url I want to use. It's not working and it's definitely something to do with the props, idk how else to pass in the a
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [],
//url: {props.url}////////////////////////////// this doesnt work.
//if i dont use brackets it compiles, but I get this error
//Error: Unexpected token < in JSON at position 0
};
}
componentDidMount() {
fetch(this.url)//////////////////////////////////////////////
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.articles
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
console.log(items);
return (
<ul>
{items.map(item => (
<ArticleCard key={item.title} title={item.title} desc={item.description} imgsrc={item.urlToImage} url={item.url}/>
))}
</ul>
);
}
}
}
export default ApiCalls;
import React, { Component } from 'react';
import ApiCalls from "./ApiCalls";
class SearchButton extends React.Component {
handleClick = () => {
console.log('this is:', this);
return (<ApiCalls url="myUrlHasMyAPIKeySoThisIsAPlaceHolder/>); ////////////////////////
///this is where I want to specify the url
}
You can achieve this by simply doing this.
import React, { Component } from 'react';
import Child from "./ApiCalls";
class SearchButton extends React.Component {
handleClick = () => {
console.log('this is:', this);
return (<ApiCalls url="myUrl"/>);
}
....
And In Child Component.
import React, { Component } from 'react';
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [],
};
}
componentDidMount() {
fetch(this.props.url){/* Access url from props */}
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.articles
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
....
Hope, this will help you.
I'm trying to access the first object from data[]. Then, grab the keys using Object.keys() but it gives me this error:
"TypeError: Cannot convert undefined or null to object".
I need the output to be an array of the keys.
import React, { Component } from 'react';
class CodecChart extends Component {
constructor(props) {
super(props);
this.state = {
post: [],
isLoaded: false,
}
}
componentDidMount() {
const url = 'https://jsonplaceholder.typicode.com/users';
fetch(url)
.then(result => result.json())
.then(post => {this.setState({ post: post })
})
}
render() {
const data = this.state.post;
// cannot reach the first object of data[]
var keys = Object.keys(data[0]);
return (
<div>
//output should be an array of the keys
<h5>{keys}</h5>
</div>
)
}
}
export default CodecChart;
The first time you try to access data[0], it's still empty:
this.state = {
post: [],
isLoaded: false,
}
and const data = this.state.post; means that data[0] is undefined.
it's only after the component is mounted, and the state is set correctly that data[0] is defined (or not, depending on what the API returns).
I found a way for it to work by adding another "then" so it can set the "keys" state right after the "posts" state was set. But I wonder if there is another way to make it more elegant. Thank you for trying to help.
constructor(props) {
super(props);
this.state = {
posts: [],
isLoaded: false,
keys: []
}
}
componentDidMount() {
const url = 'https://jsonplaceholder.typicode.com/users';
fetch(url)
.then(result => result.json())
.then(posts => {
this.setState({ posts: posts })
})
.then(_ => { this.setState({ keys: Object.keys(this.state.posts[0]) }) })
}
render() {
const keys = this.state.keys;
return (
<div>
<h5>{keys}</h5>
</div>
)
}
In pure react, I have written a function that I call in componentDidMount ():
getTasks = (userId, query, statusTask, pageNumber) => {
let check = {};
axios({
url: `/api/v1/beta/${userId}`,
method: 'GET'
})
.then(res => {
check = res.data;
if (res.data) {
this.setState({
checkRunning: res.data,
checkRunningId: res.data.id
});
this.utilizeTimes(res.data.task_id);
}
})
.catch(error => {
console.log(error);
})
.then(() => {
const params = {
sort: 'name'
};
if (query) {
params['filter[qwp]'] = query;
if (this.state.tasks[0]) {
this.setState({
selectedId: this.state.tasks[0].id,
selectedTabId: this.state.tasks[0].id
});
}
}
axios({
url: '/api/v1//tasks',
method: 'GET',
params
})
.then(res => {
if (res.status === 200 && res.data) {
this.setState({
tasks: res.data,
lengthArrayTasks: parseInt(res.headers['x-pagination-total-count'])
});
if (!check && res.data && res.data[0]) {
this.setState({
selectedTabId: res.data[0].id,
});
this.load(res.data[0].id);
}
let myArrayTasks = [];
myArrayTasks = res.data;
let findObject = myArrayTasks.find(task => task.id === this.state.runningTimerTask.id);
if (
!findObject &&
this.state.runningTimerTask &&
this.state.runningTimerTask.id &&
this.state.query === ''
) {
this.setState({
tasks: [this.state.runningTimerTask, ...myArrayTasks]
});
}
}
})
.catch(error => {
console.log(error);
});
});
};
I am trying to rewrite it to redux, but with poor results. First it makes one request / api / v1 / beta / $ {userId}, writes the answer in the variable check. check passes to the nextthen. In the next then carries out the request '/ api / v1 // tasks' Can somebody help me? I am asking for some tips. Is this somehow complicated?
So far, I've managed to create something like this:
store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
actions
export const RUNNING_TIMER = 'RUNNING_TIMER';
export const GET_TASKS = 'GET_TASKS';
export const FETCH_FAILURE = 'FETCH_FAILURE';
export const runningTimer = (userId, query, statusTask, pageNumber) => dispatch => {
console.log(userId);
axios({
url: `/api/v1/beta/${userId}`,
method: 'GET'
})
.then(({ data }) => {
dispatch({
type: RUNNING_TIMER,
payload: data
});
})
.catch(error => {
console.log(error);
dispatch({ type: FETCH_FAILURE });
})
.then(() => {
const params = {
sort: 'name'
};
axios({
url: '/api/v1//tasks',
method: 'GET',
params
})
.then(({ data }) => {
dispatch({
type: GET_TASKS,
payload: data
});
})
.catch(error => {
console.log(error);
});
});
};
reducer
import { RUNNING_TIMER, GET_TASKS } from '../actions';
const isRunningTimer = (state = {}, action) => {
const { type, payload } = action;
switch (type) {
case RUNNING_TIMER:
return {
checkRunningTimer: payload,
checkRunningTimerId: payload && payload.id ? payload.id : null
};
break;
case GET_TASKS:
return {
tasks: payload,
lengthArrayTasks: parseInt(action.headers['x-pagination-total-count'])
};
default:
return state;
}
};
const rootReducer = combineReducers({ isRunningTimer });
export default rootReducer;
App
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
componentDidMount() {
this.props.runningTimer();
}
render() {
return (
<div>
</div>
);
}
}
const mapStateToProps = state => {
const { isRunningTimer } = state;
return {
isRunningTimer
};
};
const mapDispatchToProps = dispatch => ({
runningTimer: (userId, query, statusTask, pageNumber) => dispatch(runningTimer()),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Number 1 Consider your state design.
I find it useful to consider what the state object would look like at a given point in time.
Here is an example of initialState used in an application of mine.
const initialState = {
grocers: null,
coords: {
latitude: 37.785,
longitude: -122.406
}
};
This is injected at the createStore.
Breaking down your application state object/properties, should assist you in making your actions simpler as well.
Number 2
Consider breaking down your actions.
My thoughts, decouple the action code, at the .then at the second .then .(Consider saving the results somewhere in a user: object)
.then(response => {
const data = response.data.user;
setUsers(data);})
.catch(error => {
console.log('There has been a problem with your fetch operation: ' + error.message);
})
function setUsers(data){
dispatch({
type: FETCH_USERS,
payload: data
});
}
This refers to the S in SOLID design principles. Single Responsibility Principle.
https://devopedia.org/solid-design-principles
Number 3
Consider this, if the 'getUser' info fetch fails.
Having the process/response separated will allow the application to be debugged more cleanly. In example, the user api failed or the getTask api failed, etc.
More resources on redux.
https://redux.js.org/introduction/learning-resources#thinking-in-redux
Extending previous answer from #Cullen, this is what I did:
Since you already have a action to GET_TODOS, just make the action creator for runningTimer to do one and only one thing - make API call to /api/v1/beta/<userId> and dispatch respective actions.
export const runningTimer = (
userId,
query,
statusTask,
pageNumber
) => dispatch => {
return axios({
url: `/api/v1/beta/${userId}`,
method: "GET"
})
.then(({ data }) => {
dispatch({
type: RUNNING_TIMER,
payload: data
});
})
.catch(error => {
console.log(error);
dispatch({ type: FETCH_FAILURE });
});
};
Update props of your app component to read store data.
...
const mapStateToProps = state => {
const { isRunningTimer, todos, todo } = state;
return {
todos,
todo,
isRunningTimer,
};
};
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos()),
getTodo: id => dispatch(getTodo(id)),
runningTimer: (userId, query, statusTask, pageNumber) => dispatch(runningTimer(userId)),
});
...
Update the implementation of componentDidMount to dispatch isRunningTimer -
componentDidMount() {
...
// call with userId 1
this.props.runningTimer(1).then(() => {
console.log(this.props);
// additional params for getTasks
const params = {
sort: 'name'
};
// another call for getTodos with names sorted
this.props.getTodos(params);
});
...
Note: You need to update your getTodos action to take in an optional params arguments (which is initialized to empty object if not passed).
Hope this helps you.
Live sandbox for this is present here - https://stackblitz.com/edit/react-redux-more-actions
Check out React-boilerplate. Great boilerplate for react and redux. They use redux-saga and redux-hooks as well.
I am trying to create a React component called 'Proposals' that will render a tabular list of information received from the Django backend.
I am using the Reactable-Search component to form the table, but I've kept getting an error when I try to map the this.props.proposals values such as id, and proj_name to the table cells - Uncaught TypeError: Cannot read property 'cells' of undefined
Really not sure why because when I map this.props.proposals directly in the render return of a typical html table tags it is working i.e. rendering the backend data ok. and I've also used the Reactable-Search component with the mapping in other cases and it's worked fine.
Log output of this.props.proposals shows the correct array of objects also...:
Really appreciate if someone can nudge me in the right direction, thanks!
The Proposals component:
import React, { Component } from "react";
import { connect } from "react-redux";
import SearchTable from "reactable-search";
import { proposals } from "../actions";
class Proposals extends Component {
componentDidMount() {
this.props.fetchProposals();
}
constructor(props) {
super(props);
this.state = {};
}
render() {
var rows = this.props.proposals.map(p => ({
selected: this.state.selectedRow === p.id,
onClick: () => {
this.setState({ selectedRow: p.id });
},
cells: {
"#": p.id,
"Project Name": p.proj_name
}
}));
return (
<SearchTable
showExportCSVBtn={true}
searchPrompt="Type to search"
rows={rows}
/>
);
}
}
const mapStateToProps = state => {
return {
proposals: state.proposals
};
};
const mapDispatchToProps = dispatch => {
return {
fetchProposals: () => {
dispatch(proposals.fetchProposals());
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Proposals);
The proposals reducer:
const initialState = [];
export default function proposals(state = initialState, action) {
switch (action.type) {
case "FETCH_PROPOSALS":
return [...action.proposals];
default:
return state;
}
}
The proposals action
export const fetchProposals = () => {
return dispatch => {
let headers = { "Content-Type": "application/json" };
return fetch("/api/proposals/", { headers })
.then(res => res.json())
.then(proposals => {
return dispatch({
type: "FETCH_PROPOSALS",
proposals
});
});
};
};
The problem is that you are requesting the proposals asynchronously but the SearchTable component doesn't seem to work with empty initial proposals object. Try passing in an empty array as its rows prop and you'll get the exact same error message about undefined object.
To fix this you need to show a loading indicator instead of the SearchTable while the proposals are being fetched. Your reducer should look something like this, except you should also handle failure case:
const initialState = { isLoading: false, error: null, proposals: [] };
export default function proposals(state = initialState, action) {
switch (action.type) {
case "FETCH_PROPOSALS":
return {
...state,
isLoading: true
};
case "FETCH_PROPOSALS_SUCCESS":
return {
...state,
isLoading: false,
proposals: action.proposals
};
case "FETCH_PROPOSALS_FAILURE":
return {
...state,
isLoading: false,
error: action.error,
};
default:
return state;
}
}
The component should then render an activity indicator or loading status or anything other than SearchTable when isLoading is active:
import React, { Component } from "react";
import { connect } from "react-redux";
import SearchTable from "reactable-search";
import { proposals } from "../actions";
class Proposals extends Component {
componentDidMount() {
this.props.fetchProposals();
}
constructor(props) {
super(props);
this.state = {};
}
render() {
const { proposals, error, isLoading } = this.props;
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>{error.message}</div>;
}
if (proposals.length === 0) {
return <div>No proposals</div>;
}
var rows = proposals.map(p => ({
selected: this.state.selectedRow === p.id,
onClick: () => {
this.setState({ selectedRow: p.id });
},
cells: {
"#": p.id,
"Project Name": p.proj_name
}
}));
return (
<SearchTable
showExportCSVBtn={true}
searchPrompt="Type to search"
rows={rows}
/>
);
}
}
const mapStateToProps = state => {
return {
proposals: state.proposals.proposals,
isLoading: state.proposals.isLoading,
error: state.proposals.error,
};
};
const mapDispatchToProps = dispatch => {
return {
fetchProposals: () => {
dispatch(proposals.fetchProposals());
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Proposals);
And the thunk action:
export const fetchProposals = () => {
return dispatch => {
dispatch({type: "FETCH_PROPOSALS"});
let headers = { "Content-Type": "application/json" };
return fetch("/api/proposals/", { headers })
.then(res => res.json())
.then(proposals => {
dispatch({
type: "FETCH_PROPOSALS_SUCCESS",
proposals
});
})
.catch(error => {
dispatch({
type: "FETCH_PROPOSALS_FAILURE",
error,
});
});
};
};
I have 2 components:
Orders - fetch some data and display it.
ErrorHandler - In case some error happen on the server, a modal will show and display a message.
The ErrorHandler component is warping the order component
I'm using the axios package to load the data in the Orders component, and I use axios interceptors to setState about the error, and eject once the component unmounted.
When I navigate to the orders components back and forward i sometimes get an error in the console:
Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in Orders (at ErrorHandler.jsx:40)
in Auxiliary (at ErrorHandler.jsx:34)
in _class2 (created by Route)
I tried to solve it by my previous case React Warning: Can only update a mounted or mounting component but here I can't make an axios token by the inspectors. Has anyone solved this issue before?
Here are my components:
Orders:
import React, { Component } from 'react';
import api from '../../api/api';
import Order from '../../components/Order/Order/Order';
import ErrorHandler from '../../hoc/ErrorHandler/ErrorHandler';
class Orders extends Component {
state = {
orders: [],
loading: true
}
componentDidMount() {
api.get('/orders.json')
.then(response => {
const fetchedOrders = [];
if (response && response.data) {
for (let key in response.data) {
fetchedOrders.push({
id: key,
...response.data[key]
});
}
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch(error => {
this.setState({ loading: false });
});
}
render() {
return (
<div>
{this.state.orders.map(order => {
return (<Order
key={order.id}
ingrediencies={order.ingrediencies}
price={order.price} />);
})}
</div>
);
}
}
export default ErrorHandler(Orders, api);
ErrorHandler:
import React, { Component } from 'react';
import Auxiliary from '../Auxiliary/Auxiliary';
import Modal from '../../components/UI/Modal/Modal';
const ErrorHandler = (WrappedComponent, api) => {
return class extends Component {
requestInterceptors = null;
responseInterceptors = null;
state = {
error: null
};
componentWillMount() {
this.requestInterceptors = api.interceptors.request.use(request => {
this.setState({ error: null });
return request;
});
this.responseInterceptors = api.interceptors.response.use(response => response, error => {
this.setState({ error: error });
});
}
componentWillUnmount() {
api.interceptors.request.eject(this.requestInterceptors);
api.interceptors.response.eject(this.responseInterceptors);
}
errorConfirmedHandler = () => {
this.setState({ error: null });
}
render() {
return (
<Auxiliary>
<Modal
show={this.state.error}
modalClosed={this.errorConfirmedHandler}>
{this.state.error ? this.state.error.message : null}
</Modal>
<WrappedComponent {...this.props} />
</Auxiliary>
);
}
};
};
export default ErrorHandler;
I think that's due to asynchronous call which triggers the setState, it can happen even when the component isn't mounted. To prevent this from happening you can use some kind of flags :
state = {
isMounted: false
}
componentDidMount() {
this.setState({isMounted: true})
}
componentWillUnmount(){
this.state.isMounted = false
}
And later wrap your setState calls with if:
if (this.state.isMounted) {
this.setState({ loading: false, orders: fetchedOrders });
}
Edit - adding functional component example:
function Component() {
const [isMounted, setIsMounted] = React.useState(false);
useEffect(() => {
setIsMounted(true);
return () => {
setIsMounted(false);
}
}, []);
return <div></div>;
}
export default Component;
You can't set state in componentWillMount method. Try to reconsider your application logic and move it into another lifecycle method.
I think rootcause is the same as what I answered yesterday, you need to "cancel" the request on unmount, I do not see if you are doing it for the api.get() call in Orders component.
A note on the Error Handling, It looks overly complicated, I would definitely encourage looking at ErrorBoundaries provided by React. There is no need for you to have interceptors or a higher order component.
For ErrorBoundaries, React introduced a lifecycle method called: componentDidCatch.
You can use it to simplify your ErrorHandler code to:
class ErrorHandler extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true, errorMessage : error.message });
}
render() {
if (this.state.hasError) {
return <Modal
modalClosed={() => console.log('What do you want user to do? Retry or go back? Use appropriate method logic as per your need.')}>
{this.state.errorMessage ? this.state.errorMessage : null}
</Modal>
}
return this.props.children;
}
}
Then in your Orders Component:
class Orders extends Component {
let cancel;
state = {
orders: [],
loading: true
}
componentDidMount() {
this.asyncRequest = api.get('/orders.json', {
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
})
})
.then(response => {
const fetchedOrders = [];
if (response && response.data) {
for (let key in response.data) {
fetchedOrders.push({
id: key,
...response.data[key]
});
}
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch(error => {
this.setState({ loading: false });
// please check the syntax, I don't remember if it is throw or throw new
throw error;
});
}
componentWillUnmount() {
if (this.asyncRequest) {
cancel();
}
}
render() {
return (
<div>
{this.state.orders.map(order => {
return (<Order
key={order.id}
ingrediencies={order.ingrediencies}
price={order.price} />);
})}
</div>
);
}
}
And use it in your code as:
<ErrorHandler>
<Orders />
</ErrorHandler>