Create Pdf Using React Unhandled Rejection (Error): write after end - javascript

Describe the bug
Hi everyone,
I'm trying to generate a PDF which includes some user-provided info. In particular, I want my user to choose the filename of the pdf which will be also the title of it.
In order to do this I'm using:
"#react-pdf/renderer": "^1.6.12" for the pdf rendering
"react-bootstrap": "^1.3.0" for the page layout
"formik": "^2.2.0" for form management
The problem I have consists in the fact that whenever the user starts typing the desired filename/title in the apposite input the page crashes with the following error:
Unhandled Rejection (Error): write after end
3 stack frames were expanded.
writeAfterEnd
node_modules/stream-browserify/node_modules/readable-stream/lib/_stream_writable.js:288
(anonymous function)
node_modules/stream-browserify/node_modules/readable-stream/lib/_stream_writable.js:332
addContent
node_modules/#react-pdf/pdfkit/dist/pdfkit.browser.es.js:4184
After deeper investigations, I noticed that the problem resides in the "dynamicity" of the generated pdf content.
The code is the following:
const DownloadModal = ({configuration}) => {
const [show, setShow] = useState(false);
const formik = useFormik({
initialValues: {
name: "",
}
});
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Button variant="primary"
onClick={() => {
handleShow()
}}
>
Download PDF <DownloadIcon/>
</Button>
<Modal size={"lg"} show={show} onHide={handleClose} animation={true}>
<Modal.Header closeButton>
<Modal.Title>Choose filename</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={(event) => {
event.preventDefault();
}}>
<Form.Group as={Row}>
<Form.Label column sm={2}>
Filename
</Form.Label>
<Col sm={10}>
<InputGroup className="mb-3">
<FormControl
type="text"
id={"name"}
name={"name"}
placeholder="filename"
onChange={formik.handleChange}
value={formik.values.name}
autoComplete={"off"}
/>
<InputGroup.Append>
<InputGroup.Text id="basic-addon2">.pdf</InputGroup.Text>
</InputGroup.Append>
</InputGroup>
</Col>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
<DownloadPDFButton
data={configuration}
title={formik.values.name}
/>
</Modal.Footer>
</Modal>
</>
);
};
const DownloadPDFButton = ({data, title}) => {
return (
<PDFDownloadLink
document={
<PdfDocument
data={data}
title={{title}}
/>}
fileName={`${title}.pdf`}
className={"btn btn-link"}
>
{({blob, url, loading, error}) =>
loading ? <Spinner animation="border" variant="primary"/> : "Download"
}
</PDFDownloadLink>
)
}
I think that the problem resides here, in the way I display title.title. But I really cannot find a way to make it works.
const Title = ({title}) => {
return (
<View style={styles.titleContainer}>
<Text>{title.title}</Text>
</View>
)
}
;
const PdfDocument = (props) => {
const configurationData = {
plant: props.data.plant,
flow: props.data.flow,
optionals: props.data.optionals
};
const utilitiesData = {
total_installed_power: props.data.total_installed_power,
total_absorbed_power: props.data.total_absorbed_power,
makeup_water: props.data.makeup_water,
compressed_air: props.data.compressed_air
};
return (
<Document>
<Page key={"page-0"} style={styles.page}>
<Image style={styles.logo} src={logo}/>
<Title title={props.title}/>
<ConfigurationPDF configuration={configurationData}/>
<ResultsPDF results={utilitiesData}/>
</Page>
</Document>
);
};
Thanks to everyone in advance!!
Running environment:
OS: [MacOS]
Browser [chrome, safari]

I think your problem is solved now
I only convert formik state to react state
Codesandbox: https://codesandbox.io/s/reverent-browser-1bn4n

Related

Warning: Can't perform a React state update on an unmounted component. 2021 Not Async

