implementation of search functionality in reactjs - javascript

I am trying to implement search functionality in reactjs. i am not getting how to do. below i have given code which i have tried.
i need to display result in table after serach.
render() {
return (
<div>
<input onChange={this.handleSearchChange} placeholder="Search"/>
</div>
)
}
// below is my function
handleSearchChange = e => {
const { value } = e.target;
var self = this
axios.post("http://localhost:4000/get", { name: value })
.then(function(res){
console.log("detail",res.data)
})
.catch(function(err){
console.log('Error',err)
})
};
//below is my api response
[
{color: "green",name: "test",age: "22"},
{color: "red",name: "test2",age: "23"}
]

Once you have the data you need to add it to state so that when the state changes you can iterate over the data and rerender the view. I'm using React hooks in this example. I hope it helps a little.
table {
background-color: white;
border-collapse: collapse;
}
tr:nth-child(even) {
background-color: #efefef;
}
td {
padding: 1em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/#babel/standalone#7/babel.min.js"></script>
<script type="text/babel">
// Grab the hooks you need
const { useState, useEffect } = React;
// The main function that will loop over the data
// and create the rows for the table
function createRows(data) {
// Iterate over the data and for each object
// return a new HTML row
return data.map((row, i) => {
const { color, name, age } = row;
return (
<tr key={i}>
<td>{color}</td>
<td>{name}</td>
<td>{age}</td>
</tr>
)
});
}
// Mock API call which returns data after a 2 second delay
function fakeAPICall() {
return new Promise((res, rej) => {
setTimeout(() => {
res('[{"color": "green","name": "test","age": "22"},{"color": "red","name": "test2","age": "23"}]');
}, 2000);
});
}
function Example () {
// Set up the state in the componenent
const [data, setData] = useState([]);
// When the component renders load in the data
// and set that as your state.
useEffect(() => {
async function getData() {
const response = await fakeAPICall();
const data = JSON.parse(response);
setData(data);
}
getData();
}, []);
// If there's no data in state display nothing...
if (!data.length) return <div>No data</div>
// ...otherwise pass the data into the createRows function
// and return them the row data
return (
<table>
<thead>
<th>Color</th>
<th>Name</th>
<th>Age</th>
</thead>
<tbody>
{createRows(data)}
</tbody>
</table>
)
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
</script>
<div id="react"></div>
And here's how I would do it with a class component:
table {
background-color: white;
border-collapse: collapse;
}
tr:nth-child(even) {
background-color: #efefef;
}
td {
padding: 1em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<script type="text/babel">
// The main function that will loop over the data
// and create the rows for the table
function createRows(data) {
// Iterate over the data and for each object
// return a new HTML row
return data.map((row, i) => {
const { color, name, age } = row;
return (
<tr key={i}>
<td>{color}</td>
<td>{name}</td>
<td>{age}</td>
</tr>
)
});
}
// Mock API call which returns data after a 2 second delay
function fakeAPICall() {
return new Promise((res, rej) => {
setTimeout(() => {
res('[{"color": "green","name": "test","age": "22"},{"color": "red","name": "test2","age": "23"}]');
}, 2000);
});
}
class Example extends React.Component {
// Set up the state in the componenent
constructor() {
super();
this.state = { data: [] };
}
// When the component renders load in the data
// and set that as your state.
componentDidMount() {
fakeAPICall().then(response => {
return JSON.parse(response);
}).then(data => {
this.setState({ data });
});
}
// ...otherwise pass the data into the createRows function
// and return them the row data
render() {
if (!this.state.data.length) return <div/>
return (
<table>
<thead>
<th>Color</th>
<th>Name</th>
<th>Age</th>
</thead>
<tbody>
{createRows(this.state.data)}
</tbody>
</table>
)
}
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
</script>
<div id="react"></div>

If I understand your question correctly, you are trying to update your table rendering. I will assume, as you mentioned, that your API is working fine at the call.
In React, there is a feature called "state", which can preserve a variable state. The cool thing about it also is that when you change that state, your component re-renders. A little explanation or ReactJS states:
// intialization of a variable "varX" of type integer with value 1
// setVarX(int) is used to change the variable state
const [varX, setVarX] = React.useState(1);
// changing the state
setVarX(3);
So for your problem, we need two things: a state that holds the API response and that we update whenever the API has new data, and a table display of your data.
State
In your function that is rendering (I assume it is name TableView), let's add this state and have it updated in the handler when the API succeeds
function TableView(){
// table data, initialized to empty array
const [tableData, setTableData] = React.useState([]);
// handle updated with set state
handleSearchChange = e => {
const { value } = e.target;
var self = this.axios.post("http://localhost:4000/get", { name: value })
.then(function(res){
console.log("detail",res.data)
setTableData(res.data) // update the table data!
})
.catch(function(err){
console.log('Error',err)
})
};
return (...) // render function in the next section
}
Render
The function would use the map feature of react:
render() {
return (
<div>
<input onChange={this.handleSearchChange} placeholder="Search"/>
<table style="width:100%">
<tr>
<th>Color</th>
<th>Name</th>
<th>Age</th>
</tr>
</table>
{
tableData.map((entry, index) =>
<tr index={index}>
<td>{entry.color}</td>
<td>{entry.name}</td>
<td>{entry.age}</td>
</tr>
)
}
</div>
)
}
PS: I am not the best at JSX feel free to edit and enhance the render section

Related

React-table data flow between outside and inside of hooks.visibleColumns.push invocation

I would like to create an array outside useTable invocation, manipulate the array, and based on the array state make checkbox checked or unchecked. After each click on a button, length of the array is increased by adding one element to the array. When length become greater than 3, the input should be checked.
The problem is that the array state is different inside and outside checked attribute of input. Outside it works as expected: the array length increases. Inside, the array length is equal to initial length of 0.
I have attached code with some logging. I think that the relevant part probably ends with the end of useTable invocation (then is some code which I took from react-table docs with button and mock data, columns added). What changes should I introduce to the code to make it work as I expect?
import React, { useMemo, useState } from 'react';
import { useTable } from 'react-table'
function Table({ columns, data }) {
// neither stateArr nor simpleArr help reach what I would like to
const [stateArr, setStateArr] = useState([]);
let simpleArr = [...stateArr];
const handleOnButtonClick = () => {
console.log("Outside checked: simpleArr, stateArr");
console.log(simpleArr);
console.log(stateArr);
setStateArr([...stateArr, 1]);
// in this case unnecessary, since, as I understand, simpleArr is rendered and (re)assigned above
// simpleArr = [...stateArr];
};
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({
columns,
data,
},
(hooks) => {
hooks.visibleColumns.push((columns) => {
return [
{
id: 'checkedInputs',
Header: () => {
return (<div>
<input type="checkbox"
// working, not most elegant way to combine logging and computing boolean
checked={console.log("Inside checked: simpleArr, stateArr") || console.log(simpleArr)
|| console.log(stateArr) || simpleArr.length > 3 || stateArr.length > 3} />
</div>);
},
Cell: () => {
return (<div>R</div>);
},
},
...columns,
];
});
}
);
return (
<div>
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
<button type="button" onClick={handleOnButtonClick}>Click Me!</button>
</div>
)
}
function App() {
const columns = useMemo(
() => [
{
Header: 'Animal Type',
accessor: 'animalType',
},
{
Header: 'Number of legs',
accessor: 'numberOfLegs',
},
],
[],
);
const data = useMemo(
() => [
{
animalType: 'dog',
numberOfLegs: 4,
},
{
animalType: 'snake',
numberOfLegs: 0,
},
],
[],
);
return (
<Table columns={columns} data={data} />
)
}
export default App;
Stale Data
The hooks.visibleColumns.push function is called one time when the table is created. It creates a Header render component that takes some props and returns a JSX element. The function which renders the Header based on these props is called every time that the table updates. The function which creates this Header component is called once.
In your example, you create a Header component which prints out some data based on the values of simpleArr and stateArr at the time that it was created, not at the time that it was called.
Table State
If we want our Header component to render with current data then we should get that data from props. The Header is called with quite a lot of props but the one that we will use is state which is the state of the table. We will set the initialState of the table to an object { stateArr: [] }. This gets merged with the standard table state { hiddenColumns: [] }.
The table state is updated through a useReducer hook, so we update it by disptaching an action. We need a custom stateReducer to update the table state based on the contents of the action.
import React, { useMemo } from "react";
import { useTable } from "react-table";
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
dispatch,
state
} = useTable(
{
columns,
data,
initialState: {
stateArr: []
},
stateReducer: (newState, action, prevState) => {
console.log(action, newState, newState.stateArr);
switch (action.type) {
case "incrementChecks":
return {
...newState,
stateArr: [...newState.stateArr, action.payload]
};
default:
return newState;
}
}
},
(hooks) => {
hooks.visibleColumns.push((columns) => {
return [
{
id: "checkedInputs",
Header: (props) => {
console.log("header props", props); // so you can see all the data you get
console.log("stateArr", props.state.stateArr);
return (
<input
type="checkbox"
readOnly
checked={props.state.stateArr.length > 3}
/>
);
},
Cell: () => {
return <div>R</div>;
}
},
...columns
];
});
}
);
const handleOnButtonClick = () => {
// payload is the item which we are appending to the array
dispatch({ type: "incrementChecks", payload: 1 });
};
console.log("stateArr", state.stateArr);
return (
<div>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>{column.render("Header")}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
<button type="button" onClick={handleOnButtonClick}>
Append
</button>
</div>
);
}
function App() {
const columns = useMemo(
() => [
{
Header: "Animal Type",
accessor: "animalType"
},
{
Header: "Number of legs",
accessor: "numberOfLegs"
}
],
[]
);
const data = useMemo(
() => [
{
animalType: "dog",
numberOfLegs: 4
},
{
animalType: "snake",
numberOfLegs: 0
}
],
[]
);
return <Table columns={columns} data={data} />;
}
export default App;
CodeSandbox Link

Reactjs - Re render data on button click

I am using an API to fetch some data. When the page loads it fetches some random data, but I want to allow the user to sort the data by clicking a button. I have made a function to sort these data from the API I am using. What I want to do now is: When the button to sort data is clicked, I want the new data to be replaced with the old data.
Here is my current code:
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
offset: 0, perPage: 12 // ignore these two
};
}
// The random data that I want to be displayed on page load
receivedData() {
axios
.get(`https://corona.lmao.ninja/v2/jhucsse`)
.then(res => {
const data = res.data;
const slice = data.slice(this.state.offset, this.state.offset + this.state.perPage) // ignore this
const postData = slice.map(item =>
<tr key={Math.random()}>
<td>{item.province}, {item.country}</td>
<td>{item.stats.confirmed}</td>
<td>{item.stats.deaths}</td>
<td>{item.stats.recovered}</td>
</tr>
)
this.setState({
pageCount: Math.ceil(data.length / this.state.perPage), // ignore this
postData
})
});
}
// The data to be sorted when the "country" on the table head is clicked
sortData() {
axios
.get(`https://corona.lmao.ninja/v2/jhucsse`)
.then(res => {
const data = res.data;
var someArray = data;
function generateSortFn(prop, reverse) {
return function (a, b) {
if (a[prop] < b[prop])
return reverse ? 1 : -1;
if (a[prop] > b[prop])
return reverse ? -1 : 1;
return 0;
};
}
// someArray.sort(generateSortFn('province', true))
const tableHead = <tr>
<th onClick={() => someArray.sort(generateSortFn('province', true))}>Country</th>
<th>Confirmed Cases</th>
<th>Deaths</th>
<th>Recovered</th>
</tr>
this.setState({
tableHead
})
});
}
componentDidMount() {
this.receivedData()
this.sortData() // This function should be called on the "Country - table head" click
}
render() {
return (
<div>
<table>
<tbody>
{this.state.tableHead}
{this.state.postData}
</tbody>
</table>
</div>
)
}
}
export default Data;
Think a litte bit different. In the componentDidMount get you're Data in some form. Set it with setState only the raw Data not the html. Then resort the data on button click. React rerenders if the state changes automatically
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data
}
}
getData() {
fetchData('url').then(res) {
this.setState({data: res.data})
}
}
componentDidMount() {
this.getData()
}
sort() {
let newSorted = this.state.data.sort //do the sorting here
this.setState({data: newSorted})
}
render() {
return() {
<table>
<tablehead><button onClick={this.sort.bind(this)}/></tablehead>
{this.state.data.map(data => {
return <tablecell>{data.name}</tablecell>
})}
</table>
}
}
}

