Open modal form while retaining a fetch objects properties - reactjs

const curTodos = useRef({});
const handleClickOpen = (o) => {
console.log(o);
curTodos.current = o;
setOpen(true);
};
const allTodos = todos.map((o) => {
console.log("re-render");
return (
<>
<div key={o.id} className="row">
<span>{o.name}</span>
<span>{o.id}</span>
<span>{o.email}</span>
<span>{o.task}</span>
<Button onClick={() => handleClickOpen(o)} variant="outlined">
Edit Todo
</Button>
</div>
</>
);
});
https://codesandbox.io/s/sweet-platform-du3i8x?file=/src/App.js:1593-1664
I made a different component for my modal
When I click on edit todo I want the todo form modal to contain the name and task that the row is on. Currently it just shows up as an empty input
That is,
currently:
I want:
curTodos is a reference to todo object
When I click on edit todos I want the default value to be set to the one on the rows.
Since its already rendered this wont work it just shows up as empty input.

useState(default) value runs only once on mount. Since you're using a component that does not unmount in this view, you can include an effect to update the form state.
// in FormModal
useEffect(() => {
setName(o.name)
setTask(o.task)
}, [o]);

Related

React custom hook with state variable as parameter

I have a react function component which sets an array of ids based on an user event on a link click(which opens a popup with some options that can be selected and has a callback once it is closed which will return the id of the selected element). these ids are passed to a child component which has a custom hook which uses these ids to perform some action. whenever i click on the link and select an element and close the popup.. get the error
"VM10715 react_devtools_backend.js:2430 You have changed a parameter while calling a hook which is supposed to remain unchanged [Array(2)]
0: (2) ["", "asdsadsad"]
lastIndex: (...)
lastItem: (...)
length: 1"
is there a way to make this work without running into this error? please see the code sample below
const TestComp = () => {
const [newIds, setNewIds] = useState([]);
const onPopupElementSelect = (ids) => {
setNewIds([...newIds, ids]);
};
return (
//renders some components
<>
<ImageComponent images={images} ids={newIds} onClick={handleClick} />
<Popup onSelect={onPopupElementSelect} />
</>
);
};
const ImageComponent = (props) => {
const { newIds, images } = props;
const newImages = useImages(ids || ['']); //customhook that fetches image details by ids
const imgs = images.map((i) => (
<div key={i.imageId}>
<img src={i.imageUrl} alt="" />
<Link onClick={handleClick} /> //opens the popup for user to select a new
image
</div>
));
return <div>{imgs}</div>;
};
ps: the paramerter names are not the issue.. this code is just a sample to give the basic idea of what i'm trying to do.
I think it is because you gave the same name to parameter and the state may be try newID as the parameter name
const onPopupElementSelect = (newId) => {
setIds(oldIds => [...oldIds, newId]);
};

why child state value is not updating in parent callback function at first time?

why child state value is not updating in parent callback function at first time? i want to make my input-Field disable based on state.
Sandbox with full example: https://z3wu6.csb.app/
Issue
PageHeader
Initial state is true, when you click the the button the editViewHandler is called. It toggles the edit state of this component but then calls the editCallback callback with the current value of edit which is initially true. You're sending the non-updated value to the callback! (you set it to true again in UserProfile) You can fix this by also inverting the edit value sent to editCallback.
const [edit, setEditView] = useState(true);
const editViewHandler = () => {
setEditView(!edit);
editCallback(!edit); // <-- send the same value you update state to
};
I see you've also duplicated this edit state in UserProfile. You shouldn't duplicate state. You want a single source of truth.
You already pass editCallback from UserProfile so just attach that as the callback to the button.
Suggestion Solution
Toggle the value in the source callback in UserProfile
const UserProfile = () => {
const [edit, setEdit] = useState(true);
const editCallback = () => setEdit(edit => !edit);
return (
<>
<PageHeader
button
editCallback={editCallback}
title="User Profile"
subtitle="Overview"
/>
<UserAccountDetails edit={edit} />
</>
);
};
And attach to button's onClick handler
const PageHeader = ({ title, subtitle, button, editCallback }) => (
<div className="page-header py-4 withBtn d-flex align-items-center">
{button ? <Button onClick={editCallback}>Edit Detail</Button> : null}
</div>
);

Problem with useEffect() on loading the page

