state update not affecting on view - reactjs

I'm working on a basic react project, where I have react state with object and array, what I'm trying to get done here is I want to push the object to the images array, and I want to render that in component, so far I googled things I found few methods to get it done array.concat and array.push, I tried both methods it's updating the state but it's not affecting on the component
here below is the code I had tried
const [proClrData, setProClrData] = useState([
{ color: "black", price: "", qty: "", images: [] },
{ color: "white", price: "", qty: "", images: [] },
{ color: "red", price: "", qty: "", images: [] }
]);
const addImages = (key) => {
var obj = { src: "", imgdata: "" };
proClrData[key].images = proClrData[key].images.concat(obj);
console.log(proClrData);
};
below is component code
{proClrData.length >= 1 &&
proClrData?.map((clr, key) => {
return (
<div key={key}>
<p className="pt-3 ">{clr.color}</p>
<div className="form-group">
<input
label="Price"
name="price"
value={clr?.price}
type="number"
fullWidth
required
/>
</div>
<div className="form-group pt-3">
<input
label="Quantity"
name="qty"
value={clr?.qty}
type="number"
fullWidth
required
/>
</div>
<div className="text-end pt-3">
<button
variant="contained"
color="primary"
onClick={() => addImages(key)}
>
Add Images
</button>
</div>
{clr?.images?.map((img, imgkey) => {
return (
<div className="form-group pt-3" key={imgkey}>
<input
label="Image"
name="images"
value=""
accept="image/*"
type="file"
fullWidth
required
/>
</div>
);
})}
</div>
);
})}
here is codeSandbox of working code.

Issue
You are mutating your state object and then not updating state at all.
const addImages = (key) => {
var obj = { src: "", imgdata: "" };
proClrData[key].images = proClrData[key].images.concat(obj); // mutation!!
console.log(proClrData);
};
Solution
Enqueue a state update with the new image data you want in state. Use a functional state update to update from the previous state. Shallow copy the proClrData array, and for the matching element, also shallow copy it so it is a new object reference, and then also shallow copy the nested images array and append the new object.
const [proClrData, setProClrData] = useState([
{ color: "black", price: "", qty: "", images: [] },
{ color: "white", price: "", qty: "", images: [] },
{ color: "red", price: "", qty: "", images: [] }
]);
const addImages = (key) => {
const obj = { src: "", imgdata: "" };
setProClrData(data => data.map((el, i) => i === key ? {
...el,
images: [...el.images, obj],
} : el));
};
Since React state updates are asynchronously processed, you will want to move the console log into an useEffect hook to log the state after it updates and is available on the next render.
useEffect(() => {
console.log(proClrData);
}, [proClrData]);

you must call setProClrData function to update the state.
Remember that you must not mutate the state, instead you must provide a new reference (a new array in your case) and call the updater function, otherwise React will not be aware that is should re-render the component.
const [proClrData, setProClrData] = useState([
{ color: "black", price: "", qty: "", images: [] },
{ color: "white", price: "", qty: "", images: [] },
{ color: "red", price: "", qty: "", images: [] }
]);
const addImages = (key) => {
const img = { src: "", imgdata: "" };
const newProClrData = proClrData.map((obj, index) => {
return index !== key ? data : { ...obj, images: [...data.images, img] };
});
setProClrData(newProClrData);
};

You should modify proClrData accordingly and be using setProClrData in order to trigger a re-render.
Check the documentation: https://reactjs.org/docs/hooks-state.html

You update the state directly
Use the function for this:
setProClrData(prev => prev.push() // or concat)

Related

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} ... />
</>
);
}

React: Nested Array Form - Input field onChange handler

