pass arrow function to component prop - reactjs

I am trying to pass the result of the handleRedirectUrl() function to the ShortUrlField component as a prop.
I don't know what I am doing wrong, please help me
const handleRedirectUrl = () => {
urlService
.getShortenedUrl(urls.slice(-1)[0].short_url)
.then((returnedUrl) => {
setRedirectedUrl(returnedUrl);
})
.catch((error) => {
handleCreateErrors(error);
})
.finally(() => {
return redirectedUrl;
});
};
//display shortened url
const shortUrlDisplay = renderShortUrl ? (
<ShortUrlField
originalUrlValue={urls.slice(-1)[0].original_url}
shortUrlValue={urls.slice(-1)[0].short_url}
redirectedUrlValue={handleRedirectUrl()}
/>
) : (
<EmptyField />
);
The urlService function
const getShortenedUrl = (urlToGet) => {
const request = axios.get(redirectShortenedUrl + `${urlToGet}`);
return request.then((response) => response.data);
};
Edit 1:
I was not returning anything with my handleRedirectUrl function. Also, I was not passing it properly to the props. I have changed my code to
const handleRedirectUrl = () => {
return urlService
.getShortenedUrl(urls.slice(-1)[0].short_url)
.then((returnedUrl) => {
setRedirectedUrl(returnedUrl);
})
.catch((error) => {
handleCreateErrors(error);
})
.finally(() => {
return redirectedUrl;
});
};
//display shortened url
const shortUrlDisplay = renderShortUrl ? (
<ShortUrlField
originalUrlValue={urls.slice(-1)[0].original_url}
shortUrlValue={urls.slice(-1)[0].short_url}
redirectedUrlValue={handleRedirectUrl}
/>
) : (
<EmptyField />
);
It does not work. the getShortenedUrl function is never called
Edit 2: Added the ShortUrlField component code
import React from "react";
const ShortUrlField = (props) => {
return (
<div>
<p>
<a href={props.originalUrlValue}>{props.originalUrlValue}</a> became{" "}
<a href={props.redirectUrlValue}>{props.shortUrlValue}</a>
</p>
</div>
);
};
export default ShortUrlField;
Edit 3: I made it work!!
Many thanks to #ZsoltMeszaros for pointing out the right path to me.
I have passed a state variable to my conditional rendered component, and added an effect hook that basically sets the state if the component is rendered.
Much thanks to all of you that commented.

I didn't understand where is this setRedirectURL function, but anyway your handleRedirectUrl function returns nothing
const handleRedirectUrl = () => {
return urlService
.getShortenedUrl(urls.slice(-1)[0].short_url)
.then((returnedUrl) => {
setRedirectedUrl(returnedUrl);
})
.catch((error) => {
handleCreateErrors(error);
})
.finally(() => {
return redirectedUrl;
});
};
May this can work as you can see like your axios request, this is returning result of urlService.

Related

Undefined when selecting div with information

