How to Create Tabs in NextJS - tabs

I want to create tabs for categories. I get data categories with graphql and I want to display list data by the category selected.
tabs category
function a11yProps(index) {
console.log(index);
return {
id: `simple-tab-${index}`,
"aria-controls": `simple-tabpanel-${index}`,
};
}
.....
const [value, setValue] = useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
......
{data.categories.map((category, i) => (
<Tab
key={category.id}
label={category.name}
id={`scrollable-prevent-tab-${category.id}`}
aria-controls={`scrollable-prevent-tabpanel-${category.id}`}
{...a11yProps(category.id)}
/>
))}
<ItemNews value={value} index={a11yProps()} />
ItemNews is component for display list data. And below some code for get list data
const { value, index } = props;
const { loading, error, data, refetch } = detailCategory({
variables: {
id: index,
},
});

Related

How to limit the number of options in a select?

I am working on a project using React and tailwind.
I would like to filter the options I mean I want see to at most 3 options. I tried slice but it is not a solution because using slice for instance if I type a I want to see at most 3 words which contains the letter a if I type b I want to see at most 3 words which contains the letter b and that words for a and b can be different so slice cannot be a solution.
Here is my code :
import React, { Component } from "react";
import Select, { components} from "react-select";
import { useState } from "react";
let cheeses = ["Wagasi", "Kalari", "Halloumi", "Manouri"];
let options = [];
options = options.concat(cheeses.map((x) => "Cheese - " + x));
const Foo = () => {
const [value, setValue] = useState("");
function MakeOption(x) {
if (value) {
return { value: x, label: x };
} else {
return { value: "", label: "" };
}
}
const handleInputChange = (value, e) => {
if (e.action === "input-change") {
setValue(value);
}
};
const Input = props => <components.Input {...props} maxLength={5} />;
return (
<Select
isMulti
name="colors"
options={options.map((x) => MakeOption(x)).filter(opt => opt.value !== "")}
className="basic-multi-select"
classNamePrefix="select"
closeMenuOnSelect={false}
onInputChange={handleInputChange}
inputValue={value}
noOptionsMessage={() => null}
/>
);
};
export default Foo;
Could you help me please ?
I think this code works like you want.
The problem have been solved with a second variable for the select options.
import React, { Component } from "react";
import Select, { components} from "react-select";
import { useState } from "react";
let cheeses = ["Wagasi", "Kalari", "Halloumi", "Manouri"];
let options = [];
options = options.concat(cheeses.map((x) => "Cheese - " + x));
const Foo = () => {
const [value, setValue] = useState("");
const [optionsToShow, setOptionsToShow] = useState([]);
function MakeOption(x) {
return { value: x, label: x };
}
const handleInputChange = (value, e) => {
if (e.action === "input-change") {
setValue(value);
const nextOptions = value ? options.map((x) => MakeOption(x)).filter((opt) => opt.label.toLowerCase().includes(value.toLowerCase())) : [];
setOptionsToShow(nextOptions.length > 3 ? nextOptions.splice(1,3) : nextOptions);
}
};
const Input = props => <components.Input {...props} maxLength={5} />;
return (
<Select
isMulti
name="colors"
options={optionsToShow}
className="basic-multi-select"
classNamePrefix="select"
closeMenuOnSelect={false}
onInputChange={handleInputChange}
inputValue={value}
noOptionsMessage={() => null}
/>
);
}
export default Foo;
I hope I've helped you

React js material ui core table get data from row on click

