react router, listen to push requests - next.js

I am working on a webs site which uses framer motion and next.js, the problem is that I need to remove a class that allow animations to happen before the path changes.
At first i tried
import {useRouter} from 'next/router'
export default function Projects(){
const [animated,setAnimated] = useState(true)
const {asPath} = useRouter()
useEffect(() => {
if(asPath!== '/projects'){
setAnimated(false)
}else{
setAnimated(true)
}
},[asPath])
return(
<div className={styles.activeLayer}>
<Image layout="fill" height='100%'src={allProjects[currentDisplay].backgroundImg} alt=""/>
<div className={`${styles.descriptionBox} + ${(**animated** ? styles.animatedOne:'')}`}>
<h2>
SomeTitle
</h2>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Modi excepturi esse ex odit saepe ipsam facere beatae similique doloremque quo quidem tempore quas molestias, commodi corporis natus possimus atque voluptatem?</p>
<div className={`${styles.iconComponent} + ${(animated ? styles.animatedFive:'')}`}>
<Tool imgArray={allProjects[currentDisplay].tools} />
</div>
this doesn't work cause animations run for a millisecond before it changes, and it actually needs to listen to the push request from a <Link>, but I don't know how to do it.
,

You can swap it. Set the state to default as false.
import {useRouter} from 'next/router'
const [animated,setAnimated] = useState(false)
const {asPath} = useRouter()
useEffect(() => {
if(asPath !== '/projects'){
setAnimated(false)
}else{
setAnimated(true)
}
},[asPath])

Instead of messing with some possibly incorrect initial state and using the useEffect hook to update it after the initial render, just set the correct initial state.
Example:
import { useRouter } from 'next/router';
export default function Projects() {
const {asPath} = useRouter();
const animated = asPath === '/projects';
return (
<div className={styles.activeLayer}>
<Image
layout="fill"
height='100%'
src={allProjects[currentDisplay].backgroundImg}
alt=""
/>
<div
className={`${styles.descriptionBox} + ${animated
? styles.animatedOne : ''}`
}
>
<h2>SomeTitle</h2>
<p>....</p>
<div
className={`${styles.iconComponent} + ${animated
? styles.animatedFive : ''}`
}
>
<Tool imgArray={allProjects[currentDisplay].tools} />
</div>
</div>
</div>
);
}
animated is what could be considered derived state and it's often considered a React anti-pattern to store derived state in React state. Just use the asPath === '/projects' value directly.
Example:
import { useRouter } from 'next/router';
export default function Projects() {
const {asPath} = useRouter();
return (
<div className={styles.activeLayer}>
<Image
layout="fill"
height='100%'
src={allProjects[currentDisplay].backgroundImg}
alt=""
/>
<div
className={`${styles.descriptionBox} + ${animated
? styles.animatedOne : ''}`
}
>
<h2>SomeTitle</h2>
<p>.....</p>
<div
className={`${styles.iconComponent} + ${animated
? styles.animatedFive : ''}`
}
>
<Tool imgArray={allProjects[currentDisplay].tools} />
</div>
</div>
</div>
);
}

Related

How to get slots's html content as string in vue3?

I want to pass slot's content into a wrap components which one of it's prop will accept html string and render it, here's the code
Parent.vue
<Ellipsis style="max-width: 240px"
>Genes that helped people survive during the time of the Black Death are
more likely to be found in people with autoimmune diseases alive today.
Does this tell scientists anything about what surviving the COVID-19
pandemic might mean for the world’s population?
<template #tooltip>
<div style="text-align: center">
<i>Lorem Ipsum</i><br />
Sed ut perspiciatis unde omnis<br />
iste natus error sit voluptatem accusantium doloremque
laudantium,<br />
totam rem aperiam
</div>
</template>
</Ellipsis>
Ellipsis.vue
<template>
<Tooltip
ref="tooltipRef"
:content="tooltipContent"
>
<span
ref="content"
:class="ellipsisClassRef.valueOf()"
:style="ellipsisStyleRef"
#click="handleClickRef"
>
<slot></slot>
</span>
</Tooltip>
</template>
setup(props, { slots }) {
onMounted(() => {
if (slots.default !== undefined) {
tooltipContent.value = slots.default()[0].children;
}
if (slots.tooltip !== undefined) {
// have to get the html content like below
tooltipContent.value = `<div style="color: blue">hello world</div>`;
}
});
}
So the main problem is how to convert the slot's content( html content) I got from parent, and turn it into the string and pass it to the wrap content. When I console.log(slots.tooltip), I know I can get the html content like a nested object like following,
0
:
{__v_isVNode: true, __v_skip: true, type: 'div', props: {…}, key: null, …}
length
:
1
[[Prototype]]
:
Array(0)
at
:
ƒ at()
length
:
1
name
:
"at"
arguments
:
(...)
caller
:
(...)
[[Prototype]]
:
ƒ ()
concat
:
ƒ concat()
constructor
:
ƒ Array()
copyWithin
:
ƒ copyWithin(
but there a lot of things to cover if I want to concat them into a string, so I wonder if there's a way to just get the html content as a string or like a helper function to call to get format like <div style="color:blue">Hello world</div>.
You can use v-html directive to return Raw HTML.
<span v-html="rawHtml"></span>
The other option could be Fallback Content in the slot, if the fallback content is static.
<button type="submit">
<slot>
Submit <!-- fallback content -->
</slot>
</button>
But if the slot content is dynamic, then you should better use the Render Functions
h('div', { style: { color: 'blue' }, innerHTML: 'hello world' })
Check my other answer here for details:
Adding custom HTML element inside template using functions Vue.js
Example
const { createApp, h } = Vue;
const myComponent = {
setup(props) {
return () => h('div', { style: { color: 'blue' }, innerHTML: 'hello world' })
}
}
const App = {
components: {
myComponent
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
<my-component></my-component><br/>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>

Next.js 13 Updating SSR Component state after action

I am learning Nextjs for the first time, and have experimented by creating a small web app that allows someone to simply register then login. Everything works, except one small issue.
On the page, you can see your status. If you are logged in, it shows your username. If not, it provides a link to the sign in page.
This "Sign In" button is a part of the UserInfoBar component, which is a SSR-component.
The problem is, that when you successfully login, and are redirected to the homepage with router.push('/'), the "Sign In" link is not replaced with your username.
It is only when you refresh the page that your username will show.
I am really at a loss here. The only solution that I can think of is to make UserInfoBar a client component, but then I would have to manually retrieve the session with a fetch call.
Is there not a way to address this from the server side?
src/app/components/UserInfoBar.tsx
import React from 'react'
import Link from 'next/link'
import { getSession } from '../../lib/session'
export default async function UserInfoBar() {
let sessionData = getSession();
console.log('on the server')
console.log(sessionData)
return (
<div className="flex-grow text-right px-2 py-1 text-white">
{sessionData ?
sessionData.username :
<Link href="/signin" className='px-2 py-1 text-white '>Sign In</Link>}
</div>
)
}
src/app/Header.tsx
import React from 'react'
import Link from 'next/link'
import { Suspense } from 'react';
import UserInfoBar from './components/UserInfoBar'
export default function Header() {
return (
<header className='flex justify-center bg-slate-700 mb-7'>
<div className='flex max-width space-x-4 p-4'>
<Link href="/" className='px-2 py-1 text-white '>Home</Link>
<Link href="/decks" className='px-2 py-1 text-white '>Decks</Link>
<Link href="/signup" className='px-2 py-1 text-white '>About</Link>
<Suspense fallback={<p>loading...</p>}>
<UserInfoBar />
</Suspense>
</div>
</header>
)
}
src/app/page.tsx
import React from 'react'
export default function Page() {
return (
<div>
<h2 className="text-4xl font-extrabold text-slate-700">Demo</h2>
<div>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam faucibus erat a gravida ultricies.
</div>
)
}
src/app/lib/session.ts
import { cookies } from 'next/headers';
import jwt from 'jsonwebtoken'
export const getSession = () => {
const nxtCookies = cookies();
if (nxtCookies.has('session')) {
const cookie = nxtCookies.get('session');
let sessionData = jwt.verify(cookie.value, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if (err) return false;
return user;
});
if (sessionData) return sessionData;
}
return false;
}
While re-reading the Next.js docs, I missed this crucial part:
Invalidating the Cache: The cache can be invalidated using
router.refresh(). For more information, see the API reference. In the
future, mutations will automatically invalidate the cache.
Simply adding router.refresh() before my router.push() call fixed the problem.

Unable to get background images rendering in NextJS React components [solved but unexplained]

I'm struggling to render a background image on an element in NextJs using Typescript and Tailwind. I don't think it's a typescript issue as I get no errors or IntelliSense warnings. I have the following code:
var classnames = require('classnames')
import Image from 'next/image'
interface LandingPageSection
{
imagedata: imagedata,
children?: React.ReactNode
}
interface imagedata
{
src: string,
width: number,
height: number,
alt: string
}
export default function Section({ imagedata }: LandingPageSection)
{
// var styleatt={backgroundImage: `url(${imagedata.src})`}
return (
<div className={classnames("shadow bg-white border border-black")}>
<div className={classnames(`bg-size bg-cover bg-center`)} style={{backgroundImage: `url(${imagedata.src})`}}>
<Image src={imagedata.src} width={150} height={150} alt={imagedata.alt}>
</Image>
<div className={classnames("p-4 h-32 flex items-end text-black")}>
<h3 className={classnames("mb-2")}>Card Title</h3>
</div>
</div>
<div className={classnames("p-4")}>
<p className={classnames("text-grey-600 text-sm)")}>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo
ligula eget dolor.
Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus
mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. </p>
<div className={classnames("mt-4")}>
<a href="#" className={classnames("no-underline mr-4 text-blue-500 hover:text-blue-400")}>Link 1</a>
</div>
</div>
</div>
)
Chrome dev tools tells me ==>
element.style {
background-image: url(./images/Landing page images/Top_banner/Top_banner_face_leaves14690559.jpeg);
}
on hover ==> 'invalid property value'.
Why is it invalid and how can I make it valid? Some people say you have to import the image up top but I don't know why that would be necessary and I can't do that because it has to vary based on props.
I had found a tailwind template from this webpage that I want to adapt to Next and TypeScript and I foresee the need for background images as I continue the project
I understand why that template site uses inline styles, as it's the most concise and complete way to describe what you need to do, but those in the react community online and the educational websites all seem to be fond of inline styling for this particular goal, but inline styling in jsx/tsx or elsewhere is something I want to avoid as much as possible. I'd rather be using classes and frameworks, and external style sheets if I must, but it got me thinking that inline styling might be required in order to have dynamic images that vary based on props. How else would they be able to vary? You can't can't pass a path into a stylesheet dynamically, so this might be the only way to do it, I suppose. But that still doesn't explain why although my code works, this property doesn't render, and there's very little documentation on the subject (which is kinda surprising).
Thanks!
EDIT:
It was the whitespaces. Credit to aegatlin for pointing this out.
Nevertheless it breaks even though I'm passing in a string as below and the string syntax loads the icon image fine. Something is different, whether it's due to the fact of inline styling or the passing of props, and I'm curious as to what, but the fix is easy enough. Rename the folders to not have spaces.
import Head from 'next/head'
import Infobar from '../components/Infobar'
import Section from '../components/Section'
import styles from '../styles/LandingPage.module.scss'
var classnames = require('classnames')
export default function LandingPage()
{
return (
<div className={styles.container}>
<Head>
<title> Acme Widgets| FREE TRIAL</title>
<link rel="icon" href="/images/Landing page images/Logo/Logo_D logo.png" />
</Head>
<main className={classnames(`${styles.w99}`)}>
<Infobar>
</Infobar>
{/* top banner */}
<Section imagedata={{src:"/images/Landing page images/Top_banner/Top_banner_face_leaves14690559.jpeg", height:50, width:50, alt:"woman's face"}}>
</Section>
{/* second banner */}
<Section imagedata={{src:"/images/Landing page images/Second_banner/cerreal2_large_1545151190.png", height:50, width:50, alt:"bowl of cereal with fruit"}}>
</Section>
{/* etc */}
</main>
</div>
)
}
Here's where I'm passing the string in from. is right, there are
Most likely your imagedata.src string value is invalid. Can you provide an example string?
The src value needs to be a valid URL. It is a static file that would need to be served from either your backend or served from somewhere else online.
If you are using a string like the one you have displayed in your question with the strikethrough text, then that is an invalid URL because of the spaces in the file path.
Lastly, in NextJS, make sure you are serving static files, like images, in the /public directory.

How to use search in react js and get the result in particular div?

while searching, the results should appear as a div like below :
i use jquery to search in table,how to get the result like above.
my component code is:
<div id="modaldash" style={{ display: (searching ? 'block' : 'none') }}>
<p className="font-weight-medium" id="name"> <img id="logo" className="logo" src={jessica} alt="pam-logo" /> Jessica James </p>
<button id="Addlist" onClick={this.onSubmitdata} className="btn info">{this.state.shown ? "Addded" : "Add to list"}</button>
<p id="mailid">jessicajames#gmail.com </p>
<p id= "address">Mountain view,Ave</p>
</div>
its just a static content for css. how to use search and get results like above.
export default function App() {
// considering the data object to search on name
const [searchedData, setSearchedData] = useState([]);
const users = [
{
name: "abc1",
emailid: "abc1#gmail.com",
address: "23rd main, 2nd street"
},
{
name: "adb2",
emailid: "abc2#gmail.com",
address: "23rd main, 2nd street"
},
{
name: "adc3",
emailid: "abc3#gmail.com",
address: "23rd main, 2nd street"
}
];
const handleSearch = event => {
const data = users.filter(
user => user.name.indexOf(event.target.value) !== -1
);
setSearchedData(data);
};
const showSearchedData = () => {
return searchedData.map(user => (
<div key={user.emailid}>
<p className="font-weight-medium" id="name">
{" "}
<img id="logo" className="logo" src="" alt="pam-logo" />
{user.name}
</p>
<button id="Addlist"> Added/ add to list</button>
<p id="mailid">{user.emailid} </p>
<p id="address">{user.address}</p>
</div>
));
};
return (
<div className="App">
<input type="text" onChange={handleSearch} />
<div id="modaldash">{searchedData.length > 0 && showSearchedData()}</div>
</div>
);
}
You can add CSS to make a look and feel like shown in image attached.
Check the working example here https://codesandbox.io/s/falling-sun-r3rim

Add a popup in Drupal 8 admin

I want to create a popup that display example of possible usable tag in a textarea, I need it to be displayed on every content edition page on the admin.
What do I need to do to have that kind of block in the admin content edition page ?
Thanks.
I would create a module with:
A twig file containing the contents of the dialog.
A small JS file to show the dialog, using the jquery.dialog library included in Drupal 8.
Implement a hook_from_alter to attach the changes based on the form_ids.
Like the following, which works on node edit forms, but is not thoroughly tested:
file: templates/popuptag.html.twig
<div style="display: none;">
<div id="popuptag-dialog" title="Tag Usage">
<p>The tag can be used at vero eos et accusamus et iusto odio
dignissimos ducimus qui blanditiis praesentium voluptatum
deleniti atque corrupti quos dolores et quas molestias
excepturi sint occaecati cupiditate non provident, similique sunt.</p>
</div>
</div>
file: js/popuptag.dialog.js
(function ($) {
'use strict';
Drupal.behaviors.popuptag_dialog = {
attach: function (context) {
$( "#popuptag-dialog" ).dialog();
}
};
})(jQuery);
file: popuptag.module
/**
* Implements hook_theme().
*/
function popuptag_theme() {
return [
'popuptag' => [
'template' => 'popuptag',
'render element' => 'dialog',
],
];
}
/**
* Implements hook_form_alter().
*/
function popuptag_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
// Add here other form_ids if needed
if (in_array($form_id, ['node_page_edit_form'])) {
$form['popuptag'] = [
'#theme' => 'popuptag',
'#form_id' => $form_id,
];
$form["#attached"]["library"][] = 'popuptag/dialog';
}
}
file: popuptag.libraries.yml
dialog:
js:
js/popuptag.dialog.js: { minified: true }
dependencies:
- core/drupal.dialog
file: popuptag.info.yml
name: 'popuptag'
type: module
description: 'popuptag'
core: 8.x
package: 'Custom'

Resources