How to re-write URL based on fetched JSON - javascript

I am mapping through an array from JSON, where I get all the informations I need.
E.g. I have cards of blog posts fetched -> title, short description, published date and its url. So the card is linked based on the url from json - when I click I'll go to new page e.g -> click on blog n.1 -> redirect to mywebsite/blogNumberOne, but of course the page does not exist.
I have another JSON file, which have in the middle name of all the blogs -> so if I take that json and put the right parameter to it -> (blogNumberOne) -> it gives me new json with all the info from that post.
What I need to do is to connect it somehow and make my app to understand that now I click on blog n.1 so it has to go to /blogNumberOne and give me all the correct info from blog n.1. The same applies for each blog post of course.
I know I need to make the request to json, but I just don't know why.
Here is my fetch for printing out the blogs:
useEffect(() => {
fetch('myJSONurl')
.then((resp) => resp.json())
.then((data) => {
setGuides(data.guides);
console.log(data);
});
}, []);
// mapping through
{guides.map((blog) => {
return (
<Card style={{ width: '15rem' }}>
<Card.Img
variant="top"
src={blog.img}
/>
<Card.Body>
<Card.Title>
{blog.title}
</Card.Title>
<a
href={blog.url}
class="stretched-link"
></a>
</Card.Body>
</Card>

Make a page state in you app:
const [page, setpage] = useState(1);
Then on change of page, fetch it's respective data:
useEffect(() => {
// Fetch data according to current page
fetch(`${JSONURL}/${page}`)
},[page]);
// Then map the data
page.map(data => ....)
When you click on a blog post, it should have some id of some sort so you can fetch the data from it:
{
posts.map(post => <BlogPost onClick={() => setPage(/*here mention something unique to each post to fetch data*/post.id)} />)
}

Related

How to call the state when the response comes from the api?

What I'm trying to do is to display in a list all the clients.
When there is no client in the list, it is another view that is displayed which says no client exists.
The initial value of the state its an empty array.
So when the axios.get() method gets the data from the backend, two times its called the initial value(which is an empty array), and after its filled with the client list that comes from backend.
const [clientList, setClientList] = useState([])
useEffect(() => {
axios
.get(`api/${phone}`)
.then(setClientList(response.data.data))
})
.catch((error) => console.log(error));
}
});
}, [clientList]);
console.log(clientList)
return(
{clientList.length > 0? (
<View>
<FlatList
data={clientList}
renderItem={(item) => {
let client= item.item.fullName;
return (
<View >
<Text>{client}</Text>
/>
</View>
)
}}
/>
</View>
) : (
<Text> No Client List </Text>
)}
)
When I console the clientList it will show:
clientList[]
clientList[]
clientList[
{
fullName: John Doe
},
{
fullName: Nick Smith
}
]
The first two empty arrays (that comes from the initial value of the useState) it will show the No Client List every time the user goes to the client list screen or reload the screen.
How can I prevent showing the <Text> No Client List </Text> when the clientList has clients on the array?
you can add a new state isLoading which will be true by default to handle the clientList initial empty array case.
const [isLoading, setIsLoading] = useState(true)
...
useEffect(() => {
axios
.get(`api/${phone}`)
.then(() => {
setIsLoading(false)
setClientList(response.data.data)
})
.catch(() => {
setIsLoading(false)
});
}, []);
...
// you can conditionally return UI based on `isLoading` or you can use `ListEmptyComponent` prop from FlatList along with `isLoading`
if(isLoading) {
return(<Text>loading</Text>)
}
// remaining code
return(
...

Mapping over data so that it renders on the page newest to oldest

Editing because I have more relevant information: I am connecting to an api to get a recipe made by having the user input a recipe name and ingredients. The api then answers back with instructions on a recipe based off their input. I have it set up, however I'm trying to map over the data so that the user can keep inputting different recipe names and ingredients. This list should be arranged from newest to oldest. This is the form used to grab the data and the api post call:
export default function RecipeForm() {
const [recipeInput, setRecipeInput] = useState("");
const [ingredientsInput, setIngredientsInput] = useState("");
const [result, setResult] = useState();
const [recipeArray, setRecipeArray] = useState([]);
const data = {
prompt: `Write a recipe based on the recipe name, ingredients and instructions:Recipe name:\n\n${recipeInput}\n\nIngredients:\n\n${ingredientsInput}\n\nInstructions:`,
temperature: 0.7,
max_tokens: 256,
top_p: 1.0,
};
async function submitHandler(e) {
e.preventDefault();
await axios({
method: "post",
url: "https://api/url.com",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.API_KEY}`,
},
data: JSON.stringify(data),
}).then((response) => {
setResult({
recipe: recipeInput,
ingredients: ingredientsInput,
instructions: response.data.choices[0].text,
});
console.log(response.data.choices[0].text);
});
setRecipeInput(recipeInput);
setIngredientsInput(ingredientsInput);
setRecipeArray(recipeArray.push(result));
}
return (
<div>
<Card>
<CardContent>
<TextField
id="recipeInput"
label="Enter a recipe name here"
value={recipeInput}
onChange={(e) => setRecipeInput(e.target.value)}
/>
<TextField
id="ingredientsInput"
label="Enter your ingredients here"
value={ingredientsInput}
onChange={(e) => setIngredientsInput(e.target.value)}
/>
<Button onClick={submitHandler}>See your recipe!</Button>
</CardContent>
</Card>
{recipeArray.map((recipes) => {
return (
<Recipe
recipeInput={recipes.ecipeInput}
setRecipeInput={recipes.setRecipeInput}
ingredientsInput={recipes.ingredientsInput}
setIngredientsInput={recipes.setIngredientsInput}
result={recipes.result}
setResult={recipes.setResult}
/>
);
})}
</div>
);
}
I'd like to empty the form once the user hits the button, I think since I reshaped how setResults shows it's results, I may be able to empty the setRecipeInput and setIngredints input if I save the usestate as ("").
Currently when I try to map over the data I get an error in the console saying:"Uncaught TypeError: recipeArray.map is not a function". I am guessing it's because the data that I'm trying to get on setResult isn't going through and being turned into an array in order to map through it but I am stuck.
The 'Form' and 'Recipe' component share the same states 'recipeInput' and 'ingredientsInput', so when you empty the form by 'setRecipeInput('')' and 'setIngredientsInput('')' once the user hits the button, the 'Recipe' also gets empty.
Use another two states such as 'recipeSaved' and 'igredientsSaved' for 'Recipe', once the user hits the button, set 'recipeInput' and 'ingredientsInput' values to 'recipeSaved' and 'igredientsSaved', then empty them.
By the way, the 'capitalize' and 'capitalizeAgain' functions in your second code section are the same!

Updating cache after delete mutation using React, Apollo, GQL, Hasura

For reference, this is what I've been trying to accomplish: https://hasura.io/learn/graphql/react-native/update-delete-mutations/4-remove-todos-integration/
I've been stuck trying to figure out how to solve this problem. I have a page with a list of blog posts and each post has a delete button that gets rendered within it.
const renderBlogs = (blogs) => {
return data.blogs.map(({ id, title, body }) => (
<ListItem key={id}>
<div>
<div>
<h3>
<Link to={`/blog/${id}`}>{title}</Link>
</h3>
<div>
<Button
onClick={(e) => {
e.preventDefault();
let result = window.confirm(
'Are you sure you want to delete?',
);
if (result) {
deleteBlog({
variables: { id },
update: updateCache,
});
history.push('/blog');
}
}}
>
Delete
</Button>
</div>
</div>
<p>{body}</p>
</div>
</ListItem>
));
Each of these li's get rendered out with:
<List>{renderBlogs(data.blogs)}</List>
When the delete button is clicked, it calls the deleteBlog function from const [deleteBlog] = useMutation(DELETE_BLOG); which successfully deletes the blog. So far so good. Now the problem is trying to update the cache so the page doesn't need to be refreshed to show the changes. Here is my updateCache function which does not work properly:
const updateCache = (client) => {
data.blogs.map(({ id, title, body }) => {
const data = client.readQuery({
query: FETCH_BLOGS,
variables: {
title,
body,
},
});
const newData = {
blogs: data.blogs.filter((t) => t.id !== id),
};
client.writeQuery({
query: FETCH_BLOGS,
variables: {
title,
body,
},
data: newData,
});
return newData;
});
};
When the delete button is clicked, it deletes ALL of the blogs in the cache (something to do with using .map()) but upon refreshing the page, only the blog that was actually deleted is gone (as desired).
I know there is some error in my logic withing updateCache() but I'm not sure how to fix it. Any help is appreciated. Thanks.

Best way to handle a large set of images from an API using React/Redux?

I've been messing around with this Pokemon api https://pokeapi.co/ and building a very rough version of a Pokedex by listing all of the pokemon on a page and making them links that send you to a more detailed page of the Pokemon you selected.
Obviously it's a very large set of data, almost 1000 entries. It's a very simple React app that uses Axios to map through the Pokemon and grab their name and image to display on the home page.
So that's where my problem starts, on every reload Axios is doing about 1000 requests for images.
What would be the best way to cache and serve the images and names so that I don't have to load them every time I hit the page?
Here's a bit of the code to give you an idea of what I've done so far:
function App() {
const [pokemonList, setPokemonList] = useState([]);
useEffect(() => {
const fetchData = async () => {
let response = await axios.get('https://pokeapi.co/api/v2/pokemon/');
setPokemonList(response.data.results)
return response;
};
fetchData();
}, []);
return (
<Grid container>
<Router>
<Switch>
<Route path="/pokedex/:id">
<PokedexEntry pokemonList={pokemonList} />
</Route>
<Route path="/">
<Home pokemonList={pokemonList} />
</Route>
</Switch>
</Router>
</Grid>
);
}
This is the actual "Tile" that contains the pokemon image and name. There would be roughly 800 of these.
const PokemonTile = ({ data }) => {
const [pokemonData, setPokemonData] = useState([]);
useEffect(() => {
const fetchData = async () => {
let response = await axios.get(data.url);
setPokemonData(response.data)
return response;
};
fetchData();
}, []);
return (
<li className="pokemonTile">
<Link to={`/pokedex/${pokemonData.id}`}>
{pokemonData.sprites ? <img src={pokemonData.sprites.front_default} /> : ''}
<div>
{pokemonData.name}
</div>
</Link>
</li>
)
}
Preferably, I don't want to implement any sort of lazy loading as I'd like the user to see all of the entries seamlessly once they've loaded. Should I try to serve the images myself rather than call them from and API and just let the API handle grabbing text?
The best thing would be to use a Service Worker and cache all the images. Then render them virtually so the browser only loads the image when it's needed. Check out React's virtual lists.
If you want to cache the API calls I suggest you to use our official wrapper. https://github.com/PokeAPI/pokeapi-js-wrapper

Use AsyncStorage values kept in state and display it

I'm making my first app, "mark what album you've listened" kind of app. I used iTunes search API and for every album listened, I create a key with AsyncStorage using the ID and for the value, the url to the artwork.
So here is the question: I'm stuck at the last step of the app. I want to display all the artwork of all the albums I've listened. For that, I would like to make a foreach loop that for every element in listened, it would take its URL (now that it only contains URLs), put it in an Image tag, return it and display it... But, can I do that?
For that, I created a state called listened. It takes all the AsyncStorage thanks to this function:
importData = async () => {
try {
const keys = await AsyncStorage.getAllKeys();
const result = await AsyncStorage.multiGet(keys);
console.log(result)
//listened takes all asyncstorage data
this.setState({listened: result.map(req => JSON.stringify(req[1]))});
} catch (error) {
console.error(error)
}
}
Then I made a renderArtwork() function that returns the state when I arrive to the Navigation. For now, it just displays all the URLs:
renderArtwork(){
this.importData();
return(
<Text>{this.state.listened}</Text>
)
}
And the "main":
render() {
return(
<View style={styles.main_container}>
{this.renderArtwork()}
</View>
)
}
Thank you for your help
It better to move the importData() to your componentDidMount which will call and get the data from asyncstorage when the screen is mounted.
As for displaying the images, Lets say that your current array 'listened' has the below format
listened = ['url1','url2'];
renderArtwork() {
this.importData();
return this.state.listened.map((url) => (
<Image
style={{
width: 50,
height: 50,
}}
source={{
uri: url,
}}
/>
));
}
You can simply map and show all the images in your array, Also the JSON.stringify part wont be necessary as its already a string.

Categories