I have found a code for a material table that accepts a list as input and applies pagination, sorting and filtering on it. The thing is I need to find a way to extract the data from the row onClick and redirect the page to a new route along with those data. How can I do that?
In the component, I call the table as follows:
export default function ViewAllUsers() {
const [filterFn, setFilterFn] = useState({ fn: items => { return items; } })
const records = ....//List of records
const {
TblContainer,
TblHead,
TblPagination,
recordsAfterPagingAndSorting
} = useTable(records, headCells, filterFn);
const handleSearch = e => {
let target = e.target;
//Handle search
}
return (
<>
<Paper className={classes.pageContent}>
<Toolbar>
<Controls.Input onChange={handleSearch}/>
</Toolbar>
<TblContainer>
<TblHead />
<TableBody>
{
recordsAfterPagingAndSorting().map(item =>
(<TableRow key={item.id}>
<TableCell>{item.id}</TableCell>
<TableCell>{item.fullName}</TableCell>
</TableRow>)
)
}
</TableBody>
</TblContainer>
<TblPagination/>
</Paper>
}
and the useTable hook is:
export default function useTable(records, headCells, filterFn) {
const pages = [5, 10, 25]
const [page, setPage] = useState(0)
const [rowsPerPage, setRowsPerPage] = useState(pages[page])
const [order, setOrder] = useState()
const [orderBy, setOrderBy] = useState()
const TblContainer = props => (
<Table className={classes.table}>
{props.children}
</Table>
)
const TblHead = props => {
const handleSortRequest = cellId => {
//Some code
}
return (<TableHead>
<TableRow>
{
headCells.map(headCell => (
<TableCell key={headCell.id}
sortDirection={orderBy === headCell.id ? order : false}>
{headCell.disableSorting ? headCell.label :
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : 'asc'}
onClick={() => { handleSortRequest(headCell.id) }}>
{headCell.label}
</TableSortLabel>
}
</TableCell>))
}
</TableRow>
</TableHead>)
}
const TblPagination = () => (
<TablePagination
component="div"
page={page}
rowsPerPageOptions={pages}
rowsPerPage={rowsPerPage}
count={records.length}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
id="TablePagination"
/>
)
return {
TblContainer,
TblHead,
TblPagination,
recordsAfterPagingAndSorting
}
}
You can simply use an onClick handler to pass the item data through it:
export default function ViewAllUsers() {
const [filterFn, setFilterFn] = useState({ fn: items => { return items; } })
const records = ....//List of records
const {
TblContainer,
TblHead,
TblPagination,
recordsAfterPagingAndSorting
} = useTable(records, headCells, filterFn);
const handleSearch = e => {
let target = e.target;
//Handle search
}
const handleItemClick = item => {
//Redirect to new route from here with the item data
}
return (
<>
<Paper className={classes.pageContent}>
<Toolbar>
<Controls.Input onChange={handleSearch}/>
</Toolbar>
<TblContainer>
<TblHead />
<TableBody>
{
recordsAfterPagingAndSorting().map(item =>
(<TableRow key={item.id} onClick={() => handleItemClick(item)}>
<TableCell>{item.id}</TableCell>
<TableCell>{item.fullName}</TableCell>
</TableRow>)
)
}
</TableBody>
</TblContainer>
<TblPagination/>
</Paper>
</>
)
}

Update state in class from const

