Reusable container component with state and dynamic child component - reactjs

I am trying to have a reusable component that can instantiate a dynamic component:
function EditableElement({endpoint, data, ElementFormClass}) {
const [edit, setEdit] = useState(false);
const [formData, setFormData] = useState(data);
const [deleted, setDeleted] = useState(false);
if (deleted) return (<p>X_X</p>);
if (edit) return ElementFormClass(
endpoint,
data,
setFormData,
pk => {
setDeleted(true);
},
e => setEdit(false)
)
return (
<p onClick={e => setEdit(true)}>{formData.name}</p>
)
}
function ElementFormDisplayOne({endpoint, data, cancel, updateData, deleteData}) {
const [formData, setFormData] = useState(data);
// const [deleted, setDeleted] = useState(false);
// if (deleted) return (<p style={{color: 'red'}}>DELETED</p>);
return (
<form onSubmit={e => {
e.preventDefault();
e.stopPropagation();
updateData(formData);
cancel();
}}>
<label htmlFor={formData.pk}>{formData.name}</label>
<input type="text" id={formData.pk} value={formData.name}
onChange={e => setFormData({...formData, name: e.target.value})}/>
<button type="button" onClick={cancel}>cancel</button>
<button type="submit">ok</button>
<button type="button" onClick={e => deleteData(formData.pk)}>delete</button>
</form>
)
}
It's used like this:
<div>
<h3>Gallery Form ONE</h3>
{data.map(el => <EditableElement endpoint={'update'} key={el.pk} data={el}
ElementFormClass={ElementFormDisplayOne}/>)}
</div>
<div>
<h3>Gallery Form TWO</h3>
{data2.map(el => <EditableElement endpoint={'update'} key={el.pk} data={el}
ElementFormClass={ElementFormDisplayTwo}/>)}
</div>
But it throws
Error: Rendered more hooks than during the previous render.
Is what I want possible in ReactJs?
Note: I'm not using Redux and don't intend to
Codesandbox example

Fixed your error in line 104 of your codesandbox by converting your function into an actual JSX element.
if (edit)
return (<ElementFormClass
endpoint={endpoint}
data={data}
setFormData={setFormData}
cb1 = {(pk) => setDeleted(true)}
cb2 = {(e) => setEdit(false)}
/>);
This is a common error people face in react, where they use a react component as a function rather than JSX.

Related

How to render form after submission in react?

