To update a property in object in Array with React functional component - javascript

I have an input field which is written in a child component
and its inside return function
const EditSectionComponent = ({
editCaption,
editName,
}) => {
const {name,caption} = details;
return (
<input
type="text"
className="imageNameDetails"
value={name}
onChange={e => editName(e.target)}
/>
)
}
and in parent Component, it's like
const onEditClick = id => {
const selectedAsset = All.find(i => i.id === id);
setDetails(selectedAsset);
}
const [details, setDetails] = useState([]);
const editName = target => {
setDetails({ ...details, [name]: target.value })
};
Initial page load I can see both caption and name in the text field, but I am not able to change its value
It's not updating the UI. Is the right way to reflect the newly edited text in the input field

Make sure to destructure the details from props in EditSectionComponent.
In your parent, the initial state i.e. details is defined as an array. It need to be an object.
Also while doing setDetails, you need to specify the key. (not a dynamic name in your case)
Updated code is here:
const EditSectionComponent = ({
editCaption,
editName,
details,
}) => {
const {name,caption} = details;
return (
<input
type="text"
className="imageNameDetails"
value={name}
onChange={e => editName(e.target)}
/>
)
}
const [details, setDetails] = useState({name: '', caption: ''});
const editName = target => {
setDetails(prev => ({...prev, name: target.value}))
};

Related

Prevent local storage from being changed when filtering in React