I am having trouble with my react quiz app. Here follows the description:
This is from App.js file:
...
const [createQuiz, setCreateQuiz] = useState(false);
...
useEffect(()=> {
const reRender = () => {
setCreateQuiz(true)
}
window.onload=function(){
document.getElementById("myBtn").addEventListener("click", reRender);
}
// return document.getElementById("myBtn").removeEventListener("click", reRender);
}, [createQuiz])
return (
<QuizContextProvider>
{
(createQuiz) ? (
<div>Form</div>
) : (
<div>
<Modal/>
<Question question={questions[questionNumber]} next={goToTheNext} />
</div>
)
}
{console.log(createQuiz)}
</QuizContextProvider>
);
}
As can be seen it is a conditional rendering: a Modal window asks a user whether they want to take the existing quiz or create their own and when the user clicks "Create your own " button, the app should re-render over again, this time the useEffect() (in App.js) sets the value of createQuiz to true. the code excerpt below is from <Modal /> component:
return (
<div className='benclosing' style={{display:displayValue}}>
<div className='modal'>
<h1>Welcome!</h1>
<p>Do you want to take an existing quiz or create your own?</p>
<button onClick={hideWindow} >Existing quiz</button>
<button id='myBtn'>Create your own</button>
</div>
</div>
)
}
Everthing works fine as expected, except for 1: whenever reload icon is clicked, my page re-renders over-again and the user is again asked if they want to take the existing quiz. I want that refreshing affect nothing. I am stuck with this problem. How can I achieve the desired result?
I also tried this:
const reRender = () => {
setCreateQuiz(true)
}
useEffect(()=> {
reRender()
//return setCreateQuiz(false)
}, [createQuiz])
It didn't work as expected. I described what it caused in my 2nd comment to Red Baron, please have a look.
The proper way to achieve what you want is to create an event handler inside your App component that will set createQuiz to true when the Create your own button gets clicked inside the Modal component.
function App() {
const [createQuiz, setCreateQuiz] = React.useState(false);
const handleShowQuizForm = () => {
setCreateQuiz(true);
};
return (
<div>
{createQuiz ? (
<div>Form</div>
) : (
<>
<Modal showQuizForm={handleShowQuizForm} />
</>
)}
</div>
);
}
function Modal(props) {
return (
<div>
<button type="button" onClick={props.showQuizForm}>
Create your own
</button>
</div>
);
}
Here's an example:
CodeSandbox
There's no need for the useEffect hook here and the window.onload event implies to me that you'd want to set createQuiz to true then "refresh" your page and expect createQuiz to now be true - it won't work like that.
Additionally, the way you're using the useEffect hook could be problematic - you should try to stay away from updating a piece of state inside of a useEffect hook that's also part of the dependency array:
React.useEffect(() => {
const reRender = () => {
setCreateQuiz(true);
}
// although not an issue here, but if this hook was
// rewritten and looked like the following, it would
// case an infinite re-render and eventually crash your app
setCreateQuiz(!createQuiz);
}, [createQuiz]);

How to render a different component with React Hooks

