How do I conditionally render elements based off of path react.js - reactjs

So I'm creating a blog app with react.js and ruby on rails. I have these buttons that are in my nav component that I need to conditionally render based off of the path the user is in. I'm using useLocation to accomplish this and almost have it where I want it. The problem I'm having is getting them to render in the three main paths where posts can be seen ('/general', '/resources', & '/events') while hiding them when a user goes into a post to view the comments. The buttons will show up in those paths if I remove the /path/id but as I stated I need them not to render in the /path/id just the /path. How can I accomplish this?
const [getAllPosts, setGetAllPosts] = useState([])
useEffect(() => {
const fetchData = async () => {
const res = await fetchAllPosts()
setGetAllPosts(res.filter(post => {
return post.category.title === 'general' || post.category.title === 'resources' || post.category.title === 'events'
}))
}
fetchData()
}, [])
return (
{getAllPosts.forEach((generalPost, resourcePost, eventsPost) => {return location.pathname === '/user' || location.pathname === '/about' || location.pathname === '/create' || location.pathname === `/general/${generalPost.id}` || location.pathname === `/general/${resourcePost.id}` || location.pathname === `/general/${eventsPost.id}` ? (<></>) : (<div className={open ? 'RegisterContainerMobileOpen' : 'RegisterContainerMobile'}>
{currentUser ? (
<button onClick={handleLogout} className='logoutmobile'>Logout</button>
) : (
<Link to='/user' className='resgisterlinkmobile'>
<button className='registermobile'>Signin/Signup</button>
</Link>
)}
</div>)})})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
If you need to see the rest of my code just let me know. I've been working day and night on this with little sleep so I feel I am missing something simple.
Edit: I've also tried using .map() but get back multiple instances of the same button since map returns a new array. I've looked into React.memo in my research, but I'm not fully sure how I'd use that or even if it would be a fix to the mapping issue
Note: after reading more documentation on React.memo it does not seem like that would help