Given the following form, I need whenever the form is submitted, the new post to be listed/rendered without having to refresh the page.
const PostCreate = () => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;
I tried adding this.forceUpdate() and this.setState(this.state), neither works, and I still have to refresh the page for the new post to show.
Here's how the posts are rendered:
const PostList = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
This is what App.js looks like
const App = () => {
return <div>
<h1>Create Post</h1>
<PostCreate></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList></PostList>
</div>;
};
export default App;
and is eventually rendered using:
ReactDOM.render(
<App></App>,
document.getElementById('root')
)
In your PostList, useEffect called once when you first load your component, so when you create new post, it will not be re-rendered
You should bring your fetchPost logic to your App component, and add function props onPostCreated to PostCreate component, trigger it after you finish creating your new post
The code should be:
const App = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
return <div>
<h1>Create Post</h1>
<PostCreate onCreatePost={() => fetchPost()}></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList posts={posts}></PostList>
</div>;
};
export default App;
const PostList = ({ posts }) => {
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
const PostCreate = ({ onCreatePost }) => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
onCreatePost && onCreatePost();
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;
I think the problem you are having is not in the code you have displayed. The component is indeed rerendering after you change its state and also when you forceUpdate() it. I assume the posts you are trying to display are taken from the same API that you post to. Even if this component is being rerendered, your GET request which gives the data to the component who renders it is not called again so the data doesn't update. You need to refetch it. This can be done by many different ways (useEffect(), callbacks, reactQuery refetch) depending on the rest of your code. I would need the component that renders the data and the API call to help you further.
Another thing that you didn't ask but is good practice. In your PostCreate component you don't need to manage the state of fields that are in the form, because it already does it for you. Just give a name to your inputs and use the form data. I've given an example below.
import { useState } from "react";
const PostCreate = () => {
const onSubmit = async (event) => {
event.preventDefault();
console.log(event.target.elements.title.value);
};
return (
<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input name="title" className="form-control" />
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>
);
};
export default PostCreate;

How to add items to array in react

Code:
export default function App() {
const [name,setName] = useState("");
var myArray = [];
const handleAdd = () => {
myArray = [...myArray,name]
setName("")
}
return (
<div className="App">
<input placeholder="type a name" onChange={(e) => setName(e.target.value)}/>
<button onClick={handleAdd}>add</button>
<button onClick={() => console.log(myArray)}>test</button>
{myArray.map((n) => {
return <h2>{n}</h2>
})}
</div>
);
}
OnClick it isn't adding the name to the array.
this is how you "push" to an array with useState
const [array, setArray] = useState([])
setArray(previous => [...previuous, newItem])
You should use a state for your array and set that state to see the changes reflected:
export default function App() {
const [name, setName] = useState('');
const [myArray, setMyArray] = useState([]);
const handleAdd = () => {
setMyArray([...myArray, name]);
setName('');
};
return (
<div className="App">
<input
placeholder="type a name"
onChange={(e) => setName(e.target.value)}
/>
<button onClick={handleAdd}>add</button>
<button onClick={() => console.log(myArray)}>test</button>
{myArray.map((n) => {
return <h2>{n}</h2>;
})}
</div>
);
}
We can also set the state of myArr to be an empty array initially, making it easier to manipulate the subsequent state of that array. The onClick event handler does not fire the handleAdd function, for some reason, it only resets the form and does not provide any state. To submit the form and materialize the state, we can also use the onSubmit event handler instead of onClick. In the same way, we can use the name state as a value/prop for the name input, which will be used by the onChange handler.
import React, { useState } from 'react'
const App = () => {
const [name, setName] = useState('')
const [myArr, setMyArr] = useState([])
const submit = (event) => {
event.preventDefault()
setMyArr(myArr.concat(name))
setName('')
}
//console.log(myArr)
return (
<div className="App">
<form onSubmit={submit}>
<div>
<label htmlFor="name">Name</label>
<input
placeholder="type a name"
type="text"
value={name}
onChange={({ target }) => setName(target.value)}
/>
</div>
<div>
<button type="submit">Add</button>
</div>
</form>
<div>
{myArr.map((arr, index) => (
<div key={index}>
<p>{arr}</p>
</div>
))}
</div>
</div>
)
}
export default App
I have a proclivity of inserting items on an array using concat.
import React, { useState } from 'react'
// ...
const App = () => {
// ...
const [myArr, setMyArr] = useState([])
// somewhere on your event handler e.g. Submit handler
setMyArr(myArr.concat(name))
// ...
}

Focus Trap React and a few component's in 2 popup's

I have 2 popup's(I reuse CloseButton(component) and Modal(component) in 2 popup's) and need to do focus trap at all. I lf answer 4 better way.
1 popup Screen, components: ModalLogin-Modal-CloseButton.
I read about some hooks: useRef() and forwardRef(props, ref)
but i don't undestand why it's not work in my case. I am trying to find a solution. I need help :)
In ModalLogin, I try to do a focus trap. To do this, I mark what should happen with focus when moving to 1 and the last element. I need to pass my ref hook obtained via Modal-CloseButton. I read that you can't just transfer refs to functional components. I try to use the forwardref hook in the necessary components where I transfer it, here's what I do:
All links without focus-trap and hook's!.
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/form-login/modal-login.jsx [Modal-login full]
const ModalLogin = () => {
const topTabTrap* = useRef();
const bottomTabTrap* = useRef();
const firstFocusableElement = useRef();
const lastFocusableElement = useRef();
useEffect(() => {
const trapFocus = (event) => {
if (event.target === topTabTrap.current) {
lastFocusableElement.current.focus()
}
if (event.target === bottomTabTrap.current) {
firstFocusableElement.current.focus()
}
}
document.addEventListener('focusin', trapFocus)
return () => document.removeEventListener('focusin', trapFocus)
}, [firstFocusableElement, lastFocusableElement])
return (
<Modal onCloseModal={() => onCloseForm()} ref={lastFocusableElement}>
<form >
<span ref={topTabTrap} tabIndex="0" />
<Logo />
<Input id="email" ref={firstFocusableElement} />
<Input id="password" />
<Button type="submit" />
<span ref={bottomTabTrap} tabIndex="0"/>
</form>
</Modal>
);
};
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/modal/modal.jsx [Modal full]
const Modal = forwardRef(({ props, ref }) => {
const { children, onCloseModal, ...props } = props;
const overlayRef = useRef();
useEffect(() => {
const preventWheelScroll = (evt) => evt.preventDefault();
document.addEventListener('keydown', onEscClick);
window.addEventListener('wheel', preventWheelScroll, { passive: false });
return () => {
document.removeEventListener('keydown', onEscClick);
window.removeEventListener('wheel', preventWheelScroll);
};
});
const onCloseModalButtonClick = () => {
onCloseModal();
};
return (
<div className="overlay" ref={overlayRef}
onClick={(evt) => onOverlayClick(evt)}>
<div className="modal">
<CloseButton
ref={ref}
onClick={() => onCloseModalButtonClick()}
{...props}
/>
{children}
</div>
</div>
);
});
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/close-button/close-button.jsx [CloseButton full]
const CloseButton = forwardRef(({ props, ref }) => {
const {className, onClick, ...props} = props;
return (
<button className={`${className} close-button`}
onClick={(evt) => onClick(evt)}
tabIndex="0"
ref={ref}
{...props}
>Close</button>
);
});
And now i have a lot of errors just like: 1 - Cannot read properties of undefined (reading 'children') - Modal, 2 - ... className undefined in CloseButton etc.
2 popup Screen, components: Modal(reuse in 1 popup) - InfoSuccess- CloseButton(reuse in 1 popup)
I have only 1 interactive element - button (tabindex) and no more. Now i don't have any idea about 2 popup with focus-trap ((
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/success-modal/success-modal.jsx [SuccessModal full]
const SuccessModal = ({ className, onChangeVisibleSuccess }) => {
return (
<Modal onCloseModal={() => onChangeVisibleSuccess(false)}>
<InfoSuccess className={className} />
</Modal>
);
};
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/info-block/info-block.jsx [Infoblock full]
const InfoBlock = ({ className, title, desc, type }) => {
return (
<section className={`info-block ${className} info-block--${type}`}>
<h3 className="info-block__title">{title}</h3>
<p className="info-block__desc">{desc}</p>
</section>
);
};
const InfoSuccess = ({ className }) => (
<InfoBlock
title="Спасибо за обращение в наш банк."
desc="Наш менеджер скоро свяжется с вами по указанному номеру телефона."
type="center"
className={className}
/>
);
I know about 3 in 1 = 1 component and no problem in popup with Focus-Trap. But i want understand about my case, it's real to life or not and what best practice.