I'm new to React JS and I'm coding a really simple task manager. So, I have all tasks in state element of MyTodoList class (each task has: id, name, description, completed). Then I draw each task separately with Task constant.
I want to implement changing buttons below every task (if task is completed button should be "Done", if not - "Not done").
I do not understand how I can update "completed" attribute (which is in MyTodoList class in state) from const Task.
Would be grateful for any hint!
Code:
import logo from './logo.svg';
import './App.css';
import React from 'react';
function DoneButton({onClick}) {
return (
<button onClick={onClick}>
Done
</button>
);
}
function NotDoneButton({onClick}) {
return (
<button onClick={onClick}>
Not done
</button>
);
}
const Task = ({id, name, description, completed}) => {
const handleDoneClick = () => {
completed= false //something different should be here
}
const handleNotDoneClick = () => {
completed= true //something different should be here
}
let button;
if (completed) {
button = <DoneButton onClick={handleDoneClick} />
} else {
button = <NotDoneButton onClick={handleNotDoneClick} />
}
return (
<div className='task'>
<h3>{name}</h3>
<div>{description}</div>
<div>{completed}</div>
{button}
</div>
)
}
class MyTodoList extends React.Component {
state = {
tasks: [
{
id: 1,
name: 'Walk the dog',
description: 'Have to walk the dog today',
completed: false,
},
],
}
render () {
return(
<div>
<header><h1>TO-DO</h1></header>
<div>{this.state.tasks.map(task => <Task id={task.id} name={task.name}
description={task.description} completed={task.completed}/>)}
</div>
</div>
)
}
}
const App = () => {
return (
<MyTodoList />
)
}
export default App;
You should never re-assign parameters unless it is the only solution you have, but you should definitely never re-assign parameters which you plan to depend on in the render method.
The proper solution would be this:
import React, { useState } from 'react';
...
const Task = ({ id, name, description, completed }) => {
const [isCompleted, setIsCompleted] = useState(completed);
const handleDoneClick = () => {
setIsCompleted(true);
};
const handleNotDoneClick = () => {
setIsCompleted(false);
};
let button;
if (isCompleted) {
button = <DoneButton onClick={handleDoneClick} />;
} else {
button = <NotDoneButton onClick={handleNotDoneClick} />;
}
return (
<div className="task">
<h3>{name}</h3>
<div>{description}</div>
<div>{isCompleted}</div>
{button}
</div>
);
};
You need to use local state, in which you will set the initial value (completed, or not completed) which you are receiving from props, and then change the state, and not the parameter. Furthermore, continue using the state value of your completed (isCompleted) so React will react to its change.
This is not the final solution though, as this will only keep the local change of the task, and not change the task status in tasks list.
Basically, if you component A holds the tasks and their complete status, you need to create a method in the component A which will modify the respective task by ID, to the correct status. Then you need to pass the respective method to component B which will call the method and pass along the id and complete status (true / false) The method which is assigned in component A will then look through the list of tasks, find the proper task by ID, and assign its new completed value you passed from component B. After that, react does its thing and automatically updates completed prop you passed to component B
Working snippet:
function DoneButton({ onClick }) {
return <button onClick={onClick}>Done</button>;
}
function NotDoneButton({ onClick }) {
return <button onClick={onClick}>Not done</button>;
}
const Task = ({ id, name, description, completed, onTaskClick }) => {
const handleDoneClick = () => {
onTaskClick(id, false);
};
const handleNotDoneClick = () => {
onTaskClick(id, true);
};
let button;
if (completed) {
button = <DoneButton onClick={handleDoneClick} />;
} else {
button = <NotDoneButton onClick={handleNotDoneClick} />;
}
return (
<div className="task">
<h3>{name}</h3>
<div>{description}</div>
<div>{completed}</div>
{button}
</div>
);
};
const MyTodoList = () => {
const [tasks, setTasks] = React.useState([
{
id: 1,
name: 'Walk the dog',
description: 'Have to walk the dog today',
completed: false,
}
]);
const onTaskClick = React.useCallback(
(id, isCompleted) => {
const updatedTasks = [...tasks].map((task) => {
if (task.id === id) {
return {
...task,
completed: isCompleted,
};
}
return task;
});
setTasks(updatedTasks);
},
[tasks]
);
return (
<div>
<header>
<h1>TO-DO</h1>
</header>
<div>
{tasks.map((task) => (
<Task onTaskClick={onTaskClick} id={task.id} name={task.name} description={task.description} completed={task.completed} />
))}
</div>
</div>
);
};
const App = () => <MyTodoList />;
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>

React: Passing a Prop in w/ event for setState

