React fetch-api and react table - reactjs

I am trying to fetch data and render it on to a react table. I have no idea why it's not working. Could someone help me? I get the data in to the Network response, but I don't know why it's not rendered to the website. Here is my code:
import React, { useState, useEffect } from "react";
import 'react-table/react-table.css';
import ReactTable from 'react-table';
export default function Traininglist() {
const [trainings, setTrainings] = useState([]);
useEffect(() => fetchData(), []);
const fetchData = () => {
fetch("https://customerrest.herokuapp.com/api/trainings")
.then(response => response.json())
.then(data => setTrainings(data.content))
.catch(err => console.error(err))
};
const columns = [
{
title: 'Date',
field: 'date',
},
{
title: 'Duration (min)',
field: 'duration'
},
{
title: 'Activity',
field: 'activity'
},
];
return (
<div>
<h1>Trainings</h1>
<ReactTable filterable={true} data={trainings} columns={columns} />
</div>
);
and here you can see what the data looks like:

You may want to read through the Docs. Your columns should at the very least have a key called accessor that targets a key of your data set from which it will render a value. Looks like you have nested data here, so you may need to use a Cell field to render what you need.

Related

Maintaining multiple user inputs to antd Select in a single useState variable

I want to store the information of multiple inputs entered into antd Select components in a single state variable but am having trouble getting the below to work.
This example is solved here for a form but the same solution doesn't seem to work for antd Select component. There are two inputs: a first name and a last name that I want to remember. The below code doesn't work because e doesn't have an attribute called name is what the console tells me. I also tried e.target.name and e.target.value but I get an error that e doesn't have an attribute called a target either. What is the right way to do this?
import React, { useState } from 'react';
import { Select } from 'antd';
const App = () =>{
const [varState, setVarState] = useState({firstName:'Jack', lastName:'Smith'});
const firstNameOptions = [ {label:'Jack', value:'Jack'}, {label:'Jill',value:'Jill'}, {label:'Bill',value:'Bill'} ];
const lastNameOptions = [ {label:'Smith', value:'Smith'}, {label:'Potter',value:'Potter'}, {label:'Bach',value:'Bach'} ];
const changeState = (e) => {
setVarState( prevState => ({ ...prevState, [e.name]: e.value}));
console.log(varState)
};
return ( <>
<div>
<Select name={'firstName'} defaultValue={'Pick One'} options={firstNameOptions} onChange={changeState} />
<Select name={'lastName'} defaultValue={'Pick One'} options={lastNameOptions} onChange={changeState} />
</div>
</>
);
}
export default App;
At the heart of it, it seems that I don't know how to name the Select components in such a way that their names can be passed on to the onChange handler.
More generally, given a component like antd Select, how can I figure out what the right "name field" is for this component so that it's value can be passed on to an onChange handler? For instance, what in the documentation for select gives this information?
The Select component seems to be sending the value instead of the events object. So, You can just make a closure and pass the name of the select. Also, for consoling you can make use of a useEffect which only consoles when the state has been updated. Otherwise, you could see previous state as state updates are asynchronous. Below is a working solution.
import React, { useEffect, useState } from "react";
import { Select } from "antd";
const App = () => {
const [varState, setVarState] = useState({
firstName: "Jack",
lastName: "Smith"
});
const firstNameOptions = [
{ label: "Jack", value: "Jack" },
{ label: "Jill", value: "Jill" },
{ label: "Bill", value: "Bill" }
];
const lastNameOptions = [
{ label: "Smith", value: "Smith" },
{ label: "Potter", value: "Potter" },
{ label: "Bach", value: "Bach" }
];
// for consoling when the state updates
useEffect(() => {
console.log(varState);
}, [varState.firstName, varState.lastName]);
const changeState = (value, identifier) => {
// console.log(value, identifier);
setVarState((prevState) => ({ ...prevState, [identifier]: value }));
};
return (
<>
<div>
<Select
name={"firstName"}
defaultValue={"Pick One"}
options={firstNameOptions}
onChange={(val) => changeState(val, "firstName")}
/>
<Select
name={"lastName"}
defaultValue={"Pick One"}
options={lastNameOptions}
onChange={(val) => changeState(val, "lastName")}
/>
</div>
</>
);
};
export default App;
yes, Actually antd doesn't have attribute name for input fields. antdesign directly gives the selected value, we need to do some tweeks to achieve this.
Here is the solution:
import React, { useState } from 'react';
import { Select } from 'antd';
const firstNameOptions = [ {label:'Jack', value:'Jack'}, {label:'Jill',value:'Jill'}, {label:'Bill',value:'Bill'} ];
const lastNameOptions = [ {label:'Smith', value:'Smith'}, {label:'Potter',value:'Potter'}, {label:'Bach',value:'Bach'} ];
const App = () =>{
const [varState, setVarState] = useState(null);
const changeState = (fieldName) => (value) => {
setVarState( prevState => ({ ...prevState, [fieldName]: value}));
console.log(varState)
};
return ( <>
<div>
<Select defaultValue={'Pick One'} options={firstNameOptions} onChange={changeState('firstName')} />
<Select defaultValue={'Pick One'} options={lastNameOptions} onChange={changeState('lastName')} />
</div>
</>
);
}
export default App;
I hope this helps 😊

trigger Axios data fetch on button click in another component

I have component structure like this
src
--App
--DataTableComponent
--ButtonComponnet
axios
--useAxios
On app load I am calling Axios library in DataTableComponent (through custom hook useAxios) to fetch some data, then later when user clicks a button in ButtonComponent I would like Axios to load data again in DataTableComponent, but I am not sure how as the components are siblings.
In your situation what you'll want to do is lift state up. Here a link to the official react documentation
In addition, what I've done is created a code sandbox sample for your particular situation that demonstrates how you could do some thing similar in your app. Here's the link to that code sandbox https://codesandbox.io/s/romantic-brook-sfvpd?file=/src/App.tsx
The principle behind lifting state up is that if you have 2 or more components that must share information then lift that information one level up to their parent. So, what I do, as you see in the code sandbox is that the app component now gets the information and pushes it down to your data table component.
So, you'll want your useAxios hook to live in your parent app component. The button components job is to simply trigger the fetch.
Once the fetch is triggered, new data is returned by the useAxios hook.
The fetch also causes the App's useEffect hook to run and this updates the state and pushes the data to the data table component.
So, in your case you'll probably want to wrap your useAxios hook in your own custom hook so you can pass parameters which in turn your useAxios hook can use to fetch data from your API.
Continue to click on the fetch data button and each time I return a random number of items so you'll see your data components data getting updated. Remember to open the console to see the useAxios hook getting called followed by the data table contents being updated.
I have used a similar approach in some of my production apps and in those apps I've created similar custom wrapper around useSWR hooks
Using redux or some thing similar is a good idea if you have data that must be shared across the application i.e. global data but data that is specific to a few components doesn't need that approach and one should then go with the "lift state up" way of doing things.
For completeness the code is also given below.
import { useEffect, useState } from "react";
import "./styles.css";
// useAxios hook
const useAxios = (_: boolean) => {
console.log("useAxios");
// mock data
const arr = [
{
name: "Bianca Paul",
phone: "1-453-676-9140",
email: "mollis.integer#google.ca",
address: "221-3571 Nam Street",
id: 8
},
{
name: "Hadley Gordon",
phone: "1-235-486-3229",
email: "adipiscing.mauris#icloud.com",
address: "3255 Nec, Road",
id: 3
},
{
name: "Irma Bryan",
phone: "1-818-417-5465",
email: "ornare.in#icloud.net",
address: "136-222 Facilisis Rd.",
id: 2
},
{
name: "Simon Nash",
phone: "1-872-216-6482",
email: "enim.nec#aol.couk",
address: "Ap #873-5860 Erat St.",
id: 101
},
{
name: "Ursula Fleming",
phone: "(998) 407-7291",
email: "semper.erat#protonmail.com",
address: "110-1550 Phasellus Ave",
id: 43
}
];
// Randomize the data
function getRandomItem() {
// get random index value
let randomIndex = Math.floor(Math.random() * arr.length);
if (randomIndex === 0) randomIndex = 1;
// get random items
const item = arr.slice(0, randomIndex);
return item;
}
// return a promise
const data = new Promise<any>((resolve, reject) => {
setTimeout(() => {
return resolve(getRandomItem());
}, 1000);
});
return { data };
};
// Button component
const ButtonComponent = (props: { clickCallback: () => void }) => {
return (
<>
<button onClick={props.clickCallback}>fetch data</button>
</>
);
};
// DataComponent
const DataTableComponent = ({
data
}: {
data:
| [
{
name: string;
phone: string;
email: string;
address: string;
id: string;
}
]
| null;
}) => {
return (
<>
{data ? (
data.map((v) => (
<div key={v.id}>
{v.name},{v.address}, {v.phone}
</div>
))
) : (
<span>loading</span>
)}
</>
);
};
// App Parent component
export default function App(): JSX.Element {
const [fetch, setFetch] = useState(true);
const [returnedData, setReturnedData] = useState<
| [
{
name: string;
phone: string;
email: string;
address: string;
id: string;
}
]
| null
>(null);
const { data } = useAxios(fetch);
const buttonClicked = () => {
setFetch(true);
};
useEffect(() => {
let isMounted = true;
if (fetch) {
(async () => {
if (isMounted) setReturnedData(await data);
if (isMounted) setFetch(false);
console.log(await data);
})();
}
return function () {
isMounted = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fetch]);
return (
<div className="App">
<h1>Lift state up</h1>
<div>
<DataTableComponent data={returnedData} />
</div>
<div>
<ButtonComponent clickCallback={buttonClicked} />
</div>
</div>
);
}
You can just send some prop var or function that will return true or something when button is clicked. Do you call ButtonComponnent in DataTableComponent or somewhere else ?
What you're trying to do is get two sibling components to communicate and call functions from each other, if I'm understanding correctly. If they're siblings, I'd recommend using a third-party library like Redux or EventHub
If you have Component A with the function "updateData", then you can call it in Component B using Redux by setting up a reducer called "updateData", and having DataTableComponent subscribe to the "data"
Redux file:
import { createStore, combineReducers } from "redux";
const reducer = (state = {}, action) => {
... {axios fetch here}
};
const store = createStore(reducer);
export default store;
DataTableComponent.jsx:
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import axios from "axios";
import { updateData } from "../redux/actions";
const DataTableComponent = () => {
const data = useSelector(state => state.data);
const dispatch = useDispatch();
const fetchData = () => dispatch(updateData());
return (
<div>
<button onClick={fetchData}>Fetch Data</button>
<table>{data.map((item, index) => <tr key={index}><td>{item.id}</td><td>{item.name}</td></tr>)}</table>
</div>
);
};
ButtonComponent.jsx:
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import axios from "axios";
import { updateData } from "../redux/actions";
const ButtonComponent = () => {
const data = useSelector(state => state.data);
const dispatch = useDispatch();
const fetchData = () => dispatch(updateData());
return (
<div>
<button onClick={fetchData}>Fetch Data</button>
<table>{data.map((item, index) => <tr key={index}><td>{item.id}</td><td>{item.name}</td></tr>)}</table>
</div>
);
};

How to Jest/Unit Test a Component Reliant on TableRefs

I have a table which given the downloadRequested it will use the tableRef provided and return the sortedData within the table. When trying to add unit tests to make sure that the <CSVLink> is rendered on downloadRequested = true I keep hitting the below error:
TypeError: Cannot read property 'getResolvedState' of null
Am I supposed to mock the ref? or do I need to somehow provide it? How do I get around this issue?
Code:
import React, { useRef } from "react";
import { CSVLink } from "react-csv";
function MyTable(props) {
const tableRef = useRef(null);
const getColumns = () => {
return [{ Header: "Name", accessor: "name" }, { Header: "Id", accessor: "id" }];
};
const getCsvData = () => {
const keys = ["name", "id"];
return tableRef.current.getResolvedState().sortedData.map(row => getCsvDataFromTable(keys, row));
};
return (
<>
{props.downloadRequested && (
<CSVLink data={getCsvData()} target="_blank" filename={`myTable.csv`} data-testid="csvLink">
<div
data-testid="csvLinkDiv"
ref={e => {
if (e) {
e.click();
}
}}
/>
</CSVLink>
)}
<Table columns={getColumns(props)} filterable forwardedRef={tableRef} {...props} />
</>
);
}
export default MyTable;
Test Suite:
import React from "react";
import { render } from "#testing-library/react";
import MyTable from "./MyTable";
describe("MyTable", () => {
const sampleData = [{ id: "123", name: "John Doe" }, { id: "456", name: "Doe John" }];
it("Should render MyTable with CSV Link correctly", () => {
const { queryByTestId } = render(<MyTable data={sampleData} downloadRequested={true} />);
expect(queryByTestId("csvLink")).toBeTruthy();
});
});
Well, I figured this out. My problem stems from the fact that the CSV-Link needs to have data at run time, and the way this is set up it doesnt get that till later. Anyways, what was happening is that getCsvData() was being called before the table was initialized, hence the ref was always null. One work around I used was to render it with downloadRequested = false first, then rerender it (now a table exists) with the value true.

Fetch firestore document from document id in array field and display in React

I have 2 collections on firestore, boxes contains a field shoesthat is an array of reference id to the shoes collections:
My Boxes component is fetching all boxes and displaying their number. I also want to display properties of the shoes in it, like a photo. So I go about like that:
Boxes.jsx
// DEPENDENCIES
import React, { useEffect, useContext } from 'react';
// COMPONENTS
import BoxCard from '../Components/BoxCard';
// CONTEXT
import ShoesContext from '../Contexts/ShoesContext';
// HELPERS
import db from '../config/firebase';
let initialBoxes = [];
const Boxes = () => {
const { boxes, setBoxes } = useContext(ShoesContext);
useEffect(() => {
initialBoxes = [];
db.collection('boxes')
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
initialBoxes.push(doc);
});
setBoxes(initialBoxes);
});
}, []);
return (
<div>
<h3>You have {boxes.length} boxes:</h3>
{!boxes ? (
<div>Loading..</div>
) : (
boxes.map(box => {
return <BoxCard box={box} key={box.id} />;
})
)}
</div>
);
};
export default Boxes;
Boxes.jsx
import React from 'react';
import TestComponent from './TestComponent';
const BoxCard = ({ box }) => {
const theBox = box.data();
return (
<div>
<h5>
Box number {theBox.number} has {theBox.shoes.length} shoes:{' '}
</h5>
{theBox.shoes.map(shoe => {
return <TestComponent shoe={shoe} />;
})}
</div>
);
};
export default BoxCard;
and this is where it all breaks:
import React from 'react';
const TestComponent = ({ shoe }) => {
useEffect(() => {
let hell;
shoe.get().then(doc => {
hell = doc.data();
});
}, []);
return <div>{hell ? hell.season : 'loading...'}</div>;
};
export default TestComponent;
hell is undefined. I have not found a way to render the nested docs so I can use them in my TestComponent component. My extensive research online has not been succesful so far, hence my question today.
Thanks!
Update:
I seem to have found the answer, answer from Josh below put me on the right track. See below code for TestComponent.jsx:
import React, { useEffect, useState } from 'react';
// HELPERS
import db from '../config/firebase';
const TestComponent = ({ shoe }) => {
const [hell, setHell] = useState();
useEffect(() => {
db.collection('shoes')
.doc(shoe.id)
.get()
.then(doc => {
setHell(doc.data());
});
}, []);
return <div>{hell ? hell.season : 'loading...'}</div>;
};
export default TestComponent;
What is shoe in shoe.get()... in the TestComponent? Shouldn't it be db.doc(shoe).get()....

