React not able to setState from within a function - javascript

I'm trying to convert a class-based component to a functional component. I believe I have the fundamentals for functional-based components in place, but for some reason, useState is not flipping the boolean value when the button is clicked.
Working code:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import BootstrapTable from "react-bootstrap-table-next";
import "bootstrap/dist/css/bootstrap.min.css";
import "react-bootstrap-table-next/dist/react-bootstrap-table2.min.css";
import "./styles.css";
import { Button } from "react-bootstrap";
const products = [
{ id: 1, name: "Item 1", price: 100 },
{ id: 2, name: "Item 2", price: 102 }
];
class App extends Component {
constructor() {
super();
this.state = {
// For displaying data
columns: [
{
dataField: "id",
text: "id",
sort: true
},
{
dataField: "name",
text: "Name",
sort: true
},
{
dataField: "price",
text: "Product Price"
},
{
dataField: "follow",
text: "Follow",
formatter: this.linkFollow,
sort: true
}
],
isFollow: true
};
this.onFollowChanged.bind(this);
}
onFollowChanged() {
this.setState({ isFollow: !this.state.isFollow });
console.log(this.state.isFollow);
}
linkFollow = (cell, row, rowIndex, formatExtraData) => {
return (
<Button
onClick={() => {
this.onFollowChanged(row);
}}
>
Follow
</Button>
);
};
render() {
return (
<div style={{ padding: "20px" }}>
<h1 className="h2">Products</h1>
<BootstrapTable
keyField="id"
data={products}
columns={this.state.columns}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working demo in sandbox
Non-Working Code:
import React, { useState } from "react";
import BootstrapTable from "react-bootstrap-table-next";
import "bootstrap/dist/css/bootstrap.min.css";
import "react-bootstrap-table-next/dist/react-bootstrap-table2.min.css";
import { Button } from "react-bootstrap";
export default function App() {
function onFollowChanged() {
setIsPresent(!isPresent);
console.log(isPresent);
}
let buttonFollow = (cell, row, rowIndex, formatExtraData) => {
return <Button onClick={() => onFollowChanged(row)}>Present</Button>;
};
const [isPresent, setIsPresent] = useState(true);
const students = [
{
id: 100,
fName: "John",
lName: "Doe",
grade: "first"
},
{
id: 200,
fName: "Jane",
lName: "Doe",
grade: "second"
},
{
id: 300,
fName: "Peter",
lName: "Paker",
grade: "third"
},
{
id: 400,
fName: "Clark",
lName: "Kent",
grade: "fourth"
},
{
id: 500,
fName: "LeBron",
lName: "James",
grade: "fifth"
}
];
const columns = [
{
dataField: "id",
text: "id",
sort: true
},
{
dataField: "fName",
text: "First Name",
sort: true
},
{
dataField: "lName",
text: "Last Name"
},
{
dataField: "grade",
text: "Grade",
sort: true
},
{
dataField: "present",
text: "Present",
formatter: buttonFollow
}
];
return (
<div>
<BootstrapTable keyField="id" data={students} columns={columns} />
</div>
);
}
With my code, when I click the "Present" button the console log keeps printing the initial value of isPresent. I'm expecting it to toggle between true/false.
Non-working demo in sandbox.

You are nearly there, I grabed your code from the sandbox:
const [isPresent, setIsPresent] = useState(true);
function toggleValue() {
setIsPresent(!isPresent);
console.log(isPresent);
}
return (
<div>
<BootstrapTable keyField="id" data={students} columns={columns} />
<button onClick={toggleValue}>Test</button>
{isPresent ? "true" : "false"} // conditional render string value
</div>
);
Resource recommendation: React beta docs

Related

MUI V4: How to use conditional row coloring

I have a basic Material UI v4 datagrid. I am trying to change the any row that has age of 16 to all grey color: 'grey'. I am struggling to do this. The docs are not very clear on how to change an entire rows font color. Here is the code.
import * as React from "react";
import { DataGrid } from "#material-ui/data-grid";
const columns = [
{ field: "id", headerName: "ID", width: 70 },
{ field: "firstName", headerName: "First name", width: 130 },
{ field: "lastName", headerName: "Last name", width: 130 },
{
field: "age",
headerName: "Age",
type: "number",
width: 90
},
{
field: "fullName",
headerName: "Full name",
description: "This column has a value getter and is not sortable.",
sortable: false,
width: 160,
valueGetter: (params) =>
`${params.getValue("firstName") || ""} ${
params.getValue("lastName") || ""
}`
}
];
const rows = [
{ id: 1, lastName: "Snow", firstName: "Jon", age: 35 },
{ id: 2, lastName: "Lannister", firstName: "Cersei", age: 42 },
{ id: 3, lastName: "Lannister", firstName: "Jaime", age: 45 },
{ id: 4, lastName: "Stark", firstName: "Arya", age: 16 }
];
export default function App() {
const [selectionModel, setSelectionModel] = React.useState([]);
return (
<div style={{ height: 400, width: "100%" }}>
<DataGrid
rows={rows}
columns={columns}
pageSize={25}
checkboxSelection
hideFooterPagination
onSelectionModelChange={(newSelection) => {
setSelectionModel(newSelection.selectionModel);
}}
selectionModel={selectionModel}
/>
{selectionModel.map(val =><h1>{val}</h1>)}
</div>
);
}
I'm trying to do something like this (of course this doesn't work)
const greyOut = () => {
const data = row.age
if (data == 16){
return (
<TableRow style={{ color: 'grey'}}>{row}</TableRow>
)}
}
Can anyone help with this?
You can make use of getRowClassName prop in <DataGrid/>. This way you can apply certain css classes to all rows matching the condition.
params.row lets you access all values inside a row.
<DataGrid
...
getRowClassName={(params) => {
return params.row.age === 16 ? "highlight" : "";
}}
...
/>
Now you can either use a classical css stylesheet or add an additional sx prop to your <DataGrid/>:
<DataGrid
...
sx={{
".highlight": {
bgcolor: "grey",
"&:hover": {
bgcolor: "darkgrey",
},
},
}}
/>

How to add images in MaterialDatatable?

I'm using MaterialDatatable in react-app this is working fine but now I want to add country icons with country names so how can I add images with table value?
My Code:-
<MaterialDatatable
columns={headers}
data={items}
options={options}
/>
items is coming from below list:-
export function getHeaders() { return [
{
name: 'Username',
field: 'username',
type: 'string'
},
{
name: 'Country',
field: 'countryName',
type: 'string',
className: 'lftAlign',
},
{
name: 'Name',
field: 'fullName',
type: 'string'
}] }
Package Link:-
https://www.npmjs.com/package/material-datatable
Countries flag images will be come from below array:-
const countries = [
{
value: '1',
title: 'Afghanistan',
icon: '/flags/afghanistan.png'
},
{
value: '2',
title: 'Albania',
icon: '/flags/albania.png'
}
]
You can use customValue for that purpose:
See: https://github.com/Diaver/material-datatable
There is a sandbox example at https://codesandbox.io/s/lp8pvk1zk9?file=/index.js:0-1728:
import React from "react";
import ReactDOM from "react-dom";
import MaterialDatatable from "material-datatable";
class App extends React.Component {
render() {
const columns = [
{ name: "Name", field: "name" },
{ name: "Title", field: "title" },
{ name: "Location", field: "location" },
{ name: "Age", field: "age" },
{ name: "Salary", field: "salary" },
{
name: "Name and Title Custom Column",
field: "salary",
options: {
noHeaderWrap: true,
width: 200,
customBodyRender: (value, tableMeta, updateValue) => {
return `${value.name} (${value.title})`;
}
}
},
{
name: "Custom Render",
field: "salary2",
options: {
noHeaderWrap: true,
width: 200,
customBodyRender: (value, tableMeta, updateValue) => {
return <h4>{value.name}</h4>;
},
customValue: (value, tableMeta, updateValue) => value.name,
customSortValue: (value, tableMeta, updateValue) => value.name
}
}
];
const data = [
{
name: "Name 1",
title: "Title 1",
location: "Location 1",
age: 30,
salary: 10
},
{
name: "Name 2",
title: "Title 2",
location: "Location 2",
age: 31,
salary: 11
}
];
const options = {
filterType: "dropdown",
responsive: "scroll"
};
return (
<MaterialDatatable
title={"ACME Employee list"}
data={data}
columns={columns}
options={options}
/>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
Note that the relevant part is
{
name: "Custom Render",
field: "salary2",
options: {
noHeaderWrap: true,
width: 200,
customBodyRender: (value, tableMeta, updateValue) => {
return <h4>{value.name}</h4>;
},
customValue: (value, tableMeta, updateValue) => value.name,
customSortValue: (value, tableMeta, updateValue) => value.name
}
}
Note that we have a customValue as
customValue: (value, tableMeta, updateValue) => value.name,
You could have something like
customValue: (value, tableMeta, updateValue) => value.icon,
Yes, I know, this only displays the relative path. But, instead of that, you can change this to return something like
`<img src="${value.icon}">`
the above is just a dummy example. You can replace it with a more React-like code and you can even put there some components.

how to solve asynchronous behaviour in search Box react

so im trying to implement a search box with useState and useEffect. we have an array of objects and want to filter it according to our search term. here is my implementation:
import React, {useEffect, useState} from "react";
const array = [
{ key: '1', type: 'planet', value: 'Tatooine' },
{ key: '2', type: 'planet', value: 'Alderaan' },
{ key: '3', type: 'starship', value: 'Death Star' },
{ key: '4', type: 'starship', value: 'CR90 corvette' },
{ key: '5', type: 'starship', value: 'Star Destroyer' },
{ key: '6', type: 'person', value: 'Luke Skywalker' },
{ key: '7', type: 'person', value: 'Darth Vader' },
{ key: '8', type: 'person', value: 'Leia Organa' },
];
let available = []
const Setup = () => {
const [state, setState] = useState('');
useEffect(() => {
available = array.filter(a => a.value.startsWith(state));
},[state])
const show = state ? available : array;
return <>
<input value={state} onChange={e => setState(e.target.value)} type="text" className="form"/>
{show.map(a => {
return <Data id={a.key} key={parseInt(a.key)} value={a.value} type={a.type}/>
})}
</>
}
const Data = (props) => {
return <>
<div>
<p>{props.value}</p>
</div>
</>
}
export default Setup;
the problem starts when we give our search box a valid search term(like 'T'). i expect it to change the output accordingly(to only show 'Tatooine') but the output does not change.
meantime if you add another character to search term(like 'a' which would set our search term to 'Ta') it will output the expected result. in the other words, search term is not applied synchronously. do you have any idea why is that
The useEffect hook is triggered when the component mounts, rerenders or unmounts. In your case, the change of the search field causes a rerender because of the change of the state. This results in your useEffect triggering after the state change and is too late for what you need.
If you type "Ta" into your field, you'll see it works, but it appears as if the search is one step behind.
You can simply remove the use of useEffect and filter when you render. This means you can also remove the whole logic around the available and show variables:
const Setup = () => {
const [state, setState] = useState("");
return (
<>
<input
value={state}
onChange={(e) => setState(e.target.value)}
type="text"
className="form"
/>
{array
.filter((a) => a.value.startsWith(state))
.map((a) => (
<Data
id={a.key}
key={parseInt(a.key, 10)}
value={a.value}
type={a.type}
/>
))}
</>
);
};
There is some good information in the Using the Effect Hook docs.
You just add toLowerCase mehtod to your filter function. just like this :
import React, { useEffect, useState } from "react";
const array = [
{ key: "1", type: "planet", value: "Tatooine" },
{ key: "2", type: "planet", value: "Alderaan" },
{ key: "3", type: "starship", value: "Death Star" },
{ key: "4", type: "starship", value: "CR90 corvette" },
{ key: "5", type: "starship", value: "Star Destroyer" },
{ key: "6", type: "person", value: "Luke Skywalker" },
{ key: "7", type: "person", value: "Darth Vader" },
{ key: "8", type: "person", value: "Leia Organa" }
];
let available = [];
const Setup = () => {
const [state, setState] = useState("");
useEffect(() => {
available = array.filter((a) => a.value.toLowerCase().startsWith(state));
}, [state]);
const show = state ? available : array;
return (
<>
<input
value={state}
onChange={(e) => setState(e.target.value)}
type="text"
className="form"
/>
{show.map((a) => {
return (
<Data
id={a.key}
key={parseInt(a.key)}
value={a.value}
type={a.type}
/>
);
})}
</>
);
};
const Data = (props) => {
return (
<>
<div>
<p>{props.value}</p>
</div>
</>
);
};
export default Setup;
and here is the working example : here
You can simply just pull out useEffect.
import React, { useState } from 'react';
const array = [
{ key: '1', type: 'planet', value: 'Tatooine' },
{ key: '2', type: 'planet', value: 'Alderaan' },
{ key: '3', type: 'starship', value: 'Death Star' },
{ key: '4', type: 'starship', value: 'CR90 corvette' },
{ key: '5', type: 'starship', value: 'Star Destroyer' },
{ key: '6', type: 'person', value: 'Luke Skywalker' },
{ key: '7', type: 'person', value: 'Darth Vader' },
{ key: '8', type: 'person', value: 'Leia Organa' },
];
let available = [];
const Setup = () => {
const [state, setState] = useState('');
available = array.filter(a => a.value.startsWith(state));
const show = state ? available : array;
return (
<>
<input
value={state}
onChange={e => setState(e.target.value)}
type='text'
className='form'
/>
{show.map(a => {
return (
<Data
id={a.key}
key={parseInt(a.key)}
value={a.value}
type={a.type}
/>
);
})}
</>
);
};
const Data = props => {
return (
<>
<div>
<p>{props.value}</p>
</div>
</>
);
};
export default Setup;
This must solve it
import React, { useEffect, useState } from "react";
const array = [
{ key: "1", type: "planet", value: "Tatooine" },
{ key: "2", type: "planet", value: "Alderaan" },
{ key: "3", type: "starship", value: "Death Star" },
{ key: "4", type: "starship", value: "CR90 corvette" },
{ key: "5", type: "starship", value: "Star Destroyer" },
{ key: "6", type: "person", value: "Luke Skywalker" },
{ key: "7", type: "person", value: "Darth Vader" },
{ key: "8", type: "person", value: "Leia Organa" }
];
const Setup = () => {
const [state, setState] = useState("");
const [available, setAvailable] = useState(array);
useEffect(() => {
setAvailable(array.filter((a) => a.value.startsWith(state)));
}, [state]);
return (
<>
<input
value={state}
onChange={(e) => setState(e.target.value)}
type="text"
className="form"
/>
{available.map((a) => {
return (
<Data
id={a.key}
key={parseInt(a.key)}
value={a.value}
type={a.type}
/>
);
})}
</>
);
};
const Data = (props) => {
return (
<>
<div>
<p>{props.value}</p>
</div>
</>
);
};
export default Setup;

Nativescript RadDataForm convert property not working with json array

I have json array with id and name property after submiting the form I am getting NaN I want to convert converter code from typescript to Javascript
I created a Playground example
file : Data.js
{ id: 123, name: 'Zootopia' },
{ id: 217, name: 'Captain America' },
{ id: 324, name: 'The Jungle Book' }
];
export class MovieConverter {
constructor(_movies) {
this._movies = _movies;
}
convertFrom(id) {
return this._movies.filter((movie) => movie.id === id)[0].name;
}
convertTo(name) {
return this._movies.filter((movie) => movie.name === name)[0].id;
}
}
Test.vue
<template>
<Page>
<ActionBar :title="Simple">
<NavigationButton text="Back" android.systemIcon="ic_menu_back"
#tap="onNavigationButtonTap"></NavigationButton>
</ActionBar>
<StackLayout>
<Button text="Button" #tap="onSubmit" />
<RadDataForm :source="ticket" :metadata="ticketMetadata" />
</StackLayout>
</Page>
</template>
<script>
import Home from "./Home";
import {
Movies,
MovieConverter
} from "../Data";
//const movies = getMovies();
export default {
methods: {
onNavigationButtonTap() {
this.$navigateTo(Home);
}
},
mounted() {
console.log("movies", Movies);
Movies.map(Movie => {
console.log(Movie.name);
});
},
data() {
return {
ticket: {
movie: 123,
date: "2016-04-06",
time: "20:00",
type: "2D",
price: 9.5,
numberOfTickets: 2,
contactName: null,
contactPhone: null,
contactEmail: null,
agreeTerms: false
},
ticketMetadata: {
isReadOnly: false,
commitMode: "Immediate",
validationMode: "Immediate",
propertyAnnotations: [{
name: "movie",
displayName: "Movie Name",
index: 0,
editor: "Picker",
valuesProvider: Movies.map(Movie => Movie.name),
converter: new MovieConverter(Movies)
},
{
name: "date",
displayName: "Date",
index: 1,
editor: "DatePicker",
hintText: "This is a hint for you"
},
{
name: "time",
displayName: "Time",
index: 2,
editor: "TimePicker"
},
{
name: "type",
displayName: "Type",
index: 3,
editor: "SegmentedEditor",
valuesProvider: ["2D", "3D"]
},
{
name: "price",
displayName: "Price",
index: 4,
editor: "Decimal",
readOnly: true
},
{
name: "numberOfTickets",
displayName: "Number Of Tickets",
index: 5,
editor: "Stepper",
editorParams: {
minimum: 0,
maximum: 20,
step: 2
}
},
{
name: "contactName",
displayName: "Contact Name",
index: 6,
editor: "Text"
},
{
name: "contactPhone",
displayName: "Contact Phone",
index: 7,
editor: "Phone"
},
{
name: "contactEmail",
displayName: "Contact Email",
index: 8,
editor: "Email"
},
{
name: "agreeTerms",
displayName: "I Agree with Terms",
index: 9,
editor: "Switch"
}
]
}
};
},
methods: {
onSubmit() {
console.log("submit", this.ticket.movie);
}
}
};
</script>
<style>
</style>
after onSubmit() of the form I should get 'submit' 123
but getting 'submit' NaN.
I check out the documentation of converter it showing a class with 2 methods convertFrom() & convertTo() which I think is proper not understand as all the documentation is in typescript and I am using javascript.

add and remove item in array on click reactjs

I'm working on a simple table using reactjs and ant design.
My plan is to add and remove a new item on the list on button click.
My problem is I don't know how to do that.
I tried to follow this thread but no luck.
Hope you understand me.
Thanks.
sample code
function remove() {
console.log("remove");
}
function add() {
console.log("add");
}
const columns = [
{
title: "Num",
dataIndex: "num"
},
{
title: "Name",
dataIndex: "name"
},
{
title: "Age",
dataIndex: "age"
},
{
title: "Address",
dataIndex: "address"
}
];
const data = [
{
key: "1",
num: 1,
name: "John Brown",
age: 32,
address: "New York No. 1 Lake Park"
},
{
key: "2",
num: 2,
name: "Jim Green",
age: 42,
address: "London No. 1 Lake Park"
},
{
key: "3",
num: 3,
name: "Joe Black",
age: 32,
address: "Sidney No. 1 Lake Park"
}
];
<Table pagination={false} columns={columns} dataSource={data} />
<Button type="primary" onClick={add}>
add
</Button>
<Button type="danger" onClick={remove}>
remove
</Button>
You need to use react state. State holds the data, when you want to add or remove, update this state and react with re-render the table.
I have updated your code. On click of add a new random row is added. On click of remove last row is removed.
CodeSandbox
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Table, Button } from "antd";
function remove() {
console.log("remove");
}
const columns = [
{
title: "Num",
dataIndex: "num"
},
{
title: "Name",
dataIndex: "name"
},
{
title: "Age",
dataIndex: "age"
},
{
title: "Address",
dataIndex: "address"
}
];
let data = [
{
key: "1",
num: 1,
name: "John Brown",
age: 32,
address: "New York No. 1 Lake Park"
},
{
key: "2",
num: 2,
name: "Jim Green",
age: 42,
address: "London No. 1 Lake Park"
},
{
key: "3",
num: 3,
name: "Joe Black",
age: 32,
address: "Sidney No. 1 Lake Park"
}
];
export default class MyTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data
};
}
add = () => {
var row = {
key: "99",
num: 99,
name: "I am New",
age: 32,
address: "New Address"
};
var newStateArray = [...this.state.data];
newStateArray.push(row);
this.setState(() => {
return {
data: newStateArray
};
});
}
remove = () => {
var newStateArray = [...this.state.data];
if(newStateArray.length > 1) {
newStateArray.pop();
}
this.setState(() => {
return {
data: newStateArray
};
});
}
render() {
return (
<div>
<Table
pagination={false}
columns={columns}
dataSource={this.state.data}
/>
<Button type="primary" onClick={this.add}>
add
</Button>
<Button type="danger" onClick={this.remove}>
remove
</Button>
</div>
);
}
}
ReactDOM.render(<MyTable />, document.getElementById("container"));

Categories

Resources