How to use dangerouslySetInnerHTML - reactjs

I have html script data.
I want to put it in div. but when I do that It just shows "}" this and that's it.
I wonder what I did wrong.
When I log newDataHTML, I can get html string.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const getData = () => {
axios
.get('/api/data')
.then( (data) => {
// get new Data
const newDataHTML = data.data[0].rule;
return {__html: newDataHTML};
})
.catch( err => console.log(err));
}
return (
<div className="App">
<Tabs defaultTab="data" onChange={(tabId) => { console.log(tabId) }}>
<TabList>
<Tab tabFor="data">New Data</Tab>
</TabList>
<TabPanel tabId="data">
<div dangerouslySetInnerHTML={getData()}></div>;
</TabPanel>
</Tabs>
</div>
);
}
export default App;

It is not working because you can't return outside .then method and also you will encounter rerender loop... You can put the method in useEffect hook so it runs once and also put the return data in useState...
const [data, setData] = useState();
useEffect(() => {
const getData = () => {
axios.get('/api/data').then((res) => {
const newDataHTML = res.data[0].rule;
setData({__html: newDataHTML})
}).catch( err => console.log(err));
}
getData();
}, [])
you can now use the data variable in your jsx instead of calling the function in your jsx.
It will also be nice to use packages like DomPurify to avoid xss attacks.
https://www.npmjs.com/package/dompurify

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;

How to check data loading in useEffect

