I have this solution for handling a fix number of states for a fix number of checkboxes:
import { useState } from 'react';
function App() {
const [arrValues, setArrValues] = useState(
// use a function here to only create the initial array on mount
() => Array.from(
{ length: 10 }
)
);
const setcheckBoxValue = (i) => {
setArrValues(
arrValues.map((v, j) => j !== i ? v : !v)
);
console.log(arrValues);
}
return (
<div className="App">
{arrValues.map( (val, i) =>
<input
key={i}
type="checkbox"
checked={val}
onChange={() => setcheckBoxValue(i)}
>
</input>)
}
</div>
);
}
export default App;
In the code it is 10, but, what if the length of the array is not known until I read some value in a database,
Rafael
You can do something like this. The example below is using an object rather than an array, but this is just example. Also, the “initialValues” could be easily generated from data (from database). These could be the undetermined values you mentioned.
const MyTest = () => {
const initialValues = {
values: { checkbox1: true, checkbox2: false, checkbox3: true }
}
const [state, setState] = useState(initialValues || {})
const handleChange = (event) => {
const target = event.target
const value = target.checked
const name = target.name
setState((prevState) => ({
values: {
...prevState.values,
[name]: value
}
}))
}
return (
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
{state.values &&
Object.keys(state.values).map((checkboxKey, i) => (
<input
name={checkboxKey}
key={i}
type="checkbox"
checked={state.values[checkboxKey]}
onChange={handleChange}
/>
))}
</div>
)
}
Related
I have list of controls which are sent by backend. Sometimes it is necessary to update options of dropdown control.
I thought that it would work. However, memoizedControls is not rerendered.
I believe that it should work like this:
press the button and handleFieldsChange() function is triggered.
then setTechSpec('') sets techSpec to ''
then custom hook usePerson is triggered because it has techSpec in its dependency array
then memoizedControls is triggered because it has personList in its dependency array
and updateControlOptions() updates options of controls
However, UI does not have rerendered and new options of personList is not rerendered.
const [techSpec, setTechSpec] = useState('1')
const [personList, isLoadingPersonList] = usePerson(techSpec)
const handleFieldsChange = (changedValue: EditablePersonValues) => {
setTechSpec('')
fetchPerson()}
const updateControlOptions = (
controls: FormControl[],
controlName: string,
newOptions: SelectOption[],
) =>
controls.map((control) =>
control.name === controlName
? { ...control, options: newOptions }
: { ...control },)
const memoizedControls = useMemo(() => {
console.log('memoizedControls')
if (personList.length > 0)
return updateControlOptions(
controls,
'personId',
personList,
)
return controls
}, [controls, personList])
const fetchPerson = () => {
const localTechSpecification = form.getFieldValue('techSpecification')
setTechSpec(localTechSpecification)
form.setFieldsValue({ personId: undefined })
}
and:
return (
{memoizedControls.map(
({ name, type, displayName, required, options, measure }) => {
return (
<MyDynamicField
key={name}
name={name}
controlType={type}
displayName={`${displayName}${measure ? `, ${measure}` : ''}`}
required={required}
value={value}
itemOptions={options}
/>
)
},
)}
)
My question is that "usePerson" hook is being re-executed when the "techSpec" state value changes. personList is updated. But memoizedControls does not show new values of personList. Maybe do you know the reason of why memoizedControls is not rerendered?
Please, does anybody know what I am doing wrong?
import React, { useState, useMemo } from 'react';
const MyComponent = ({ personList }) => {
const [techSpec, setTechSpec] = useState('1');
const [isLoadingPersonList, setIsLoadingPersonList] = useState(false);
const handleFieldsChange = (changedValue) => {
setTechSpec('');
fetchPerson();
};
const updateControlOptions = (controls, controlName, newOptions) =>
controls.map((control) =>
control.name === controlName
? { ...control, options: newOptions }
: { ...control }
);
const memoizedControls = useMemo(() => {
console.log('memoizedControls');
if (personList.length > 0) {
return updateControlOptions(controls, 'personId', personList);
}
return controls;
}, [controls, personList]); // personList is included in the dependency array
const fetchPerson = () => {
const localTechSpecification = form.getFieldValue('techSpecification');
setTechSpec(localTechSpecification);
form.setFieldsValue({ personId: undefined });
};
return (
<div>
{memoizedControls.map(({ name, type, displayName, required, options, measure }) => (
<DynamicField
key={name}
name={name}
controlType={type}
displayName={`${displayName}${measure ? `, ${measure}` : ''}`}
required={required}
value={value}
itemOptions={options}
/>
))}
</div>
);
};
The above code was fine, the problem was in component <MyDynamicField/>.
So itemOptions should be added in dependency array [height, width, itemOptions]) of useEffect. So code would look like this:
export const MyDynamicField = ({
name,
controlType,
displayName,
required,
value,
itemOptions,
moulds,
initData,
height,
width,
}: Props) => {
const [options, setOptions] = useState<SelectOption[]>([])
useEffect(() => {
const prepareOptions = filterOptions(
name,
moulds,
initData,
itemOptions,
height,
width,
)
setOptions(prepareOptions)
}, [height, width, itemOptions])
return (
<ControlFactory
key={name}
name={name}
controlType={controlType}
displayName={displayName}
required={required}
options={options}
value={value}
/>
)
}
and then useEffect and useState can be removed and useMemo can be used:
export const MyDynamicField = ({
name,
controlType,
displayName,
required,
value,
itemOptions,
moulds,
initData,
height,
width,
}: Props) => {
const options = useMemo(
() => filterOptions(name, moulds, initData, itemOptions, height, width),
[name, moulds, initData, itemOptions, height, width],
)
return (
<ControlFactory
key={name}
name={name}
controlType={controlType}
displayName={displayName}
required={required}
options={options}
value={value}
/>
)
}
Below, i am rendering <App/> component with children as <Input/> component array. I added few inputs using "add new" button. I am able to add input text components. But, when i am typing value in text, it is not displaying. i am not able to modify object in state array since index is showing as "-1" in setData function. Due to this, value is not showing when we type in text box. Please let me know why state is [] when i am accessing in setData function.
function Input(props)
{
return (
<div>
<label htmlFor='variable'>Name</label>
<input id='variable'
type='text'
value={props.value}
onChange={(e) => props.setData(props.id, e.target.value)} />
</div>
)
}
function App()
{
let [state, setState] = React.useState([])
let [inputs, setInputs] = React.useState([])
let setData = ((id, value) =>
{
console.log(state); // prints []
let index = state.findIndex(ele => ele.key === id);
console.log(index); // prints -1
if (!(index === -1))
{
setState(state =>
{
state[idx]["value"] = value;
})
}
})
let handleAdd = () =>
{
let idx = `${new Date().getTime()}`
let tempState = {
"key": idx,
"value": "",
}
setState(state => [...state, tempState])
let input = <Input key={tempState.key}
value={tempState.value}
id={tempState.key}
setData={setData} />
setInputs(inputs => [...inputs, input])
}
return (
<div>
<button onClick={handleAdd}>add new</button>
<div>
{inputs}
</div>
</div>
)
}
When you create an Input component inside handleAdd, it creates a closure and as a result setData gets the state that existed when the component was created, missing the newly added state.
In general, creating components and saving them to state is not a good approach. Instead it's better to only save the data onto state and render the components based on it.
Here's one way to do this, note how much simpler the component and its logic are.
function App() {
let [state, setState] = React.useState([]);
let setData = (id, value) => {
const newState = state.map((st) => {
if (st.key === id) {
st.value = value;
}
return st;
});
setState(newState);
};
const addInput = () => {
const idx = `${new Date().getTime()}`;
setState([...state, { key: idx, value: '' }]);
};
return (
<div>
<button onClick={addInput}>add new</button>
<div>
{state.map((st) => (
<Input value={st.value} key={st.key} setData={setData} id={st.key} />
))}
</div>
</div>
);
}
I have this table with select menu:
export interface IActivePairsProps extends StateProps, DispatchProps, RouteComponentProps<{ url: string }> {}
export const ActivePairs = (props: IActivePairsProps) => {
const [paginationState, setPaginationState] = useState(
overridePaginationStateWithQueryParams(getSortState(props.location, ITEMS_PER_PAGE, 'id'), props.location.search)
);
const [exchangeId, setExchangeId] = useState('');
const getAllEntities = () => {
props.getEntities(paginationState.activePage - 1, paginationState.itemsPerPage, `${paginationState.sort},${paginationState.order}`);
props.getExchangesList();
};
const sortEntities = () => {
getAllEntities();
const endURL = `?page=${paginationState.activePage}&sort=${paginationState.sort},${paginationState.order}&exchangeId=${exchangeId}`;
if (props.location.search !== endURL) {
props.history.push(`${props.location.pathname}${endURL}`);
}
};
useEffect(() => {
sortEntities();
}, [paginationState.activePage, paginationState.order, paginationState.sort]);
useEffect(() => {
const params = new URLSearchParams(props.location.search);
const page = params.get('page');
const sort = params.get('sort');
if (page && sort) {
const sortSplit = sort.split(',');
setPaginationState({
...paginationState,
activePage: +page,
sort: sortSplit[0],
order: sortSplit[1],
});
}
const exchangeId = params.get('exchangeId');
}, [props.location.search]);
const sort = p => () => {
setPaginationState({
...paginationState,
order: paginationState.order === 'asc' ? 'desc' : 'asc',
sort: p,
});
};
const handlePagination = currentPage =>
setPaginationState({
...paginationState,
activePage: currentPage,
});
const handleSyncList = () => {
sortEntities();
};
const { activePairsList, exchangesList, match, loading, totalItems } = props;
return (
<div>
<div className="table-responsive">
{activePairsList && activePairsList.length > 0 ? (
<Table responsive>
<thead>
<tr>
.....
<select onChange={e => setExchangeId(e.target.value)}>
{exchangesList
? exchangesList.map(otherEntity => (
<option value={otherEntity.exchangeId} key={otherEntity.exchangeId}>
{otherEntity.exchangeLongName} - {otherEntity.exchangeId}
</option>
))
: null}
</select>
.........
</Table>
) : (
!loading && <div className="alert alert-warning">No Active Pairs found</div>
)}
</div>
{props.totalItems ? (
<div className={activePairsList && activePairsList.length > 0 ? '' : 'd-none'}>
<Row className="justify-content-center">
<JhiItemCount page={paginationState.activePage} total={totalItems} itemsPerPage={paginationState.itemsPerPage} />
</Row>
<Row className="justify-content-center">
<JhiPagination
activePage={paginationState.activePage}
onSelect={handlePagination}
maxButtons={5}
itemsPerPage={paginationState.itemsPerPage}
totalItems={props.totalItems}
/>
</Row>
</div>
) : (
''
)}
</div>
);
};
const mapStateToProps = ({ activePairs, exchangesList }: IRootState) => ({
activePairsList: activePairs.entities,
exchangesList: exchangesList.entities,
loading: activePairs.loading,
totalItems: activePairs.totalItems,
});
const mapDispatchToProps = {
getEntities,
getExchangesList,
};
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = typeof mapDispatchToProps;
export default connect(mapStateToProps, mapDispatchToProps)(ActivePairs);
How I can reload the table data when I change the select menu item? I would like to reload the data from the table data with the new selected exchageId param.
useEffect(fn, deps);
As we can see in the React documentation, the way we use the effect hook looks like this:
,fn is the effectful function, and deps is an array of values it depends on. Every time the component renders, React checks if all the values in the deps array are still the same. If any of them has changed since the last render, fn is run again.,All right, so far all the examples exhibit the same behavior. The effect simply doesn't run again if the dependency value doesn't change.
So you only need to give the useEffect hook exchageId as deps and the component's loading function as fn, then UseEffect will rerenders your component.
I have a number, n, that can be any value and I want to render n input fields while keeping track of each input's state but I'm having trouble figuring out how. For example, if n = 3, I want to render something like this:
<div>
<input onChange={(e) => setValue1(e.target.value)}/>
<input onChange={(e) => setValue2(e.target.value)}/>
<input onChange={(e) => setValue3(e.target.value)}/>
< /div>
In this example, I would manually need to create three states: value1, value2, value3. My goal is to have it dynamic so if in the future I change n to 4 or any other number, I don't have to manually create more states and mess with the component. Is there a good way to accomplish this using hooks?
You have to create a inputs state in order to track every input:
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [inputs, setInputs] = useState(Array(10).fill(''));
const inputChangedHandler = (e, index) => {
const inputsUpdated = inputs.map((input, i) => {
if (i === index) {
return e.target.value;
} else {
return input;
}
});
setInputs(inputsUpdated);
};
return (
<div>
{inputs.map((input, i) => (
<input onChange={e => inputChangedHandler(e, i)} value={input} />
))}
</div>
);
}
You can check here, how things work:
https://stackblitz.com/edit/react-sdzoqh
You can create a new array with useState hook of size num that is passed from its parent and then using its index i.e. i you can change its input value using setValue function.
CODESANDBOX DEMO
Just for DEMO purpose and make the input come to a new line so I've wrapped it into div.
export default function App({ num }) {
const [arr, setValue] = useState(Array(num).fill(""));
console.log(arr);
function onInputChange(index, event) {
console.log(event.target.value);
setValue((os) => {
const temp = [...os];
temp[index] = event.target.value;
return temp;
});
}
return (
<div className="App">
{arr.map((n, i) => {
return (
<div key={i}>
<input onChange={(e) => onInputChange(i, e)} value={n} />
</div>
);
})}
</div>
);
}
Maybe I would create a custom hook to generate my inputs like
import React, { useState } from "react";
const CreateInput = (n) => {
const array = new Array(n).fill("");
const [valueInput, setValueInput] = useState({});
const handleChange = (event) => {
const { name, value } = event.target;
setValueInput({
...valueInput,
[name]: value,
});
};
const Input = array.map((_, i) => (
<input key={i} name={i} onChange={handleChange} />
));
return {
Input,
};
};
const Inputs = () => {
const { Input } = CreateInput(3);
console.log(Input);
return <div>{Input}</div>;
};
export default Inputs;
This could be done with an array in the state, with the values in the inputs. Initialize with empty strings
const [values, setValues] = useState(Array(n).fill(""))
const handleChange = (e, i) => {
const copy = values;
copy[i] = e.target.value
setValues(copy)
}
return (
<div>
{Array(n).map((x,i) => (
<input value={values[i]} onChange={e => handleChange(e,i)} />
))}
</div>
)
you can use useState([]) with array as default value for example
import "./styles.css";
import React, { useState } from "react";
export default function App() {
const n = 3;
const [values, setValues] = useState(new Array(n).fill(1, 0, n));
const handleChange = (i, value) => {
const v = [...values];
v[i] = value;
setValues(v);
};
const inputsRendrer = (values) => {
return values.map((v, i) => {
return (
<input
key={i}
value={values[i]}
onChange={(event) => handleChange(i, event.target.value)}
/>
);
});
};
return <div className="App">{inputsRendrer(values)}</div>;
}
new Array(n).fill(1, 0, n) // this create new array with values of 1 and length of n`
I started learning React not so long ago. Decided to make some kind of "life checklist" as one of my beginner projects. I have been using Functional Components in the core.
FYI:
I have data.js as an array of objects where "action", "emoji" and unique ID are stored.
I import it into my App.js.
const App = () => {
//Looping over data
const items = data.map((item) => {
return (
<ChecklistItem action={item.action} emoji={item.emoji} key={item.id} />
);
});
return (
<>
<GlobalStyle />
<StyledHeading>Life Checklist</StyledHeading>
<StyledApp>{items}</StyledApp>
<h2>Overall number: {data.length}</h2>
</>
);
};
export default App;
Here is my <ChecklistItem/> component:
const ChecklistItem = ({ action, emoji }) => {
//State
const [isActive, setIsActive] = useState(false);
//Event Handlers
const changeHandler = () => {
setIsActive(!isActive);
};
return (
<StyledChecklistItem isActive={isActive}>
<input type="checkbox" checked={isActive} onChange={changeHandler} />
<StyledEmoji role="img">{emoji}</StyledEmoji>
<StyledCaption>{action}</StyledCaption>
</StyledChecklistItem>
);
};
export default ChecklistItem;
I would be satisfied with the functionality so far, but I need to show how many "active" checklist items were chosen in the parent <App/> component like "You have chosen X items out of {data.length}. How can I achieve this?
I assume that I need to lift the state up, but cannot understand how to implement this properly yet.
You can do that by simply creating a state for storing this particular count of active items.
To do that, you would need to update your <App/> component to something like this
const App = () => {
const [activeItemsCount, setActiveItemsCount] = useState(0);
//Looping over data
const items = data.map((item, index) => {
return (
<ChecklistItem
key={index}
action={item.action}
emoji={item.emoji}
setActiveItemsCount={setActiveItemsCount}
/>
);
});
return (
<>
<h1>Life Checklist</h1>
<div>{items}</div>
<div>Active {activeItemsCount} </div>
<h2>Overall number: {data.length}</h2>
</>
);
};
export default App;
And then in your <ChecklistItem /> component, you would need to accept that setActiveItemsCount function so that you can change the state of the activeItemsCount.
import React, { useState, useEffect } from "react";
const ChecklistItem = ({ action, emoji, setActiveItemsCount }) => {
const [isActive, setIsActive] = useState(false);
const changeHandler = () => {
setIsActive(!isActive);
};
useEffect(() => {
if (!isActive) {
setActiveItemsCount((prevCount) => {
if (prevCount !== 0) {
return prevCount - 1;
}
return prevCount;
});
}
if (isActive) {
setActiveItemsCount((prevCount) => prevCount + 1);
}
}, [isActive, setActiveItemsCount]);
return <input type="checkbox" checked={isActive} onChange={changeHandler} />;
};
export default ChecklistItem;
By using the useEffect and the checks for isActive and 0 value, you can nicely increment or decrement the active count number by pressing the checkboxes.
How about this?
const data = [
{ action: '1', emoji: '1', id: 1 },
{ action: '2', emoji: '2', id: 2 },
{ action: '3', emoji: '3', id: 3 },
];
const ChecklistItem = ({ action, emoji, isActive, changeHandler }) => {
return (
<div isActive={isActive}>
<input type="checkbox" checked={isActive} onChange={changeHandler} />
<div>{emoji}</div>
<div>{action}</div>
</div>
);
};
const PageContainer = () => {
const [checkedItemIds, setCheckedItemIds] = useState([]);
function changeHandler(itemId) {
if (checkedItemIds.indexOf(itemId) > -1) {
setCheckedItemIds((prev) => prev.filter((i) => i !== itemId));
} else {
setCheckedItemIds((prev) => [...prev, itemId]);
}
}
const items = data.map((item) => {
const isActive = checkedItemIds.indexOf(item.id) > -1;
return (
<ChecklistItem
isActive={isActive}
changeHandler={() => changeHandler(item.id)}
action={item.action}
emoji={item.emoji}
key={item.id}
/>
);
});
return (
<div className="bg-gray-100">
<div>{items}</div>
<h2>
You have chosen {checkedItemIds.length} items out of {data.length}
</h2>
</div>
);
};
When data is used by a child component, but the parent needs to be aware of it for various reasons, that should be state in the parent component. That state is then handed to the child as props.
One way to do this would be to initialize your parent component with a piece of state that was an array of boolean values all initialized to false. Map that state into the checkbox components themselves and hand isActive as a prop based on that boolean value. You should then also hand the children a function of the parent that will change the state of the boolean value at a certain index of that array.
Here's a bit of a contrived example:
// Parent.tsx
const [checkBoxes, setCheckboxes] = useState(data.map(data => ({
id: data.id,
action: data.action,
emoji: data.emoji
isActive: false,
})));
const handleCheckedChange = (i) => {
setCheckboxes(checkBoxes => {
checkBoxes[i].isActive = !checkBoxes[i].isActive;
return checkBoxes;
})
}
return(
checkBoxes.map((item, i) =>
<ChecklistItem
action={item.action}
emoji={item.emoji}
key={item.id}
index={i}
isActive={item.isActive}
handleChange={handleCheckedChange}
/>
)
);
// CheckListItem.tsx
const CheckListItem = ({ action, emoji, index, isActive, handleChange }) => (
<StyledChecklistItem isActive={isActive}>
<input type="checkbox" checked={isActive} onChange={() => handleChange(index)} />
<StyledEmoji role="img">{emoji}</StyledEmoji>
<StyledCaption>{action}</StyledCaption>
</StyledChecklistItem>
)