Send array of objects to render in React

Having an array of this form:
[{"game_id":4,"city":"London","year":2018,"athlete_id":"1"},
{"game_id":2,"city":"Paris","year":2016,"athlete_id":"2"}]
it is received from back-end and stored like this:
callAPI() {
fetch('http://localhost:9000/testAPI')
.then((res) => res.text())
.then((res) => this.setState({ apiResponse: res }));
}
and then, in the render is send as props to the Table component:
render() {
return (
<div className='App'>
<header className='App-header'>
<Table data={this.state.apiResponse} />
</header>
</div>
);
}
The problem comes here when I want to send to the table only parts of apiResponse.
This is the component:
class Table extends React.Component {
constructor(props) {
super(props);
}
getGames = function () {
return this.props.data;
};
render() {
return (
<div>
<table>
<thead>
<tr>{this.getGames()}</tr>
</thead>
</table>
</div>
);
}
}
The above code sends all the data, so I tried to send only the data that I want, for example only the keys and make headers out of them.
So I replaces the content of getGames() with this:
getGames = function () {
return Object.keys(this.props.data[0]);
};
but it throws this error:
TypeError: Cannot convert undefined or null to object
What I want is to create a table with headers: game_id, city, year, athelete_id and their columns the show their corresponding data.

Select table cell in react bootstrap table