Having an issue with a piece of my code. I fetch from flask server, and display with div in React. I want to select the div and have that information pass to a new object array to return back to flask, but I keep getting undefined.
Code snippet:
function PLCPage() {
const [myjunk, setMyjunk] = useState([]);
const [devList, setDevList] = useState ([]);
const Scan = () => {
fetch('/api/home').then(response => {
if(response.status === 200){
return response.json()
}
})
.then(data => setMyjunk(data))
.then(error => console.log(error))
}
const Clear = () => {
setMyjunk({})
}
Creating the divs:
{Object.keys(myjunk).map((key) =>{
return (
<div className='plc-container' key={key} onClick={ReadStuff}>
<h1>ID:{myjunk[key]['name']}</h1>
<h1>IP:{myjunk[key]['IP']}</h1>
</div>
)
Clicking on the div, just to return a console log returns undefined.
const ReadStuff = () => {
console.log(this.IP)
}
I eventually want to return the data I have in the 2 h1 tags to a new object (devList) but I can't even get it to console log. Sorry if this is basic but I've been stumped at this for a week. Thanks
I've tried this.IP, myjunk.IP, this,myjunk.IP. myjunk['IP']. Nothing returns. And when I do myjunk.IP I get "cant read from undefined"
One way to do this is to create a separate component:
const JunkButton = ({junk}) => (
<div className='plc-container' key={key} onClick={() => ReadStuff(junk)}>
<h1>ID:{junk['name']}</h1>
<h1>IP:{junk['IP']}</h1>
</div>
)
Now your map() looks like:
{Object.keys(myjunk).map((key) =>{ <JunkButton junk={ myjunk[key] }/> }
And ReadStuff becomes:
const ReadStuff = (junk) => { console.log(junk) }
Notice how in React we explicitly pass things around as props or function parameters.
first you need to pass myjuck to function and then console it like this:
{Object.keys(myjunk).map((key) =>{
return (
// sending myjuck to function whatever that is
<div className='plc-container' key={key} onClick={() => ReadStuff(myjunk)}>
<h1>ID:{myjunk[key]['name']}</h1>
<h1>IP:{myjunk[key]['IP']}</h1>
</div>
)
ReadStuff function
const ReadStuff = (myjunk) => { console.log(tmyjunk) }

data rendering issue after button is clicked in react

I am having a data rendering issue in react. Somehow, data is not automatically updated after it's updated in the server side. I can't put all the code in here, cuz the code is kind of lengthy. so i pasted/renamed some variables. Even if some variables are missing, please understand. Basically, I have a button on the page and when the button is clicked, the status changes to 'UPLOADING' and the function checkIfDataExists is called to fetch data from the server side and data should be automatically updated without page refresh, but when I test this, data is successfully retrieved from the server side, but the updated data is not rendered. I see 'successful...' on the Console. Is there anything wrong?
const Settings: React.FC<IProps> = props => {
const { orgId } = props
const password = 'dummy'
const { data } = httpCall(`/${orgId}/${userId}/settings`)
return (
<div>
{data && <SettingsForm data={data} password={password} {...props} />}
</div>
)
}
const SettingsForm: React.FC<Settings & IProps> = ({
data,
password
}) => {
const [status, setStatus] = useState<'ERROR' | 'DONE' | 'UPLOADING'>()
const service = getServiceInstance(data.organizationId)
function checkIfDataExists(user: any) {
return () => {
httpCall
.getClient(user.id)
.then(value => {
console.log('successful...')
data.modeUsername = value.modeUsername
data.modePassword = value.modePassword
})
.catch(() => {
setStatus('ERROR')
})
}
}
useEffect(() => {
if (!status) return
switch (status) {
case 'UPLOADING': {
const timer = setInterval(
checkIfDataExists({ id: data.id }),
2000
)
return () => clearInterval(timer)
}
}
}, [status, client
])
<div className="info-section">
<p className="detail">Username</p>
<p>{data.modeUsername}</p>
</div>
<div className="info-section">
<p className="detail">Password</p>
<p>{data.modePassword}</p>
</div>
The problem I see is that after you setInterval an API you didn't set in the state to trigger the component to rerender. You don't need to be explicit to define resData to data because if you define data already useState already it types.
const SettingsForm: React.FC<Settings & IProps> = ({
data,
password
}) => {
const [resdata,setResData] = useState(data)
const [status, setStatus] = useState<'ERROR' | 'DONE' | 'UPLOADING'>()
const service = getServiceInstance(data.organizationId)
function checkIfDataExists(user: any) {
return () => {
httpCall
.getClient(user.id)
.then(value => {
console.log('successful...')
setResData({
modeUsername: value.modeUsername,
modePassword: value.modePassword,
})
// data.modeUsername = value.modeUsername
// data.modePassword = value.modePassword
})
.catch(() => {
setStatus('ERROR')
})
}
}
useEffect(() => {
if (!status) return
switch (status) {
case 'UPLOADING': {
const timer = setInterval(
checkIfDataExists({ id: data.id }),
2000
)
return () => clearInterval(timer)
}
}
}, [status, client
])
<div className="info-section">
<p className="detail">Username</p>
<p>{resdata.modeUsername}</p>
</div>
<div className="info-section">
<p className="detail">Password</p>
<p>{resdata.modePassword}</p>
</div>

React: updating state after deletion

I'm trying to update elements after deletion, without refreshing a page. Currently, if delete a record, need to refresh a page to see the result. As I understand, need to update useState, but I do not understand how to do it. If I loop useEffect it works but slowly, but I think it's not the best idea to loop get response.
Get all records from a database.
const PostsGetUtill = () => {
const [posts, setPosts] = useState([]);
const fetchPosts = () => {
axios.get("api/v1.0/post/get").then(response => {
console.log(response.data);
setPosts(response.data);
}).catch(function (error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log(error.config);
});
};
useEffect(() => {
fetchPosts();
}, []); // }, [fetchPosts]); <--- working well with loop
return (
<section className="container-post">
<PostMansonry posts={posts} columns={3} />
</section>
);
};
export default PostsGetUtill;
Sort and map records
export default function PostMansonry({ posts, columns }) {
return (
<section className="masonry" style={{ gridTemplateColumns: `repeat(${columns}, minmax(275px, 1fr))` }}>
{posts.sort((a, b) => a.zonedDateTime < b.zonedDateTime ? 1 : -1).map((posts, index) =>
<MasonryPost {...{ posts, index, key: index }} />)
}
</section>
)
}
Put data to the card
export default function MasonryPost({ posts, index }) {
return (
<div key={index} className="masonry-post">
<div className="card">
<div className="card-body">
<h5 className="card-title">{posts.title}</h5>
<p className="card-text">{posts.description}</p>
<p className="card-text"><small className="text-muted"> {posts.zonedDateTime}</small></p>
<div><button type="button" onClick={(e) => PostsDeleteUtill(posts.post_Id)} className="btn btn-danger">Delete</button></div>
</div>
</div>
</div>
)
}
Deleting
const PostsDeleteUtill = async (post_Id) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
console.log(response);
}).catch((error) => {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log('error config', error.config);
});
};
export default PostsDeleteUtill;
Basically what you need to do is, in your PostsDeleteUtill function, in the promise return of your axios.delete, you need to update your posts state, which is set in PostsGetUtill.
In order to do that, you have 2 options:
Use a global state (React Context, Redux, etc)
Pass your setPosts handle all the way to your PostsDeleteUtill
I think option 1 is a bit cleaner for your specific case, but if you don't need global state anywhere else in your project, maybe it is fine to have a not so clean solution instead of implementing the whole global state structure for only one thing.
Option 1 pseudo code:
Your PostsGetUtill component would use a global state instead of local state:
const PostsGetUtill = () => {
// Remove this:
// const [posts, setPosts] = useState([]);
const fetchPosts = () => {
axios.get("api/v1.0/post/get").then(response => {
console.log(response.data);
// Instead of a local "setPosts" you would have a global
// "setPosts" (in Redux, this would be a dispatch)
dispatch({type: "PUT_POSTS", action: response.data})
}).catch(function (error) {
// No changes here...
});
};
// This runs only the first time you load this component
useEffect(() => {
fetchPosts();
}, []);
// Use your global state here as well:
return (
<section className="container-post">
<PostMansonry posts={globalState.posts} columns={3} />
</section>
);
};
export default PostsGetUtill;
In your PostsDeleteUtill function:
const PostsDeleteUtill = async (post_Id) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
// Update global state here. Probably filter the data to remove
// the deleted record
const updatedPosts = globalState.posts.filter(post => post.id !== response.data.id)
}).catch((error) => {
// No changes here
});
};
export default PostsDeleteUtill;
Option 2 pseudo code:
In your PostsGetUtill component, create and pass on a handleRemovePost:
// Your existing code ...
const handleRemovePost = (postID) => {
const filteredPosts = posts.filter(post => post.id !=== postID)
setPosts(filteredPosts)
}
return (
<section className="container-post">
<PostMansonry posts={posts} columns={3} handleRemovePost={handleRemovePost} />
</section>
);
In your PostMansonry, pass on again your handleRemovePost
export default function PostMansonry({ posts, columns, handleRemovePost }) {
return (
// Your existing code ...
<MasonryPost {...{ posts, index, key: index, handleRemovePost }} />)
)
}
Again in your MasonryPost
export default function MasonryPost({ posts, index, handleRemovePost }) {
return (
// Your existing code ...
<button type="button" onClick={(e) => PostsDeleteUtill(posts.post_Id, handleRemovePost)} className="btn btn-danger">Delete</button>
)
}
And finally:
const PostsDeleteUtill = async (post_Id, handleRemovePost) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
handleRemovePost(response);
})
};
PS: Please note that I only added a pseudo-code as a reference, trying to point out specific parts of the code that needs to be updated. If you need more information about global state, you can check React Context and Redux

Modifying object inside array with useContext

I've been having trouble using React's useContext hook. I'm trying to update a state I got from my context, but I can't figure out how. I manage to change the object's property value I wanted to but I end up adding another object everytime I run this function. This is some of my code:
A method inside my "CartItem" component.
const addToQuantity = () => {
cartValue.forEach((item) => {
let boolean = Object.values(item).includes(props.name);
console.log(boolean);
if (boolean) {
setCartValue((currentState) => [...currentState, item.quantity++])
} else {
return null;
}
});
};
The "Cart Component" which renders the "CartItem"
const { cart, catalogue } = useContext(ShoppingContext);
const [catalogueValue] = catalogue;
const [cartValue, setCartValue] = cart;
const quantiFyCartItems = () => {
let arr = catalogueValue.map((item) => item.name);
let resultArr = [];
arr.forEach((item) => {
resultArr.push(
cartValue.filter((element) => item === element.name).length
);
});
return resultArr;
};
return (
<div>
{cartValue.map((item, idx) => (
<div key={idx}>
<CartItem
name={item.name}
price={item.price}
quantity={item.quantity}
id={item.id}
/>
<button onClick={quantiFyCartItems}>test</button>
</div>
))}
</div>
);
};
So how do I preserve the previous objects from my cartValue array and still modify a single property value inside an object in such an array?
edit: Here's the ShoppingContext component!
import React, { useState, createContext, useEffect } from "react";
import axios from "axios";
export const ShoppingContext = createContext();
const PRODUCTS_ENDPOINT =
"https://shielded-wildwood-82973.herokuapp.com/products.json";
const VOUCHER_ENDPOINT =
"https://shielded-wildwood-82973.herokuapp.com/vouchers.json";
export const ShoppingProvider = (props) => {
const [catalogue, setCatalogue] = useState([]);
const [cart, setCart] = useState([]);
const [vouchers, setVouchers] = useState([]);
useEffect(() => {
getCatalogueFromApi();
getVoucherFromApi();
}, []);
const getCatalogueFromApi = () => {
axios
.get(PRODUCTS_ENDPOINT)
.then((response) => setCatalogue(response.data.products))
.catch((error) => console.log(error));
};
const getVoucherFromApi = () => {
axios
.get(VOUCHER_ENDPOINT)
.then((response) => setVouchers(response.data.vouchers))
.catch((error) => console.log(error));
};
return (
<ShoppingContext.Provider
value={{
catalogue: [catalogue, setCatalogue],
cart: [cart, setCart],
vouchers: [vouchers, setVouchers],
}}
>
{props.children}
</ShoppingContext.Provider>
);
};
edit2: Thanks to Diesel's suggestion on using map, I came up with this code which is doing the trick!
const newCartValue = cartValue.map((item) => {
const boolean = Object.values(item).includes(props.name);
if (boolean && item.quantity < item.available) {
item.quantity++;
}
return item;
});
removeFromStock();
setCartValue(() => [...newCartValue]);
};```
I'm assuming that you have access to both the value and the ability to set state here:
const addToQuantity = () => {
cartValue.forEach((item) => {
let boolean = Object.values(item).includes(props.name);
console.log(boolean);
if (boolean) {
setCartValue((currentState) => [...currentState, item.quantity++])
} else {
return null;
}
});
};
Now... if you do [...currentState, item.quantity++] you will always add a new item. You're not changing anything. You're also running setCartValue on each item, which isn't necessary. I'm not sure how many can change, but it looks like you want to change values. This is what map is great for.
const addToQuantity = () => {
setCartValue((previousCartValue) => {
const newCartValue = previousCartValue.map((item) => {
const boolean = Object.values(item).includes(props.name);
console.log(boolean);
if (boolean) {
return item.quantity++;
} else {
return null;
}
});
return newCartValue;
});
};
You take all your values, do the modification you want, then you can set that as the new state. Plus it makes a new array, which is nice, as it doesn't mutate your data.
Also, if you know only one item will ever match your criteria, consider the .findIndex method as it short circuits when it finds something (it will stop there), then modify that index.

How to pass react hook state to another component for each click

I stuck in this moment creating store with different products, that I want to add to the basket. The problem occur when I wanted to pass the state of cardList into Basket component to change the information from "Basket is empty" to display information how many items are currently in basket.
Below I paste my main hooks component with basket component which include all functionality.
Basket component:
import React from 'react'
const Basket = (props) => {
return (
<div>
{props.cardItems.length === 0 ? "Basket is empty" : <div> You have {props.cardItems.length} products in basket!</div>}
</div>
)
}
export default Basket;
Main component:
function
const [cardItems, setCardItems] = useState([]);
const price = 2.50;
useEffect(() => {
fetch(URL, {
method: 'GET',
headers: {
Accept: "application/json",
}
}).then(res => res.json())
.then(json => (setBeers(json), setFilteredBeers(json))
);
}, [])
function handleMatches(toMatch) {...
}
const displayFilterBeers = event => {...
}
const handleRemoveCard = () => {...
}
const handleAddToBasket = (event, beer) => {
setCardItems(state => {
let beerAlreadyInBasket = false;
cardItems.forEach(item => {
if (item.id === beer.id) {
beerAlreadyInBasket = true;
item.count++;
};
});
if (!beerAlreadyInBasket) {
cardItems.push({ ...beer, count: 1 })
}
localStorage.setItem('baketItems', JSON.stringify(cardItems));
console.log('cardItems: ', cardItems, cardItems.length);
return cardItems;
})
}
return (
<div className="App">
<div className='search'>
<input type='text' placeholder='search beer...' onChange={displayFilterBeers} />
</div>
<BeerList BeersList={filteredBeers} price={price} handleAddToBasket={handleAddToBasket} />
<Basket cardItems={cardItems}/>
</div>
);
}
export default App;
I saw an example that without React hooks that in basket component someone used const {cartItems} = this.props; but I don't know how to achieve something similar using hooks.
I think what you are facing is related to this issue.
So when dealing with array or list as state, react doesn't re-render if you don't set state value to a new instance. It assumes from the high-level comparison that the state hasn't been changed. So it bails out from the re-rendering.
from the issue I found this solution is better than the others -
const handleAddToBasket = (event, beer) => {
const nextState = [...cardItems, beer] // this will create a new array, thus will ensure a re-render
// do other stuffs
setCardItems(nextState);
};

Resources