react-select component options are not applied - reactjs

I'm trying to give options props programmingly, but options do not appear.
I'm getting data from graphql server by using react-apollo-hooks, and try to give some data to options of react-select.
I checked data and they were fine. However, using data doesn't work (Select component shows "No Options"), while declaring array works.
I checked that I properly passed variables between files, and received right data.
Here's my code:
Conatiner.js
const generated_list = [];
const listGenerator = () => {
data.sample.map( single => {
const temp = { label: single.A, value: single.B };
generated_list.push(temp);
}
}
where data is
data.sample = [ { A: '1', B: '1', C: '1' }, { A: '2', B: '2', C: '2' } ]
Presenter.js
<Button onClick={() => listGenerator()} text={'Get Lists'} />
<Select options={generated_list} />
But following code worked as I expect.
const declared_list = { [label: '1', value: '1'],
[label: '2', value: '2'] };
...
<Button onClick={() => listGenerator()} text={'Get Lists'} />
<Select options={declared_list} />
I thought generated list differs from declared list, so I compared them by console log.
console.log(
typeof generated_list,
generated_list`,
JSON.stringify(generated_list),
typeof declared_list,
declared_list`,
JSON.stringify(declared_list),
JSON.stringify(generated_list) === JSON.stringify(declared_list),
);
All things were same, and === operator returned true.
I don't know what to check next, so any helps would be much appreciated.

As suggested by Sathish Kumar in the comments, changes on the array do not affect React to trigger a rerender of the component.
You should move the array into the state and update the array using this.setState (or if you are using a functional component, use Reacts new Hooks).
class MySelect extends Component {
state = {
options: [],
}
populateArray = () => {
var newOptions = [];
/* Populate the `newOptions` array */
this.setState({ options: newOptions });
}
render() {
return (<>
<Button onClick={this.populateArray}>Click me</Button>
<Select { ... } options={this.state.options} />
<>);
}
}

Related

SolidJS: input field loses focus when typing

I have a newbie question on SolidJS. I have an array with objects, like a to-do list. I render this as a list with input fields to edit one of the properties in these objects. When typing in one of the input fields, the input directly loses focus though.
How can I prevent the inputs to lose focus when typing?
Here is a CodeSandbox example demonstrating the issue: https://codesandbox.io/s/6s8y2x?file=/src/main.tsx
Here is the source code demonstrating the issue:
import { render } from "solid-js/web";
import { createSignal, For } from 'solid-js'
function App() {
const [todos, setTodos] = createSignal([
{ id: 1, text: 'cleanup' },
{ id: 2, text: 'groceries' },
])
return (
<div>
<div>
<h2>Todos</h2>
<p>
Problem: whilst typing in one of the input fields, they lose focus
</p>
<For each={todos()}>
{(todo, index) => {
console.log('render', index(), todo)
return <div>
<input
value={todo.text}
onInput={event => {
setTodos(todos => {
return replace(todos, index(), {
...todo,
text: event.target.value
})
})
}}
/>
</div>
}}
</For>
Data: {JSON.stringify(todos())}
</div>
</div>
);
}
/*
* Returns a cloned array where the item at the provided index is replaced
*/
function replace<T>(array: Array<T>, index: number, newItem: T) : Array<T> {
const clone = array.slice(0)
clone[index] = newItem
return clone
}
render(() => <App />, document.getElementById("app")!);
UPDATE: I've worked out a CodeSandbox example with the problem and the three proposed solutions (based on two answers): https://codesandbox.io/s/solidjs-input-field-loses-focus-when-typing-itttzy?file=/src/App.tsx
<For> components keys items of the input array by the reference.
When you are updating a todo item inside todos with replace, you are creating a brand new object. Solid then treats the new object as a completely unrelated item, and creates a fresh HTML element for it.
You can use createStore instead, and update only the single property of your todo object, without changing the reference to it.
const [todos, setTodos] = createStore([
{ id: 1, text: 'cleanup' },
{ id: 2, text: 'groceries' },
])
const updateTodo = (id, text) => {
setTodos(o => o.id === id, "text", text)
}
Or use an alternative Control Flow component for mapping the input array, that takes an explicit key property:
https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#Key
<Key each={todos()} by="id">
...
</Key>
While #thetarnav solutions work, I want to propose my own.
I would solve it by using <Index>
import { render } from "solid-js/web";
import { createSignal, Index } from "solid-js";
/*
* Returns a cloned array where the item at the provided index is replaced
*/
function replace<T>(array: Array<T>, index: number, newItem: T): Array<T> {
const clone = array.slice(0);
clone[index] = newItem;
return clone;
}
function App() {
const [todos, setTodos] = createSignal([
{ id: 1, text: "cleanup" },
{ id: 2, text: "groceries" }
]);
return (
<div>
<div>
<h2>Todos</h2>
<p>
Problem: whilst typing in one of the input fields, they lose focus
</p>
<Index each={todos()}>
{(todo, index) => {
console.log("render", index, todo());
return (
<div>
<input
value={todo().text}
onInput={(event) => {
setTodos((todos) => {
return replace(todos, index, {
...todo(),
text: event.target.value
});
});
}}
/>
</div>
);
}}
</Index>
Dat: {JSON.stringify(todos())}
</div>
</div>
);
}
render(() => <App />, document.getElementById("app")!);
As you can see, instead of the index being a function/signal, now the object is. This allows the framework to replace the value of the textbox inline.
To remember how it works: For remembers your objects by reference. If your objects switch places then the same object can be reused. Index remembers your values by index. If the value at a certain index is changed then that is reflected in the signal.
This solution is not more or less correct than the other one proposed, but I feel this is more in line and closer to the core of Solid.
With For, whole element will be re-created when the item updates. You lose focus when you update the item because the element (input) with the focus gets destroyed, along with its parent (li), and a new element is created.
You have two options. You can either manually take focus when the new element is created or have a finer reactivity where element is kept while the property is updated. The indexArray provides the latter out of the box.
The indexArray keeps the element references while updating the item. The Index component uses indexArray under the hood.
function App() {
const [todos, setTodos] = createSignal([
{ id: 1, text: "cleanup" },
{ id: 2, text: "groceries" }
]);
return (
<ul>
{indexArray(todos, (todo, index) => (
<li>
<input
value={todo().text}
onInput={(event) => {
const text = event.target.value;
setTodos(todos().map((v, i) => i === index ? { ...v, text } : v))
}}
/>
</li>
))}
</ul>
);
}
Note: For component caches the items internally to avoid unnecessary re-renders. Unchanged items will be re-used but updated ones will be re-created.

React Hooks - Is it recommended to use a separate state handler for every button/toggle switch?

Super new to React and trying out something.
I have 3 toggle switches on a single screen. Should I be using a separate state handler for every switch?
Right now I have used just one, but obviously this will not work as expected because isChecked is being shared by all the three toggle switches.
const [isChecked, setIsChecked] = useState(false)
const data = [
['Sample Text 1', <ToggleSwitch showLabel={true} checked={isChecked} onChange={() => setIsChecked(!isChecked)}/>],
['Sample Text 2', <ToggleSwitch showLabel={true} checked={isChecked} onChange={() => setIsChecked(!isChecked)}/>],
['Sample Text 3', <ToggleSwitch showLabel={true} checked={isChecked} onChange={() => setIsChecked(!isChecked)}/>]
]
Can I use some sort of id to change the state of individual switches using the same handler(isClicked)? Or is it recommended to use separate state handlers for every switch?
Yes, if it's a static form, usually you just use several useState. Because you can make sure the variable(state) names are readable, and it's easy to add new fields (or remove unneeded ones). It might look dumb but the maintainability and readability are better than any kind of "clever code".
But sometimes we may have a more dynamic form. For example, all the fields are from some backend APIs, or you have a huge list/table and the toggle fields don't have any semantic meanings, then of course you have to write the code in a more dynamic way.
Here is an example that uses a useReducer:
const initialState = {
1: { checked: false },
2: { checked: false },
3: { checked: false },
4: { checked: false },
}
function reducer(state, action) {
const { type, payload } = action
switch (type) {
case 'toggle':
return {
...state,
[payload.id]: { checked: !state[payload.id].checked },
}
}
}
const YourComponent = () => {
const [toggleState, dispatch] = useReducer(reducer, initialState)
return (
<div>
{Object.keys(toggleState).map((id) => (
<ToggleSwitch
key={id}
checked={toggleState[id].checked}
onChange={() => dispatch({ type: 'toggle', payload: { id } })}
/>
))}
</div>
)
}
I'm using objects here. An array of objects is also fine, or even a simple array: [false, false, false, false]. I think it depends on your use case.
It's also possible to use just one setState, but useReducer in this case is flexible. You can easily add new actions like checkAll or uncheckAll, and put your logics in the reducer.
And then, you can even extract this to a custom hook. Please check this CodeSandbox https://codesandbox.io/s/stackoverflow-toggles-l9b2do?file=/src/App.js for a full working example.
And just for your reference, I also tried to modify your code and used just useState and an array:
const YourComponent = () => {
const [toggles, setToggles] = useState([
['text 1', true],
['text 2', false],
['text 3', false],
['text 4', false],
])
const handleChange = (index) => {
const newState = [...toggles]
newState[index] = [newState[index][0], !newState[index][1]]
setToggles(newState)
}
return (
<div>
{toggles.map(([text, checked], index) => (
<ToggleSwitch
key={index}
checked={checked}
onChange={() => handleChange(index)}
/>
))}
</div>
)
}
It really deepends what you want to achive.
The useState React hook controlls of one state.
The useReducer can control multiple states which related.

How does a parent know about the state of a list of child forms using formik?

I have a form consisting of a list of building materials which can be materials already registered for the construction and new materials:
The MaterialBar:
function MaterialBar({
materialBarId,
material,
onDelete,
onChange,
}: MaterialBarProps) {
const { values, handleChange } = useFormik<MaterialBar>({
initialValues: material,
onSubmit: console.log,
enableReinitialize: true,
});
const updateField = (event) => {
handleChange(event);
onChange(materialBarId, values);
};
return (
<DropdownWrapper>
<Dropdown
label="Material"
items={/* A list of available materials */}
selectedItem={values.material}
onSelect={updateField}
/>
.... Other fields
<TextField
name="amount"
id="material-amount"
label="Amount"
type="number"
onChange={updateField}
/>
<DeleteButton onClick={() => onDelete(materialBarId)}>
<img src={remove} />
</DeleteButton>
</DropdownWrapper>
);
}
The parent (ConstructionMaterials):
function ConstructionMaterials({
project,
materials,
attachMaterial,
}: ProjectMaterialsProps) {
const [allMaterials, setAllMaterials] = useState<IProjectMaterial[]>(
getProjectMaterialsFrom(project.materials)
);
const saveAllMaterials = () => {
allMaterials.forEach((newMaterial) => {
if (newMaterial.isNew) {
attachMaterial(newMaterial);
}
});
};
const updateNewMaterial = (
materialBarId: number,
updatedMaterial: MaterialBar
): void => {
const updatedList: IProjectMaterial[] = allMaterials.map((material) => {
if (material.projectMaterialId === materialBarId) {
return {
...material,
materialId: materials.find(
(currentMaterial) =>
currentMaterial.name === updatedMaterial.material
)?.id,
type: updatedMaterial.type,
length: updatedMaterial.length,
width: updatedMaterial.width,
value: updatedMaterial.amount,
};
}
return material;
});
setAllMaterials(updatedList);
};
// Adds a new empty material to the list
const addMaterial = (): void => {
setAllMaterials([
...allMaterials,
{
projectMaterialId: calculateMaterialBarId(),
projectId: project.id,
isNew: true,
},
]);
};
return (
<>
{allMaterials.map((material) => {
const materialBar: MaterialBar = {
material: material.name || "",
type: material.type || "",
amount: material.value || 0,
length: material.length || 0,
width: material.width || 0,
};
return (
<AddMaterialBar
key={material.projectMaterialId}
materialBarId={material.projectMaterialId!}
materials={materials}
onDelete={removeMaterial}
onChange={updateNewMaterial}
/>
);
})}
<Button onClick={() => saveAllMaterials()}>
{texts.BUTTON_SAVE_MATERIALS}
</Button>
</>
);
}
I have a hard time figuring out how to manage the list of materials. I use Formik (the useFormik hook) in the MaterialBar to take care of the values of each field.
My challenge is how to keep all the data clean and easily pass it between the components while knowing which materials are new and which already exist. If I just use Formik in the MaterialBar, then ConstructionMaterials does not know about the changes made in each field and it needs the updated data because it calls the backend with a "save all" action (the "Save"-buttons in the image should not be there, but they are my temporary fix).
To circumvent this, I also keep track of each material in ConstructionMaterials with the onChange on MaterialBar, but that seems redundant, since this is what Formik should take care of. I have also added a isNew field to the material type to keep track of whether it is new, so I don't two lists for existing and new materials.
I have had a look at FieldArray in ConstructionMaterials, but shouldn't Formik be used in the child, since the parent should just grab the data and send it to the API?
So: is there a clever way to handle a list of items where the parent can know about the changes made in the childs form, to make a bulk create request without the parent having to also keep track of all the objects in the children?
Sorry about the long post, but I don't know how to make it shorter without loosing the context.

Keeping state of variable mapped from props in functional react component after component redraw

Recently I started learning react and I decided to use in my project functional components instead of class-based. I am facing an issue with keeping state on one of my components.
This is generic form component that accepts array of elements in order to draw all of necessary fields in form. On submit it returns "model" with values coming from input fields.
Everything working fine until I added logic for conditionally enabling or disabling "Submit" button when not all required fields are set. This logic is fired either on component mount using useEffect hook or after every input in form input. After re-render of the component (e.g. conditions for enabling button are not met, so button becomes disabled), component function is fired again and my logic for creating new mutable object from passed props started again, so I am finished with empty object.
I did sort of workaround to make a reference of that mutated object outside of scope of component function, but i dont feel comfortable with it. I also dont want to use Redux for that simple sort of state.
Here is the code (I am using Type Script):
//component interfaces:
export enum FieldType {
Normal = "normal",
Password = "password",
Email = "email"
}
export interface FormField {
label: string;
displayLabel: string;
type: FieldType;
required: boolean;
}
export interface FormModel {
model: {
field: FormField;
value: string | null;
}[]
}
export interface IForm {
title: string;
labels: FormField[];
actionTitle: string;
onSubmit: (model: FormModel) => void;
}
let _formState: any = null;
export function Form(props: IForm) {
let mutableFormModel = props.labels.map((field) => { return { field: field, value: null as any } });
//_formState keeps reference outside of react function scope. After coponent redraw state inside this function is lost, but is still maintained outside
if (_formState) {
mutableFormModel = _formState;
} else {
_formState = mutableFormModel;
}
const [formModel, setFormModel] = useState(mutableFormModel);
const [buttonEnabled, setButtonEnabled] = useState(false);
function requiredFieldsCheck(formModel: any): boolean {
let allRequiredSet = true;
formModel.model.forEach((field: { field: { required: any; }; value: string | null; }) => {
if (field.field.required && (field.value === null || field.value === '')) {
allRequiredSet = false;
}
})
return allRequiredSet;
}
function handleChange(field: FormField, value: string) {
let elem = mutableFormModel.find(el => el.field.label === field.label);
if (elem) {
value !== '' ? elem.value = value as any : elem.value = null;
}
let submitEnabled = requiredFieldsCheck({ model: mutableFormModel });
setFormModel(mutableFormModel);
setButtonEnabled(submitEnabled);
}
useEffect(() => {
setButtonEnabled(requiredFieldsCheck({ model: mutableFormModel }));
}, [mutableFormModel]);
function onSubmit(event: { preventDefault: () => void; }) {
event.preventDefault();
props.onSubmit({ model: formModel })
}
return (
<FormStyle>
<div className="form-container">
<h2 className="form-header">{props.title}</h2>
<form className="form-content">
<div className="form-group">
{props.labels.map((field) => {
return (
<div className="form-field" key={field.label}>
<label>{field.displayLabel}</label>
{ field.type === FieldType.Password ?
<input type="password" onChange={(e) => handleChange(field, e.target.value)}></input> :
<input type="text" onChange={(e) => handleChange(field, e.target.value)}></input>
}
</div>
)
})}
</div>
</form>
{buttonEnabled ?
<button className={`form-action btn btn--active`} onClick={onSubmit}> {props.actionTitle} </button> :
<button disabled className={`form-action btn btn--disabled`} onClick={onSubmit}> {props.actionTitle} </button>}
</div>
</FormStyle >
);
}
So there is quite a lot going on with your state here.
Instead of using a state variable to check if your button should be disabled or not, you could just add something render-time, instead of calculating a local state everytime you type something in your form.
So you could try something like:
<button disabled={!requiredFieldsCheck({ model: formModel })}>Click me</button>
or if you want to make it a bit cleaner:
const buttonDisabled = !requiredFieldsCheck({model: formModel});
...
return <button disabled={buttonDisabled}>Click me</button>
If you want some kind of "caching" without bathering with useEffect and state, you can also try useMemo, which will only change your calculated value whenever your listeners (in your case the formModel) have changes.
const buttonDisabled = useMemo(() => {
return !requiredFieldsCheck({model: formModel});
}, [formModel]);
In order to keep value in that particular case, I've just used useRef hook. It can be used for any data, not only DOM related. But thanks for all inputs, I've learned a lot.

React Hooks Inputs inside the List

Good day,
I'm trying to create a form inside the list using react hooks. So far, I'm struggling since I'm only familiar how to make state changes on single state.
import React from "react"
export function TestApp () {
const [input, setInput] = useState("");
function submitData(){
// TODO
}
return (
<>
<input onChange={e=>setInput(e.target.value)} value={value} />
<button type="button" onClick={submitData}>Submit</button>
</>
)
}
My sample scenario is like: there is a list of data from web api where I need to iterate in a list. Inside that list, there's an input where user can type or update the data. Something like this below, but it doesn't work properly.
import React from "react"
export function TestApp () {
const [customers, setCustomers] = useState([])
useEffect(()=>{
axios.get(`api/customers`).then(response => {
setCustomers(response.data)
// suppose the result will be something like:
// [{id:1, name: "James", {id: 2, name: "Josh"}]
).catch(error=>console.log(error));
}, [])
const [inputs, setInputs] = useState([]);
function submitData(){
// TODO
console.log(inputs)
// something like the result will be if the user just added "updated" after the name inside the input list
[{id:1, name: "James updated", {id: 2, name: "Josh updated"}]
}
function handleOnChange(e, data){
setInputs(...inputs, {
id: data.id,
name: data.name
})
}
return (
<>
{customers.map((data)=> (
<input onChange={e=>handleOnChange(e, data)} value={data.name} />
))};
<button type="button" onClick={submitData}>Submit</button>
</>
)
}
What I want to do is, when the user types inside the selected row, then it mutate changes/updates inside the array. Because the data inside the inputs is where I can use to make changes/updates in the backend.
Thank you and I hope somebody can help me with this.
When mapping, take the current index of the item being iterated over and pass it to a function. In the function, slice the array before and after that index, and at the index itself, replace the name property:
const changeName = (i, newName) => {
setCustomers([
...customers.slice(0, i),
{ ...customers[i], name: newName },
...customers.slice(i + 1)
]);
};
// ...
{customers.map((item, i)=> (
<input onChange={e => changeName(i, e.currentTarget.value)} value={item.name} />
))};
// ...

Resources