Whenever I dispatch a search action using context and useReducer for an object in an array stored in local storage, it returns the object, but when I delete the search query from the input box, the list is not returned and the page is blank, can anyone help please?
This is my context:
const NotesContext = createContext(null);
const NotesDispatchContext = createContext(null);
const getStoredNotes = (initialNotes = InitialNotes) => {
return JSON.parse(localStorage.getItem("storedNotes")) || initialNotes;
};
export const NotesProvider = ({ children }) => {
const [NOTES, dispatch] = useReducer(NotesReducer, getStoredNotes());
useEffect(() => {
localStorage.setItem("storedNotes", JSON.stringify(NOTES));
}, [NOTES]);
return (
<NotesContext.Provider value={NOTES}>
<NotesDispatchContext.Provider value={dispatch}>
{children}
</NotesDispatchContext.Provider>
</NotesContext.Provider>
);
};
export const useNotesContext = () => {
return useContext(NotesContext);
};
export const useNotesDispatchContext = () => {
return useContext(NotesDispatchContext);
};
const App = () => {
const [query, setQuery] = useState("");
const dispatch = useNotesDispatchContext();
useEffect(() => {
if (query.length !== 0) {
dispatch({
type: "searchNotes",
query: query,
});
}
}, [query]);
return (
<div className="container">
<header>
<Title title={"Notes"} className={"app_title"} />
<form className="search_container">
<span class="material-symbols-outlined">search</span>
<input
type="search"
placeholder="search notes"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
</form>
</header>
This is my reducer function
case "searchNotes": {
[...NOTES].filter((note) =>
note.title.toLowerCase().includes(action.query)
);
}
The function seems to actually remove the all data from the local storage instead of filtering based on the query string.
Issue
When you dispatch searchNotes you are changing NOTES and the blow useEffect runs. So if the filter resulted to an empty array, there would be nothing in localStorage.
useEffect(() => {
localStorage.setItem("storedNotes", JSON.stringify(NOTES));
}, [NOTES]);
Solution
What you can do is to remove that useEffect in App that has query as dependency and dispatching searchNotes. And filter directly while rendering, something like this:
{
NOTES.filter((note) => note.title.toLowerCase().includes(query)).map((note, index) => (
<div key={index}>{note.title}</div>
))
}
And at this point you can remove searchNotes case from your reducer.

React Hooks: state variable having wrong value in event handlers, not able to type in input

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

Updated - Changing the state from a functional child component

I have three components which App is the parent component(class-based) and two children component(functional-based) named User & Input. I've imported User to App and Input to User. I want to change the name by the value in Input(there is a button -> onClick).
Updated
The problem is the function in App.jsx(I'm pretty sure the way I try to do it is true).
// App.jsx
Imported User
state = {
users : [
{id: 1, fullName: 'Amirreza Amini'},
{id: 2, fullName: 'John Smith'},
...
]
};
handleChangeName = name => {
const editedUsers = [...this.state.users];
const users = editedUsers.map(user => user.fullName = name);
this.setState({ users });
};
render() {
const { users } = this.state;
const userComponent = users.map(user => (
<User
key={user.id}
fullName={user.fullName}
edit={() => this.handleChangeName(user.fullName)}
/>
));
.
// User.jsx
Imported Input
const User = ({ fullName }) => {
return (
<section>
<h1>{ fullName }</h1>
<Input fullName={fullName} />
</section>
);
};
export default User;
.
// Input.jsx
const Input = ({ fullName }) => {
return (
<section>
{/* The value of this input must be the fullName after I clicked the button */}
<input className="user-edit-input" placeholder={fullName} />
<button className="edit-button" onClick={edit(document.querySelector('.user-edit-input').value)}>Change</button>
</section>
);
};
export default Input;
Two big options as I see it:
Option one is changing App.jsx to functional component and send the function to change the fullname to User.jsx and the to Input.jsx as props letting the Input component change the value by itself
Option two that can leave your App.jsx component as is, keep it more secured and allow the child component to deal with only what it needs to actually know, is similarly sending a function from the parent App component so the Input component can trigger sending the old fullname and new fullname and the parent component function would handle the rest of the process.
example for parent function:
const updateFullname = (previousName, newName) => {
// Get previous object data and location in array
let originalUsersState = this.state.users;
let originalUser = originalUsersState.filter(
(user, index) => user.fullName === previousName
)
const originalUserIndex = originalUsersState.findIndex(
(user, index) => user.fullName === previousName
)
// Create new updated object and insert it to updated object array
let updatedUser = originalUser
updatedUser.fullName = newName;
let updatedUsersState = originalUsersState;
updatedUsersState[originalUserIndex] = updatedUser;
// Update the state with the new updated object array
this.setState(
{
users: updatedUsersState
}
);
}
Abbriviation for a solution for the question code with the new proposed function and process:
// App.jsx
Imported User
state = {
users : [
{id: 1, fullName: 'Amirreza Amini'},
{id: 2, fullName: 'John Smith'},
...
]
};
handleChangeName = name => {
const editedUsers = [...this.state.users];
const users = editedUsers.map(user => user.fullName = name);
this.setState({ users });
};
const updateFullname = (previousName, newName) => {
// Get previous object data and location in array
let originalUsersState = this.state.users;
let originalUser = originalUsersState.filter(
(user, index) => user.fullName === previousName
)
const originalUserIndex = originalUsersState.findIndex(
(user, index) => user.fullName === previousName
)
// Create new updated object and insert it to updated object array
let updatedUser = originalUser
updatedUser.fullName = newName;
let updatedUsersState = originalUsersState;
updatedUsersState[originalUserIndex] = updatedUser;
// Update the state with the new updated object array
this.setState(
{
users: updatedUsersState
}
);
}
render() {
const { users } = this.state;
const userComponent = users.map(user => (
<User
key={user.id}
fullName={user.fullName}
updateFullname={updateFullname}
edit={() => this.handleChangeName(user.fullName)}
/>
));
// User.jsx
Imported Input
const User = ({ fullName, updateFullname }) => {
return (
<section>
<h1>{ fullName }</h1>
<Input fullName={fullName} updateFullname={updateFullname} />
</section>
);
};
export default User;
// Input.jsx
const Input = ({ fullName }) => {
const [inputValue, setInputValue] = useState(undefined);
return (
<section>
<input className="user-edit-input" value={inputValue} onChange={event => setInputValue(event.target.value)} placeholder={fullName} />
<button className="edit-button" onClick={(fullname, inputValue) => updateFullname}>Change</button>
</section>
);
};
export default Input;
You can send input as props to callback from User component. From Users, with the callback function, call one more function to set state in User component

Draft-js do not save database

I am unable to save description as part of the component's state. I can only save the title. How do I save title and description to the database?
const BlogCreate = ({ history }) => {
const [blogCreate, setBlogCreate] = useState({
title: "",
description: ""
});
const [editorState, setEditorState] = useState(
EditorState.createEmpty(),
);
const handleChange = ({ currentTarget }) => {
const { name, value } = currentTarget;
setBlogCreate({...blogCreate, [name]: value});
};
const onEditorStateChange = editorState => {
setEditorState(editorState);
};
const handleSubmit = async event => {
event.preventDefault();
const data = draftToHtml(convertToRaw(editorState.getCurrentContent()));
try {
await blogAPI.create(blogCreate, data);
} catch (error) {
console.log(error)
}
}
console.log(data);
}
return(
<Field type="text" name="title" error={errors.title} value={blogCreate.title}
onChange={handleChange}
/>
<Editor name="description" editorState={editorState} onEditorStateChange={editorState => onEditorStateChange(editorState)}
/>
<button type="submit">Save</button>
);
}
export default BlogCreate;
Based on the full code you've provided me, I realised that you aren't properly updating the blogCreate state whenever there is a change at the Editor component.
The onEditorStateChange() should be updating the blogCreate state, and in addition, changeValue() needs to return the result value.
const changeValue = editorState => {
const value = ....
return value;
};
const onEditorStateChange = editorState => {
const description = changeValue(editorState);
setBlogCreate({
...blogCreate,
description,
});
setEditorState(editorState);
};
This way, description will be properly updated on your state, and it will be sent to your server side when you make the blogAPI.create() request.

Input onchange checker if data exist in JSON data

I have a user requirement when adding a form it should check if the name of the form is already exist. How can I do that in es6? I'm using AntDesign and ReactJS.
Here's my code
<Form.Item label="Form Name">
{getFieldDecorator('formName', {
rules: [formConfig],
})(<Input name="formName" onChange={onChange} />)}
</Form.Item>
const handleChange = e => {
const { name, value } = e.target;
setState(() => ({
...state,
[name]: value,
}));
let isExist = [...formDataSource];
let foundExistItem = isExist.filter(
item => item.formName === formName
);
};
If you want dynamically query the form fields, you should wrap your form with Form.create.
It injects useful functionalities like onFieldsChange listener:
const onFieldsChange = (_, changedFiels) => {
const { username } = changedFiels;
if (username) {
// Make your API checks
console.log(`Now changing ${username.name}`);
}
};
const ValidatedFields = Form.create({ onFieldsChange })(App);
Note: You should keep your Form.Items uncontrolled using getFieldDecorator therefore avoiding onChange while collecting form data.
If you still insist to make form items controlled, you should use getFieldValue:
const handleChange = e => {
const { name, value } = e.target;
const { getFieldValue } = form;
setState(() => ({
...state,
[name]: value
}));
// or use getFieldValue for a single query
const values = getFieldsValue(['formName',...more fields]);
if (values.length) {
// API CALL
}
};

Categories