Without going too deeply into the task, try to add return and instead of forEach use map. Remember that forEach returns nothing, unlike map.
So it could look like this:
useEffect(() => {
...
}
return getAllPosts.map(...

Related

I want only one component state to be true between multiple components

I am calling components as folloews
{userAddresses.map((useraddress, index) => {
return (
<div key={index}>
<Address useraddress={useraddress} />
</div>
);
})}
Their state:
const [showEditAddress, setShowEditAddress] = useState(false);
and this is how I am handling their states
const switchEditAddress = () => {
if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
}
};
Well, it's better if you want to toggle between true and false to use the state inside useEffect hook in react.
useEffect will render the component every time and will get into your condition to set the state true or false.
In your case, you can try the following:
useEffect(() => { if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
} }, [showEditAddress])
By using useEffect you will be able to reset the boolean as your condition.
Also find the link below to react more about useEffect.
https://reactjs.org/docs/hooks-effect.html
It would be best in my opinion to keep your point of truth in the parent component and you need to figure out what the point of truth should be. If you only want one component to be editing at a time then I would just identify the address you want to edit in the parent component and go from there. It would be best if you gave each address a unique id but you can use the index as well. You could do something like the following:
UserAddress Component
const UserAddress = ({index, editIndex, setEditIndex, userAddress}) => {
return(
<div>
{userAddress}
<button onClick={() => setEditIndex(index)}>Edit</button>
{editIndex === index && <div style={{color: 'green'}}>Your editing {userAddress}</div>}
</div>
)
}
Parent Component
const UserAddresses = () => {
const addresses = ['120 n 10th st', '650 s 41 st', '4456 Birch ave']
const [editIndex, setEditIndex] = useState(null)
return userAddresses.map((userAddress, index) => <UserAddress key={index} index={index} editIndex={editIndex} setEditIndex={setEditIndex} userAddress={userAddress}/>;
}
Since you didn't post the actual components I can only give you example components but this should give you an idea of how to achieve what you want.

Next.js refresh values without reloading page in props list

probably I got a little bit lost on the following task. I have an admin page in my application where you can see all the posts from the plattform. I'm requesting the posts from an api and Im displaying them on the page as a list. I Inserted two buttons to enable/disable the post by calling a function which does tag the post to be enabled/disabled.
Everything works fine, but I want to change the state of the button without reloading the page. Im passing disable parameter threw the button tag. I don't know why its not working, if I console.log the values its already changed there. Is this a proper way to use useeffect? I tried to use it but I failed using it correct.
Somebody can help please?
Simplified Code ( I removed the enable function, since its nearly the same like disable)
export default function Feed(props) {
const [postStatus, setPostStatus] = useState(props.posts)
async function disablePost(id, e){
e.preventDefault();
const userInput = { postId: id }
try{
const res = await axios.post(`${baseurl}/api/auth/admin/disable-post`, userInput, {
})
var currentPostStatus = postStatus;
currentPostStatus.map((el) => {
if(el.id === id){
el.disabled = true;
}
console.log(el.id)
});
setPostStatus(currentPostStatus)
console.log(postStatus)
}catch(error){
console.log(error)
return
}
}
return (
<>
<HeaderAdmin></HeaderAdmin>
<div>
{/* {console.log(props.userCount)} */}
<p>Alle Posts</p>
{postStatus.map((post) =>
<React.Fragment key={post.id}>
<p>{post.id}</p>
<p>{post.email}</p>
<p>{post.desc}</p>
<p>{post.disabled == true ? 'Post deaktviert' : 'Post aktiviert'}</p>
<button disabled={ post.disabled } onClick={(e) => disablePost(post.id, e)}>Post deaktivieren</button>
<button disabled={ !post.disabled } onClick={(e) => enablePost(post.id, e)}>Post aktivieren</button>
</React.Fragment>
)}
</div>
</>
);
}
Your screen can't refresh to the new version after you clicked the disable or enable.
var currentPostStatus = postStatus;
currentPostStatus.map((el) => {
if(el.id === id){
el.disabled = true;
}
});
In your code, you are only changing the internal property of postStatus, but React only cares about the reference of the object. so you need to do
setPostStatus([...currentPostStatus])
The above line create a new array. I personally believe this is something React should improve in the future, because half of the question about React in stackoverflow is talking about this :)

useEffect not applying classNames

I'm currently working on a project using Next JS, where I seem to have encountered an issue with React. Here is the simplified version of my code. I did try to replicate the issue in codesandbox but I couldn't. I'll keep trying though and if I can, I'll update this post with the link.
const Nav = React.forwardRef<
HTMLDivElement,
{ className?: string; disableAnimation?: boolean }
>((props, ref) => {
const navWrapperRef = useRef<HTMLDivElement>(null);
const navItemsRef = useRef<HTMLSpanElement[]>([]);
useEffect(() => {
const path = window.location.pathname;
if (path === "/") navItemsRef.current[0].classList.add("nav-active");
else if (path.includes("/packages"))
navItemsRef.current[1].classList.add("nav-active");
else if (path.includes("/bhutan"))
navItemsRef.current[2].classList.add("nav-active");
}, []);
return (
<nav className={props.className || "nav"} ref={navWrapperRef}>
<div className="nav-container">
<ul className="nav-ul">
{navLinks.map((link) => (
<button
key={uniqueId(`nav-links-${new Date().getUTCDate()}`)}
data-name={link.name}
className="nav-links"
>
<span
ref={(el) => navItemsRef.current.push(el as HTMLSpanElement)}
className="nav-span"
>
<Link href={link.href}>{link.name}</Link>
</span>
</button>
))}
</ul>
</div>
</nav>
);
});
My objective here is to implement a navigation component without the use of states. I'd like to render out the current active navigation link on the initial page load using the empty dependency array as for the useEffect hook. But I can't seem to get it to work.
My desired output is the following on page load:
The output I get:
However, if I remove the dependency array altogether then all seems to work fine as expected. But if I am not wrong I think this causes performance issues as it re-renders each and every time if there are any other state changes. Any help would be greatly appreciated!
The contents of your useEffect hook will run once on mount and whenever its dependencies change.
As this is reliant on what you have defined as path, I'd move it out of the useEffect and add it as a dependency.
Update: you will have to use next/router's useRouter hook instead of the window directly when working with next.
Demo here.
const { asPath } = useRouter();
useEffect(() => {
if (asPath === "/") navItemsRef.current[0].classList.add("nav-active");
else if (asPath.includes("/packages"))
navItemsRef.current[1].classList.add("nav-active");
else if (asPath.includes("/bhutan"))
navItemsRef.current[2].classList.add("nav-active");
}, [asPath]);