The form data is set by an the data object. Need help figuring out how to update /handleChange the text inputs
I've tried unique name, those probably won't work because it wont match the "key" in the object.
Any help / input is appreciated!
Data:
export default
{
name: "Restaurants Name",
menu: [
{
category: "Appetizers",
items:
[ {
imgurl: "https://source.unsplash.com/400x200/?863127",
title: "Food 2",
desc: "",
price: "500"
},
{
imgurl: "",
title: "Food 1",
desc: "",
price: "300"
}
]
},
{
category: "Entree",
items:
[ {
imgurl: "https://source.unsplash.com/400x200/?863127",
title: "Entree 1",
desc: "",
price: "500"
},
{
imgurl: "",
title: "Entree 1",
desc: "",
price: "300"
}
]
},
]
}
Code:
import React, { useEffect, useState } from "react";
import "./../App.css"
import MenuData from "../data"
function Edit() {
const [formState, setFormState] = useState(MenuData);
useEffect(() => {
console.log(formState)
}, []);
const handleNameChange = (event) => {
const name = event.target.name;
// console.log(name)
setFormState(prevState => ({
formState: { // object that we want to update
...prevState.formState, // keep all other key-value pairs
[name]: event.target.value, // update the value of specific key
menu: {
...prevState.menu,
items: {
...prevState.menu.items
}
}
}
}))
// setFormState({
// ...formState,
// [name]: event.target.value,
// })
};
const handleChange = (categoryIndex, event) => {
// const values = [...formState]
// values[categoryIndex][event.target.name] = event.target.value;
// setFormState(values);
const name = event.target.name;
// setFormState(prevState => ({
// formState: {
// ...prevState.formState,
// menu: {
// ...prevState.menu,
// items{
// ...prevState.items
// }
// }
// }
// }));
};
return (
<div className="App">
<div>
<input name="nameField" id="nameField" maxLength="300" value={formState.name} onChange={handleNameChange} /> <br />
{formState.menu && formState.menu.map((menuitem, categoryIndex) => {
return (
<div key={categoryIndex}>
<div class="line"></div>
<h2>{menuitem.category}</h2>
<input name={"category-" + categoryIndex} id="CategoryField" maxLength="40" categoryIndex={categoryIndex} onChange={event => handleChange(categoryIndex, event)} value={menuitem.category} />
{
menuitem.items.map((item, index) => {
return(
<div key={index}>
<input name={"title-" + index + categoryIndex} id="titleField" maxLength="40" categoryIndex={categoryIndex} onChange={handleChange} value={item.title} /> <br />
<input name="desc" id="descField" maxLength="200" categoryIndex={categoryIndex} onChange={handleChange} value={item.desc} />
<br /><br />
</div>
)
})
}
</div>
)
}
)
}
</div>
</div>
);
}
export default Edit;
UPDATED
Not able to figure out the onChange function to updated nested items

Doesn´t render items React useState

