how handle multiple inputs thats created dynamically from object react native - reactjs

i have a file contain objects data.js.
components: [
{
id: "1",
nameC: "name",
type: "TextInput",
options: { placeholder: "saisir nom", required: true },
},
{
id: "2",
nameC: "Phone",
type: "Phone",
options: { placeholder: "saisir number", required: false },
},
{
id: "3",
nameC: "name",
type: "TextInput",
options: { placeholder: "saisir nom", required: true },
},
i got those objects to create inputs Dynamically in this file TemplateScreen.js .
<>
{getData.length === 0 ? (
<Empty />
) : (
getData.map((item, index) => {
switch (item.type) {
case "TextInput":
return (
<>
<InputText
ModuleName={item.nameC}
placeholder={item.options.placeholder}
required={item.options.required}
/>
</>
);
case "Phone":
return (
<>
<Phone
ModuleName={item.nameC}
placeholder={item.options.placeholder}
required={item.options.required}
/>
</>
);
default:
return <Text>Nothing hear</Text>;
}
})
)}
</>
i render inputs successfully but i'can't handle those inputs :( .i'm tried many methods but anyone worked for me.i am tried many tricks from reactjs tuto but nothing worked for me .i'm blocked 4 days in this problem,please anyone can help me :(
this file contain TextInput component, i am called it in templateScreen.js
Phone component is the same as TextInput component with a bit of difference
export const InputText = (props) => {
const [state, setState] = React.useState("");
return (
<View style={styles.container} key={props.keys}>
<View style={styles.Namecontainer}>
<Text style={styles.moduleName}>{props.ModuleName}</Text>
{props.required ? <Text style={styles.required}>*</Text> : <></>}
</View>
<TextInput
{...props}
value={state}
onChangeText={(text) => setState(text)}
placeholder={props.placeholder}
style={styles.inputtext}
/>
</View>
);
};```

Instead of placing state inside the Input text and Phone text component, why not just use useRef hook inside the template screen js? We should generate refs depending on the length of the data, thus
const inputRefs = getData.reduce((acc,curr)=>{
const ref = useRef(“”);
acc[curr.nameC] = ref;
return acc:
}, {});
this will generate refs for each of your inputs. Now in our map method, we just place our input refs to each ex:
<InputText
inputRefs[item.nameC]
ModuleName={item.nameC}
placeholder={item.options.placeholder}
required={item.options.required}
/>
In order to get their values, map the inputRefs and try console.log(inputRef.current) to see.

I have created the replica in reactjs because react-native is not set up on my PC but the logic remains the same, as I correctly understand what you wanna do is render dynamic input form according to the data this is provided to you and store the value in the state
import React, { useEffect, useState } from "react";
const components = [
{
id: "1",
nameC: "name",
type: "TextInput",
options: { placeholder: "saisir nom", required: true },
},
{
id: "2",
nameC: "phone",
type: "Phone",
options: { placeholder: "saisir number", required: false },
},
{
id: "3",
nameC: "city",
type: "TextInput",
options: { placeholder: "saisir nom", required: true },
},
];
const DynamicInput = () => {
const [field, setField] = useState();
const handleChange = (event) => {
setField({ ...field, [event.target.name]: event.target.value });
};
useEffect(() => {
let obj = {};
components.forEach((item, index) => {
obj = { ...obj, [item.nameC]: "" };
});
setField(obj);
}, []);
console.log(field)
return (
<div>
{!field ? (
<div>Loading...</div>
) : (
components.map((item) => {
const value = field[item.nameC];
switch (item.type) {
case "TextInput":
return (
<InputText
key={item.nameC}
value={value}
onChangeHandler={handleChange}
placeholder={item.options.placeholder}
isRequired={item.options.required}
name={item.nameC}
/>
);
case "Phone":
return <div key={item.nameC}>This will be same as input</div>;
default:
return <div key={item.nameC}>Nothing hear</div>;
}
})
)}
</div>
);
};
export default DynamicInput;
export const InputText = ({
value,
onChangeHandler,
placeholder,
isRequired,
name,
}) => {
return (
<input
value={value}
name={name}
onChange={onChangeHandler}
placeholder={placeholder}
required={isRequired}
/>
);
};

Related

How to create react form with dynamic fields fetched from api

I want to create a react form with dynamic inputs, those inputs are fetched from server.
I fetched the data and I have displayed the form but the problem was how to get the values of the inputs with the onChange method
Use a Usestate hook and assign the value fetched from the API there
eg:-
const [value,setValue] = useState(fetch());
function fetch(){
//API call...
//return the value got from the API
}
Now in form input field you can use this as value and have an onChange and change the value state.
eg:-
<input value={value.name} name="name" onChange={(e)=>handlechange(e)} />
in handlechange :
const handlechange = (e)=>{
const {name,value} = e.target;
setValue({...value,[name]:value});
};
After this you can have an useEffect, which will POST the value to the backend using another API for every changes made in the form
Here is the code sandbox link:https://codesandbox.io/s/blazing-moon-wxp4w7?file=/src/App.js
after some researches on stackoverflow here is my solution:
const [fields, setFields] = useState([{field:"", value:""}]);
useEffect(()=>{
if(fetchedData){
setFields(fetchedData.map((item)=>{
var rObj ={field:"", value:""};
rObj["field"] = item;
return rObj;
}));
}
},[fetchedData])
const handleChange = ( index, event ) => {
if(fields) {
let data = [...fields];
data[index]["value"] = event.target.value;
setFields(data);
}
}
/////////////////////////////////////////
{fetchedData.map((field, index) => (
<div key={index}>
<input
required
type="text"
name={field}
value={fields.field}
placeholder= {field}
onChange={event=>{handleChange(index, event)}}
/>
</div>
))}
Here is a sample code:
const GenerateForm() => {
const [formInputs, setFormInputs] = useState({});
const handleInputChange = event => {
const { name, value } = event.target;
setFormInputs({ ...formInputs, [name]: value });
};
return (
<form>
{formData.map(input => {
if (input.type === "text" || input.type === "number") {
return (
<div key={input.name}>
<label htmlFor={input.name}>{input.label}:</label>
<input
type={input.type}
name={input.name}
value={formInputs[input.name] || ""}
onChange={handleInputChange}
/>
</div>
);
} else if (input.type === "select" || input.type === "radio") {
return (
<div key={input.name}>
<label>{input.label}:</label>
{input.options.map(option => (
<div key={option.value}>
<input
type={input.type}
name={input.name}
value={option.value}
checked={formInputs[input.name] === option.value}
onChange={handleInputChange}
/>
{option.label}
</div>
))}
</div>
);
}
return null;
})}
</form>
);
}
const formData = [
{
name: "firstName",
label: "First Name",
type: "text"
},
{
name: "lastName",
label: "Last Name",
type: "text"
},
{
name: "age",
label: "Age",
type: "number"
},
{
name: "gender",
label: "Gender",
type: "radio",
options: [
{
value: "male",
label: "Male"
},
{
value: "female",
label: "Female"
}
]
},
{
name: "country",
label: "Country",
type: "select",
options: [
{
value: "india",
label: "India"
},
{
value: "usa",
label: "USA"
}
]
}
];

How can I have a button in a const object?

I'm learning React.js and this is a table showing which user has which items.
I would like to have a button for each item and delete the corresponding item.
How do you have or {FaTrash} icon in a const object?
This is my full code below
const columns = [
{
name: "Username",
selector: "username",
sortable: true
},
{
name: "Email",
selector: "email",
sortable: true
},
{
name: "Item",
selector: "items",
sortable: true,
right: true
},
{
name: "Action",
value: <button>Edit</button>
}
]
const Admin = () => {
const [data, setData] = useState(allUsers);
const handleRowClicked = row => {
const updatedData = data.map(item => {
if (row.id !== item.id) {
return item;
}
return {
...item,
toggleSelected: !item.toggleSelected
};
});
setData(updatedData);
}
return ( <>
<div className='users p-5'>
<DataTable
title="Users"
columns={columns}
data={data}
defaultSortField="title"
pagination
onRowClicked={handleRowClicked}
/>
</div>
</> );
}
export default Admin;
I used to pass a function that returns a piece of layout with handler
{
name: "Action",
actionRenderer: ({ index, item }) => {
return (
<button onClick={() => onhandle(item)}>
ActionName <!--or icon component-->
</button>
)
}
},
Than you need to create <DataTableRow> component wich will render each object in your columns array. Somewhere in the <DataTableRow> you will be able to access to actionRenderer and your data item:
<div>{actionColumn.actionRenderer({ index, item })}</div>

Select dependendent on each others reactjs

On selecting first dropdown then the second dropdown should show corresponding phonenumber in second dropdownlist and vice versa .how can we achieve it?
Code
import Select from "react-select";
const data = [
{ value: 1, label: "max", phone: "123" },
{ value: 2, label: "sam", phone: "345" },
{ value: 3, label: "denis", phone: "4444" }
];
export default function App(props) {
const [select1, setSelect1] = useState([]);
const [select2, setSelect2] = useState([]);
const filter1=data.filter((x)=>{return x.value===select2.value})
const filter2=data.filter((x)=>{return x.value===select1.value})
return (
<div className="App">
<Select
options={data}
value={select1}
onChange={(e) => {
console.log(e);
setSelect1(e);
setSelect2(e.phone);
}}
/>
<Select
options={data}
value={select2}
onChange={(e) => {
setSelect2(e);
setSelect1(e.value);
}}
/>
</div>
);
}

Custom nodes in react-flow; saving additional data to a node after it has been created

This is my first introduction to react-flow. I am looking to create a custom node where after creation, the user can enter information in the node and save/display it. From the react-flow documentation on custom nodes, they have a similar example where they created a TextUpdaterNode that console.logs the user input.
Instead of logging it it via console, I am looking for a way to save the information to the node itself and display it on the node. For example, if a user were to enter "24, male" into the input and hit the "enter" key, I want the node to be updated with that information.
What are the ways I can go about doing this?
What you're trying to do needs a little more than that:
You can see alive example here: https://codesandbox.io/s/dank-waterfall-8jfcf4?file=/src/App.js
Basically, you need:
Import useNodesState from 'react-flow-renderer';
Instead of basic definition of nodes, you will need to use: const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
Then, will have to define the onAdd, which looks like:
const onAdd = useCallback(() => {
const newNode = {
id: getNodeId(),
data: { label: `${state.name} (${state.age})` },
position: {
x: 0,
y: 0 + (nodes.length + 1) * 20
}
};
setNodes((nds) => nds.concat(newNode));
}, [nodes, setNodes, state.name, state.age]);
You can include edit, pretty similar like:
const onEdit = () => {
setNodes((nds) =>
nds.map((node) => {
if (node.id === editState.id) {
node.data = {
...node.data,
label: `${node.id} - ${editState.name} (${editState.age})`
};
}
return node;
})
);
};
Finally, draw the flow: <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
The whole code looks like:
import React, { useState, useCallback } from "react";
import ReactFlow, {
ReactFlowProvider,
useNodesState,
useEdgesState
} from "react-flow-renderer";
import "./styles.css";
const getNodeId = () => `randomnode_${+new Date()}`;
const initialNodes = [
{ id: "1", data: { label: "Node 1" }, position: { x: 100, y: 100 } },
{ id: "2", data: { label: "Node 2" }, position: { x: 100, y: 200 } }
];
const initialEdges = [{ id: "e1-2", source: "1", target: "2" }];
const FlowExample = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges] = useEdgesState(initialEdges);
const [state, setState] = useState({ name: "", age: "" });
const onAdd = useCallback(() => {
const newNode = {
id: getNodeId(),
data: { label: `${state.name} (${state.age})` },
position: {
x: 0,
y: 0 + (nodes.length + 1) * 20
}
};
setNodes((nds) => nds.concat(newNode));
}, [nodes, setNodes, state.name, state.age]);
return (
<div>
Name:{" "}
<input
type="text"
onChange={(e) => {
setState((prev) => ({ ...prev, name: e.target.value }));
}}
/>
Age:{" "}
<input
type="text"
onChange={(e) => {
setState((prev) => ({ ...prev, age: e.target.value }));
}}
/>
<button onClick={onAdd}>add node</button>
<div style={{ width: "500px", height: "500px" }}>
<ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
</div>
</div>
);
};
export default () => (
<ReactFlowProvider>
<FlowExample />
</ReactFlowProvider>
);
Also, with edit:
import React, { useState, useCallback } from "react";
import ReactFlow, {
ReactFlowProvider,
useNodesState,
useEdgesState
} from "react-flow-renderer";
import "./styles.css";
const getNodeId = () => `${String(+new Date()).slice(6)}`;
const initialNodes = [
{ id: "1", data: { label: "Node 1" }, position: { x: 100, y: 100 } },
{ id: "2", data: { label: "Node 2" }, position: { x: 100, y: 200 } }
];
const initialEdges = [{ id: "e1-2", source: "1", target: "2" }];
const FlowExample = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges] = useEdgesState(initialEdges);
const [state, setState] = useState({ name: "", age: "" });
const [editState, setEditState] = useState({ id: "", name: "", age: "" });
const onEdit = () => {
setNodes((nds) =>
nds.map((node) => {
if (node.id === editState.id) {
node.data = {
...node.data,
label: `${node.id} - ${editState.name} (${editState.age})`
};
}
return node;
})
);
};
const onAdd = () => {
const id = getNodeId();
const newNode = {
id,
data: { label: `${id} - ${state.name} (${state.age})` },
position: {
x: 0,
y: 0 + (nodes.length + 1) * 20
}
};
setNodes((nds) => nds.concat(newNode));
};
return (
<div>
Name:{" "}
<input
type="text"
onChange={(e) => {
setState((prev) => ({ ...prev, name: e.target.value }));
}}
/>
Age:{" "}
<input
type="text"
onChange={(e) => {
setState((prev) => ({ ...prev, age: e.target.value }));
}}
/>
<button onClick={onAdd}>add node</button>
<br />
Id:{" "}
<input
type="text"
onChange={(e) => {
setEditState((prev) => ({ ...prev, id: e.target.value }));
}}
/>
Name:{" "}
<input
type="text"
onChange={(e) => {
setEditState((prev) => ({ ...prev, name: e.target.value }));
}}
/>
Age:{" "}
<input
type="text"
onChange={(e) => {
setEditState((prev) => ({ ...prev, age: e.target.value }));
}}
/>
<button onClick={onEdit}>Edit node</button>
<div style={{ width: "500px", height: "500px" }}>
<ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
</div>
</div>
);
};
export default () => (
<ReactFlowProvider>
<FlowExample />
</ReactFlowProvider>
);
A more helpful example from documentation would be:
https://reactflow.dev/docs/examples/interaction/save-and-restore/
https://reactflow.dev/docs/examples/nodes/update-node/
But you have to remove all the extra information (Also, you can use it to go deeper!)
I managed to come up a solution to create such a custom node that allowed you to input, save and display information. I have tried to include relevant information and the code block I used below.
Custom Node
import { useCallback } from 'react';
import { Handle, Position} from 'react-flow-renderer';
const handleStyle = { left: 10 };
//Custom node requires props of data to be passed to it.
function CustomNode({ data }) {
let serviceType = "offered";
//This handles pressing enter inside the description
const handleKeyDown = (evt) => {
if (evt.key === "Enter") {
//Check if empty string
if (evt.target.value.length !== 0) {
//This code is because services are either offered or borrowed.
if (serviceType === "offered") {
data.serviceOffered.push(evt.target.value);
} else if (serviceType === "borrowed") {
data.serviceBorrowed.push(evt.target.value);
}
//Clearing input after pressing enter
evt.currentTarget.value = "";
}
}
};
const onChange = useCallback((evt) => {
//Update service type without pressing enter
serviceType = evt.target.value;
});
return (
<div className="text-updater-node">
<Handle type="target" position={Position.Top} />
<div>
<p>Entity</p>
<label htmlFor="text"><p className='nodeTitle'>{data.label}</p></label>
<input id="text" name="text" onKeyDown={handleKeyDown} />
<select name="type" onChange={onChange}>
<option value="offered" >Offered </option>
<option value="borrowed">Borrowed</option>
</select>
<div className="info">
{/* This is where the description information is displayed. It checks if it is empty, if not it loops through and displays it. */}
<h2>Service Borrowed</h2>
<ul>
{data.serviceBorrowed.length? data.serviceBorrowed.map(service => (<li key={service}>{service}</li>)) : <span></span>}
</ul>
<h2>Service Offered</h2>
<ul>
{data.serviceOffered.length? data.serviceOffered.map(service => (<li key={service}>{service}</li>)) : <span></span>}
</ul>
</div>
</div>
<Handle type="source" position={Position.Bottom} id="a" style={handleStyle} />
<Handle type="source" position={Position.Bottom} id="b" />
</div>
);
}
export default CustomNode;
I have a parent reactFlow component with the following code block. The important thing about this is to set the custom node type of react flow and pass in an object containing information about the nodes and edges to be rendered.
import { Fragment, useCallback, useState } from "react";
import ReactFlow, {
addEdge,
applyEdgeChanges,
applyNodeChanges,
} from "react-flow-renderer";
import initialNodes from "../data/nodes"; //This both ended up being empty file
import initialEdges from "../data/edges"; //This both ended up being empty file
import CustomNode from "./customNode";
import "./customNode.css";
//Set nodetype as Custom node, IMPORTANT!
const nodeTypes = { customNode: CustomNode };
function Flow() {
const defaultEdgeOptions = { animated: true };
//Input Elements
const [name, setName] = useState("");
const addNode = () => {
setNodes((e) =>
e.concat({
id: (e.length + 1).toString(),
data: { label: `${name}`, serviceOffered: [], serviceBorrowed: [] },
position: { x: 0, y: 0 },
type: "customNode",
})
);
};
//Nodes and edges containing information of the nodes and edges
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
//Boiler plate code for reactFlow
const onNodesChange = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[setNodes]
);
const onEdgesChange = useCallback(
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
[setEdges]
);
const onConnect = useCallback(
(connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges]
);
return (
<Fragment>
<Row>
<Col lg={9}>
<ReactFlow
className="Canvas mt-1 border border-secondary rounded"
nodes={nodes} //Node information is passed here
edges={edges} //Edges information is passed here
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
defaultEdgeOptions={defaultEdgeOptions}
style={{ width: "100%", height: "80vh" }}
fitView
nodeTypes={nodeTypes}
/>
</Col>
</Row>
</Fragment>
);
}
export default Flow;
I added more information inside the data property of my node.js. It ended up being initialize as empty but this template should be helpful in understanding how I saved the information for the node. The edge followed the standard format shown on react-flow documentation.
export default [
// {
// id: '1',
// type: 'customNode',
// data: { label: 'Input Node', info: [{id:1, action:"Everything is burning"}, {id:2, action:"I'm fine"}], noOfActions:2 },
// position: { x: 250, y: 25 },
// },
];
I hope this has been useful!
Accepted answer is about modifying properties of components which is not React way. That code may break easily.
There are other ways to bring callback to custom nodes.
Put callback into node's data
This is from React flow documentation: https://reactflow.dev/docs/examples/nodes/custom-node/
setNodes([
...
{
id: '2',
type: 'selectorNode',
data: { onChange: onChange, color: initBgColor },
...
Cons: you need pay extra attention when you modify or create new nodes dynamically
or Define custom types dynamically
In this approach, you keep node data and behavior concerns separate.
I'm using TypeScript in order to show types of data we operate along the way.
First, you extend your custom node properties with your callback:
import {NodeProps} from "react-flow-renderer/dist/esm/types/nodes";
// by default, custom node is provisioned with NodeProps<T>
// we extend it with additional property
export type CustomNodeProps = NodeProps<CustomData> & {
onClick: (id: string) => void
}
function CustomNode(props: CustomNodeProps) {
return <button onClick={() => props.onClick(props.id)}>Do it</button>
}
Then you create new constructor that provides callback and put it into custom nodes mapping using memoization:
function Flow() {
const [graph, dispatchAction] = useReducer(...);
...
// useMemo is neccessary https://reactflow.dev/docs/guides/troubleshooting/#it-looks-like-you-have-created-a-new-nodetypes-or-edgetypes-object-if-this-wasnt-on-purpose-please-define-the-nodetypesedgetypes-outside-of-the-component-or-memoize-them
const nodeTypes = useMemo(() => {
return {
custom: (props: NodeProps<CustomData>) => {
return CustomNode({...props, onClick: (id: string) => {
dispatchAction({
type: 'customNodeButtonClicked',
nodeId: id,
})
}})
}
}
}, [])
return (
<>
<ReactFlow nodeTypes={nodeTypes} ... />
</>
);
}

Generate React form fields from JSON

i am trying to generate a form from JSON config. I am parsing the JSON and using map functions to generate material UI TextField components.
But issue is that the generated components do not get rendered, instead the whole JS code appears on screen. Not sure why.
here is my code in 2 files:
FormConfig.js:
"Form1": {
"fields": [
{
uiElement: "TextField",
id: '"standard-name"',
name: "'asdada'",
className: "{classes.textField}",
value: "{this.state.name}",
onChange: '{this.handleChange("name")}',
required: true,
margin: '"normal"'
},
{
uiElement: "TextField",
id: '"standard-uncontrolled"',
name: '"asda"',
label: '"Required"',
className: '"{classes.textField}"',
value: '"asd"',
onChange: "{}",
required: true,
margin: '"normal"'
}
]
},
"OtherForm":
{
"fields": [{}, {}]
}
}
const getForm = formName => {
return FormConfig[formName].fields.map(field => `<${field.uiElement} `+
Object.keys(field).filter(k => k !== 'uiElement')
.map(k => {
return k + "=" + field[k];
})
.join(" ") + `/>`
)
}
export default getForm;
TestForm.js
class TextFields extends React.Component {
state = {
name: 'Cat in the Hat',
age: '',
multiline: 'Controlled',
currency: 'EUR',
};
handleChange = name => event => {
this.setState({ [name]: event.target.value });
};
render() {
const { classes } = this.props;
return (
<form className={classes.container} noValidate autoComplete="off">
{
getForm("Form1")
}
<TextField
required
id="standard-required"
label="Required"
defaultValue="Hello World"
className={classes.textField}
margin="normal"
/>
I was expecting that the call to getForm() would have rendered my fields, but instead it spits out this on the web page. Am I doing something wrong?
<TextField id="standard-name" name='asdada' className={classes.textField} value={this.state.name} onChange={this.handleChange("name")} required=true margin="normal"/><TextField id="standard-uncontrolled" name="asda" label="Required" className="{classes.textField}" value="asd" onChange={} required=true margin="normal"/><TextField id="standard-read-only-input" name="asd" label="Read Only" className={classes.textField} value="asd" onChange={} required=false margin="normal" InputProps={{readOnly: true}}/><TextField id="standard-dense" name="w3rg" label="Dense" className={classNames(classes.textField, classes.dense)} value="sdas" onChange={} required=false margin="dense"/>
try to return the component at mapping:
const getForm = formName => {
return FormConfig[formName].fields.map(field => evalComponent(field))
}
const evalComponent = field => {
let { uiElement, ...props } = field
switch(uiElement) {
case 'TextField':
return <TextField {...props}/>
default:
return false
}
}

Resources