I have a parent component with an if statement to show 2 different types of buttons.
What I do, on page load, I check if the API returns an array called lectures as empty or with any values:
lectures.length > 0 ? show button A : show button B
This is the component, called main.js, where the if statement is:
lectures.length > 0
? <div onClick={() => handleCollapseClick()}>
<SectionCollapse open={open} />
</div>
: <LectureAdd dataSection={dataSection} />
The component LectureAdd displays a + sign, which will open a modal to create a new Lecture's title, while, SectionCollapse will show an arrow to show/hide a list of items.
The logic is simple:
1. On page load, if the lectures.lenght > 0 is false, we show the + sign to add a new lecture
OR
2. If the lectures.lenght > 0 is true, we change and show the collpase arrow.
Now, my issue happens when I add the new lecture from the child component LectureAdd.js
import React from 'react';
import { Form, Field } from 'react-final-form';
// Constants
import { URLS } from '../../../../constants';
// Helpers & Utils
import api from '../../../../helpers/API';
// Material UI Icons
import AddBoxIcon from '#material-ui/icons/AddBox';
export default ({ s }) => {
const [open, setOpen] = React.useState(false);
const [ lucturesData, setLecturesData ] = React.useState(0);
const { t } = useTranslation();
const handleAddLecture = ({ lecture_title }) => {
const data = {
"lecture": {
"title": lecture_title
}
}
return api
.post(URLS.NEW_COURSE_LECTURE(s.id), data)
.then(data => {
if(data.status === 201) {
setLecturesData(lucturesData + 1) <=== this doesn't trigger the parent and the button remains a `+` symbol, instead of changing because now `lectures.length` is 1
}
})
.catch(response => {
console.log(response)
});
}
return (
<>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
<AddBoxIcon />
</Button>
<Form
onSubmit={event => handleAddLecture(event)}
>
{
({
handleSubmit
}) => (
<form onSubmit={handleSubmit}>
<Field
name='lecture_title'
>
{({ input, meta }) => (
<div className={meta.active ? 'active' : ''}>
<input {...input}
type='text'
className="signup-field-input"
/>
</div>
)}
</Field>
<Button
variant="contained"
color="primary"
type="submit"
>
ADD LECTURE
</Button>
</form>
)}
</Form>
</>
)
}
I've been trying to use UseEffect to trigger a re-render on the update of the variable called lucturesData, but it doesn't re-render the parent component.
Any idea?
Thanks Joe
Common problem in React. Sending data top-down is easy, we just pass props. Passing information back up from children components, not as easy. Couple of solutions.
Use a callback (Observer pattern)
Parent passes a prop to the child that is a function. Child invokes the function when something meaningful happens. Parent can then do something when the function gets called like force a re-render.
function Parent(props) {
const [lectures, setLectures] = useState([]);
const handleLectureCreated = useCallback((lecture) => {
// Force a re-render by calling setState
setLectures([...lectures, lecture]);
}, []);
return (
<Child onLectureCreated={handleLectureCreated} />
)
}
function Child({ onLectureCreated }) {
const handleClick = useCallback(() => {
// Call API
let lecture = callApi();
// Notify parent of event
onLectureCreated(lecture);
}, [onLectureCreated]);
return (
<button onClick={handleClick}>Create Lecture</button>
)
}
Similar to solution #1, except for Parent handles API call. The benefit of this, is the Child component becomes more reusable since its "dumbed down".
function Parent(props) {
const [lectures, setLectures] = useState([]);
const handleLectureCreated = useCallback((data) => {
// Call API
let lecture = callApi(data);
// Force a re-render by calling setState
setLectures([...lectures, lecture]);
}, []);
return (
<Child onLectureCreated={handleLectureCreated} />
)
}
function Child({ onLectureCreated }) {
const handleClick = useCallback(() => {
// Create lecture data to send to callback
let lecture = {
formData1: '',
formData2: ''
}
// Notify parent of event
onCreateLecture(lecture);
}, [onCreateLecture]);
return (
<button onClick={handleClick}>Create Lecture</button>
)
}
Use a central state management tool like Redux. This solution allows any component to "listen in" on changes to data, like new Lectures. I won't provide an example here because it's quite in depth.
Essentially all of these solutions involve the same solution executed slightly differently. The first, uses a smart child that notifies its parent of events once their complete. The second, uses dumb children to gather data and notify the parent to take action on said data. The third, uses a centralized state management system.

increment/decrement click component. How to save data to db.json?

I got a react functional component that can increment or decrement a number.
When the page is loaded, i want this number to be read in the db.json file from my JSON-SERVER and displayed in my view. I also want to update the number in the db.json file when i increment or decrement the value in my page.
I tried to console.log the data coming from the db.json, and what i see is that console.log is displayed 2 times in my console :
first time an empty []
second time it is good [{"clicks":20,"id":1},{"clicks":50,"id":2}]
What i tried so far was to display the clicks value with { clicked[0].clicks }
but the '.clicks' leads me to an undefined error...
What am i doing wrong ? Thanks for your help!
const CountClick = (props) => {
const componentTitle = 'Count Click Component';
const [clicked, setClicked] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'http://localhost:4000/clicked'
);
setClicked(result.data);
};
fetchData();
},[]);
console.log('clicked array', JSON.stringify(clicked));
return (
<div>
<span className="result">Counter value: { clicked.clicks }</span>
<button onClick={ () => {setClicked(clicked.clicks + 1);saveClicks(clicked.clicks + 1)} }>+1</button>
<button onClick={ () => {setClicked(clicked.clicks - 1);saveClicks(clicked.clicks - 1)} }>+1</button>
</div>
);
I except to display the "clicks" value in my view
Assuming save and load functions work correctly is a simple check for the display, you can use sth like lodash isEmpty or check length of the array if more than 1 item display count.
IsEmpty(Clicked) ? Loading : clicked[0].clicks
UseEffect works in a similar pattern to component did mount. The data is loaded after the component renders to screen so at the time your clicked value is empty and no clicks can be displayed aka undefined

Resources