window is not defined in next js - reactjs

some help here
I am trying to use react-wysiwyg-editor in my next js app, but it prints an error of window is undefined
import React, { useState } from "react";
import { Editor } from "react-draft-wysiwyg";
import { EditorState } from "draft-js";
import "../node_modules/react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import classes from "./temps.module.css";
const EmailTemps = () => {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
const handleClick = async () => {
const response = await fetch("/api/sendMail", {
method: "POST",
body: JSON.stringify({ editorState }),
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
console.log(data.message);
};
if (typeof window !== undefined) {
<div className={classes.nnn}>
<Editor editorState={editorState} onEditorStateChange={setEditorState} />
<button onClick={handleClick} className={classes.but}>
send email
</button>
</div>;
}
};
export default EmailTemps;
i have tried working around this by
if (typeof window !== undefined) {...}
but the error is persisting, i am not sure if i am just not doing it the right way
also, i have tried the next/dynamic, but i couldnt dynamically import something from nodemodules
Right now I know that all i am left with is to use useEffect, which i wouldnt want to, if i have a better alternative. my inquiry, is there any better way i can achieve this ?

I think your main problem is not from your own window object but it's from react-draft-wysiwyg which only supports on the client-side.
To fix it, you need to use dynamic import
import dynamic from 'next/dynamic';
const Editor = dynamic(
() => import('react-draft-wysiwyg').then(mod => mod.Editor),
{ ssr: false }
)
The 2nd problem is your window check should be against a string "undefined" instead of undefined
typeof window !== "undefined"
The 3rd problem is you never return your component elements
if (typeof window === "undefined") {
return null //return nothing on the server-side
}
//return only on the client-side
return <div className={classes.nnn}>
<Editor editorState={editorState} onEditorStateChange={setEditorState} />
<button onClick={handleClick} className={classes.but}>
send email
</button>
</div>
The full implementation
import React, { useState } from "react";
import dynamic from 'next/dynamic';
const Editor = dynamic(
() => import('react-draft-wysiwyg').then(mod => mod.Editor),
{ ssr: false }
)
import { EditorState } from "draft-js";
import "../node_modules/react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import classes from "./temps.module.css";
const EmailTemps = () => {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
const handleClick = async () => {
const response = await fetch("/api/sendMail", {
method: "POST",
body: JSON.stringify({ editorState }),
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
console.log(data.message);
};
if (typeof window === "undefined") {
return null //return nothing on the server-side
}
//return only on the client-side
return <div className={classes.nnn}>
<Editor editorState={editorState} onEditorStateChange={setEditorState} />
<button onClick={handleClick} className={classes.but}>
send email
</button>
</div>
}
};
export default EmailTemps;

I have solved this problem by re-using next/dynamic, in the component where i am using EmailTemps like below
import dynamic from 'next/dynamic'
import styles from '../styles/Home.module.css'
export default function Home() {
const EmailEditor = dynamic( ()=> {
return import('../comps/emailtemps');
}, { ssr: false } );
return (
<div className={styles.container}>
<EmailEditor />
</div>
)
}
and it just worked, thank you guys, however in the client console, it prints this error
next-dev.js?3515:32 Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the r component.
kindly help how i can go about this now

Related

How to dynamically render react components?

I have a website where if I press a button it fetches a post object from a database and adds it into an array. I need to somehow display all the objects in the array as react components and also to update the list every time when a new post is added.
I've been trying to use the map() method but I can't get it to display the new posts that are added when I click the button.
Main component:
import Post from './Post'
import { useState, useEffect, createElement } from 'react'
import { useCookies } from 'react-cookie';
import Axios from 'axios'
const Content = () => {
const [postArr, setPostArr] = useState([])
const getPosts = ()=>{
Axios.defaults.withCredentials = true
Axios({
method: 'get',
url: 'http://localhost:3010/api/getpost/',
headers: {'Content-Type': 'multipart/formdata' }
})
.then((response) => {
addPostToPostArray(response)
})
.catch((response) => {
console.log(response);
});
}
const addPostToPostArray = (response) => {
let imgName = response.data.imgurl.slice(68, 999999)
let sendObj = {
id:response.data.id,
posterid:response.data.posterid,
imgurl:`http://localhost:3010/images/${imgName}`,
title:response.data.title,
likes:response.data.likes,
date:response.data.date
}
postArr.push(sendObj)
/*
A fetched post will look like this:
{
id:123, posterid:321, imgurl:`http://localhost:3010/images/333.png`,
title:'best title', likes:444, date:111111
}
*/
}
return (
<div>
{postArr.map((e) => {
return <Post post={e}/>
})}
<button onClick={getPosts}>load post</button>
</div>
);
}
export default Content;
Post component:
const Post = (props) => {
const post = props.post
return (
<div className='post-frame'>
<h1>{post.title}</h1>
<div className="image-frame">
<img src={post.imgurl}></img>
</div>
<p>{post.likes}</p>
<p>{post.posterid}</p>
</div>
);
}
export default Post;
To update the state of the component you need to call that setPostArr function with the updated array. Without that the state of the component never get's updated.
Here's an example
const Content = () => {
const [postArr, setPostArr] = useState([])
const getPosts = () => {
...
}
const addPostToPostArray = (response) => {
let sendObj = {
...
}
// ~ This part here
setPostArr([...postArr, sendObj])
// ~ Instead of
// postArr.push(sendObj)
}
return ...
}

How to update state value to textarea using onChange?

Currently following a slightly older tutorial, but learning using React 18 -- trying to update the text area in a notes app
It looks like when I type, a character appears and then immediately is deleted automatically
Can anyone confirm if I might be missing a detail here?
for reference if familiar with the project at time 1:37:03 : https://www.youtube.com/watch?v=6fM3ueN9nYM&t=377s
import React, {useState, useEffect} from 'react'
import notes from '../assets/data'
import { useParams } from 'react-router-dom';
import { Link } from 'react-router-dom'
import { ReactComponent as ArrowLeft } from '../assets/arrow-left.svg'
const NotePage = ( history ) => {
const {id} = useParams();
// let note = notes.find(note => note.id===Number(id))
// console.log(id)
let [note, setNote] = useState(null)
useEffect(() => {
getNote()
}, [{id}])
let getNote = async () => {
let response = await fetch(`http://localhost:8000/notes/${id}`)
let data = await response.json()
setNote(data)
}
// let updateNote = async () => {
// await fetch(`http://localhost:8000/notes/${id}`, {
// method: 'PUT',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify({...note, 'updated':new Date()})
// })
// }
// let handleSubmit = () => {
// updateNote()
// history.push('/')
// }
return (
<div className="note">
<div className="note-header">
<h3>
<Link to="/">
<ArrowLeft /*onClick={handleSubmit}*/ />
</Link>
</h3>
</div>
<textarea onChange={(e) => {
setNote({...note, 'body': e.target.value}) }}
value={note?.body}>
</textarea>
</div>
)
}
export default NotePage
Your value in the useEffect dependency array is incorrect and causing getNote to be called every time you make changes in the textArea. Every time getNote is called, it's resetting the note state back to whataver is being received by getNote. Which in your case is probably a blank note
Change this :
useEffect(() => {
getNote();
}, [{ id }]);
To this:
useEffect(() => {
getNote();
}, [id]);

when re rendering react, object is null

im calling an object from the pokeapi, exactly the name property and on first render after saving the file i get the name but i dont know why, re render and then the propertie is null and i get an error
this is my component card
import {
EditOutlined,
EllipsisOutlined,
SettingOutlined,
} from "#ant-design/icons";
import { Avatar, Card, Col, Row } from "antd";
function Pokecard(values: any) {
const { response} = values;
const { Meta } = Card;
return (
<Row gutter={[10, 10]}>
<Col>
<Card
style={{ width: 300 }}
cover={
<img
alt={"" }
src={response && response['sprites']['front_default']}
/>
}
actions={[
<SettingOutlined key="setting" />,
<EditOutlined key="edit" />,
<EllipsisOutlined key="ellipsis" />,
]}
>
<Meta
avatar={<Avatar src="https://joeschmoe.io/api/v1/random" />}
title={response.name}
description=""
/>
</Card>
</Col>
</Row>
);
}
export default Pokecard;
this is my view
import { Methods } from "../interfaces/request";
import { useEffect, useState } from "react";
import Pokecard from "../components/pokecard/Pokecard";
import useAxios from "../plugins/Useaxios";
function App2() {
const { response, loading, error } = useAxios({
method: Methods["get"],
url: "/ditto",
body: JSON.stringify({}),
headers: JSON.stringify({}),
});
const [data, setData] = useState([]);
useEffect(() => {
if (response !== null) {
setData(response);
}
}, [response]);
let args: any = {
response,
};
return (
<>
<Pokecard {...args} />;
</>
);
}
export default App2;
and this is my plugin axios
import axios from "axios";
import Request from "../interfaces/request";
import { useState, useEffect } from "react";
enum Methods {
get = "get",
post = "post",
default = "get",
}
const useAxios = ({ url, method, body, headers }: Request) => {
axios.defaults.baseURL = "https://pokeapi.co/api/v2/pokemon";
const [response, setResponse] = useState(null);
const [error, setError] = useState("");
const [loading, setloading] = useState(true);
const fetchData = () => {
axios[method](url, JSON.parse(headers), JSON.parse(body))
.then((res: any) => {
setResponse(res.data);
})
.catch((err: any) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
useEffect(() => {
fetchData();
}, [method, url, body, headers]);
return { response, error, loading };
};
export default useAxios;
im learning to destructuring objects
im tried saving the object in the store but i got an Undifined
sorry for my english
you can try something like this
title={response?.name || ''}
Try using the resonse directly
const { response, loading, error } = useAxios({
method: Methods["get"],
url: "/ditto",
body: JSON.stringify({}),
headers: JSON.stringify({}),
});
const name = response?.name;
const src = response?.sprites?.?front_default;
// use the properties directly inside the child
return (
<>
<Pokecard name={name} src={src}/>
</>
);
You can check examples of how when useEffect is not needed

Fetch runs in a loop

I am fetching data in my react component
import React, { useState, useEffect } from 'react';
import { fetchBookData } from './bookData';
import "./App.css";
export default function Books ({ books }) {
const [payload, setPayload] = useState(null)
fetchBookData(books).then((payload) => setPayload(payload));
return (
<div className="App">
<h1>Hello</h1>
</div>
);
}
Here is the fetch function itself
const dev = process.env.NODE_ENV !== 'production';
const server = dev ? 'http://localhost:3001' : 'https://your_deployment.server.com';
// later definable for developement, test, production
export const fetchBookData = (books) => {
const options = {
method: `GET`,
headers: {
accept: 'application/json',
},
};
return fetch(`${server}/books`, options)
.then((response) => {
if(response.ok){
return response.json()
}
throw new Error('Api is not available')
})
.catch(error => {
console.error('Error fetching data in book data: ', error)
})
}
But when I start the server fetch runs in a loop, component making endless get requests to the server. I tried to wrap it in a useEffect, but didn't work. Fetch should run once on load
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. More
example (codesandbox)
export default function App({books}) {
const [payload, setPayload] = useState(null);
useEffect(() => {
fetchBookData(books).then((payload) => setPayload(payload));
}, [books]);
if (!payload) {
return <h1>Loading...</h1>;
}
return (
<div className="App">
<h1>{payload.title}</h1>
</div>
);
}

useEffect Memory Leak in AWS Amplify Storage.get function

I have a pretty simple component in React that gets an image name and then displays it using the code below. It works fine but when I leave the page and then come back to it, I get a memory leak. Perhaps there is something basic about effects I am not understanding:
import React, {useState, useEffect} from 'react';
import {Storage} from 'aws-amplify';
const S3Image = ({photoName, ...props}) => {
const [imageURL, setImageURL] = useState("")
useEffect(() => {
var response = ""
async function s3Fetch() {
if (photoName !== "") {
response = Storage.get(photoName);
const data = await response;
setImageURL(data);
}
}
s3Fetch();
}, [imageURL])
return (
<>
{ imageURL === "" ?
null
:
<img {...props} src={imageURL} alt={photoName} />
}
</>
)};
export default S3Image
If I return a function at the end to clean up like:
return () => usetImageUrl("")
Then it goes in a crazy loop of rendering and then re-rendering.
Thanks for any help!
You are updating the imageURL in the effect and also rerun the effect if the url changes => Loop.
You need set the dependency array to [photoName] since that's the variable you are listening for and is the deciding factor in the fetch function:
import React, {useState, useEffect} from 'react';
import {Storage} from 'aws-amplify';
const S3Image = ({photoName, ...props}) => {
const [imageURL, setImageURL] = useState("")
useEffect(() => {
var response = ""
async function s3Fetch() {
if (photoName !== "") {
response = Storage.get(photoName);
const data = await response;
setImageURL(data);
}
}
s3Fetch();
}, [photoName])
return (
<>
{ imageURL === "" ?
null
:
<img {...props} src={imageURL} alt={photoName} />
}
</>
)};
export default S3Image
If you have a linter installed, it should tell you that as well.
#Domino987 answer was really helpful, but I needed to add the logic with "componentIsMounted" variable to get rid of the warning. We set the variable as false in the cleanup function of the useEffect. This way, we only set the imageUrl if the component is mounted.
import React, { useState, useEffect } from 'react';
import { Storage } from 'aws-amplify';
const S3Image = ({ imgKey, ...props }) => {
const [imageURL, setImageURL] = useState('');
useEffect(() => {
async function s3Fetch() {
if (imgKey !== '') {
try {
return Storage.get(imgKey, { level });
} catch (e) {
console.error(e);
}
}
}
let componentIsMounted = true;
s3Fetch().then((url) => {
if (componentIsMounted) {
if (url) setImageURL(url);
}
});
return () => {
componentIsMounted = false;
};
}, [imgKey]);
return (
<>
{imageURL === '' ? null : (
<img {...props} src={{ uri: imageURL }} />
)}
</>
);
};
export default S3Image;

Resources