I would like to set up a language selector at the component Header.jsx so that when clicking on the language switch, the content of MainPage.js would change to the selected language.
the files sturcture like this
project / public / locales/ en / translation.json
project/src/i18n.js
project/src/MainPage/mainPage.js
project/Resources/Component/Header.jsx
project/Resources/Component/Footer.jsx
{
"title": "Welcome to react using react-i18next",
"description": {
"part1": "To get started, edit <1>src/App.js</1> and save to reload.",
"part2": "Switch language between English and Chinese using buttons above."
},
"agreement": "Consent",
"agree": "Agree",
"disagree":"Disagree"
}
project / public / locales/ tw / translation.json
{
"title": "歡迎使用 react-i18next",
"description": {
"part1": "要開始,請編輯 src/App.js 並保存以重新加載。",
"part2": "使用上面的按鈕在英語和中文之間切換語言。"
},
"agreement": "同意書",
"agree": "我同意'",
"disagree":"我拒絕"
}
Here is the coding at MainPage.js
import React, { useState, useEffect, Component , Suspense, useCallback} from "react";
import Header from "../Resources/Components/Header";
import Footer from "../Resources/Components/Footer";
import { useTranslation, withTranslation, Trans } from 'react-i18next';
import {MainPageContext, useMainPageContext } from "../Resources/Hooks/mainPageContext";
import "./MainPage.css";
class LegacyWelcomeClass extends Component {
render() {
const { t } = this.props;
return <h2>{t('title')}</h2>;
}
}
const Welcome = withTranslation()(LegacyWelcomeClass);
const MainPage = () => {
const { t, i18n } = useTranslation();
const context = useMainPageContext();
const changeLanguage = useCallback((lng) => {
i18n.changeLanguage(lng);
}, [i18n]);
let language=[0,1];
const [introductionPage, setIntroductionPage] = useState(0);
console.log("language is", changeLanguage('en'))
//const [language, setLanguage]= useState(0);
//below is the script of the test description. 隨時可以加入新array做新language。
const renderLanguageSwitch = () => {
return [t('testStart'), t('testAgreement')];
};
const renderButtonSwitch = () => {
return [t('agree'), t('disagree')];
};
return (
<div className="MainPage">
<Trans i18nKey="title">
<h2>{t('title')}</h2>
</Trans>
<Header />
<div
style={{
width: "100%",
height: "100vh",
backgroundColor: "#F5821F",
margin: "0",
}}
>
{introductionPage === 0 && (
<button
className="testStartBox"
onClick={() => {
setIntroductionPage(1);
}}
>
t('agree'), t('disagree')
</button>
)}
{introductionPage !== 0 && (
<div>
<div
className="testDescriptionBox"
onClick={() => {
setIntroductionPage(introductionPage + 1);
}}
>
t('description')
</div>
<div className="testAgreement">
</div>
</div>
)}
<div
className="buttonWrapper"
style={{ display: introductionPage === 1 ? "" : "none" }}
>
<button onClick={() => {
setIntroductionPage(introductionPage + 1);
}}> t('description')</button>
<button onClick={() => {
setIntroductionPage(0);
}}>{t('agreement')}</button>
</div>
</div>{" "}
<Footer />
</div>
);
};
export default MainPage;
Here is the coding Header.jsx
import React , {useCallback}from "react";
import { useMainPageContext } from "../Hooks/mainPageContext";
import { useTranslation } from 'react-i18next';
import { Trans } from "react-i18next";
import i18n from '../../i18n'
export const LanguageContext = React.createContext();
const Header = () => {
const { t, i18n } = useTranslation();
const context = useMainPageContext();
const {
language,
onSetLanguage,
} = context;
const changeLanguage = useCallback((lng) => {
i18n.changeLanguage(lng);
}, [i18n]);
const toggle = lng => i18n.changeLanguage(lng);
if (this.props.event){
return (
<div className="header">
<h1
style={{
display: "flex",
flexFlow:"row",
alignItems:"center",
width: "calc(100% - 10%)",
height: "4vh",
backgroundColor: "white",
paddingTop:"0",
padding: "2.5%",
paddingLeft: "5%",
paddingRight: "5%",
justifyContent:"space-between"
}}
>
<div style={{display:"flex", color: "#F5821F",}}>
<img src={require("../Images/cmghLogo.png")} style={{width:"50%", height:"7.5%", marginTop:"0vh"}} alt="中心"/>
<div style={{ top: "0", margin: "0vh", marginLeft:"2vw", width:" 100%", fontSize:"3vw"}}>中心</div>
</div>
<div>
<header>
<button onClick={() => toggle(this.props.event)}>English/</button>
<button onClick={() => toggle(this.props.event)}>TW</button>
</header>
</div>
</h1>
</div>
);
}
}
export default Header;
Here is the coding of i18n.js
import { createInstance } from 'i18next';
import { initReactI18next } from 'react-i18next';
const i18n = createInstance({
fallbackLng: 'en',
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
resources: {
en: {
translation: {
hello: 'Hello from other i18n instance',
},
},
tw: {
translation: {
hello: 'Hallo aus einer anderen i18n Instanz',
},
},
},
});
i18n.use(initReactI18next).init();
export default i18n;
Related
I would like the wording used for the layout inside mainPage would change according to the language selected at component Header.jsx. However, change in Header.jsx could not pass to Header.jsx, therefore, noting is changed when clicking on the language selector.
import React, { useState, useEffect } from "react";
import Header from "../Resources/Components/Header";
import Footer from "../Resources/Components/Footer";
import {MainPageContext, useMainPageContext } from "../Resources/Hooks/mainPageContext";
import "./MainPage.css";
const MainPage = () => {
const context = useMainPageContext();
const {
language,
} = context;
useEffect(()=>{
console.log("is me hi!")
},[language])
const [introductionPage, setIntroductionPage] = useState(0);
console.log("language is", language)
//const [language, setLanguage]= useState(0);
//below is the script of the test description. 隨時可以加入新array做新language。
const renderLanguageSwitch= (language) => {
switch(language) {
case 0:
return ['測試開始','測試資料採集同意書'];
case 1:
return ['Test Start', 'Test Data Collection Agreement']
default:
return ['測試開始','測試資料採集同意書'];
}
};
const renderButtonSwitch= (language) => {
switch(language) {
case 0:
return ['我同意', '我拒絕'];
case 1:
return ['I agree', 'I disagree']
default:
return ['我同意', '我拒絕'];
}
};
return (
<div className="MainPage">
<Header />
<div
style={{
width: "100%",
height: "100vh",
backgroundColor: "#F5821F",
margin: "0",
}}
>
{introductionPage === 0 && (
<button
className="testStartBox"
onClick={() => {
setIntroductionPage(1);
}}
>
{renderLanguageSwitch(language)[0]}
</button>
)}
{introductionPage !== 0 && (
<div>
<div
className="testDescriptionBox"
onClick={() => {
setIntroductionPage(introductionPage + 1);
}}
>
{renderLanguageSwitch(language)[1]}
</div>
<div className="testAgreement">
</div>
</div>
)}
<div
className="buttonWrapper"
style={{ display: introductionPage === 1 ? "" : "none" }}
>
<button onClick={() => {
setIntroductionPage(introductionPage + 1);
}}> {renderButtonSwitch(language)[0]}</button>
<button onClick={() => {
setIntroductionPage(0);
}}>{renderButtonSwitch(language)[1]}</button>
</div>
</div>{" "}
<Footer />
</div>
);
};
export default MainPage;
There is a language selector on the component Header.jsx, I would like to change the language, then change the content of MainPage. However, it doesn't work.
import React from "react";
import { useMainPageContext } from "../Hooks/mainPageContext";
const Header = () => {
const context = useMainPageContext();
const {
language,
onSetLanguage,
} = context;
return (
<div className="header">
<h1
style={{
display: "flex",
flexFlow:"row",
alignItems:"center",
width: "calc(100% - 10%)",
height: "4vh",
}}
>
<div style={{display:"flex", color: "#F5821F",}}>
<img src={require("../Images/Logo.png")} style={{width:"50%", height:"7.5%", marginTop:"0vh"}} alt="image name"/>
<div style={{ top: "0", margin: "0vh", marginLeft:"2vw", width:" 100%", fontSize:"3vw"}}>中心</div>
</div>
<div><div style={{marginTop:"1vh", fontSize:"2vw"}} onClick={()=>{language===1? onSetLanguage(0):onSetLanguage(1);
}}>繁體/ English</div></div>
</h1>
</div>
);
};
export default Header;
I have changed the coding , so that it is a context variable passed to the mainPage.js, however, I have another problem, the missing transition key.
Here is my i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
i18n
.use(initReactI18next)
.init({
backend: {
loadPath: `/locales/{{lng}}/translation.json`,
parse: data => data,
},
lng: 'en',
fallbackLng: 'en',
debug: true,
resources: {
'en': {
translation: 'en',
},
'tw': {
translation: 'tw',
},
},
interpolation: {
escapeValue: false
}
});
export default i18n;
Here is the App.js
import React , { Component, Suspense , useState, useCallback} from 'react';
import { useTranslation, withTranslation, Trans ,I18nextProvider} from 'react-i18next';
import ReactDOM from "react-dom/client";
import{
createBrowserRouter, RouterProvider,BrowserRouter as Router
} from "react-router-dom";
import logo from './logo.svg';
import './App.css';
//Import the pages
import MainPage from "./MainPage/MainPage";
import A1SubjectPage1 from "./ASubject/A1SubjectPage1";
import B1ResearcherPage1 from "./BResearcher/B1ResearcherPage1";
import Header from './Resources/Components/Header';
// use hoc for class based components
class LegacyWelcomeClass extends Component {
render() {
const { t } = this.props;
return <h2>{t('title')}</h2>;
}
}
const Welcome = withTranslation()(LegacyWelcomeClass);
// Component using the Trans component
/*function MyComponent() {
return (
<Trans i18nKey="description.part1">
To get started, edit <code>src/App.js</code> and save to reload.
</Trans>
);
}
*/
// page uses the hook
function App() {
const { t, i18n } = useTranslation();
const [language, setLanguage] = useState('en');
const onSetLanguage = useCallback((lng) => {
setLanguage(lng);
}, []);
const router = createBrowserRouter([
{
path: "/",
element: <div><MainPage/></div>,
},
{
path: "/mainPage",
element: <div>wowkdjfkdow<MainPage /></div>,
},
{
path: "/A1SubjectPage1",
element: <div>puripuri<A1SubjectPage1 /></div>,
},
{
path: "/B1ResearcherPage1",
element: <div>ReRe<B1ResearcherPage1 /></div>,
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<I18nextProvider i18n={i18n}>
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
</I18nextProvider>
);
let routes;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> This is a project
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Hello World Again and Again
</a>
</header>
<Router>
<div className="App-intro">
</div>
<main>{routes}</main>
</Router>
</div>
);
}
export default App;
Here is the mainPage.js
import React, { useState, useEffect, Component , Suspense, useCallback} from "react";
import Header from "../Resources/Components/Header";
import Footer from "../Resources/Components/Footer";
import { useTranslation, withTranslation, Trans } from 'react-i18next';
import {MainPageContext, useMainPageContext } from "../Resources/Hooks/mainPageContext";
import "./MainPage.css";
class LegacyWelcomeClass extends Component {
render() {
const { t } = this.props;
return <h2>{t('title')}</h2>;
}
}
const Welcome = withTranslation()(LegacyWelcomeClass);
const MainPage = () => {
const { t, i18n } = useTranslation();
const context = useMainPageContext();
const [language, setLanguage] = useState('tw');
const onSetLanguage = useCallback((lng) => {
setLanguage(lng);
}, []);
const [introductionPage, setIntroductionPage] = useState(0);
//const [language, setLanguage]= useState(0);
//below is the script of the test description. 隨時可以加入新array做新language。
const renderLanguageSwitch = () => {
return [t('testStart'), t('testAgreement')];
};
const renderButtonSwitch = () => {
return [t('agree'), t('disagree')];
};
return (
<div className="MainPage">
<Trans i18nKey="title">
<h2>{t('title')}</h2>
</Trans>
<Header onSetLanguage={onSetLanguage} />
<div
style={{
width: "100%",
height: "100vh",
backgroundColor: "#F5821F",
margin: "0",
}}
>
{introductionPage === 0 && (
<button
className="testStartBox"
onClick={() => {
setIntroductionPage(1);
}}
>
{t('agree')}, {t('disagree')}
</button>
)}
{introductionPage !== 0 && (
<div>
<div
className="testDescriptionBox"
onClick={() => {
setIntroductionPage(introductionPage + 1);
}}
>
{t('description')}
</div>
<div className="testAgreement">
</div>
</div>
)}
<div
className="buttonWrapper"
style={{ display: introductionPage === 1 ? "" : "none" }}
>
<button onClick={() => {
setIntroductionPage(introductionPage + 1);
}}> {t('description')}</button>
<button onClick={() => {
setIntroductionPage(0);
}}>{t('agreement')}</button>
</div>
</div>{" "}
<Footer />
</div>
);
};
export default MainPage;
Here is the Header.js
import React, { useCallback, useContext } from "react";
import { useMainPageContext } from "../Hooks/mainPageContext";
import { useTranslation } from 'react-i18next';
const Header = () => {
const { t, i18n } = useTranslation();
const context = useMainPageContext();
const { onSetLanguage } = context;
const toggleLanguage = useCallback((lng) => {
i18n.changeLanguage(lng);
onSetLanguage(lng);
}, [i18n, onSetLanguage]);
const currentLanguage = i18n.language;
return (
<div className="header">
<h1
style={{
display: "flex",
flexFlow:"row",
alignItems:"center",
width: "calc(100% - 10%)",
height: "4vh",
backgroundColor: "white",
paddingTop:"0",
padding: "2.5%",
paddingLeft: "5%",
paddingRight: "5%",
justifyContent:"space-between"
}}
>
<div style={{display:"flex", color: "#F5821F",}}>
<img src={require("../Images/cmghLogo.png")} style={{width:"50%", height:"7.5%", marginTop:"0vh"}} alt="logo"/>
<div style={{ top: "0", margin: "0vh", marginLeft:"2vw", width:" 100%", fontSize:"3vw"}}>中心</div>
</div>
<div>
<header>
<button onClick={() => toggleLanguage('en')} disabled={currentLanguage === 'en'}>English</button>
<button onClick={() => toggleLanguage('tw')} disabled={currentLanguage === 'tw'}>中文</button>
</header>
</div>
</h1>
</div>
);
};
export default Header;
The i18n.js did not set properly.
so that failed to read the translation key-value pair.
After I changed to install and using "i18next-http-backend" plugin, modified the i18n.js, the problem is fixed as the following:
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
i18n
// load translation using http -> see /public/locales
// learn more: https://github.com/i18next/i18next-http-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
fallbackLng: 'en',
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
});
export default i18n;
I am new to react and i am trying to get the reference value from DisplayMapClass bellow is the code. The main objective is to get the MAP object from the bellow code.
DisplayMapClass.js
import * as React from 'react';
export class DisplayMapClass extends React.Component {
mapRef = React.createRef();
state = {
map: null
};
componentDidMount() {
const H = window.H;
const platform = new H.service.Platform({
apikey: "{enter api key}"
});
const defaultLayers = platform.createDefaultLayers();
const map = new H.Map(
this.mapRef.current,
defaultLayers.vector.normal.map,
{
center: { lat: 50, lng: 5 },
zoom: 4,
pixelRatio: window.devicePixelRatio || 1
}
);
const behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
const ui = H.ui.UI.createDefault(map, defaultLayers);
this.setState({ map });
}
componentWillUnmount() {
this.state.map.dispose();
}
render() {
return (
<div ref={this.mapRef} style={{ height: "500px" }} />
);
}
}
When I reassign the ref the whole page breaks so i don't know what i am doing wrong.
const map = <DisplayMapClass ref={"newRef"}/>
Bellow is the code for App.js
App.js
import logo from './logo.svg';
import './App.css';
import React, { useEffect, useState } from 'react';
import axios from 'axios'
import Button from '#mui/material/Button';
import TextField from '#mui/material/TextField';
import {BounceLoader, BarLoader, BeatLoader} from 'react-spinners'
import {DisplayMapClass} from './DisplayMapClass';
const H = window.H;
function App() {
const [isLoading, setLoading] = useState(false)
const map = <DisplayMapClass ref={"newRef"}/>
const asyncFunc = async () => {
setLoading(true);
const response = await fetch('http://000.000.000.000:000/test/8.8.8.8' );
const data = await response.json();
for(var x in data){
addMarker(map, x["lat"], x["lng"])
}
setLoading(false)
}
const addMarker = (map, x, y) => {
var marker = new H.map.Marker({"lat": x, "lng": y})
map.addObject(marker);
}
return (
<div className="App">
{ isLoading ?
<header className="App-header">
<p>please type an ip address and when done press the button</p>
<TextField
sx=
{
{
input:
{
color: 'white'
}, root:
{
color: 'white'
}, floatingLabelFocusStyle:
{
color: "white"
}
}
}
id="ipTextArea" label="Enter an IP address" variant="filled"
/>
<Button onClick={asyncFunc} variant="outlined">Map My Stalkers</Button>
<BarLoader/>
</header>
:
<header className="App-header">
<p>please type an ip address and when done press the button</p>
<TextField
sx=
{
{
input:
{
color: 'white'
}, root:
{
color: 'white'
}, floatingLabelFocusStyle:
{
color: "white"
}
}
}
id="ipTextArea" label="Enter an IP address" variant="filled"
/>
<Button onClick={asyncFunc} variant="outlined">Map My Stalkers</Button>
</header>
}
<p> <br /> </p>
<div> {map} </div>
</div>
);
}
export default App;
I am trying to create a modal that will display all the data on the element clicked.
For exemple, if i clicked the id(367) of a movie, it will display the title, the genres and the released date.
First i created a Context.js that will send data in the component Movie.js:
Context.js
import React, { useState, useEffect, createContext } from "react";
import { getAxios } from "../Function/index";
const MovieContext = createContext();
const MovieProvider = ({ children }) => {
const [datas, setDatas] = useState([]);
const [testdatas, testsetDatas] = useState([]);
const getMovie = async () => {
const data = await getAxios(
"https://api.themoviedb.org/3/movie/upcoming?api_key={}"
);
setDatas(data.results);
};
useEffect(() => {
getMovie();
}, []);
return (
<MovieContext.Provider value={{ datas, testdatas }}>{children}</MovieContext.Provider>
);
};
export { MovieContext, MovieProvider };
Movie.js
import React, { useContext } from "react";
import { MovieContext } from '../Context'
import { Card, Row, Tag } from 'antd'
const Main = () => {
const { datas } = useContext(MovieContext)
return (
<Row gutter={16}>
{datas.map((item, id) => (
<Card
key={id}
style={{ width: 300, margin: 10 }} bordered={true}
hoverable
>
<div>
<p style={{ textAlign: "left" }}>{item.title} <span style={{ float: "right" }}>{item.vote_average}</span></p>
</div>
<p><img src={`https://image.tmdb.org/t/p/w500/${item.poster_path}`} alt="#" width="200" height="200" /></p>
<Tag color="red"> {item.title}</Tag>
</Card>
))}
</Row >
);
};
export default Main;
So I have a basic messenger chat application with a search bar to retrieve chatrooms, and it works fine whenever I type in letters, yet whenever I backspace, why wont it retrieve the list I had previous?
Examples below
1.All chatrooms are shown below:
2.Typing "funny" filters a list with chatrooms labeled "funny":
3.Pressing backspace does not show all the chatrooms:
import { Avatar, IconButton } from '#material-ui/core';
import React, { useEffect, useState } from 'react';
import './Sidebar.css';
import SearchIcon from '#material-ui/icons/Search';
import { RateReviewOutlined } from '#material-ui/icons';
import { SidebarChat } from './SidebarChat';
import { useSelector } from 'react-redux';
import { selectUser } from './features/userSlice';
import db, { auth } from './firebase';
import { makeStyles } from '#material-ui/core/styles';
import Modal from '#material-ui/core/Modal';
import Backdrop from '#material-ui/core/Backdrop';
import Fade from '#material-ui/core/Fade';
const useStyles = makeStyles((theme) => ({
modal: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
paper: {
backgroundColor: theme.palette.background.paper,
border: '2px solid #000',
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
}));
export function Sidebar(props) {
const user = useSelector(selectUser);
const [chats, setChats] = useState([]);
const classes = useStyles();
const [open, setOpen] = useState(false);
const [search, setSearch] = useState('');
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
useEffect(() => {
db.collection('chats').onSnapshot((snapshot) =>
setChats(
snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
}))
)
);
}, []);
const addChat = () => {
const chatName = prompt('Please enter a chat name');
if (chatName) {
db.collection('chats').add({
chatName: chatName,
});
}
};
const searchFunction = (e) => {
setSearch(e);
console.log(search);
console.log(chats);
const filtered = chats.filter(chat => {
return chat.data.chatName.toLowerCase().includes(e.toLowerCase())
});
// console.log(filtered)
setChats(filtered);
};
return (
<div className="sidebar">
<div className="sidebar__header">
<Avatar
onClick={handleOpen}
src={user.photo}
className="sidebar__avatar"
/>
<div className="sidebar__input">
<SearchIcon />
<input
placeholder="Search"
value={search}
onChange={(e) => searchFunction(e.target.value)}
/>
</div>
<IconButton variant="outlined" className="sidebar__inputButton">
<RateReviewOutlined onClick={addChat} />
</IconButton>
</div>
<div className="sidebar__chats">
{chats.map(({ id, data: { chatName } }) => (
<SidebarChat key={id} id={id} chatName={chatName} />
))}
</div>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={open}
onClose={handleClose}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
<Fade in={open}>
<div className={classes.paper}>
<h2 id="transition-modal-title">
Are you sure you want to sign out?
</h2>
<button onClick={() => auth.signOut()}>Yes</button>
<button onClick={handleClose}>No</button>
</div>
</Fade>
</Modal>
</div>
);
}
You need to save the initial list of chats in a variable and once the search is set to blank '' you need to update your chats with the initial saved chats.
Problem here is -
const filtered = chats.filter(chat => {
return chat.data.chatName.toLowerCase().includes(e.toLowerCase())
});
// console.log(filtered)
setChats(filtered);
I can't figure out how to animate routing with <AnimateSharedLayout /> component by framer-motion.
Basically in the code down here i want to display a list of images, and when clicking on them i want to navigate to /images/[image_id] and display it wide. I don't have the images locally so I have to fetch them. There are some problems here:
The same element gets fetched one first time on the index page, then gets refetched on the detail page (and it would be nice if there was a way to bring it over to the detail page)
Before changing route, the page automatically scrolls to the top, ruining the animation;
If the user is on a slow network, the animation is laggy or not even kicking in;
going back to the previous route doesn't replay the animation (I may have to cache api calls to avoid refetching them all?)
here's the code _app.tsx
import '../styles/globals.css';
import 'bulma/css/bulma.css';
import { AppProps } from "next/app";
import { AnimateSharedLayout, AnimatePresence } from 'framer-motion';
const MyApp: React.FC<AppProps> = ({ Component, pageProps, router }) => {
return (
<AnimateSharedLayout>
<AnimatePresence>
<Component {...pageProps} key={router.route} />
</AnimatePresence>
</AnimateSharedLayout>
);
}
export default MyApp;
here's pages/index.tsx
import { motion } from "framer-motion";
import { useEffect, useState } from "react";
import Picture, { PictureProps } from "./components/Picture";
export const base = 'https://jsonplaceholder.typicode.com';
const Home: React.FC = () => {
const [pics, setPics] = useState<PictureProps[]>([]);
useEffect(() => {
fetch(`${base}/photos?_start=0&_limit=15`)
.then(res => res.json())
.then(res => setPics(res as PictureProps[]))
.catch(err => console.log("fetching error", err));
}, []);
return (
<div className="container pt-4">
<div className="columns is-multiline">
{
pics.map(pic => (
<div key={pic.id} className="column is-one-third-desktop is-half-tablet is-full-mobile">
<Picture {...pic} />
</div>
))
}
</div>
</div>
);
}
export default Home;
here's pages/images/[image].tsx
import { motion } from "framer-motion";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { PictureProps } from "../components/Picture";
import { base } from '../index';
const Image: React.FC = () => {
const router = useRouter();
const { image } = router.query;
const [pic, setPic] = useState<PictureProps>({} as PictureProps);
useEffect(() => {
image && fetch(`${base}/photos/${image}`)
.then(res => res.json())
.then(res => setPic(res))
.catch(err => console.log(err));
}, [image]);
return (
<motion.div>
{
image &&
<motion.figure layoutId={`img-${image}`} className='image'>
<img src={pic.url} alt={pic.title} />
</motion.figure>
}
</motion.div>
);
}
export default Image;
and here's components/Picture.tsx
import { motion, Variants } from "framer-motion";
import Link from "next/link";
export type PictureProps = {
albumId: number,
id: number,
title: string,
url: string,
thumbnailUrl: string,
};
const cardVariants: Variants = {
unloaded: {
y: -100,
opacity: 0,
transition: {
when: "afterChildren",
},
},
loaded: {
y: 0,
opacity: 1,
transition: {
when: "beforeChildren",
},
}
};
const textVariants: Variants = {
unloaded: {
scale: .7,
opacity: 0,
},
loaded: {
scale: 1,
opacity: 1,
},
};
const Picture: React.FC<PictureProps> = ({
id, title, url,
albumId, thumbnailUrl
}) => {
console.log(`img-${id}`);
return (
<motion.div className='card' variants={cardVariants} initial="unloaded" animate="loaded" exit="exit">
<div className="card-image">
<motion.figure
className="image"
layoutId={`img-${id}`}
>
<img src={url} alt={title} />
</motion.figure>
</div>
<motion.div className="card-content">
<div className="media">
<div className="media-left">
<Link href={`images/${id}`}>
<a>
<figure className="image is-50by50">
<img src={thumbnailUrl} alt="thumbnail" />
</figure>
</a>
</Link>
</div>
<motion.div variants={textVariants} className="media-content">
<p className="title is-6">{title}</p>
</motion.div>
</div>
<motion.div variants={textVariants} className='content'>
Some random text Some random text
Some random text Some random text
<br />
<strong>Album id</strong>: <em>{albumId}</em>
</motion.div>
</motion.div>
</motion.div>
);
}
export default Picture;
figured it out, here's the new code
pages/_app.tsx
import '../styles/globals.css';
import 'bulma/css/bulma.css';
import { AppProps } from "next/app";
import { AnimateSharedLayout, AnimatePresence } from 'framer-motion';
const MyApp: React.FC<AppProps> = ({ Component, pageProps, router }) => {
return (
<AnimateSharedLayout type='crossfade'>
<Component {...pageProps} />
</AnimateSharedLayout>
);
}
export default MyApp;
pages/index.tsx
import Picture, { PictureProps } from "../components/Picture";
import { NextPage, GetStaticProps } from "next"
export const base = 'https://jsonplaceholder.typicode.com';
type IndexProps = {
pictures: PictureProps[],
}
const Home: NextPage<IndexProps> = ({ pictures }) => {
return (
<div className="container pt-4">
<div className="columns is-multiline">
{
pictures.map(pic => (
<div key={pic.id} className="column is-one-third-desktop is-half-tablet is-full-mobile">
<Picture {...pic} />
</div>
))
}
</div>
</div>
);
}
export const getStaticProps: GetStaticProps = async () => {
const pics = await fetch(`${base}/photos?_start=0&_limit=15`)
.then(res => res.json())
.then(res => res)
.catch(err => console.log("fetching error", err));
return {
props: {
pictures: pics,
},
};
}
export default Home;
pages/images/[image].tsx
import { motion } from "framer-motion";
import { PictureProps } from "../../components/Picture";
import { base } from '../index';
import { NextPage, GetStaticProps, GetStaticPaths } from "next";
import { useRouter } from 'next/router';
type ImagePageProps = {
image: PictureProps,
}
const Image: NextPage<ImagePageProps> = ({ image }) => {
const { isFallback } = useRouter();
return isFallback ? <div>loading...</div>
: (
<div>
{
image &&
<motion.div layoutId={`img-${image.id}`} style={{
backgroundImage: `url('${image.url}')`, backgroundPosition: 'center',
backgroundRepeat: 'no-repeat', backgroundSize: 'cover',
height: '70vh', position: 'relative', top: 0, left: 0, width: '100%',
}}>
</motion.div>
}
</div>
);
}
export const getStaticProps: GetStaticProps = async ctx => {
const { image } = ctx.params;
const pic = await fetch(`${base}/photos/${image}`)
.then(res => res.json())
.then(res => res)
.catch(err => console.log(err));
return {
props: {
image: pic,
},
};
}
export const getStaticPaths: GetStaticPaths = async () => {
const ids = await fetch(`${base}/photos?_start=0&_limit=15`)
.then(res => res.json())
.then(res => res.map(el => el.id.toString()))
.catch(err => console.log(err));
return {
paths: ids.map((id: string) => ({
params: {
image: id,
},
})),
fallback: true,
};
};
export default Image;
components/Picture.tsx
import { motion, Variants } from "framer-motion";
import Link from "next/link";
export type PictureProps = {
albumId: number,
id: number,
title: string,
url: string,
thumbnailUrl: string,
};
const cardVariants: Variants = {
initial: {
scale: 0.9,
transition: {
when: 'afterChildren',
},
},
enter: {
scale: 1,
transition: {
when: 'beforeChildren',
duration: .3,
},
},
};
const textVariants: Variants = {
initial: {
opacity: 0,
},
enter: {
opacity: 1,
},
};
const Picture: React.FC<PictureProps> = ({
id, title, url,
albumId, thumbnailUrl
}) => {
return (
<motion.div className='card' variants={cardVariants} initial="initial" animate="enter">
<div className="card-image">
<motion.figure
className="image"
layoutId={`img-${id}`}
>
<img src={url} alt={title} />
</motion.figure>
</div>
<motion.div variants={textVariants} className="card-content">
<div className="media">
<div className="media-left">
<Link href={`/images/${id}`}>
<a>
<figure className="image is-50by50">
<img src={thumbnailUrl} alt="thumbnail" />
</figure>
</a>
</Link>
</div>
<motion.div variants={textVariants} className="media-content">
<p className="title is-6">{title}</p>
</motion.div>
</div>
<motion.div variants={textVariants} className='content'>
Some random text Some random text
Some random text Some random text
<br />
<strong>Album id</strong>: <em>{albumId}</em>
</motion.div>
</motion.div>
</motion.div>
);
}
export default Picture;