Search code examples
reactjstypescriptreact-routersetstate

Setting state in React.js (TypeScript) is giving problem


I'm new with typescript, so I was creating a blog-app in which user can write blog(only text) with title and blog body. When, Add button is clicked blog should be added to blogs array and user should be redirected to Blogs page.

Problem: When Add button is clicked blog is added to array but user is not redirected.

Below is code:

Add.tsx

interface Props {
  setBlogs: (blog: IBlog) => void;
}

function Add(props: Props) {
  const [info, setInfo] = useState<IBlog>({ title: "", body: "" });
  const [clicked, setClicked] = useState<boolean>(false);

  function handleInfoChange(
    e:
      | React.ChangeEvent<HTMLTextAreaElement>
      | React.ChangeEvent<HTMLInputElement>
  ) {
    const name: string = e.target.name;
    const value: string = e.target.value;

    setInfo(
      (preInfo): IBlog => {
        return { ...preInfo, [name]: value };
      }
    );
  }

  function addClick() {
    if (info.body.length > 0 && info.title.length > 0) {
      props.setBlogs(info);
      setClicked(true);
    }
  }

  React.useEffect(() => {
    console.log("Clicked", clicked);
  }, [clicked]);

  return (
    <div className="add">
      <div className="compileArea">
        <input
          type="text"
          id="title"
          name="title"
          autoComplete="off"
          placeholder="Title"
          value={info.title}
          onChange={handleInfoChange}
        />
        <textarea
          name="body"
          id="body"
          cols={30}
          rows={10}
          placeholder="Type here"
          value={info.body}
          onChange={handleInfoChange}
        ></textarea>

        <div className="controls">
          <button id="add-button" onClick={addClick}>
            Add
          </button>
        </div>
      </div>
      {clicked ? <Redirect to="./blogs" /> : null}
    </div>
  );
}

export default Add;

IBlog looks like this:

type IBlog = {
  title: string;
  body: string;
};

App.tsx

function App() {
  const [blogs, setBlogs] = React.useState<IBlogs>([]);

  function setblogs(blog: IBlog): void {
    setBlogs([...blogs, blog]);
  }

  React.useEffect(() => {
    console.log("Blog", blogs);
  }, [blogs]);

  return (
    <div className="App">
      <Header />
      <Switch>
        <Route path="/" exact />
        <Route
          path="/add"
          exact
          component={() => <Add setBlogs={setblogs} />}
        />
        <Route path="/blogs" exact component={() => <Blogs blogs={blogs} />} />
      </Switch>
    </div>
  );
}

export default App;

The thing is if I remove setBlogs([...blogs, blog]); in function setblogs in file App.tsx. User is redirected to Blogs page successfully.

If someone want to reproduce the error full project is here.


Solution

  • the issue happens at Route declaration at app:

    <Route
      path="/add"
      exact
      component={() => <Add setBlogs={setblogs} />}
    />
    

    when you declare a function while passing to a prop, a new function will be created everytime App component updates. In this way, when you call props.setBlogs(info), App updates and component receives a new function causing Add to rerender everytime.

    to avoid that, one way you could pass a function like component={renderAdd}. createAdd is the same function, but alredy declared and wrapped in useCallback like:

    const renderAdd = useCallback(
      () => <Add setBlogs={setblogs} />,
      [setBlogs],
    )
    

    other way, which I think is cleaner, is to pass your component as child to Route:

    <Route
      path="/add"
      exact
    >
      <Add setBlogs={setblogs} />
    </Route>