I am making 2 otp input in my application.
In Input.tsx, I am using react-otp-input for the otp functionality
if
<OtpInput
value={"abcde"}
...
numInputs={5}
/>
The UI of react-otp-input will be
Now the problem is, when I try to change the value of otp, it throws error
Cannot set properties of undefined (setting 'value')
How can I fix it?
Input.tsx
import React, { useState } from "react";
import OtpInput from "react-otp-input";
type InputPropType = {
value: string;
setValue: (event: string) => void;
};
function Input(props: InputPropType): JSX.Element {
const { value, setValue } = props;
return (
<OtpInput
value={value}
onChange={(e: string) => {
setValue(e);
}}
numInputs={5}
/>
);
}
export default Input;
App.tsx
import React, { useState } from "react";
import Input from "./Input";
export default function App() {
type InputValueType = {
id: number;
value: string;
};
const [inputValues, setInputValues] = useState<Array<InputValueType>>([
{ id: 0, value: "" },
{ id: 1, value: "" }
]);
const InputGroup = () => {
let numOfInputs: number = 2;
var rows: Array<any> = [];
for (var i = 0; i < numOfInputs; i++) {
let inputValue: InputValueType = inputValues[i];
rows.push(
<Input
key={inputValue.id}
value={inputValue.value}
setValue={(event: string) => {
let inputValuesTemp = inputValues;
inputValuesTemp[i]["value"] = event;
setInputValues(inputValuesTemp);
}}
/>
);
}
return <>{rows}</>;
};
return (
<div className="App">
<InputGroup />
</div>
);
}
Codesandbox
https://codesandbox.io/s/react-typescript-forked-s38ck9?file=/src/App.tsx:0-918
Few things to be corrected,
There is an issue with closures in your for-loop since you have used var instead of let. All the i variables refer to the same for-loop closure in the for-loop, which means i is 2 at the end of the iteration.
All the inputValuesTemp[i] are now resolved to inputValuesTemp[2] which is definitely undefined.
Replace var with let to create closures for each iteration of the loop.
for (let i = 0; i < numOfInputs; i++) {...}
And, you also need o get a copy of the values array for react to do a rerender (to inform that the array has changed).
let inputValuesTemp = [...inputValues];
Your InputGroup component was within the App component, which caused you to lose focus for each keystroke. To fix the focus issue, move the InputGroup out of the App and keep it as a separate component.
import React, { useState } from "react";
import Input from "./Input";
type InputValueType = {
id: number;
value: string;
};
const InputGroup = () => {
const [inputValues, setInputValues] = useState<Array<InputValueType>>([
{ id: 0, value: "" },
{ id: 1, value: "" }
]);
let numOfInputs: number = 2;
var rows: Array<any> = [];
for (let i = 0; i < numOfInputs; i++) {
let inputValue: InputValueType = inputValues[i];
rows.push(
<Input
key={inputValue.id}
value={inputValue.value}
setValue={(event: string) => {
let inputValuesTemp = [...inputValues];
inputValuesTemp[i]["value"] = event;
setInputValues(inputValuesTemp);
}}
/>
);
}
return <>{rows}</>;
};
export default function App() {
return (
<div className="App">
<InputGroup />
</div>
);
}
Related
I'm having a functional components, where parent retrieves data from couple of arrays from API and passes them as single object to children. I get the data in parent's useEffect. Unfortunately it seems I'm not getting any data to the component, when debugging the dictionary object in child looks empty.
So in my parent tsx (cutted out nonimportant parts):
export default function RozliczeniaFaktur ({web, context, formType}: IRozliczeniaFakturProps) {
const [dataRows, setDataRows] = React.useState(emptyRowArr);
const [isError, setIsError] = React.useState(false);
const [errorMessage, setErrorMessage] = React.useState(null);
const [dictionary, setDictionary] = React.useState<IDictionary>({
organizacja: [], mpk: [], vat: [], zastepstwa: [], konto: []
});
const initDictionary = () => {
let dict : IDictionary = {
organizacja: [], mpk: [], vat: [], zastepstwa: [], konto: []
};
web.lists.getByTitle("Organizacja").items.get().then((items: any[])=> {
dict.organizacja = items;
}, (e: any) => setError(`"Błąd pobierania listy Organizacja: ${e}"`));
// ommited for brevity
setDictionary(dict);
}
React.useEffect(() => {
initDictionary();
},[]);
const addRow = (rowId: number) => {
let d = dataRows;
let i = dataRows[rowId];
d.push(i);
setDataRows(d);
}
return (
<div className={styles.rozliczeniaFaktur}>
{dataRows &&
dataRows.map((dataRow, i) => {
return <TableRowControl web={web} key={i} Context={context} RowId={i} PozFaktury={dataRow} FormType={formType}
onAddRowCallback={addRow} onDeleteRowCallback={delRow} Dictionary={dictionary} />
})
}
</div>
);
}
And in child tsx:
import * as React from 'react';
import styles from './TableRowControl.module.scss';
import {IPozFaktury} from '../IPozFaktury';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { DateTimePicker, DateConvention, IDateTimePickerStrings } from '#pnp/spfx-controls-react/lib/DateTimePicker';
import { Dropdown, IDropdownOption, IDropdownStyles } from 'office-ui-fabric-react/lib/Dropdown';
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
import { PeoplePicker, PrincipalType } from "#pnp/spfx-controls-react/lib/PeoplePicker";
import { WebPartContext } from '#microsoft/sp-webpart-base';
import { DayOfWeek } from 'office-ui-fabric-react';
import { Web } from '#pnp/sp';
import { IconButton } from 'office-ui-fabric-react/lib/Button';
import { IDictionary } from '../IDictionary';
export interface ITableRowControlProps {
RowId: Number;
PozFaktury: IPozFaktury;
FormType: String;
Context: WebPartContext;
web: Web;
Dictionary: IDictionary;
onAddRowCallback?: (...event: any[]) => void;
onDeleteRowCallback?: (...event: any[]) => void;
onErrorCallback?: (...event: any[]) => void;
}
const TableRowControl = ({RowId, Context, PozFaktury, FormType, onAddRowCallback, onDeleteRowCallback, web, Dictionary, onErrorCallback} : ITableRowControlProps) => {
const [oddzialRozliczeniaOptions, setOddzialRozliczeniaOptions] = React.useState<IDropdownOption[]>([]);
const [vatOptions, setVatOptions] = React.useState<IDropdownOption[]>([]);
const [mpkOptions, setMpkOptions] = React.useState<IDropdownOption[]>([]);
const [kontoGlOptions, setKontoGlOptions] = React.useState<IDropdownOption[]>([]);
const [zaliczkaOptions, setZaliczkaOptions] = React.useState<IDropdownOption[]>([]);
const [isPZrequired, setIsPZrequired] = React.useState(false);
return(
<tr>
<td className={styles.cell}>
<IconButton onClick={()=> onAddRowCallback(RowId)} iconProps={{ iconName: 'Add' }} title="Dodaj" ariaLabel="Dodaj" />
<IconButton onClick={()=> onDeleteRowCallback(RowId)} iconProps={{ iconName: 'Delete' }} title="Usuń" ariaLabel="Usuń" />
</td>
{/* Oddzial rozliczenia */}
<td className={styles.cell}>
<Dropdown options={Dictionary.organizacja.map(i => {return { key: i.Title, text: i.Organizacja }})} selectedKey={PozFaktury.OddzialRozliczenia} />
</td>
</tr>
);
}
export default TableRowControl;
I'm getting no options in dropdowns (works when bound to static array), and when debugging, the Dictionary object in child looks like in it's initial state in parent (with empty arrays).
EDIT:
OK, i seem to have solved the first issue, but now I've got another similar one. When I press 'Add', it looks like the code is adding a copy of a row to array dataRows (correctly), but on screen it's still one row. Where is the problem?
I'm learning React hooks so in order to do that I'm trying to convert a class component to a functional component but I still get some errors.
Here is the original working component written as a class:
import React, { Component } from 'react';
import NavBar from './components/navbar';
import Counters from './components/counters';
class App extends Component {
state = {
counters: [
{ id: 0, value: 5 },
{ id: 1, value: 1 },
{ id: 2, value: 2 },
],
};
handleDelete = (counterId) => {
const counters = this.state.counters.filter((c) => c.id !== counterId);
this.setState({ counters });
};
handleReset = () => {
const counters = this.state.counters.map((c) => {
c.value = 0;
return c;
});
this.setState({ counters });
};
handleIncrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters });
};
render() {
return (
<React.Fragment>
<NavBar
totalCounters={this.state.counters.filter((c) => c.value > 0).length}
/>
<main className='container'>
<Counters
counters={this.state.counters}
onReset={this.handleReset}
onDelete={this.handleDelete}
onIncrement={this.handleIncrement}
/>
</main>
</React.Fragment>
);
}
}
export default App;
And this is the converted version which uses hooks.
import React, { useState } from 'react';
import NavBar from './components/navbar';
import Counters from './components/counters';
const App = () => {
const [counters, setCounters] = useState([
{ id: 0, value: 5 },
{ id: 1, value: 1 },
{ id: 2, value: 2 },
]);
const handleDelete = (counterId) => {
const counterss = counters.filter((c) => c.id !== counterId);
setCounters({ counterss });
};
const handleReset = () => {
const counterss = counters.map((c) => {
c.value = 0;
return c;
});
setCounters({ counterss });
};
const handleIncrement = (counter) => {
const counterss = [...counters];
const index = counterss.indexOf(counter);
counterss[index] = { ...counter };
counterss[index].value++;
setCounters({ counterss });
};
return (
<React.Fragment>
<NavBar totalCounters={counters.filter((c) => c.value > 0).length} />
<main className='container'>
<Counters
counters={counters}
onReset={handleReset}
onDelete={handleDelete}
onIncrement={handleIncrement}
/>
</main>
</React.Fragment>
);
};
export default App;
Most of it works fine but it keeps throwing an error saying that filter is not a function. Here it is the message:
TypeError: counters.filter is not a function
The main culprit appears to be the way you are updating your state, which is like this:
setCounters({ counterss });
This will actually set your counters state to an object with the property counterss, so your state will contain the following:
{ counterss: [/* rest of the array */] }
The exact error being thrown is referring to the fact that you are attempting to call .filter on an object instead of an array. To fix this issue you should simply update your state like this:
setCounters(counterss);
setCounters({ counterss })
should be
setCounters(counterss)
It throws an error because you set the new state as an object setCounters({ counterss });. But you want an array: setCounters(counterss);. This way it won't throw an error the second time setCounters is called.
import ReactDOM from "react-dom";
import React, { useState } from "react";
const App = () => {
let [persons, setPersons] = useState([{ id: 11, name: "Arto Hellas" }]);
const [newName, setNewName] = useState("");
const check = (arr, name) => {
let i = 0;
let checking = true;
for (i = 0; i < arr.length; i++) {
console.log(arr[i].name === name);
if (arr[i].name === name) {
checking = false;
break;
}
console.log(i);
return checking;
}
};
const addName = event => {
event.preventDefault();
const nameObject = {
id: newName.length + 1,
name: newName
};
check(persons, nameObject.name)
? setPersons((persons = persons.concat(nameObject)))
: window.alert(`${nameObject.name} is already listed`);
setNewName("");
console.log(JSON.stringify(persons));
};
const handleNameChange = event => {
setNewName(event.target.value);
};
return (
<div>
<h2>Phonebook</h2>
<form onSubmit={addName}>
<div>
name: <input value={newName} onChange={handleNameChange} />
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
<ul>
{persons.map(person => (
<li key={person.id}>{person.name} </li>
))}
</ul>
</div>
);
};
export default App;
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
The problem is with my check function which checks if a name exists in the array. I have a for loop that iterates throughout the array but it always stops early. I checked this with console.log(i). If you add Adib once the array looks like this [{"id":11,"name":"Arto Hellas"},{"id":5,"name":"Adib"}] and the value of i is 0 since because before this the length of arr was 1. However if you add Adib again it will do so and value of i is 0 again and not 1
You have return checking in loop. Just put it after }:
const check = (arr, name) => {
let i = 0;
let checking = true;
for (i = 0; i < arr.length; i++) {
console.log(arr[i].name === name);
if (arr[i].name === name) {
checking = false;
break;
}
console.log(i);
}
return checking;
};
See full example in playground: https://jscomplete.com/playground/s522611
I'm sorry because I'm not on VScode right now I don't have a plugin which lets me see my brackets more clearly and accidentally had my return statement in a for loop.
Im wondering how I can call a method from outside of a React Functional Component. I wrote the function GetUsedLockers() which gets all the used lockers and returns amount. Now I want to call this function from another another component (OrgLocker.tsx) and display the data from the getUsedLockers() function there.
OrgLockerTables.tsx
const OrgLockerTables: React.FC = () => {
const lockerCall = 'lockers';
const [lockerData, setLockerData] = useState({
id: 0,
guid: "",
is_currently_claimable: false
}[""]);
useEffect(() => {
componentConsole().then((res) => {
setLockerData(res);
})
// eslint-disable-next-line
}, []);
if (!lockerData) return (<div>Loading...</div>);
//function to get all used lockers
function getUsedLockers() {
let amount = 0;
for (let i = 0; i < lockerData.length; i++) {
if (!lockerData.is_currently_claimable) {
amount++;
}
}
console.log('log from getusedlockers, amount: ', amount)
return (amount)
}
// function to get JSON data from the API
function componentConsole(): Promise<any> {
return new Promise<any>((resolve, reject) => {
http.getRequest('/' + lockerCall).then((res) => {
let data = res.data.data;
console.log('data:', data);
resolve(res.data.data);
}).catch((error) => {
console.log(error);
reject();
});
})
}
}
OrgLocker.tsx
import OrgLockerTables from '../tables/orgLockerTables';
const OrgLockers: React.FC = () => {
let lockerTable = new OrgLockerTables();
return (
<div className="main-div-org">
<p>Used</p>
<p>{lockerTable.getUsedLockers()}</p>
</div>
);
}
export default OrgLockers;
When trying to make a call to OrgLockerTables and storing it in the lockerTable let it gives the following error:
Expected 1-2 arguments, but got 0.ts(2554)
Any help would be greatly appreciated!
I've restructured everything making it more understandable, I hope you don't mind according to what I think you want the comment above.
locker-model.ts - The type for the particular data being called back is found
export type Locker = {
id: number;
guid: string;
isCurrentlyClaimable: boolean;
}
locker-business.ts - Where all the business logic is carried out, from the call for data to the calculation based on it
import { Locker } from "./locker-models";
const lockerCall = 'lockers';
const mockedData: Locker[] = [{
id: 0,
guid: "sample",
isCurrentlyClaimable: false,
},
{
id: 1,
guid: "sample2",
isCurrentlyClaimable: true,
},
{
id: 2,
guid: "sample3",
isCurrentlyClaimable: true,
}]
// Mocked function from your backend (componentConsole where you use lockerCall variable)
export const getLockersData = (): Promise<Locker[]> => Promise.resolve(mockedData);
export const getAmount = (lockers: Locker[]): number => {
let amount = 0;
!!lockers ?
lockers.filter(({isCurrentlyClaimable}) => { if(isCurrentlyClaimable) amount++ })
: 0;
return amount;
};
index.tsx - Here are both components that make the call to get the data and render the result you're looking for
import React, { Component } from 'react';
import { Locker } from './locker-models';
import { getLockersData, getAmount } from './locker-business';
import './style.css';
type OrgLockersProps = {
amount: number;
}
const OrgLockers: React.FC<OrgLockersProps> = ({ amount }) => {
return (
<div className="main-div-org">
<p>Lockers used:</p>
<p>{amount}</p>
</div>
);
}
type OrgLockerTableProps = {};
const OrgLockerTable : React.FC<OrgLockerTableProps> = props => {
const [lockerData, setLockerData] = React.useState<Locker[]>([]);
React.useEffect(() => {
getLockersData().then(response => setLockerData(response));
}, []);
const amount = getAmount(lockerData);
return (
<div>
<OrgLockers amount={amount} />
</div>
);
};
You can see the example here
You can create new .js file like Helpers.js and define export function with parameter it like that
export function getUsedLockers(lockerData) {
let amount = 0;
//Check your loop it can be like that
for (let i = 0; i < lockerData.length; i++) {
if (!lockerData[i].is_currently_claimable) {
amount++;
}
}
console.log('log from getusedlockers, amount: ', amount)
return (amount)
}
Then import it where do you want to use.
import {getUsedLockers} from "../Helpers";
And use it like that:
const amount = getUsedLockers(data);
I'm trying to update the state of a parent component from a child using a callback. The state and call back are passed to a text input. The callback is being called, the state of the parent is changed, but it doesn't rerender. The value of the input field stays the same. If force rendering is used, the text field updates every time a new character is added (As desired). I'm not sure what could be causing this, from my understanding the setState hooks provided are supposed to rerender unless the state is unchanged.
EDIT: (Added the parent component not just the callback)
Below is the parent component
import Card from './Card'
import Instructions from './instructions'
import Title from './title'
import React, { useRef, useState, useCallback, useEffect } from 'react'
import { DropTarget } from 'react-dnd'
import ItemTypes from './ItemTypes'
import update from 'immutability-helper'
const Container = ({ connectDropTarget }) => {
const ref = useRef(null)
const titleRef = useRef()
const instructionsRef = useRef()
const appRef = useRef()
useEffect(() => {
// add when mounted
document.addEventListener("mousedown", handleClick);
// return function to be called when unmounted
return () => { document.removeEventListener("mousedown", handleClick);};
}, []);
const handleClick = e => {
if (titleRef.current.contains(e.target)) {
setFocus("Title");
return;
} // outside click
else if(instructionsRef.current.contains(e.target)){
setFocus("Instructions");
return;
}
setFocus(null);
};
const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
const [focus,setFocus] = useState(null);
const [title, setTitle] = useState({id: "Title", text: "Default",type: "Title", data:[]});
const [instructions, setInstructions] = useState({id: "Instructions",type:"Instructions", text: "Instructions", data:[]});
const [cards, setCards] = useState([
{
id: 1,
text: 'Write a cool JS library',
},
{
id: 2,
text: 'Make it generic enough',
},
{
id: 3,
text: 'Write README',
},
{
id: 4,
text: 'Create some examples',
},
{
id: 5,
text: 'Spam in Twitter and IRC to promote it',
},
{
id: 6,
text: '???',
},
{
id: 7,
text: 'PROFIT',
},
])
const moveCard = useCallback(
(id, atIndex) => {
const { card, index } = findCard(id)
setCards(
update(cards, {
$splice: [[index, 1], [atIndex, 0, card]],
}),
)
},
[cards],
)
const findCard = useCallback(
id => {
const card = cards.filter(c => `${c.id}` === id)[0]
return {
card,
index: cards.indexOf(card),
}
},
[cards],
)
const updateItem = useCallback(
(id,field,additionalData,value) => {
return;
},
[cards], //WHat does this do?
)
const updateTitle = text => {
console.log("Updating title")
let tempTitle = title;
tempTitle['text'] = text;
//console.log(text);
//console.log(title);
//console.log(tempTitle);
setTitle(tempTitle);
//console.log(title);
//console.log("done");
forceUpdate(null);
}
connectDropTarget(ref)
return (
<div ref={appRef}>
<div ref={titleRef} >
<Title item={title} focus={focus} updateFunction={updateTitle}/>
</div>
<div ref={instructionsRef} >
<Instructions item={instructions} focus={focus}/>
</div>
<div className="Card-list" ref={ref}>
{cards.map(card => (
<Card
key={card.id}
id={`${card.id}`}
text={card.text}
moveCard={moveCard}
findCard={findCard}
item={card}
focus={focus}
/>
))}
</div>
</div>
)
}
export default DropTarget(ItemTypes.CARD, {}, connect => ({
connectDropTarget: connect.dropTarget(),
}))(Container)
The code of the component calling this function is:
import React from 'react'
function Title(props) {
if(props.focus === "Title")
return(
<input
id="Title"
class="Title"
type="text"
value={props.item['text']}
onChange = { e => props.updateFunction(e.target.value)}
/>
);
else
return (
<h1> {props.item['text']} </h1>
);
}
export default Title
The problem is here
const updateTitle = text => {
let tempTitle = title; // These two variables are the same object
tempTitle['text'] = text;
setTitle(tempTitle); // problem is here
}
React uses the object.is() method to compare two values before and after. Look at this
Object.is(title, tempTitle) // true
You should make "title" and "tempTitle" different objects, like this
const updateTitle = text => {
let tempTitle = {...title}; // tempTitle is a new object
tempTitle['text'] = text;
setTitle(tempTitle);
}
And this is a demo of mutable object.
var a= {name:1}
var b = a;
b.name=2
var result = Object.is(a,b)
console.log(result)
// true