Redux Store and nested JSON from Axios API

I tried every possible variation of this code, but I don't really manage to get whatever the API fetched into my data store. I am absolutely stuck and would appreciate some help.
I think I just don't get the essential part of this construct and I would really like to understand how it works properly.
The data looks like this - it's basically a simple JSON (from a django restframework API) with some nested elements:
EDIT 2 (changed JSON to screenshot of axios API/ Redux action)
My Redux action - works perfectly fine. console.log pulls exactly the data from above (with correct inputs) :
// ./action/plan.js
import axios from 'axios';
export function fetchBudgets(){
return function(dispatch){
axios.get("/api/budgets/")
.then((response) => {
console.log(response)
dispatch({ type: "FETCH_BUDGETS", budgets: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_DATA_REJECTED", budgets: err})
})
}
}
So until now, everything seems fine. The problems starts with the reducer - as I am not sure how to model the reducer to use the nested data.
My reducer:
// ./reducer/plan.js
const initialState = {}
export default function budgets(state=initialState, action) {
switch (action.type) {
case 'FETCH_BUDGETS':
console.log(action)
return {
...state,
id: action.budgets.id,
value_jan: action.budgets.value_jan,
value_feb: action.budgets.value_feb,
value_mar: action.budgets.value_mar,
value_apr: action.budgets.value_apr,
value_may: action.budgets.value_may,
value_jun: action.budgets.value_jun,
value_jul: action.budgets.value_jul,
value_aug: action.budgets.value_aug,
value_sep: action.budgets.value_sep,
value_oct: action.budgets.value_oct,
value_nov: action.budgets.value_nov,
value_dec: action.budgets.value_dec,
p_version: action.budgets.p_version,
entry_time: action.budgets.entry_time,
campaign: {
...state.campaign, ...action.budgets.campaign
},
segment: {
...state.segment, ...action.budgets.segment
},
touch_point: {
...state.touch_point, ...action.budgets.touch_point
},
year: {
...state.year, ...action.budgets.year
},
user: {
...state.user, ...action.budgets.user
}
}
default:
return state
}
}
I already cannot display data in here - so this.props.fetchBudgets() doesn't seem to fetch any data.
My .jsx App
//./container/PlanContainer.jsx
import React, { Component } from 'react';
import {connect} from 'react-redux';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory from 'react-bootstrap-table2-editor';
import 'jquery';
import 'popper.js'
import 'bootstrap';
import 'underscore'
import _ from 'lodash'
import {plan} from "../actions";
const columns = [
{ dataField: 'id', text: 'ID', hidden: true},
{ dataField: 'year', text: 'Year', editable: false},
{ dataField: 'segment', text: 'Segment', editable: false},
{ dataField: 'campaign.name',text: 'Campaign', editable: false},
{ dataField: 'touch_point',text: 'Touchpoint', editable: false},
{ dataField: 'value_jan',text: 'Jan'},
{ dataField: 'value_feb',text: 'Feb'},
{ dataField: 'value_mar',text: 'Mar'},
{ dataField: 'value_apr',text: 'Apr'},
{ dataField: 'value_may',text: 'May'},
{ dataField: 'value_jun',text: 'Jun'},
{ dataField: 'value_jul',text: 'Jul'},
{ dataField: 'value_aug',text: 'Aug'},
{ dataField: 'value_sep',text: 'Sep'},
{ dataField: 'value_oct',text: 'Oct'},
{ dataField: 'value_nov',text: 'Nov'},
{ dataField: 'value_dec',text: 'Dec'},
{ dataField: 'user',text: 'User'},
];
const RemoteCellEdit = (props) => {
const { columns, data, keyField } = props
const cellEdit = {
mode: 'click',
errorMessage: props.errorMessage,
blurToSave: true
};
return (
<div>
<BootstrapTable
remote={ { cellEdit: true } }
keyField = { keyField }
data={ data }
columns={ columns }
/>
</div>
);
};
class PlanContainer extends React.Component {
componentDidMount() {
this.props.fetchBudgets();
console.log(this.props.fetchBudgets())
}
render() {
return (
<div>
<RemoteCellEdit
data={ this.props.budgets }
columns = { columns }
keyField = 'id'
/>
</div>
);
}
}
const mapStateToProps = state => {
return {
budgets: state.budgets,
}
}
const mapDispatchToProps = dispatch => {
return {
fetchBudgets: () => {
dispatch(plan.fetchBudgets());
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PlanContainer);
Finally, my store - according to the console.log nothing is beeing passed:
// .Planning.jsx
import React from "react"
import { hot } from 'react-hot-loader'
import { render } from "react-dom"
import {
createStore,
compose,
applyMiddleware,
combineReducers,
} from "redux"
import { Provider } from "react-redux"
import thunk from "redux-thunk"
import PlanContainer from "./containers/PlanContainer"
import reducerApp from "./reducers";
import Sidebar from "./components/Sidebar"
import axios from 'axios';
import axiosMiddleware from 'redux-axios-middleware';
let store = createStore(reducerApp, applyMiddleware(thunk, axiosMiddleware(axios)));
console.log(store)
class Planning extends React.Component {
render() {
return (
<Sidebar>
<Provider store={store}>
<PlanContainer />
</Provider>
</Sidebar>
)
}
}
render(<Planning />, document.getElementById('Planning'))
Again, I would appreciate as I've been stuck on this issue for quite some time and I really want to understand how to do this properly.
Edit:
Here's a screenshot of my browser: 1st element is the store, second in the .jsx app, 3rd of the action (that looks perfectly fine) and 4th of the action in the reducer.
PlanContainer is messed up. Here's how:
componentDidMount() {
this.budgets = this.props.fetchBudgets();
}
this.budgets is pointing to the value returned by this.props.fetchBudgets() which, in this case, is a Promise, and not the actual data.
state = {
data: this.budgets
};
state now holds the promise, not the data.
render() {
return (
<div>
<RemoteCellEdit
data={ this.state.data }
...
}
So data here is not the actual data but the promise.
The confusion is happening because you are mixing redux state with react state. Use one or the other, not both (there are expcetions to this but not in this particular scenario).
There are some more issues with PlanContainer which are not clear as to whether they are real issues, or just a result of code ommission in OP.
See annotations below:
class PlanContainer extends React.Component {
componentDidMount() {
this.props.fetchBudgets();
}
constructor(props) {
... // removed for brevity, use the same code as you have right now
}
render() {
return (
<div>
<RemoteCellEdit
data={ this.props.budgets}
columns = { this.columns }
keyField = 'id'
errorMessage={ /* should come from props.data or similar - it's not in state */ }
/>
<tbody>
{this.props.budgets} /* not sure what this is for - I assumed RemoteCellEdit is the one rendering the data */
</tbody>
</div>
);
}
}
Fixing these should set you on the correct course. Good luck!

Resources