I'm working with a nested state object that I have been updating with onChange functions, like so:
const [someState, setSomeState] = useState({
customer: [
{
name: "Bob",
address: "1234 Main Street",
email: "bob#mail.com",
phone: [
{
mobile: "555-5555",
home: "555-5555"
}
]
}
]
});
const updateSomeStatePhone = e => {
e.persist();
setSomeState(prevState => {
prevState.customer[0].phone[0].mobile = e.target.value;
return {
...prevState
};
});
};
<p>Update Mobile Number<p>
<select
value={someState.customer[0].phone[0].mobile}
onChange={updateSomeStatePhone}
>
<option value="123-4567">"123-4567"</option>
</select>
This gets the trick done. Currently however, if I want to update multiple state properties via a large form with dropdowns/input fields etc, I have to hard code 6 different onChange handlers for those fields.
Instead, I would prefer to have only one onChange handler, and pass in the state from the form field for the state property that I am changing, but I can't figure out the syntax:
const updateSomeState = (e, prop) => {
e.persist();
setSomeState(prevState => {
prevState.prop = e.target.value;
return {
...prevState
};
});
};
<p>Update Mobile Number<p>
<select
value={someState.customer[0].phone[0].mobile}
onChange={updateSomeState(e, prop)}
>
<option value="123-4567">"123-4567"</option>
</select>
I've tried using different types of syntax to chain the passed in 'prop' value to prevState:
prevState.prop = e.target.value;
prevState.(prop) = e.target.value;
${prevState} + '.' + ${prop} = e.target.value; // Dumb, I know
But the function never recognizes the "prop" that I pass in from the function. I'm sure there must be a simple way to do this. Any help would be greatly appreciated.
Does it have to be a single useState hook? I would recommend using useReducer or simplifying it a bit with multiple useState hooks.
Multiple useState hooks
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [name, setName] = React.useState("");
const [address, setAddress] = React.useState("");
const [email, setEmail] = React.useState("");
const [mobile, setMobile] = React.useState("");
const [home, setHome] = React.useState("");
const getResult = () => ({
customer: [
{
name,
address,
email,
phone: [
{
mobile,
home
}
]
}
]
});
// Do whatever you need to do with this
console.log(getResult());
return (
<>
<input
value={name}
placeholder="name"
onChange={e => setName(e.target.value)}
/>
<br />
<input
value={address}
placeholder="address"
onChange={e => setAddress(e.target.value)}
/>
<br />
<input
value={email}
placeholder="email"
onChange={e => setEmail(e.target.value)}
/>
<br />
<input
value={mobile}
placeholder="mobile"
onChange={e => setMobile(e.target.value)}
/>
<br />
<input
value={home}
placeholder="home"
onChange={e => setHome(e.target.value)}
/>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Single useReducer (with simplified state)
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const reducer = (state, action) => {
const { type, value } = action;
switch (type) {
case "SET_NAME":
return { ...state, name: value };
case "SET_ADDRESS":
return { ...state, address: value };
case "SET_EMAIL":
return { ...state, email: value };
case "SET_MOBILE":
return { ...state, phone: [{ ...state.phone[0], mobile: value }] };
case "SET_HOME":
return { ...state, phone: [{ ...state.phone[0], home: value }] };
default:
throw Error(`Unexpected action: ${action.type}`);
}
};
const initialState = {
name: "",
address: "",
email: "",
phone: [
{
mobile: "",
home: ""
}
]
};
function App() {
const [state, dispatch] = React.useReducer(reducer, initialState);
// Do what you need with state
console.log(state);
return (
<>
<input
value={state.name}
placeholder="name"
onChange={({ target: { value } }) =>
dispatch({ type: "SET_NAME", value })
}
/>
<br />
<input
value={state.address}
placeholder="address"
onChange={({ target: { value } }) =>
dispatch({ type: "SET_ADDRESS", value })
}
/>
<br />
<input
value={state.email}
placeholder="email"
onChange={({ target: { value } }) =>
dispatch({ type: "SET_EMAIL", value })
}
/>
<br />
<input
value={state.phone.mobile}
placeholder="mobile"
onChange={({ target: { value } }) =>
dispatch({ type: "SET_MOBILE", value })
}
/>
<br />
<input
value={state.phone.home}
placeholder="home"
onChange={({ target: { value } }) =>
dispatch({ type: "SET_HOME", value })
}
/>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
useReducer is a better choice for doing this. Examples all over the internet.
Why you shouldn't use useState to pass an object is because it doesn't act like setState. The underlying object reference is the same. Therefore, react will never trigger a state change. In case you want to use the same useState for objects. You may have to implement your own version to extend that (example below ) or you can directly use useReducer hook to achieve the same.
Here's an example with useState for you to notice the state update on every change.
const [form, setValues] = useState({
username: "",
password: ""
});
const updateField = e => {
setValues({
...form,
[e.target.name]: e.target.value
});
};
Notice the ...form in there. You can do it this in every update you want or you can use your own utility or useReducer as I mentioned.
Now coming to your code, there are other concerns.
You are using your phone as an array which can be an object. Or better yet separate properties will do as well. No harm.
If you have customers as an array, you have to loop through the records. Not just update the index by hardcoding. If there's only one customer better not keep the array but just an object. Assuming it is an array of customers, and you are looping through it, here's how to update mobile.
const updatedCustomers = state.customers.map(item => {
const { phone } = item;
return { ...item, phone: { mobile: e.target.value }};
// returns newCustomer object with updated mobile property
});
// Then go ahead and call `setSomeState ` from `useState`
setSomeState(...someState, { customer: updatedCustomers });// newState in your case is
Instead, I would prefer to have only one onChange handler, and pass in
the state from the form field for the state property that I am
changing, but I can't figure out the syntax
If you haven't figured that out from the first example. Here's how in short steps.
Give your HTML element a name attribute.
Then instead use the [e.target.name]
return { ...item, phone: { [e.target.name]: e.target.value }};
Use lodash's _.set helper.
const updateSomeState = (e, prop) => {
e.persist();
setSomeState(prevState => {
let customers = [...prevState.customers] // make a copy of array
let customer = {...customers[0]} // make a copy of customer object
_.set(customer, prop, e.target.value)
customers[0] = customer;
return {
...prevState, customers
};
});
};
BTW, in your existing updateSomeStatePhone you are modifying prevState object which is supposed to be immutable.

Reactjs fetch method returns empty array

I am trying to fetch some data, which is in the form:
[
{
"id": 1,
"some_data": "..."
},
...
]
What I am trying to get is a list displaying the items from the fetch. If I put the same data in a file within the project, it works.
However when I tried to map it, I got an error saying "this.data.map is not a function". So I changed it a bit by using Array.from(). It currently looks like this:
export default class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
items = [];
};
this.getData = this.getData.bind(this);
}
getData = () => {
fetch("URL",{
method: "get",
header: { "Content-Type": "application/json" }
})
.then(response => {
var array = Array.from(response.json())
this.setState({items: array});
})
}
render() {
const list = this.state.items.map((r, i) => {
return (
<Item
id = { r[i].id }
some_data = { r[i].some_data }
...
/>
)
})
return(
<div>
<Item
p = {list}
>
</div>
)
}
}
First of all no state is neede to store the response. Its happening due to the state value is not reflecting in your render.
Call a function inside success response & map the response inside the function & set State there.
OR
Put the below code outside render function assigning to variable like below
const list = this.state.items.map((r, i) => {
return (
)
})
return(
)
}
render () {
{list}
}
Try something like this.....
It's better to load the data once component is mounted. Also, there's no URL, I'm assuming that you've hidden this.
Once you 'see' what's in response, you can code against that accordingly.
export default class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
items = [];
};
// this.getData = this.getData.bind(this);
}
componentDidMount(){
// Attempt to load data once component mounted.
this.getData();
}
getData = () => {
// Don't you need the URL below, or have you deliberately hidden it?
fetch("URL",{
method: "get",
header: { "Content-Type": "application/json" }
})
.then(response => {
console.log(response); // See exactly what is in response....
var array = Array.from(response.json())
console.log(array); // Check array is really what you want
// You could try a JSON.Parse....
var jsonArray = JSON.Parse(response);
console.log(jsonArray);
this.setState({items: array});
})
}
render() {
const list = this.state.items.map((r, i) => {
return (
<Item
id = { r[i].id }
some_data = { r[i].some_data }
...
/>
)
})
return(
<div>
<Item
p = {list}
>
</div>
)
}
}