getting this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
const MapBox = () => {
const mapContainer = useRef(null);
const map = useRef(null);
const [visible, setVisible] = useState(false)
const onClick = () => setVisible(true);
const closeHandler = () => {
setVisible(false);
}
useEffect(() => {
}, [visible])
return (
<>
<MapContainer ref={mapContainer} className="map-container" onClick={onClick}></MapContainer>
<ModalContainer visible={visible} closeHandler={closeHandler} />
</>
)
}
The modal container is just a simple modal pop, only returning a jsx component. That only shows if visible is true, and if runs closeHandler is a button is pressed.
const ModalContainer = ({ visible, closeHandler }) => {
useEffect(() => {}, [visible])
return (
<>
{visible && (
<div>
<Modal
closeButton
aria-labelledby="modal-title"
open={visible}
onClose={closeHandler}
>
<Modal.Header>
<Text id="modal-title" size={18}>
Welcome to
<Text b size={18}>
NextUI
</Text>
</Text>
</Modal.Header>
<Modal.Body>
<Input
clearable
bordered
fullWidth
color="primary"
size="large"
placeholder="Email"
/>
<Input
clearable
bordered
fullWidth
color="primary"
size="large"
placeholder="Password"
/>
<Row justify="space-between">
<Checkbox>
<Text size={14}>
Remember me
</Text>
</Checkbox>
<Text size={14}>
Forgot password?
</Text>
</Row>
</Modal.Body>
<Modal.Footer>
<Button auto flat color="error" onClick={closeHandler}>
Close
</Button>
<Button auto onClick={closeHandler}>
Sign in
</Button>
</Modal.Footer>
</Modal>
</div>)
}
</>
);
}
Any help would be much appreciated, all other people on here with this error message are running async code etc. This one is a lot simpler and the error only comes up the first time the click to close event happens.

Edit a Cloud Firestore record from ReactJS

I am trying to edit the recipes record from a ReactJS app on the Cloud Firestore database. I am not sure how to access all the parameters of the update useState hook, below is the code:
import React,{useState} from 'react'
function Recipes(props) {
const [edit, setEdit] = useState(false)
const[update,setUpdate] = useState({
id:null,
recipeName: '',
ingredients:[],
desc:''
});
const [toUpdateId, setToUpdateId] = useState('')
function showEdit(){
setEdit(true);
openUpdateDialog(props.recipeName)
}
const openUpdateDialog = (newRecipe) => {
setEdit(true);
setToUpdateId(newRecipe.id);
setUpdate({
id:newRecipe.id,
recipeName: newRecipe.title,
ingredients:newRecipe.ingredients,
desc:newRecipe.desc
});
}
function closeModal(){
if(edit){
setEdit(false)
}
}
const editRecipe = () => {
db.collection('recipes').doc(toUpdateId).update({
title: update,
ingredients:update,
// desc:update.desc
});
setEdit(false);
}
const handleClose = () => {
setEdit(false)
}
return (
<Accordion>
<Card>
<Card.Title>{props.newRecipe.title}</Card.Title>
<Card.Body>
<ol>
{props.newRecipe.ingredients.split(',').map((item)=> {
return (
<li key={item}>{item}</li>
)
})}
</ol>
<Card.Text>
{props.newRecipe.desc}
</Card.Text>
<ButtonToolbar>
<Button variant="danger" onClick={event => db.collection('recipes').doc(props.newRecipe.id).delete()}>Delete</Button>
<Button variant="info" onClick={() => {openUpdateDialog(props.newRecipe)}}>Edit</Button>
</ButtonToolbar>
</Card.Body>
</Card>
<Modal show={edit} onHide={closeModal}>
<Modal.Header closeButton>
<Modal.Title>Edit Recipe</Modal.Title>
<Modal.Body>
<FormGroup>
<FormLabel>Recipe Name:</FormLabel>
<FormControl type="text"
value={update.recipeName}
name="recipeName"
placeholder="Enter Recipe Name"
onChange={e=>setUpdate(e.target.value)}
>
</FormControl>
</FormGroup>
<FormGroup>
<FormLabel>Recipe Ingredients:</FormLabel>
<FormControl type="textarea"
value={update.ingredients}
name="ingredients"
placeholder="Enter Recipe Ingredients separated by commas"
onChange={e=>setUpdate(e.target.value)}
>
</FormControl>
</FormGroup>
<FormGroup>
<FormLabel>Recipe Description:</FormLabel>
<FormControl type="textarea"
value={update.desc}
name="desc"
placeholder="Enter Recipe description"
onChange={e=>setUpdate(e.target.value)}
>
</FormControl>
</FormGroup>
</Modal.Body>
<Modal.Footer>
<Button onClick={editRecipe}>Save</Button>
<Button onClick={handleClose}>Cancel</Button>
</Modal.Footer>
</Modal.Header>
</Modal>
</Accordion>
)
}
export default Recipes
Can someone help me with the editRecipes function which is updating the records in the database? I don't know how to access all the three variables(title,ingredients,desc) in this function using the update useState hook I have defined above.
If I remember rightly, .split converts a string into an array. Without knowing exactly what you are trying to split I expect you will need to render as an array. I suggest taking a look at this good example here. I have added a few examples of ReactJS map that you might find helpful:
render() {
return (<div>
{this.state.people.map((person, index) => (
<p key={index}>Hello, {person.name} from {person.country}!</p>
))}
</div>);
}
this.state.data.map(function(item, i){
console.log('test');
return <li key={i}>Test</li>
})
If you are using Cloud Firestore, then you might find these tutorials and guides helpful:
Getting started with Cloud Firestore and React Native.
Create React Native Firebase CRUD App with Firestore.