I am having a weird issue inside useEffect() in my React component. I have to make 2 separate axios requests to get data when the page loads. I am trying to use a hook variable to see if the data objects are populated before passing them to the JSX. Here's my current configuration:
import React, { useState, useEffect } from 'react';
import Navbar from '../components/layout/Navbar';
import ContactsCard from '../components/layout/ContactsCard';
import EmailCard from '../components/layout/EmailCard';
import MeetingsCard from '../components/layout/MeetingsCard';
import { useParams } from "react-router-dom";
import config from './../config/config';
import axios from "axios";
function SummaryPageNew() {
let { selectName } = useParams();
const [contactData, setContactData] = useState();
const [meetingData, setMeetingData] = useState();
const [loadingData, setLoadingData] = useState(true);
//API calls
async function getContactData() {
axios
.get(config.apiURL + `/affiliations/name/${selectName}`)
.then((response) => {
return setContactData(response.data[0]);
});
}
async function getMeetingData() {
axios
.get(config.apiURL + `/meetings_attendees/name/${selectName}`)
.then((response) => {
return setMeetingData(response.data);
});
}
useEffect((loadingData) => {
getContactData();
getMeetingData();
setLoadingData(false);
if (loadingData) {
//if the result is not ready so you make the axios call
getContactData();
getMeetingData();
setLoadingData(false);
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<div>
<Navbar />
<div>
<div style={{ textAlign: "center" }}>
<h3>Contact Information</h3>
<h5>Profile: {selectName}</h5>
</div>
{loadingData ? (
<p>Loading Please wait...</p>
) : (
<div className="row">
<ContactsCard contactData={contactData} />
<EmailCard emailData={meetingData} />
<MeetingsCard meetingData={meetingData} />
</div>
)}
</div>
</div>
)
}
export default SummaryPageNew
I have tried moving the setLoadingData(false) method inside the axios calls. If I move it inside the getMeetingData() call. This works ... sometimes. Apparently, on some occasions, it loads first and then the contactData doesn't get returned. In the current configuration, the DOM renders with "Loading Please wait...". What am I doing wrong here? How can I resolve this issue?
There are many issues with your code.
useEffect functions don't take any parameters. Your declaration of loadingData as a parameter is covering the actual loadingData variable in your component, and React will not pass a value for this.
You're missing a dependency on loadingData in your call to useEffect. As is, the function will only execute once and then never again as long as the component stays mounted. So, loadingData never gets set to false. Generally, it is a bad idea to avoid warnings about useEffect dependencies unless you have a very good reason.
My recommended solution would be to avoid storing extra state for the "loading" status. Instead, I would just check whether the two state values have been populated yet, and show the "Loading..." text if either is not.
This leaves you with:
function SummaryPageNew() {
let { selectName } = useParams();
const [contactData, setContactData] = useState();
const [meetingData, setMeetingData] = useState();
const isReady = contactData !== undefined && meetingData !== undefined;
//API calls
async function getContactData() { ... }
async function getMeetingData() { ... }
useEffect((loadingData) => {
getContactData();
getMeetingData();
}, []);
return (
<div>
<Navbar />
<div>
<div style={{ textAlign: "center" }}>
<h3>Contact Information</h3>
<h5>Profile: {selectName}</h5>
</div>
{isReady ? (
<div className="row">
<ContactsCard contactData={contactData} />
<EmailCard emailData={meetingData} />
<MeetingsCard meetingData={meetingData} />
</div>
) : (
<p>Loading Please wait...</p>
)}
</div>
</div>
)
}
react-query is a very powerful library for fetching data asynchronously using hooks. This avoids having to manage complex state which can easily fall out of sync. However, I'd learn the fundamentals of react hooks first!
You're dealing with async function calls. Javascript doesn't wait for your async functions to complete before it continues with your program. This means your calls are probably still fetching, while you already set loadingData to false. You can fix this by using Promise.all to get a callback when the async functions resolve:
//API calls
async function getContactData() {
return axios
.get(config.apiURL + `/affiliations/name/${selectName}`)
.then((response) => {
return setContactData(response.data[0]);
});
}
async function getMeetingData() {
return axios
.get(config.apiURL + `/meetings_attendees/name/${selectName}`)
.then((response) => {
return setMeetingData(response.data);
});
}
useEffect(() => {
let mounted = true
return () => { mounted = false }
Promise.all([getContactData(), getMeetingData()]).then(() => {
if (mounted) setLoadingData(false)
})
}, []); // eslint-disable-line react-hooks/exhaustive-deps
Also note the let mounted = true I've added: you want to make sure this component still exists whenever your async calls complete. If the calls take a while, it's not unthinkable you might have navigated away, for instance.
Finally, it's not a wise idea to disable react-hooks/exhaustive-deps. With a few changes you can setup your code in such a way that this ignore is no longer needed.
React want you to provide getContactData, getMeetingData in the dependency array. You can fix that by moving the data fetching function outside of you component. This means they no longer have access to the selectName variable, but you can provide that variable as an argument:
function SummaryPageNew() {
let { selectName } = useParams();
const [contactData, setContactData] = useState();
const [meetingData, setMeetingData] = useState();
const [loadingData, setLoadingData] = useState(true);
//API calls
useEffect(() => {
let mounted = true
Promise.all([
getContactData({ selectName }),
getMeetingData({ selectName })
]).then(([contactData, meetingData]) => {
if (!mounted) return
setContactData(contactData)
setMeetingData(meetingData)
setLoadingData(false)
})
return () => { mounted = false }
}, [selectName]);
return () // Render your component
}
async function getContactData({ selectName }) {
return axios
.get(config.apiURL + `/affiliations/name/${selectName}`)
.then((response) => {
return setContactData(response.data[0]);
});
}
async function getMeetingData({ selectName }) {
return axios
.get(config.apiURL + `/meetings_attendees/name/${selectName}`)
.then((response) => {
return setMeetingData(response.data);
});
}

How to render the sorted array of objects using UseMemo ReactHooks

I'm trying to render the sorted array of objects using ReactHooks i have used useMemo for the same and redux as well. Could someone suggest me the best practies for it. Any suggestions on what am i doing wrong here?
I have put the post.js below as well.
I'm trying to render the sorted array of objects using ReactHooks i have used useMemo for the same and redux as well. Could someone suggest me the best practies for it. Any suggestions on what am i doing wrong here?
Thanks
HomePage.js
import React, { useState, useEffect, useMemo } from "react";
import Post from "../../Components/Post/Post";
import "./HomePage.css";
import axios from "axios";
const HomePage = () => {
const [posts, setPosts] = useState("");
let config = { Authorization: "................" };
const url = ".........................";
useEffect(() => {
AllPosts();
}, []);
const AllPosts = () => {
axios
.get(`${url}`, { headers: config })
.then((response) => {
const allPosts = response.data.articles;
console.log(response);
})
.catch((error) => console.error(`Error: ${error}`));
};
const newPostsByTitle = useMemo(() => {
allPosts.sort((a, b) => a.title.localeCompare(b.title)), [posts];
});
return (
<div className="home">
<div className="select">
<select
name="slct"
id="slct"
onChange={(e) => newPostsByTitle(e.target.value)}
></select>
</div>
<Post className="Posts" posts={posts} key={posts.title} />
</div>
);
};
export default HomePage;
Post.js
import React from "react";
import "./Post.css";
import { Fragment } from "react";
const Post = (props) => {
const displayPosts = (props) => {
const { posts } = props;
if (posts.length > 0) {
return posts.map((post) => {
return (
<Fragment>
<div className="Post" key={post.title}>
<img
src={post.urlToImage}
alt="covid"
width="100%"
className="img"
/>
<h5 className="title"> {post.title}</h5>
<p className="author"> {post.author}</p>
<p className="description"> {post.description}</p>
</div>
</Fragment>
);
});
}
};
return <div className="Posts">{displayPosts(props)}</div>;
};
export default Post;
You have a incorrect understanding of what the axios call dos I think.
This is just a function that on trigger will download the data, but you need to store it somewhere (e.g. posts) and use these posts instead of the api call:
const [posts, setPosts] = useState([]); // Use an empty array as defualt so it does work without data before the call
...
const AllPosts = () => {
axios
.get(`${url}`, { headers: config })
.then((response) => {
const allPosts = response.data.articles;
setPosts(allPosts) ?? You need to save the posts somewhere, since allPosts is not accessible outside of this function. Sicne you already have a useState, save them there
console.log(response);
})
.catch((error) => console.error(`Error: ${error}`));
};
const newPostsByTitle = useMemo(() => {
return posts.sort((a, b) => a.title.localeCompare(b.title)), [posts]; // Using {} requeores the return keyword, if you omit the {} you dont need the return statement
}); // Now access the posts saved in state to sort them
Also the key in <Post className="Posts" posts={posts} key={posts.title} /> does not work, since posts is an array not an object. So remove it.

Passing down async fetched data via Context API

The data is not displaying in the child component Store. In the Storecomponent, the console.log in the useEffect() hook returns undefined. I suspect the reason being that the fetchAPI function in the parent component is only called after myContext.Provider is rendered, thus the value of myContext.Provider is undefined.
How can I pass the data(hook state) I fetched from the API in Stores(parent) down to Store(child) in this case?
export const myContext = createContext()
const Stores = () =>{
const [data, setData ] = useState([])
const fetchAPI = async() => {
var res = await fetch('https://fortnite-api.theapinetwork.com/store/get')
var result = await res.json()
var final= result.map(item => item)
setData(final)
}
useEffect(() =>{
fetchAPI().then(console.log(data))
}, [data])
return(
<div>
<myContext.Provider value={data} >
{data.map(item => {
return(
<div>
<ul>
<Link to={`stores/${item.itemId}`}><li>{item.item.name}</li></Link>
</ul>
</div>
)
})}
</myContext.Provider>
</div>
)
};
const Store = () => {
const specific = useContext(myContext)
useEffect(
() => {
console.log(specific)
}
)
return(
<>
{specific.map( item => {
return(
<div>
<h2> Description: {item.name}</h2>
</div>
)
})}
</>
)
}
The way you use fetch api isn't correct I think. fetch returns a promise, so you need to return the promise in your fetchAPI method if you want to uses .then().
.then() allow you to grab the promise and work with it, here is your code changed to work:
import React, { Component, useState, useEffect, createContext } from "react";
import { render } from "react-dom";
const myContext = createContext()
const App = () =>{
const [data, setData ] = useState([]);
const fetchAPI = () => {
// It return a promise
return fetch('https://fortnite-api.theapinetwork.com/store/get');
}
useEffect(() =>{
fetchAPI().then(data =>
// try to call .json() method. this method returns a promise
data.json().then(json => {
// if .json() succeed, then do your stuff
console.log(json);
setData(json.data.map(item => item));
})
)
// if .json() fails, promise is rejected with your error
.then(err => console.log('err', err));
}, []); // empty array to prevent looping (rule : do not update a state you are passing in this deps array)
return(
<div>
<myContext.Provider value={data} >
{data.map(item => {
return(
<div>
<ul>
<li><pre>{JSON.stringify(item)}</pre></li>
</ul>
</div>
)
})}
</myContext.Provider>
</div>
)
};
render(<App />, document.getElementById("root"));
Here is the repro on Stackblitz.

Simple function that retrieves data from an API is not returning the data

I have this React component that used to return an HTML element like this:
const PartsList = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/parts',
);
setData(result.data);
};
fetchData();
}, []);
return (
<>
{data.map((item, index) => (
<label key={index} className="inline">
<Field key={index} type="checkbox" name="machineParts" value={item.id} />
{item.name}
</label>
))}
</>
);
}
export default PartsList;
Now, I want it to return only an array of JSON, no HTML.
So I tried modifying the component so that it looks like this:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/machines',
);
setData(result.data);
console.log("data as seen in function: ", JSON.stringify(result, null, 2));
};
fetchData();
}, []);
return data;
When I write it out to the console in this function, I see all the needed data.
But when I write it out to the console in the main App.js, I just see undefined.
What could I be doing wrong?
Thanks!
Originally you wanted a component because it had to render HTML.
Now what you actually need is to move everything out to a function.
So you can do this in your main App.js:
import React from 'react';
import axios from 'axios';
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/machines',
);
return JSON.stringify(result, null, 2);
};
const App = () => {
const result = await fetchData()
console.log(result)
return <div>Main App<div>
}
export default App
This is how you make a function to return data that you can call to see the console result in your main App component.
This obviously just demonstrates the concept, you can take it further by moving that function out to its own file that you can import into your App.js folder.

Resources