Beginner with Conversion of code(from function to class) in react js - reactjs

import React, { useEffect, useState } from "react";
import { toast } from "react-toastify";
const Dashboard = ({ setAuth }) => {
const [name, setName] = useState("");
const [role, setRole] = useState("");
const getProfile = async () => {
try {
const res = await fetch("http://localhost:5000/dashboard/", {
method: "POST",
headers: { jwt_token: localStorage.token }
});
const parseData = await res.json();
console.log(parseData)
setRole(parseData.user_role);
setName(parseData.user_name);
} catch (err) {
console.error(err.message);
}
};
const logout = async e => {
e.preventDefault();
try {
localStorage.removeItem("token");
setAuth(false);
toast.success("Logout successfully");
} catch (err) {
console.error(err.message);
}
useEffect(() => {
getProfile();
}, []);
return (
<div>
<h1 className="mt-5">Dashboard</h1>
<h2>Welcome {name} as {role}</h2>
<button onClick={e => logout(e)} className="btn btn-primary">
Logout
</button>
</div>
);
};
export default Dashboard;
Hi all,
Please help...
I am trying to develop an app with a simple login registration .
I have a code in function component for login registration which I have posted above .
My entire codes of the app are in class based(class components). Could you please help me to convert the above code into
class based.

You can convert your function component to a class in five steps:
Create an ES6 class, with the same name, that extends React.Component.
Add a class constructor that assigns the initial this.state (to add your initial state of name and role ).
use this.setState to update your state ( name or role).
Add a single empty method to it called render().
Move the body of "return" inside the function component into the render() method.
Replace props with this.props in the render() body.
you can't use react Hooks (useEffect, useState ) inside you class component , so you will need to use ComponentDidMount or ComponentDidUpdate ...(depending in the situation ), in your case you will need to use ComponentDidMount because you fetching data (call getProfile inside ComponentDidMount).
you need to take a look in the references below to understand more about it and why you will need to use componentDidMount:
https://reactjs.org/docs/state-and-lifecycle.html#converting-a-function-to-a-class
https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/

Related

please explain the error to me, Error: Rendered more hooks than during the previous render

Iam newbie and now learning to make customize react hooks
here i am trying to call the function i made in app.js file, i want to use it onClick button. but fail to do so. please help me to find the error and understand it.
import React, {
useEffect,
useState
} from "react";
const useRandomJoke = () => {
const [jokes, setJokes] = useState();
useEffect(() => {
const jokeFetch = async() => {
await fetch("https://api.icndb.com/jokes/random")
//we'll run 2 "then"
.then(
// this will give us response and will return inform of res.json
(res) => res.json()
) //.json is a format
.then((data) => {
setJokes(data.value.joke);
}); // now calling data from te returned values in res.json
};
jokeFetch();
}, []);
return jokes;
};
export default useRandomJoke;
//With onClick function
function App() { const [jokes, setJokes] = useState();
return (
<div className="App">
<h1>Random Jokes</h1>
<p>{jokes}</p>
<button onClick={()=>{setJokes(useRandomJoke)}}>
Click for Jokes</button>
</div>
); } export default App;
`
useRandomJoke is a custom hook. Hooks should only be called at the top level of a component and as the custom hook already has the joke state, you don't need an additional state in the App component.
If you want to get a new joke after the component renders and every time the button gets clicked, you can do this:
const useRandomJoke = () => {
const [joke, setJoke] = useState("");
const fetchJoke = useCallback(() => {
fetch("https://api.icndb.com/jokes/random")
.then((res) => res.json())
.then((data) => {
setJoke(data.value.joke);
});
}, []);
return [joke, fetchJoke];
};
export default function App() {
const [joke, fetchJoke] = useRandomJoke();
useEffect(() => {
fetchJoke();
}, [fetchJoke]);
return (
<div className="App">
<h1>Random Jokes</h1>
<p>{joke}</p>
<button onClick={fetchJoke}>Click for a random joke</button>
</div>
);
}
You can't conditionally call React hooks, like in the onClick handler of the button, as this breaks the rules of hooks. I suggest refactoring the useRandomJoke hook to return the fetched joke and a function to fetch the next random joke. You also shouldn't mix async/await with Promise chains as this is an anti-pattern.
const useRandomJoke = () => {
const [jokes, setJokes] = useState(null);
const jokeFetch = async () => {
const res = await fetch("https://api.icndb.com/jokes/random");
const data = await res.json();
setJokes(data.value.joke)
};
return [jokes, jokeFetch];
};
Then use the hook in the app.
function App() {
const [joke, getJoke] = useRandomJoke();
return (
<div className="App">
<h1>Random Jokes</h1>
<p>{joke}</p>
<button onClick={getJoke}>Click for Joke</button>
</div>
);
}
Well, there is more than one point to talk about here:
1- in React.js, you can only call custom hooks at the top level of your function's body (react recognizes any function starting with the keyword use as a hook)
function App() {
// top level is here
const randomJokes = useRandomJoke()
const [jokes, setJokes] = useState();
return (
<div className="App">
<h1>Random Jokes</h1>
<p>{jokes}</p>
<button onClick={()=>{setJokes(useRandomJoke)}}>
Click for Jokes
</button>
</div>
); }
export default App;
2- In your example I understand you want to have a new joke each time onClick triggers, in order to do so, I don't think using a custom hook is the ideal solution here, since your custom hook runs the fetchJokes method only once on initial render (as you described in your useEffect hook), I understand a lot of people mention that useEffect is the place to make API calls, but it doesn't necessarily applies to all use cases, in your example it is simple, you don't have to use useEffect neither create a custom hook.
a possible simple solution:
function App() {
// we always call hooks at the top level of our function
const [jokes, setJokes] = useState();
const fetchNewJoke = () => {
fetch("https://api.icndb.com/jokes/random")
//we'll run 2 "then"
.then(
// this will give us response and will return inform of
res.json
(res) => res.json()
) //.json is a format
.then((data) => {
setJokes(data.value.joke);
}); // now calling data from te returned values in res.json
};
};
return (
<div className="App">
<h1>Random Jokes</h1>
<p>{jokes}</p>
<button onClick={fetchNewJoke}>Click for Joke</button>
</div>
);
} export default App;

pass async results from parent to child component react functional component

I have a functional component that designed to search tasks and show in a task resultbox, the async Task data is set via useState hook and passed to the child component "TaskResultBox". The state is not changed and values are not rendered into the child component. I have verified the values retrieved from API using debug. but not re-rendering the data in the child component.
import React from 'react'
import {useState} from 'react'
function SearchTask({onAddTask}) {
const [searchInputValue, setSearchInputValue] = useState('');
const [tasks, setTasks] = useState('');
const getTasks = () => {
return tasks;
};
const onSearchInputValueChange = (e) => {
setSearchInputValue(e.target.value);
};
const onSearch = async(e) => {
const theRequestOpts = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ searchString: searchInputValue })
};
const response = await fetch('/api/searchTasks', theRequestOpts);
const data = await response.json();
setTasks(data);
};
return (
<InputBox onSearchInputValueChange={onSearchInputValueChange}/>
<Button title="Search Tasks:" onClick={onSearch}/>
<TaskResultBox taskResults={getTasks}/>
)
}
export default SearchTask
// TaskTesultBox.js
import React from 'react'
function TaskResultBox({taskResults}) {
return (
<div>
<h1>Task Result:</h1>
<textarea value={taskResults}/>
</div>
)
}
export default TaskResultBox
getTasks is a function that returns a tasks object, so to get the return variable, you would need to invoke it
So change to:
<TaskResultBox taskResults={getTasks()}/> //<-- Invoke it
But I wonder why do you need a function just to return that variable but not put it directly to the props?
Like so:
<TaskResultBox taskResults={tasks}/>

React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application

I am practicing AWS' Cognito. For front-end I am using React and for routing I am using React-router-dom. For Cognito validation I am using amazon-cognito-identity-js package. My Congito signin, signup and confirmation logic works fine. I made one helper function where I validate the Congnito. and reuse it in different component. I split my Nav bar into two components. From Congnito current user I made one callback function and use it in useEffect, and dependencies put the callback function, by default getAuthenticatedUser is null. I add condition where it fetch the data, if getAuthenticatedUser then redirect to signin and signup page. Because of this condition I am getting the error: Can't perform a React state update on an unmounted component...... Also when I signed in it does not change the nav bar name, I have to refresh the browser then I can see the change. I share my code in codesandbox.
This is my helper function
import React, { useState, useCallback, useEffect, useMemo } from 'react';
import { CognitoUserPool } from 'amazon-cognito-identity-js';
const Pool_Data = {
UserPoolId: 'us-east-1_IEyFfUupx',
ClientId: '63fc9g5c3g9vhqdalrv9eqhoa2',
};
export default function useHandler() {
const [state, setstate] = useState({
loading: false,
isAuthenticated: false
})
const { loading, isAuthenticated } = state;
const userPool = new CognitoUserPool(Pool_Data)
const getAuthenticatedUser = useCallback(() => {
return userPool.getCurrentUser();
},
[],
);
console.log(getAuthenticatedUser());
useEffect(() => {
getAuthenticatedUser()
}, [getAuthenticatedUser])
const signOut = () => {
return userPool.getCurrentUser()?.signOut()
}
console.log(getAuthenticatedUser());
return {
loading,
isAuthenticated,
userPool,
getAuthenticatedUser,
signOut
}
};
This is my navigation
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import SigninLinks from './SigninLinks';
import SignoutLinks from './SignoutLinks';
import useHandlder from '../configHandler/useHandler';
const Nav = () => {
const { getAuthenticatedUser } = useHandlder();
const Links = getAuthenticatedUser() ? <SigninLinks /> : <SignoutLinks />
return (
<nav className="nav-wrapper grey darken-3">
<div className="container">
<h2 className="brand-logo">Logo</h2>
{
Links
}
</div>
</nav>
);
};
export default Nav;
This is Home screen where it display the data and getting error
import React, { useState, useEffect } from "react";
import { api } from './api';
import useHandlder from './configHandler/useHandler'
import { Redirect } from 'react-router-dom';
const Home = () => {
const [state, setstate] = useState([]);
const { getAuthenticatedUser } = useHandlder();
useEffect(() => {
fetchData()
}, [])
const fetchData = async () => {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts`);
const data = await response.json();
setstate(data)
}
return getAuthenticatedUser() === null ? <Redirect to="/signin" /> : //In here is the //error happening.
<div className="row">
<h1>hello welcome to home</h1>
{
state?.map((i: string, id: number) => <h1 key={id}>{i.title}</h1>)
}
</div>
};
export default Home;
Issue
The issue is your app starts on the home ("/") path and renders the Home component. Home initiates a GET request upon mounting and checks for an authenticated user, and if there is none, renders a redirect to your "/signin" route.
The fetch is asynchronous so when the redirect occurs the GET request is resolving after Home has been unmounted and it tries to update the local state with the response data, but can't.
Solution
You need to use an Abort Controller to cancel in-flight requests. If the component unmounts, an effect cleanup function cancels the fetch request. In Home update the useEffect hook to create an AbortController and signal to be used in a cleanup function.
useEffect(() => {
const controller = new AbortController(); // <-- create controller
const { signal } = controller; // <-- get signal for request
const fetchData = async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts`,
{ signal } // <-- pass signal with options
);
const data = await response.json();
setstate(data);
};
fetchData();
return () => controller.abort(); // <-- return cleanup function to abort
}, []);
Demo

React increment likes to an API endpoint

Im trying to make a system of likes for each recipe that i have on my page. The endpoint works, i basically pass the id and when i submit, it increments the number of likes by one of that recipe.
Im trying to make the frontend part for it.
Basically im building a custom hook with
import { useState, useEffect } from "react";
import axios from "axios";
const useLikes = (id) => {
const [like, setLike] = useState();
useEffect(() => {
(async (id) => {
const response = await axios.post(`
https://obscure-river-28733.herokuapp.com/recipe/like/${id}
`);
setLike(response.data);
})(like);
});
console.log("like" + like);
return like;
};
export default useLikes;
Now this is the part im a bit stuck on what to do.
Im bulding the icon like this
import React from "react";
import { BsHeart } from "react-icons/bs";
import useLikes from "./useLikes";
const Likes = ({ id }) => {
const likes = useLikes(id);
return (
<BsHeart
likes={id}
color="red"
size={18}
onClick={() => console.log(likes)}
/>
);
};
export default Likes;
And on the app component inside my map i call it like this
<Likes likes={record.id} />
id comes undifined so its not making the post to nothing viable. Could anyone help me solve this please?
Your Likes component should be defined like this:
const Likes = ({ likes: id }) => {
or change the prop name when calling it
<Likes id={record.id}/>
Api call is asynchronous so it should be done simply inside an asynchronous function
const useLikes = async (id) => {
const response = await axios.post(`
https://obscure-river-28733.herokuapp.com/recipe/like/${id}
`);
return response.data;
};
Then in the Likes component you can use the useLikes function like this
const [likes, setLikes] = useState(0);
useLikes(id).then((res) => setLikes(res));

Why doesn't componentDidMount method work when it’s called after the second time?

I'd like to change what a component shows depending on the URL parameter but at the same time to use the same component. When I execute my code componentDidMount is evoked for the first time, but after the second time, it doesn't work. As a result, I can't change what the component shows.
I'm using react.js.
Although I used componentDidUpdate instead of componentDidMount it caused an infinitive loop.
import React from "react";
import ReactMarkdown from "react-markdown";
import Contentful from "./Contentful";
import "./Article.css";
class ArticlesWithTag extends React.Component {
state = {
articleFromContentful: []
};
async componentDidMount() {
console.log("componentDidMount");
const { tag } = this.props.match.params;
//get article from contentful API
const contentful = new Contentful();
try {
const article = await contentful.getArtcleWithTags(
undefined,
"blogPost",
tag
);
this.setState({ articleFromContentful: article.items });
} catch (err) {
console.log("error");
console.log(err);
}
}
render() {
const bodyOfArticle = this.state.articleFromContentful.map(data => {
const returnTitle = () => {
return data.fields.title;
};
const returnPublishedDate = () => {
let date = data.fields.publishDate.substring(0, 10);
let replacedDate = date.replace("-", "/");
while (date !== replacedDate) {
date = date.replace("-", "/");
replacedDate = replacedDate.replace("-", "/");
}
return replacedDate;
};
const returnBody = () => {
return data.fields.body;
};
const returnTags = () => {
const tagList = data.fields.tags.map(data => {
const listContent = `#${data}`;
return <li>{listContent}</li>;
});
return tagList;
};
returnTags();
return (
<div className="article-container">
<div className="article-title">{returnTitle()}</div>
<p className="article-date">{returnPublishedDate()}</p>
<div className="article-body">
<ReactMarkdown source={returnBody()}></ReactMarkdown>
</div>
<div className="article-tags">
<ul>{returnTags()}</ul>
</div>
</div>
);
});
return <div className="article-outer">{bodyOfArticle}</div>;
}
}
export default ArticlesWithTag;
Ok componentDidMount is a lifecylce method that runs only when the component mounts
and componentDidUpdate runs everytime there's an update
You can set state in componentDidUpdate but it must be wrapped around some condition
In your case you can do something like this
async componentDidMount() {
console.log("componentDidMount");
const { tag } = this.props.match.params;
//get article from contentful API
const contentful = new Contentful();
try {
const article = await contentful.getArtcleWithTags(
undefined,
"blogPost",
tag
);
this.setState({ articleFromContentful: article.items });
} catch (err) {
console.log("error");
console.log(err);
}
}
and for further updates use compnentDidUpdate as
async componentDidUpdate(prevProps) {
const { tag } = this.props.match.params;
//get article from contentful API
const contentful = new Contentful();
if ( tag !== prevProps.match.params ) // if props have changed
{
try {
const article = await contentful.getArtcleWithTags(
undefined,
"blogPost",
tag
);
this.setState({ articleFromContentful: article.items });
} catch (err) {
console.log("error");
console.log(err);
}
}
}
Hope it helps
componentDidMount is called only when your component is mounted.
componentDidUpdate is called when the props has changed.
You should create a function from your componentDidMount logic and call it in componentDidUpdate only when tag has changed
componentDidMount() is only called when the component initially mounts onto the DOM. Re-renders caused by prop or state changes trigger componentDidUpdate(). As you mentioned, making state changes in this function causes infinite loops.
What you can do is use componentDidUpdate() and check if props have changed before deciding to update the state again. This should prevent your infinite loop situation.

Resources