noob - react typescript todo

could you please help me with a simple issue? I've always been programming in React, but I was always using pure js. Recently tried to transition to TS and I hate every single bit of it, but trying to understand all this hype. Could you please just give me an example of good usage of typescript using the bit of the code that I wrote? It's a simple to-do list, but have all the problems that I encountered - and in JS it would work!
Just show me how it should be done and I will try to write the real applications after figuring out the right logic behind it.
const NoteApp = () => {
const [notes, setNotes] = useState<Array<string>>()
const [title, setTitle] = useState<string>('')
const [body, setBody] = useState<string>('')
const addNote = (e:any) => {
e.preventDefault()
setNotes([
...notes,
{ title, body }
])
setTitle('')
setBody('')
}
const removeNote = (title:any) => {
setNotes(notes.filter((note) => note.title !== title))
}
return (
<div>
<h1>Notes</h1>
{notes.map((note) => (
<div key={note.title}>
<h3>{note.title}</h3>
<p>{note.body}</p>
<button onClick={() => removeNote(note.title)}>x</button>
</div>
))}
<p>Add note</p>
<form onSubmit={addNote}>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<textarea value={body} onChange={(e) => setBody(e.target.value)}></textarea>
<button>add note</button>
</form>
</div>
)
}
Typescript is just a tool to keep you code more redeable and less buggy.
It provide you way to ensure that ~90% of something is undefinded error are gone.
In this situation you can declare shape of your note like that
type Note = {
title: string;
body: string;
};
const NoteApp = () => {
const [notes, setNotes] = useState<Array<Note>>([]);
const [title, setTitle] = useState<string>('');
const [body, setBody] = useState<string>('');
const addNote = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setNotes([...notes, { title, body }]);
setTitle('');
setBody('');
};
const removeNote = (title: string) => {
setNotes(notes.filter((note) => note.title !== title));
};
const onTileChange = (e: React.ChangeEvent<HTMLInputElement>) => setTitle(e.target.value);
const onBodyChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => setBody(e.target.value);
return (
<div>
<h1>Notes</h1>
{notes.map((note) => (
<div key={note.title}>
<h3>{note.title}</h3>
<p>{note.body}</p>
<button onClick={() => removeNote(note.title)}>x</button>
</div>
))}
<p>Add note</p>
<form onSubmit={addNote}>
<input value={title} onChange={onTileChange} />
<textarea value={body} onChange={onBodyChange} />
<button type="submit">add note</button>
</form>
</div>
);
};

useState doesn't change the state in React

[Mycode] (https://codesandbox.io/s/romantic-kowalevski-fp00l?file=/src/App.js)
I'm practicing React by making todo-list app.
I want my input empty when i hit Enter. but it didn't work.
here is my whole code :
const Todo = ({ text }) => {
return (
<div>
<span>{text}</span>
</div>
);
};
const InputText = ({ addTodo }) => {
const [txt, setTxt] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (!txt) return;
addTodo(txt);
setTxt("");
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={(e) => setTxt(e.target.value)}></input>
</form>
);
};
function App() {
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
const newTodos = [...todos, text];
setTodos(newTodos);
};
return (
<>
<div className="todo-list">
{todos.map((val, idx) => {
return <Todo key={val + idx} text={val} />;
})}
<InputText addTodo={addTodo} />
</div>
</>
);
}
line 17 on the link, setTxt(""); doesn't change state of txt.
how can i fix it?
That is not a "controlled" component since you are not using the value property on the input.
Try
<input type="text" onChange={e => setTxt(e.target.value)} value={txt} />
https://reactjs.org/docs/forms.html
You actually need to set the input value to your state.
Try something like
<Input type="text" onChange={(e) => setTxt(e.target.value)} value={txt}/>
I hope it helps.

Resources