I would like to add new tasks but they are not rendered, here is this part of my code:
setItemsFromBackend([...itemsFromBackend, {id: uuidv4(), content: text }]);
setText("")
}
const [itemsFromBackend, setItemsFromBackend] = useState([{ id: uuidv4(), content: "First task" }]);
const [text, setText] = useState("");
const columnsFromBackend = {
[uuidv4()]: {
name: "Requested",
items: itemsFromBackend,
},
[uuidv4()]: {
name: "To do",
items: [],
},
[uuidv4()]: {
name: "In Progress",
items: [],
},
[uuidv4()]: {
name: "Done",
items: [],
},
};
<div>
<input type="text" value={text} onChange={(e) => setText(e.target.value)}/>
<button onClick={addItem}>Add</button>
</div>
Here is the complete project in codesandbox:
https://codesandbox.io/s/trello-task-yhbmu?file=/src/Kanban.jsx
Any help will be appreciated. Thanks!
setItemsFromBackend({ ...itemsFromBackend, id: uuidv4(), content: text });
itemsFromBackend is an array, so you're spreading in the wrong spot. Try this:
setItemsFromBackend([...itemsFromBackend, {id: uuidv4(), content: text }]);
First thing, instead of a div use a proper form around the form elements. And instead of listening on the button's click event, listen on form submit, so you can add an item by hitting the Enter key too, not just button click.
<form onSubmit={addItem}>
<input type="text" value={text} onChange={(e) => setText(e.target.value)} />
<button>Add</button>
</form>
I think, you should update the way you handle state. At the moment there are different pieces everywhere. You use columns, columnsFromBackend and these also contain the itemsFromBackend state. And with all these it's way too easy to mutate the state without realising it.
In your addItem method you update itemsFromBackend by using setItemsFromBackend and you forgot that you should also update the columns by using setColumns. If you don't use setColumns, React will not be aware of the changes and you won't see the updates because React won't re-render the components.
Not sure what you receive from your backend, but you should either use the columnsFromBackend object as state (which doesn't seem to be flat enough), or you could create a new state for each columns. You kind of use both of these at the moment by storing the requested column in itemsFromBackend.
The following snippet uses only columns and text states, everything unnecessary is removed, and columns is updated in addItem the same way as you do it in onDragEnd:
function Kanban() {
const [columns, setColumns] = useState({
[uuidv4()]: {
name: "Requested",
items: [{ id: uuidv4(), content: "First task" }]
},
[uuidv4()]: {
name: "To do",
items: []
},
[uuidv4()]: {
name: "In Progress",
items: []
},
[uuidv4()]: {
name: "Done",
items: []
}
});
const [text, setText] = useState("");
const addItem = (e) => {
e.preventDefault();
const item = { id: uuidv4(), content: text };
const requestedColumnId = Object.entries(columns).find(
(i) => i[1].name === "Requested"
)[0];
const column = columns[requestedColumnId];
setColumns({
...columns,
[requestedColumnId]: {
...column,
items: [...column.items, item]
}
});
setText("");
};
const onDragEnd = (result, columns, setColumns) => {
// no changes here
}
return (
<div style={{ display: "flex", justifyContent: "center", height: "100%" }}>
<form onSubmit={addItem}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button type="submit">Add</button>
</form>
{/* no changes below */}
You can find the forked sandbox here: https://codesandbox.io/s/trello-task-forked-okix3

How to properly change state value of array of objects?

Imagine this variable:
let myArray = [
{
name: "animal",
value: "",
},
{
name: "fruit",
value: "",
},
(...)
];
myArray is set in stone - it is hard-coded and its length wont change, but it is a lengthy array of 10 elements. A user will only update myArray objects values via html input. Based on above, can myArray be considered as a state in Svelte?
Is below example the correct way of changing myArray state in Svelte?
(...)
myArray.forEach(element => {
if (element.name === name) element.value = value;
});
I have a button state that its disabled attribute depends on all elements in myArray having some value. Can I use Sveltes $: btnIsDisabled reactive statements to achieve that and how?
<button type="submit" disabled={btnIsDisabled}>
Submit me
</button>
I'm assuming you plan on using your array as the component-state. And that you have an input corresponding to each field.
Try something like this: https://codesandbox.io/s/magical-fog-tfq3q
class App extends React.Component {
state = {
favorites: [
{ name: "animal", value: "" },
{ name: "city", value: "" },
{ name: "song", value: "" },
{ name: "place", value: "" },
{ name: "food", value: "" },
{ name: "sport", value: "" }
],
emptyFields: null
};
handleOnChange = event => {
const { favorites } = this.state;
let updatedArr = favorites.map(favorite => {
if (favorite.name === event.target.name) {
return {
...favorite,
value: event.target.value
};
} else {
return favorite;
}
});
let emptyFields = updatedArr.filter(favorite => {
return favorite.value.length === 0;
});
this.setState({
...this.state,
favorites: updatedArr,
emptyFields: emptyFields
});
};
createFavoriteInputs = () => {
const { favorites } = this.state;
return favorites.map(favorite => {
return (
<div key={favorite.name}>
<label>{favorite.name} :</label>
<input
value={favorite.value}
name={favorite.name}
onChange={this.handleOnChange}
/>
</div>
);
});
};
render() {
const { emptyFields } = this.state;
return (
<div>
{this.createFavoriteInputs()}
<button
disabled={!emptyFields || emptyFields.length > 0 ? true : false}
>
Submit
</button>
{!emptyFields ||
(emptyFields.length > 0 && (
<div>
The following fields are required:
<ul>
{this.state.emptyFields.map(field => {
return <li key={field.name}>{field.name}</li>;
})}
</ul>
</div>
))}
</div>
);
}
}
So now with the emptyFields state, we have a button that is disabled if there are any emptyFields.
handleOnChange() helps us navigate the right state-value to update in our array, creating a new array in our state whenever we make an update to one of the inputs on the form.

Build form by using map(), how to put info from this.state?

import React, { Component } from "react";
import myPhone from "../service/checkPhone.js";
import {usersParam} from'../variable.js';
class FormForUserChange extends Component {
constructor() {
super();
this.state = {
name: "",
age: "",
gender: "",
phone: "",
address: "",
display: "none"
};
}
componentWillMount = () => {
this.setState({ name: this.props.userToChange.name });
this.setState({ age: this.props.userToChange.age });
this.setState({ gender: this.props.userToChange.gender });
this.setState({ phone: this.props.userToChange.phone });
this.setState({ address: this.props.userToChange.address });
};
_makeListFormData=(usersParam)=>{
return usersParam.map(each => {
return (
<input
className="form-control"
type="text"
// defaultValue={this.state.{each}}
placeholder={each}
ref={input => (this.each = input)}
/>
);
});
}
_handleChange = event => {
this.setState({ gender: event.target.value });
};
_handleSubmit = event => {
event.preventDefault();
if (
this.name.value &&
this.address.value &&
this.phone.value &&
this.age.value &&
myPhone(this.phone.value)
) {
const changedUser = {
name: this.name.value,
age: this.age.value,
gender: this.state.gender,
phone: this.phone.value,
address: this.address.value,
id: this.props.userToChange.ident
};
this.props.saveChangedUser(changedUser, this.props.userToChange.hash);
} else {
this.setState({ display: "block" });
}
};
render() {
let form;
let btnText;
const styles = {
display: this.state.display
};
const inputsInForm=this._makeListFormData(usersParam);
if (this.props.openModal) {
form = (
<div className="shadow p-3 mb-5 bg-white rounded" id="form">
<form
className="form-control-file. form-container"
onSubmit={this._handleSubmit.bind(this)}
>
{inputsInForm}
<button className="btn btn-primary" type="submit">
Save changes
</button>
</form>
<span id="form-fill-error" style={styles}>
please fill out all fields correct
</span>
</div>
);
} else {
form = "";
}
return (
<div>
<button
className="btn btn-primary"
id="add-user-btn"
disabled="disabled"
>
{btnText}
</button>
{form}
</div>
);
}
}
export default FormForUserChange;
I have an array from which I build inputs for form(_makeListFormData). In phraseholder I have to put info from state(which comes from props).
So in placeholder I should put something like this.state{each} it does't work off course. Can you give me an advise how to make it?
You can use placeholder={this.state[each]} for your situation. Also, use componentDidMount since componentWillMount will be deprecated in the future as #Think-Twice explained. Also, set your state in one time, not separately like that.
const usersParam = ['name', 'age', 'gender', 'phone', 'address'];
class FormForUserChange extends React.Component {
constructor() {
super();
this.state = {
name: "",
age: "",
gender: "",
phone: "",
address: "",
display: "none"
};
}
componentDidMount = () => {
this.setState({
name: this.props.userToChange.name,
age: this.props.userToChange.age,
gender: this.props.userToChange.gender,
phone: this.props.userToChange.phone,
address: this.props.userToChange.address,
});
};
_makeListFormData = (usersParam) => {
return usersParam.map(each => {
return (
<input
className="form-control"
type="text"
// defaultValue={this.state.{each}}
placeholder={this.state[each]}
ref={input => (this.each = input)}
/>
);
});
}
render() {
const inputsInForm = this._makeListFormData(usersParam);
return(
<div>{inputsInForm}</div>
);
}
}
const userToChange = {
name: "foo",
age: 25,
gender: "male",
phone: "123",
address: "some add",
}
ReactDOM.render(<FormForUserChange userToChange={userToChange} />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
This is not solution for your issue though but few issues I would like to address.
Stop using componentWillMount because this is deprecated.
so you can directly assign props in constructor
constructor(props) {
super(props);
this.state = {
name: this.props.userToChange.name,
age: this.props.userToChange.age,
gender: this.props.userToChange.gender,
phone: this.props.userToChange..phone,
address: this.props.userToChange.address,
display: "none"
};
}
Note: you no need to use setState for each. You can do everything thing in single setState like below
this.setState({
name: this.props.userToChange.name,
age: this.props.userToChange.age,
gender: this.props.userToChange.gender,
phone: this.props.userToChange..phone,
address: this.props.userToChange.address,
display: "none"
});

Resources