React (Material UI) - Issues with form in component

So, I'm trying to create a signup form for a web-app - but are running into a few issues.
I'm using hooks with a function to render signup page, which I'm routing to from the login page.
It works fine assuming I return the html directly from the return in the function (signup), but once the signup has been engaged, I wish swap the form for an acknowledge of it being send.
From what I can tell, people simply wrap each html in an arrow function and then toggles between using a bool or similar. But that's where the issues arrive.
TLDR;
One of the signup textfields autocompletes, fetching from an API. The API then saves the content in a hook variable (address). The second I update the address variable, the form seem to reset - cursor going to the first inputfield.
This only happens when I wrap the html in components, not if I insert all the html in the (signup) return.
I tried to clean it up a bit, but the code more or less look like this.
Any help or pointers would be great :)
export default function SignUp(props)
{
const [activeStep, setActiveStep] = React.useState(0);
const [addresses, setAddresses] = React.useState([{ tekst: '' }]);
const APICall = async (e) =>
{
e.preventDefault();
// Fetchs JSON and set Addresses hook
}
const handleSubmit = props => form =>
{
form.preventDefault()
setActiveStep(activeStep + 1);
}
const CreateAccount = (e) =>
{
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Opret konto
</Typography>
<form className={classes.form} noValidate
onSubmit={handleSubmit(props)}>
<Autocomplete
id="address"
options={addresses}
getOptionLabel={(option) => option.tekst}
style={{ width: 300 }}
renderInput={(params) =>
<TextField {...params} label="Combo box" variant="outlined" onChange={userTest} />
}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign Up
</Button>
</form>
</div>
<Box mt={5}>
<Copyright />
</Box>
</Container>
);
}
const CreateAccountACK = () =>
{
return (
<React.Fragment>
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Nyt konto oprettet!
</Typography>
<Button
type="button"
variant="contained"
color="primary"
className={classes.submit}
onClick={() => { props.history.push('/') }}
>
Ok
</Button>
</div>
<Box mt={8}>
<Copyright />
</Box>
</Container>
</React.Fragment>
);
}
return (
<div>
{activeStep == 0 ? <CreateAccount /> : <CreateAccountACK />}
</div>
)
}
Got it working by breaking each function into its own functional component, then render these from the main class (signup) using conditionals. Swapping between pages are handled by callback to "handlesubmit" in this function. Pass history as the final page routes back to main. Feel like this isn't the best way of doing this tho =D, but it avoids the issues with re-renders while typing.
So now the Signup just return ...
export default function SignUp(props)
{
const [activeStep, setActiveStep] = React.useState(0);
const handleSubmit = props => form =>
{
form.preventDefault()
console.log(form)
setActiveStep(activeStep + 1);
}
return (
<div>
{activeStep == 0 ? <CreateAccount handleSubmit={handleSubmit} /> : <CreateAccountACK handleSubmit={handleSubmit} history={props.history}/>}
</div>
)
}
And each function, hooks/variables exist in their own file/function.