export class UsersTable extends React.Component {
constructor() {
super();
this.state = {
info: null
};
}
componentWillMount() {
fetch("http://localhost:8081/milltime/getUsers")
.then(res => res.json())
.then(info => {
this.setInfo(info);
});
}
setInfo(info) {
const state = this.state;
state['info'] = info;
this.setState(state);
}
render() {
const info = this.state.info;
if (!this.state.info) {
return null;
}
let listItems = [];
for (var i = 0; i < info['mta:getUsersResponse']['mta:users'].length; i++) {
listItems.push(
<tr>
<td>{info['mta:getUsersResponse']['mta:users'][i]['mta:UserId']}</td>
<td>{info['mta:getUsersResponse']['mta:users'][i]['mta:FullName']}</td>
<td>{info['mta:getUsersResponse']['mta:users'][i]['mta:CostHour']}</td>
</tr>);
}
return(
<div className="usersTable">
<Table striped bordered condensed responsive hover>
<thead>
<tr>
<th>Id</th>
<th>Full Name</th>
<th>Hour cost</th>
</tr>
</thead>
<tbody>
{listItems}
</tbody>
</Table>
</div>
);
}
}
This is the code I have for a table that get users and displays 3 columns of data. What I am having problems doing is being able to select a table and by selecting that table get the data in that cell and use it to search with the help of the id of the user in the selected cell. Has anyone got a neat solution? I'm using React bootstrap.
Bind your onClick handler when your creating the row.
See comments in code.
https://reactjs.org/docs/handling-events.html
export class UsersTable extends React.Component {
constructor() {
super();
this.state = {
info: null
};
}
componentWillMount() {
fetch("http://localhost:8081/milltime/getUsers")
.then(res => res.json())
.then(info => {
this.setInfo(info);
});
}
setInfo(info) {
const state = this.state;
state['info'] = info;
this.setState(state);
}
onSelectedRow(user, clickEvent){
//your user object and the click event
//clickEvent.currentTarget = the cell clicked
}
render() {
const info = this.state.info;
if (!this.state.info) {
return null;
}
let listItems = [];
for (var i = 0; i < info['mta:getUsersResponse']['mta:users'].length; i++) {
const user = info['mta:getUsersResponse']['mta:users'][i]; //dryer
//Bind you onclick handler to the context and you user object (or id if thats what you want)
listItems.push(
<tr onClick={this.onSelectedRow.bind(this, user)}>
<td>{user['mta:UserId']}</td>
<td>{user['mta:FullName']}</td>
<td>{user['mta:CostHour']}</td>
</tr>);
}
return(
<div className="usersTable">
<Table striped bordered condensed responsive hover>
<thead>
<tr>
<th>Id</th>
<th>Full Name</th>
<th>Hour cost</th>
</tr>
</thead>
<tbody>
{listItems}
</tbody>
</Table>
</div>
);
}
}
Api requests should be handled in componentDidMount lifecycle event as described in React docs.
Also, your are mutating your state on setInfo and this is not a good practice either. You can directly update your state like:
setInfo(info) {
this.setState({
info: info,
})
}
or simply using object shorthand
setInfo(info) {
this.setState({
info,
})
}
Should your api change in the future, you are gonna have problems replacing all the mta:** in your code. Why don't you map them upon state?
this.setState({
info: {
users: info['mta:getUsersResponse']['mta:users'].map(user => ({
id: user['mta:UserId'],
fullName: user['mta:FullName'],
costHour: user['mta:CostHour'],
}))
}
})
Click handling becomes easier from now, just create a UserRow component, send user as prop and propagate changes on onClick.
const UserRow = ({ user, onClick }) =>
<tr onClick={onClick}>
<td>{user.id}</td>
<td>{user.fullName}</td>
<td>{user.costHour}</td>
</tr>
Now you can map though your state and propagate props to it:
const UserRow = ({ user, onClick }) =>
<tr onClick={onClick}>
<td>{user.id}</td>
<td>{user.fullName}</td>
<td>{user.costHour}</td>
</tr>
class App extends React.Component {
constructor() {
super()
this.state = {
info: {
users: [
{ id: 0, fullName: 'User 1', costHour: 100 },
{ id: 1, fullName: 'User 2', costHour: 50 },
{ id: 2, fullName: 'User 3', costHour: 150 }
]
}
}
this.handleUserClick = this.handleUserClick.bind(this)
}
handleUserClick(user) {
console.log(`user ${user.id} has been clicked`)
}
render() {
return (
<div className="usersTable">
<table striped bordered condensed responsive hover>
<thead>
<tr>
<th>Id</th>
<th>Full Name</th>
<th>Hour cost</th>
</tr>
</thead>
<tbody>
{this.state.info.users.map(user =>
<UserRow
key={user.id}
user={user}
onClick={this.handleUserClick.bind(this, user)}
/>
)}
</tbody>
</table>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

ReactJS state not changing [duplicate]

This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 6 years ago.
I am working on this FreeCodeCamp leaderboard table and and when clicking in the highlighted table header the application calls either this url https://fcctop100.herokuapp.com/api/fccusers/top/recent or this one https://fcctop100.herokuapp.com/api/fccusers/top/alltime thus sorting between campers with the highest points for the past 30 days or all time.
My issue here is that I have to click twice in order to get the desired results. In the CamperLeaderboard component handleSort function when I console.log the state does not change until I have clicked twice
Click Once
handleSort = (sort) => {
console.log(sort); // alltime
console.log(this.state.sort) //recent
this.setState({ sort: sort });
console.log(this.state.sort) //recent
this.getData();
};
Click Twice
handleSort = (sort) => {
console.log(sort); // alltime
console.log(this.state.sort) //alltime
this.setState({ sort: sort });
console.log(this.state.sort) //alltime
this.getData();
};
This is the CodePen preview and below is the full code
/**
Table body component
*/
class Table extends React.Component {
handleSort = (e, sort) => {
this.props.handleSort(sort);
};
renderCampers = (key, count) => {
const camper = this.props.campers[key];
return(
<tr key={key}>
<td>{count}</td>
<td>
<a href={`https://www.freecodecamp.com/${camper.username}`} target='_blank'>
<img src={camper.img} />
{camper.username}
</a>
</td>
<td className='center'>{camper.recent}</td>
<td className='center'>{camper.alltime}</td>
</tr>
)
};
render() {
let count = 0;
return (
<div>
<table>
<caption>Leaderboard</caption>
<tr>
<th>#</th>
<th>Camper Name</th>
<th onClick={(e) => this.handleSort(e, 'recent')}><a href='javascript:void(0)'>Points in the past 30 days</a></th>
<th onClick={(e) => this.handleSort(e, 'alltime')}><a href='javascript:void(0)'>All time points</a></th>
</tr>
{Object.keys(this.props.campers).map(key => {
count++;
return this.renderCampers(key, count);
})}
</table>
</div>
);
}
}
/**
Container
*/
class CamperLeaderboard extends React.Component {
state = {
campers: [],
sort: 'recent'
};
getData() {
let url = `https://fcctop100.herokuapp.com/api/fccusers/top/${this.state.sort}`
const self = this;
axios.get(url)
.then(function (response) {
self.setState({ campers: response.data });
//console.log(self.state.campers);
})
.catch(function (error) {
console.log(error);
});
}
componentWillMount() {
this.getData();
}
handleSort = (sort) => {
this.setState({ sort: sort });
this.getData();
};
render() {
return (
<div>
<p>Click links in table header to sort</p>
<Table campers={this.state.campers}
handleSort={this.handleSort} />
</div>
);
}
}
ReactDOM.render(<CamperLeaderboard />, document.getElementById('app'));
/*
To get the top 100 campers for the last 30 days: https://fcctop100.herokuapp.com/api/fccusers/top/recent.
To get the top 100 campers of all time: https://fcctop100.herokuapp.com/api/fccusers/top/alltime.
*/
I believe #finalfreq's explanation is correct and this is how to fix it.
Update handleSort method of the CamperLeaderboard class like this:
handleSort = (sort) => {
this.setState({ sort: sort });
// You don't need to read sort from state. Just pass it :)
this.getData(sort);
}

Categories

Resources