React Navbar pathname doesn't render properly on page change?

So I'm trying to update my navbar background based on the page I am on, but when I console.log the window.location.pathname, it doesn't show the pathname properly.
Here is my function to change the navbar state based on the pathname
const [navbar, setNavbar] = useState(false)
const updateNavbar = () => {
if (window.location.pathname !== "/") {
setNavbar(true)
} else {
setNavbar(false)
}
console.log(window.location.pathname)
}
Here's my styled component
const Nav = styled.nav`
background: ${({ navbar }) => (navbar ? "red" : "blue")};
Then I pass this into my JSX
<Nav navbar={navbar}>
{menuData.map((item, index) => (
<NavLink to={item.link} key={index} onClick={updateNavbar}>
{item.title}
</NavLink>
))}
The issue is that if I click on my about menu item, in the console it shows
/
then if I click about again, then it shows
/about
So when I click about it changes the page visually on my screen, but the console logs /, so in order for the navbar to change colors, I have to click about again, which makes no sense. I pretty much have to click it twice to update the color.
Why doesn't the window.location.pathname automatically update to the page I am on instead of showing the previous link I clicked, then only after I click the link again it shows the correct path?
Also, I am using Gatsby JS and have AOS animations, but I don't think that matters.
Update: If I pass it into a useEffect hook, it shows / then /about, but why doesn't it only show me /about instead of showing both the previous and new link I clicked?
useEffect(() => {
updateNavbar()
}, [])
Your console print before your route is rendering!
So you can use:
setTimeout(function(){ console.log(window.location.pathname) }, 3000);
to print or get the path after your route change.
I suspect that code isn't working because you are setting your onClick function at the same time that it navigates to another page. What could work for you could be:
const updateNavbar = (destination) => {
if (typeof window !== 'undefined' && window.location.pathname !== "/") {
setNavbar(true)
navigate("destination")
} else if(typeof window !== 'undefined') {
setNavbar(false)
navigate("/")
}
console.log(window.location.pathname)
}
<NavLink key={index} onClick={updateNavbar(item.link)}>
{item.title}
</NavLink>
Basically, you are preventing the code to navigate to your page before it set the state. Changing the native to prop to navigate function.
Another workaround that may work for you is using the state from Gatsby <Link> component:
<Link
to={`/your/page`}
state={{ navbar: true }}
>
You can retrieve the value using: props.location.state.navbar.
I've added it to give you an approach. On the other hand, the code above will break in the build mode (gatsby build) since you are using window (a global object) and, since gatsby build occurs in the server, where there's no window, it will break the compilation. You should wrap every usage of window inside:
if (typeof window !== `undefined`) {
// your stuff
}
However, there's a more native approach. Since Gatsby extends from React (and from #react/router) it exposes at the top-level component (pages) a prop named location with all your desired information. Of course, you can pass downside from the page component to any atomized component to use it.
After some trials and errors we solved the issue by:
const updateNavbar = () => {
if (window.location.pathname !== "/") {
setNavbar(true)
} else {
setNavbar(false)
}
}
useEffect(() => {
if (window.location.pathname) {
setNavbar(window.location.pathname)
}
console.log(window.location.pathname)
}, [])
background: ${({ navbar }) => (navbar != "/" ? "#000" : "transparent")};

How to code a good re-render parent call?

render my parent component from a call in its child component. But I don't know how to write it correctly.
The context is: When the page is loaded, the code search the userId in localStorage to download the data of a user to display its information.
A link exists in child component to removed the idUser and got an empty form. The problem is that my parent don't re-render automatically.
<Link to="/consultation" onClick={() => {localStorage.removeItem('idUser'); this.props.callBack();}}>here.</Link>
-
else if(user){contents = [<Message_UserIdentified user callBack={this.forceUpdate()}/>, contentform];}
I tried something, but it's not work. Could you help me please ?
I don't know how to parse it (the item "user" and in same time the callBack).
The code write me that it don't find the function forceUpdate().
This is my parents page called "Consultation" (I removed some parts of code to be clearer):
import { Message_EmptyUserID, Message_NeedToBeConnected, Message_UserIdentified } from '../component/message_consultation';
const Consultation = () => {
const [user, setUser] = useState(null);
const [login] = useState(jwtUtils.checkToken());
const [userId, setUserId] = useState(false);
function handleChange(event) {
setUser({
...user,
[event.target.name]: event.target.value
})
};
useEffect(() => {
if (login === false || localStorage.getItem("idUser") === null) return;
axios.get(`http://localhost:5000/users/${localStorage.getItem("idUser")}`, {
headers: {
'token': localStorage.getItem('token')
}
})
.then(res => {
setUser(res.data);
if(res.data !== undefined){
setUserId(true);
}
})
.catch(error => console.log(error))
}, []);
let contents, contentform;
contentform = (
<div >...
</div>
);
if (login === false) contents = Message_NeedToBeConnected();
else if (userId === false) contents = contentform;
else if(user){contents = [<Message_UserIdentified user callBack={this.forceUpdate()}/>, contentform];}
return (
<div className="case card border-secondary mb-3" styles="max-width: 20rem;">
<div className="card-header">Consultation</div>
<div className="card-body">
{contents}
</div>
</div>
);
}
export default Consultation;
This is my child component called "Message_UserIndentified":
const Message_UserIdentified = (user) => {
return(
<Alert color="primary" className="alert alert-dismissible alert-info">
<h4>{user === null || user === undefined ? "" : user.firstname} {user === null || user === undefined ? "" : user.lastname}</h4>
If you are not {user === null || user === undefined ? "" : user.firstname} and you are <mark>already registered</mark>, find your consultation <Link to="/register" onClick={localStorage.removeItem('idUser')}>here.</Link> <hr/>
If you are not {user === null || user === undefined ? "" : user.firstname} and your are <mark>not registered</mark>, add your consultation <Link to="/consultation" onClick={() => {localStorage.removeItem('idUser'); this.props.callBack();}}>here.</Link>
</Alert>
);
}
First of all, you mix up the hooks API with the class API. You need to choose one, React doesn't allow to use both in a single component.
React functional components don't have this and this.forceUpdate. See the official note on how to force update in a functional component with hooks:
const Consultation = () => {
const [ignored, forceUpdate] = React.useReducer(x => x + 1, 0);
// ...
return <Message_UserIdentified user callBack={forceUpdate} />;
};
Second, you mustn't call the function when you want to pass it as a callback. When you run callBack="forceUpdate()", the function is executed once and the returned value is passed to the child prop. You need to run callBack="forceUpdate" to pass the function itself so that the child component can execute it whenever it wants.
This rule is applicable to class components too, but you should also bind the forceUpdate method to the current component:
<button onClick={() => this.forceUpdate()}>Demo</button>
// or
<button onClick={this.forceUpdate.bind(this)}>Demo</button>
From the architecture point of view, you shouldn't use localStorage to share data between components of the application because localStorage isn't reactive (it doesn't notify when a content changes) so you'll struggle updating all the components manually. You'd rather read'n'write localStorage only in Consultation or use a reactive state storage like Redux and MobX (in more complex applications). Local storage may be used at the same time: read on an application start and write when something changes.

Resources