Consider the following example where the resetCount function can work correctly with an empty dependency list as well.
So, should we include setCount in its dependency?
Are there any guidelines to keep in mind?
I am interested to know the guidelines for the dependency list in React.useCallback.
import React, { useState, useCallback } from 'react';
import { render } from 'react-dom';
import Child from "./Child";
import './style.css';
const Parent = () => {
const [count, setCount] = useState(0);
console.log("re-render parent component");
const resetCount = useCallback(() => {
setCount(0);
}, [setCount]); // ([] as well as [setCount] - both work) So should this dependency contain setCount?
return (
<main>
<p>Count: {count}</p>
<button onClick={() => setCount(count=>(count+1))}>Increment</button>
<Child reset={resetCount} />
</main>
)
}
render(<Parent />, document.getElementById('root'));
Well, the most definitive advice (and the best starting point for not so advanced React programmers) is: Don't lie to React about dependencies. With that, it will always work as expected, and you won't get any surprise.
Now you can make exceptions to that rule, if you know that something won't change. For the set state function returned by the useState hook, this is the case, so you can omit it, yet there is no harm done in leaving it there (cause as it does not change, it won't cause the effect to trigger).
Related
Suppose my URL looks something like this:
/blog/[post_id]/something
What is the recommended way to pass $post_id down to any component anywhere in the tree?
I know how to retrieve route parameters using getInitialProps but passing the values down is always giving me a hard time.
For pages I could technically use React Contexts although this seems a bit oversized for such a trivial use case.
For layouts I am honestly completely lost because pages are children of layouts and the return value of getInitialProps is passed to the page and not the layout.
My components could make use of useRouter but this requires useEffect and would also make my component depend on the route itself...
Any advice would be welcome (:
My components could make use of useRouter but this requires useEffect and would also make my component depend on the route itself...
useRouter seems like the obvious solution here. I'm not exactly understanding your concerns regarding the component depending on the route. I guess it does make the Layout less flexible since it needs to know that the post id is stored in the post_id query variable. But I would do it anyways :) It gives you a nice and simple way to access the query variables which can be used in a Layout that's outside of your BlogPost or in a deeply-nested component that you use inside the BlogPost.
Using the per-page layouts approach:
/components/Layout
import { useRouter } from "next/router";
import { ReactNode } from "react";
export default function Layout({ children }: { children: ReactNode }) {
const router = useRouter();
return (
<div>
<h3>You are viewing post id #{router.query.post_id}</h3>
{children}
</div>
);
}
/pages/blog/[post_id].jsx
import Layout from '../../components/Layout';
export default function BlogPost() {
return <div>Hello World</div>
}
BlogPost.getLayout = function getLayout(page) {
return (
<Layout>
{page}
</Layout>
)
}
/pages/_app.tsx (to support per-page layouts, copied from docs)
export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}
I think the easiest and the cleanest way is to use window.location.pathname. this will give you the part after the domain name. for example for
http://localhost:3001/blog/[post_id]/something
you will get /blog/[post_id]/something
const pathname=window.location.pathname
const splittedPathname=pathname.split("/") // ['', 'blog', '[post_id]', 'something']
const dynamicId=splittedPathname[2]
you can run above code in useEffect and set a state. or you could write a hook and use it in the components that under dynamicId components
import React, { useState, useEffect } from "react";
const usePathname = () => {
const [postId, setPostId] = useState("");
useEffect(() => {
const pathname = window.location.pathname;
const splittedPathname = pathname.split("/");
const dynamicId = splittedPathname[2];
setPostId(dynamicId);
}, []);
return { postId };
};
export default usePathname;
If you are looking for client side rendering, useRouter is the best way to go. If you are looking for SSR or SSG, you should rather use getStaticProps or getServerSideProps.
I'm trying to make a thin wrapper to the "jsoneditor" library using a functionnal component. I'm rather new to React and worked so far mainly with hooks. So I tried to adapt the example given by the author of the library to use hooks:
https://github.com/josdejong/jsoneditor/tree/master/examples/react_demo
This is what I came up with so far:
import React, {useRef, useState, useEffect, useCallback} from 'react'
import JSONEditor from 'jsoneditor'
import styles from './JSONEditorReact.module.css'
import 'jsoneditor/dist/jsoneditor.css';
function App(){
const [json, setJson] = useState({some_key:"some_value"})
function onChangeJson(json){
setJson(json)
}
return <JSONEditorReact onChangeJson={onChangeJson} json={json}/>
}
function JSONEditorReact({onChangeJson, json}){
const containerRef = useRef()
const editorRef = useRef() // used as a namespace to have a reference to the jsoneditor object
useEffect(
() => {
console.log("mounting")
const options = {
modes: ['tree','form','view','text'],
onChangeJSON: onChangeJson
}
editorRef.current = new JSONEditor(containerRef.current, options)
return () => editorRef.current.destroy()
},
[] //eslint complains about the missing dependency "onChangeJson" here
)
useEffect(
() => {
console.log("updating")
editorRef.current.update(json)
},
[json]
)
return (
<div className={styles.container} ref={containerRef} />
)
}
export default App;
It works - but eslint complains about onChangeJson being a missing dependency in useEffect. If I add it as a dependency, useEffect runs each time the user inputs something into the json editor. This implies that the user looses focus on the editor each time he enters a character. My understanding is that when it occurs, setJson function of App is called, so App component is refreshed, causing the onChangeJson function to be-reinstanciated, so the first useEffect is rerun, and a new JSONEditor is instanciated.
I had some ideas but they don't seem satisfying:
define onChangeJson with useCallback - issue : I find it daunting to call useCallback each time I want to use my component
pass the setter function setJson of App as the "onChangeJson" property of JSONEditorReact - issue: what if I want to perform more actions than just setting the state in my callback?
Any more relevant ideas to solve the missing dependency issue without running the first useEffect on each input?
Is this a kind of use case where class components are more relevant than functional components using hooks? (the wrapper using class components looks more straightforward than mine, where I had to use a ref to create a namespace to hold my JSONEditor instance)
I am using the CircleType.js library which allows you to curve a string of text/render it as a circle. However, I am very new to React and I'm not sure how to use the library in React. I created a functional component and used the documentation provided in the CircleType page to create... but I keep getting a 'TypeError: Cannot read property 'innerHTML' of null' error.
import React, {useEffect} from 'react'
import CircleType from 'circletype'
function RotatingCircle() {
// const circleOfSkills = new CircleType(document.getElementById('rounded-text'))
// .radius(100)
// useEffect(() => {
// return () => circleOfSkills.mount()
// }, [circleOfSkills])
return (
<p className="circle" id="rounded-text">
AND THE WORLD KEEPS GOING AROUND AND AROUND AND AROUND
</p>
)
}
export default RotatingCircle
I read that I might need to use refs but I'm really not sure how to use it as all examples I see use class components. Another forum I saw suggested using useEffect, but I'm clearly not using it correctly.
How do I reference DOM elements in a functional component?
Here is an example of CircleType implementation with React useRef hook. Avoid using getElementById for DOM manipulation as it is not the React way.
Sample code and CodeSandbox link:
import React, { useEffect, useRef } from "react";
import "./styles.css";
import CircleType from "circletype";
export default function App() {
const circleInstance = useRef();
useEffect(() => {
new CircleType(circleInstance.current).radius(100);
}, []);
return (
<div className="App">
<div ref={circleInstance}>abcdef</div>
</div>
);
}
CodeSandbox
try this
useEffect(() => {
const circleOfSkills = new CircleType(document.getElementById('rounded-text'))
.radius(100)
return circleOfSkills.mount()
}, []);
Try moving the const inside useEffect like this:
useEffect(() => {
const circleOfSkills = new CircleType(document.getElementById('rounded-text'))
.radius(100)
return () => circleOfSkills.mount()
}, []);
Calling getElementById outside of useEffect will give you null error because the element is not yet rendered on the page.
When using react I'd avoid using getElementbyID inside your components. Defining a root in your index.html and then linking it in index.js by
import React from "react";
import ReactDOM from "react-dom";
import App from "./App"; //App is your base react component
ReactDOM.render(
<App />
document.getElementById("root")
);
It will save you headaches like this and others in the future. React components are like a tree and by defining only one root you are utilizing what react was built for.
I am new to React. I am trying to build a page and having like 3 button or img on main page. When I click either one, I shall be routed to another class component. You can treat it like click the icon and route you to another category page (just an example). Below is my structure and partial code I tried. I have no idea how to achieve that, and I googled and seems cannot find the stuff I want.
Structure:
/src
.index.js
.App.js
.BookStore.js
.FruitStore.js
.FoodStore.js
index.js
import React from "react";
import { render } from "react-dom";
import App from "./App";
render(
<App />,
document.getElementById('root')
);
App.js:
import React from "react";
import BookStore from "./BookStore";
const AppContainer = () => {
return (
//do the routing
<BookStore/>
)
};
export default AppContainer;
BookStore.js
export default class BookStore extends React.Component {
}
const contentDiv = document.getElementById("root");
const gridProps = window.gridProps || {};
ReactDOM.render(React.createElement(BookStore , gridProps), contentDiv);
First, you could have a look at the/one react router, e.g. https://reactrouter.com/web/guides/quick-start
However, since you're writing you're new to react, this might be a little too much ...
First, I was wondering why you're using the "ReactDOM" in your indexjs (that seems to be correct), but also in the BookStore.js. I would also recommend to write your components as functions, like your "AppContainer" and not use the class components anymore (or do you really need to do that? - why?). You can use hooks instead to have e.g. state in the components.
You would then need any kind of state in your AppContainer which is used for the routing. Maybe like this:
const AppContainer = () => {
const [showDetail, setShowDetail] = useState();
return <>
{!showDetail && <BookStore onDetail={detail => setShowDetail(detail)} />}
{showDetail && <DetailPage detail={showDetail} onBack={() => setShowDetail(undefined)}}
</>
}
Your AppContainer then has a state wheter or not to show the Bookstore (which is shown when "showDetail" is falsy, or a DetailPage which is shown when showDetail is truthy.
For this to work, your Bookstore needs to provide callbacks to let the AppContainer know that something should change. Very simply it could look like this:
const BookStore = ({onDetail}) => {
return <button onClick={() => onDetail("anything")}>Click me</button>
}
Now when someone clicks the button on the bookstore, it calls the "onDetail" callback, which was set in the AppContainer to set the "showDetail" state. So this one will be updated to "anything" in this case. This will result in a rerender on the AppContainer which will now render a DetailPage component instead.
I am trying to export a normal functional component using the react hooks but I am getting this error.
TypeError: Object(...) is not a function
when I remove the hooks and export it without any states it works. Exporting the same code as a Class component also works.
import React,{ useState } from 'react';
const Mycomponent = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Mycomponent;
here is how I am importing and using functional component.
import Mycomponent from './mycomponet';
class MYClassComponent extends Component {
render() {
return (
<Mycomponent />
}
}
I am using react 16.6.3 and used create-react-app.
I am using react 16.6.3...
That's the problem. Hooks were added in v16.8, so useState in your code is undefined.
(This is one of those times that transpiling hid the error from you [not that you have much choice if you need to support older environments]. If that had been a native import statement, it would have failed with a useful error saying that React didn't have a useState named export. But when using a CJS or AMD version of React, your code gets converted to something doing var useState = React.useState; and so it doesn't error out, it just gives you undefined — which isn't a function. :-) )