React Jest test button onClick

I'm developing an app with React, and I'm new to its testing framework. I'm trying to develop a testing method to check if an onClick button is clicked. I've read that I need a "spy" function to get the information about whether or not it's been called.
My testing method:
test("Btn1 click", () => {
const wrapper = mount(<Register />);
const spyOn = jest.spyOn(wrapper.instance(), "openWindow")
const element = wrapper.find({id: "btn-how-to-choose-provider"}).first();
element.simulate("click");
expect(spyOn).toHaveBeenCalled();
});
The Register.js component that I want to test:
export default function Register() {
const classes = useStyles();
function openWindow(url) {
window.open(url);
}
return (
<div>
<NavBar />
<Container component="main" maxWidth="sm">
<Card className={classes.root} elevation={4}>
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<AccountCircleIcon />
</Avatar>
<Typography component="h1" variant="h5">Hi! Welcome to Solid.</Typography>
<div className={classes.form}>
<Button
id="btn-how-to-choose-provider"
fullWidth
color="primary"
className={classes.link}
startIcon={<EmojiPeopleIcon/>}
onClick={(e) => openWindow('https://solid.inrupt.com/how-it-works')}
>How to choose a Provider?</Button>
<Button
id="btn-inrupt-provider"
fullWidth
variant="outlined"
color="primary"
className={classes.submit}
startIcon={<ContactsOutlinedIcon/>}
onClick={(e) => window.open('https://inrupt.net/register')}
>Inrupt</Button>
<Button
id="btn-solid-community-provider"
fullWidth
variant="outlined"
color="primary"
className={classes.submit}
startIcon={<ContactsIcon/>}
onClick={() => window.open('https://solid.community/register')}
>Solid Community</Button>
</div>
</div>
</Card>
</Container>
<Grid
item
xs={12}
sm={12}
md={12}
style={{marginTop: '36rem'}}
>
<Footer />
</Grid>
</div>
);
}
With this configuration, I obtain the following error:
TypeError: Cannot read property 'openWindow' of null
34 | test("Btn1 click", () => {
35 | const wrapper = mount(<Register />);
> 36 | const spyOn = jest.spyOn(wrapper.instance(), "openWindow")
| ^
37 | const element = wrapper.find({id: "btn-how-to-choose-provider"}).first();
38 | element.simulate("click");
39 | expect(spyOn).toHaveBeenCalled();
at ModuleMockerClass.spyOn (node_modules/jest-mock/build/index.js:837:28)
at Object.<anonymous> (src/components/containers/register/Register.test.js:36:24)
My question is: is there any way to test the onClick function of a button with Jest or any other testing framework?
Okay, so after more research, I've found that the React Testing Library provides some tools to test the onClick event of some buttons (at least those that are like the ones I've shown in my example above). One of those tools is the fireEvent, which helps to accomplish what I wanted:
test("Click", () => {
const {container} = render(<Register />);
const button = getByTestId(container, 'btn-how-to-choose-provider');
fireEvent.click(button);
});

TypeError: Cannot read property 'recipeName' of undefined

I've now gone through my code with a fine tooth comb and I just cannot seem to see where the "recipe" property is not defined. I'm hoping some more experienced eyes would help me out and spot where I've made the mistake. Any help will be appreciated. Thank you.
Ps. Please find my code below... it's the Recipe Box project from FreeCodeCamp and I followed the walk through from Dylan Israel from CodingTutorials360. As far as I can tell my code is identical to his except for some changes to React-Bootstrap as stipulated by the Documentation.
import React, { Component } from 'react';
import './App.css';
import Panel from 'react-bootstrap/lib/Panel'
import Button from 'react-bootstrap/lib/Button'
import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar'
import Modal from 'react-bootstrap/lib/Modal'
import FormGroup from 'react-bootstrap/lib/FormGroup'
import ControlLabel from 'react-bootstrap/lib/ControlLabel'
import FormControl from 'react-bootstrap/lib/FormControl'
import PanelGroup from 'react-bootstrap/lib/PanelGroup'
class App extends Component {
state = {
showAdd: false,
showEdit: false,
currentIndex: 0,
recipes: [
],
newestRecipe: {recipeName:"", ingredients: []}
}
deleteRecipe(index){
let recipes = this.state.recipes.slice();
recipes.splice(index, 1);
localStorage.setItem('recipes', JSON.stringify(recipes));
this.setState({recipes});
}
updateNewRecipe(value, ingredients){
this.setState({newestRecipe:{recipeName: value, ingredients: ingredients}});
}
close = () => {
if(this.state.showAdd){
this.setState({showAdd: false});
} else if(this.state.showEdit){
this.setState({showEdit: false});
}
}
open = (state, currentIndex) => {
this.setState({[state]: true});
this.setState({currentIndex});
}
saveNewRecipe = () => {
let recipes = this.state.recipes.slice();
recipes.push({recipeName: this.state.newestRecipe.recipeName, ingredients: this.state.newestRecipe.ingredients});
localStorage.setItem('recipes', JSON.stringify(recipes));
this.setState({ recipes });
this.setState({newestRecipe: {recipeName: '', ingredients:[]}});
this.close();
}
updateRecipeName(recipeName, currentIndex){
let recipes = this.state.recipes.slice();
recipes[currentIndex] = {recipeName: recipeName, ingredients: recipes[currentIndex].ingredients};
this.setState({recipes});
localStorage.setItem('recipes', JSON.stringify(recipes));
this.close();
}
updateIngredients(ingredients, currentIndex){
let recipes = this.state.recipes.slice();
recipes[currentIndex] = {recipeName: recipes[currentIndex].recipeName, ingredients: ingredients};
localStorage.setItem('recipes', JSON.stringify(recipes));
this.setState({recipes});
}
componentDidMount(){
let recipes = JSON.parse(localStorage.getItem("recipes")) || [];
this.setState({recipes});
}
render() {
const {recipes, newestRecipe, currentIndex} = this.state;
return (
<div className="App container" id="display-box">
{recipes.length > 0 && (
<div>
<PanelGroup accordion id="recipe-list" defaultActiveKey="2">
{recipes.map((recipe, index)=>(
<Panel eventKey={index} key={index}>
<Panel.Heading>
<Panel.Title toggle>{recipe.recipeName}</Panel.Title>
</Panel.Heading>
<Panel.Body collapsible>
<ol>
{recipe.ingredients.map((item)=>(
<li key={item}>{item}</li>
))}
</ol>
<ButtonToolbar>
<Button bsStyle="danger" onClick={(event)=>this.deleteRecipe(index)}>Delete Recipe</Button>
<Button bsStyle="default" onClick={(event) => this.open("showEdit", index)}>Edit Recipe</Button>
</ButtonToolbar>
</Panel.Body>
</Panel>
))}
</PanelGroup>
</div>
)}
<Modal show={this.state.showEdit} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>Edit Recipe</Modal.Title>
</Modal.Header>
<Modal.Body>
<FormGroup controlId="formBasicText">
<ControlLabel>Recipe Name</ControlLabel>
<FormControl
type="text"
value={recipes[currentIndex].recipeName}
placeholder="Enter Text" onChange={(event) => this.updateRecipeName(event.target.value, currentIndex)}
/>
</FormGroup>
<FormGroup controlId="formControlsTextarea">
<ControlLabel>Ingredients</ControlLabel>
<FormControl
componentClass="textarea"
onChange={(event) => this.updateIngredients(event.target.value.split(","), currentIndex)}
placeholder="Enter Ingredients [Seperate by Commas]"
value={recipes[currentIndex].ingredients}>
</FormControl>
</FormGroup>
<Modal.Footer>
<Button bsStyle="primary" onClick={(event) => this.saveNewRecipe()}>Save Changes</Button>
</Modal.Footer>
</Modal.Body>
</Modal>
<Modal show={this.state.showAdd} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>Add Recipe</Modal.Title>
</Modal.Header>
<Modal.Body>
<FormGroup controlId="formBasicText">
<ControlLabel>Recipe Name</ControlLabel>
<FormControl
type="text"
value={newestRecipe.recipeName}
placeholder="Enter Recipe Name"
onChange={(event) => this.updateNewRecipe(event.target.value, newestRecipe.ingredients)}
>
</FormControl>
</FormGroup>
<FormGroup controlId="formControlTextarea">
<ControlLabel>Ingredients</ControlLabel>
<FormControl
type="textarea"
placeholder="Enter Ingredients [Seperate by Commas]"
onChange={(event) => this.updateNewRecipe(newestRecipe.recipeName, event.target.value.split(','))}
value={newestRecipe.ingredients}
>
</FormControl>
</FormGroup>
</Modal.Body>
<Modal.Footer>
<Button onClick={(event) => {this.saveNewRecipe()}}>Save</Button>
</Modal.Footer>
</Modal>
<Button bsStyle="primary" onClick={(event)=>this.open("showAdd", currentIndex)}>Add Recipe</Button>
</div>
);
}
}
export default App;
The problem is that you are accessing (twice) to the current recipe data when there is not a current recipe. That is because, although you are not showing the "modal", you are rendering it.
One possible solution is to render that modal only if you are editing something, you could do this replacing lines 110-139 with this:
{ this.state.showEdit &&
<Modal show={this.state.showEdit} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>Edit Recipe</Modal.Title>
</Modal.Header>
<Modal.Body>
<FormGroup controlId="formBasicText">
<ControlLabel>Recipe Name</ControlLabel>
<FormControl
type="text"
value={recipes[currentIndex].recipeName}
placeholder="Enter Text" onChange={(event) => this.updateRecipeName(event.target.value, currentIndex)}
/>
</FormGroup>
<FormGroup controlId="formControlsTextarea">
<ControlLabel>Ingredients</ControlLabel>
<FormControl
componentClass="textarea"
onChange={(event) => this.updateIngredients(event.target.value.split(","), currentIndex)}
placeholder="Enter Ingredients [Seperate by Commas]"
value={recipes[currentIndex].ingredients}>
</FormControl>
</FormGroup>
<Modal.Footer>
<Button bsStyle="primary" onClick={(event) => this.saveNewRecipe()}>Save Changes</Button>
</Modal.Footer>
</Modal.Body>
</Modal>
}
Note that the only thing I did is to add the first and the last line of that block of code.
Correct me if I'm wrong, but it looks like you are not defining your initial state correctly, which may be the reason why you are getting an undefined error.
Usually, state is initially defined in the constructor function, and not as a separate object. When I tried to define the state like you did, I got an error on codepen.
Check out how they define state in this cheat sheet (under States): https://devhints.io/react.
I've gone through the same FreeCodeCamp react projects, and some of these can take a long time to figure out, but in the end it's really worth it. Hopefully this will help you find the error :)
You should use a constructor to define the state and bind your function saveNewRecipe
constructor(props) {
super(props);
state = {
showAdd: false,
showEdit: false,
currentIndex: 0,
recipes: [
],
newestRecipe: {recipeName:"", ingredients: []}
};
this.saveNewRecipe = this.saveNewRecipe.bind(this);
}

Categories

Resources