I can't get react-spring to work. I'm fairly new to this so I have no idea what is going wrong. I'm trying to make navbar appear from top to bottom to 40vh, but it doesn't appear to be recognizing the props passed. I used create-react-app and react-spring 8.0.27
App.js:
const App = () => {
const [open, setOpen] = useState(false);
const navprops = useSpring({
from: {height: "0"},
to: {height: "40vh"}
})
return (
<Fragment>
{open ? <Navbar style={navprops}/> : null}
</Fragment>
Navbar.js:
const NavBar = styled(animated.nav)`
width: 100%;
`;
const Navbar = (props) => {
return (
<NavBar style={props.style}>
</NavBar>
);
};
This is basically the code. There are more style props but I guess it's irrelevant to functionality.
animated and useSpring are imported in both files for testing. Thank you for your help.
Here is my solution,
Demo Link
Navbar.js
import React from "react";
import styled from "styled-components";
import { animated } from "react-spring";
const NavBar = styled(animated.nav)`
width: 100%;
background: red;
`;
export default (props) => {
return <NavBar style={props.style}></NavBar>;
};
App.js
import React, { useState } from "react";
import { useTransition, config } from "react-spring";
import Navbar from "./Navbar";
export default function App() {
const [open, setOpen] = useState(false);
// const navprops = useSpring({
// from: { height: "0" },
// to: { height: "40vh" },
// config: config.wobbly
// });
const transitions = useTransition(open, null, {
initial: { height: "0px" }, //Not required
from: { height: "0px" },
enter: { height: "40vh" },
leave: { height: "0px" },
config: config.wobbly //More configs here https://www.react-spring.io/docs/hooks/api
});
return (
<div className="App">
{transitions.map(
({ item, key, props }) => item && <Navbar key={key} style={props} />
)}
<br />
<br />
<button onClick={() => setOpen(!open)}>Toggle Navbar</button>
</div>
);
}
I do not think useSpring will work on unmounted component. You were trying to animate an unmounted component.
According to documentation, useTransition can be used to animate mounting of unmounted components.
The syntax is little complicated, but they have made the syntax simpler in version 9(release candidate) of react-spring Link Here
Related
I am trying to wrap my mind around this and find the breaking point for all morning but am not able to. I have only followed the official docs until now.
I need to update the theme from dark to light in a React App but the state wont change (or resets immediately)
Heres my code:
App.tsx
import React, { createContext, useMemo, useState } from 'react';
import styles from './App.module.scss';
import {createTheme,CssBaseline,PaletteMode,responsiveFontSizes,ThemeProvider,
useMediaQuery} from '#mui/material';
import { getDefaultThemeMode } from './shared/theme/default-theme-mode';
import { getDesignTokens } from './shared/theme/mui-design-tokens';
import Main from './main/Main';
import { Navigate, Route, Routes } from 'react-router-dom';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { OPTIONS } from './shared/i18n/translations';
export const ColorModeContext = React.createContext({
toggleColorMode: () => { },
});
function App() {
// THEME
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const [mode, setMode] = React.useState<'light' | 'dark'>('light');
React.useEffect(() => {
setMode(prefersDarkMode ? 'dark' : 'light');
}, [prefersDarkMode]);
const colorMode = React.useMemo(
() => ({
toggleColorMode: () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
},
}),
[]
);
let theme = React.useMemo(
() =>
createTheme(getDesignTokens(mode)),
[mode]
);
theme = responsiveFontSizes(theme);
return (
<ColorModeContext.Provider value={colorMode}>
<ThemeProvider theme={theme}>
<div className={styles.app} style={{background: theme.palette.background.default}}>
<CssBaseline/>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/>
<Routes>
<Route path='/home' element={<Main/>}/>
<Route path='*' element={<Navigate to='home'/>}/>
</Routes>
</div>
</ThemeProvider>
</ColorModeContext.Provider>
);
}
export default App;
getDesignTokens Function:
const getDesignTokens = (mode: PaletteMode): ThemeOptions => ({
palette: {
mode,
...(mode === 'light' ? {
primary: {
main: '#3c3c3c',
},
} : {
primary: {
main: '#3c3c3c',
},
})
}});
And finally, I am trying to update the theme from outside the App.tsx component this way:
const ThemeToggle = () => {
const theme = useTheme();
const colorMode = useContext(ColorModeContext);
return (
<>
<label htmlFor='theme-toggle' className={`${styles.toggleBtn}
${theme.palette.mode === 'dark' ? styles.dark : ''}`}
onClick={colorMode.toggleColorMode}>
<input type='checkbox' id='theme-toggle' className={styles.input}/>
</label>
</>
)
}
export default ThemeToggle;
I must be missing something obvious but have tried everything. Anyone has done something similar?
I'm trying to implement the following toast manager to my react typescript application
https://codesandbox.io/s/react-toasts-melne?from-embed=&file=/src/contexts/ToastContext.js
I've created a file called toast-context.tsx
import React, { useCallback, useEffect, useState, createContext } from 'react';
const ToastContext = createContext(null);
export default ToastContext;
export function ToastContextProvider({ children }) {
const [toasts, setToasts] = useState([]);
useEffect(() => {
if (toasts.length > 0) {
const timer = setTimeout(() => setToasts((toasts) => toasts.slice(1)), 6000);
return () => clearTimeout(timer);
}
}, [toasts]);
const addToast = useCallback(
function (toast) {
setToasts((toasts) => [...toasts, toast]);
},
[setToasts],
);
return (
<ToastContext.Provider value={addToast}>
{children}
<div style={{ position: 'fixed', bottom: '1 rem', left: '1 rem' }}>
{toasts.map((toast) => (
<div style={{ background: 'green', color: 'white' }} key={toast}>
{toast}
</div>
))}
</div>
</ToastContext.Provider>
);
}
And a hook as useToast
import { useContext } from 'react';
import ToastContext from '../contexts/toast-context';
export default function useToast() {
return useContext(ToastContext);
}
And in my _app.tsx (This is nextjs)
return (
<>
<div className="app">
<ToastContextProvider>
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
</ToastContextProvider>
</div>
</>
);
};
But when i try to use the solution, it says Uncaught TypeError: addToast is not a function
export const ToastTest = () => {
const [text, setText] = useState('');
const addToast = useToast();
function handleTextChange(event) {
setText(event.target.value);
}
function handleClick() {
addToast(text);
}
return (
<div>
<h1>Hello Toasts!</h1>
<div>
<input value={text} onChange={handleTextChange} />
</div>
<button onClick={handleClick}>Show toast</button>
</div>
);
};
I followed everything that was in the example. In the example it works but not sure why it does not work in my code.Can someone please point out the issue?
When i click on the ''Show toast' button this is the error i'm getting
The error message says that it is a "TypeError" but actually this is not related to typescript. That is a JavaScript error which occurs if you try to invoke something that isn't actually a function. For example, null is not a function and would throw this error if you tried to invoke it.
The error is most likely that you are not rendering your ToastTest component inside of your ToastContextProvider component. Your call to useToast is probably getting the default value of "null" and throwing this error when it tries to call "null" as a function. Make sure that your ToastContextProvider is a parent or grandparent to your ToastTest component.
I have on one URL (pages/index.js) an <input onFocus={() => router.push('/search')} />, and on the /search url (pages/search.js) I have a full form. However, it jolts and flickers when going from one screen to the next. How do I avoid that?
You may want to setup nprogress loading when navigating between pages :
_app.js
import NProgress from "nprogress";
import Router from "next/router";
import "nprogress/nprogress.css";
NProgress.configure({ showSpinner: true });
Router.events.on("routeChangeStart", () => {
NProgress.start();
});
Router.events.on("routeChangeComplete", () => {
NProgress.done();
});
Custom css for nprogress :
#nprogress {
color: green !important;
}
#nprogress .bar {
background: green !important;
}
#nprogress .spinner-icon {
border-top-color: green !important;
border-left-color: green !important;
}
Note
If you don't want to have a flicker meaning no navigating between pages then you should handle input focus with states to show the form :
function Form() {
const [show , setShow] = React.useState(false)
return (
<div>
{show ? <YourFormComponent/> : <input onFocus={()=>setShow(true)} ></input>}
</div>
)
}
Extra Note
If you want to update the url and have your inputs show I suggest using queries :
import React from "react";
import { useRouter } from "next/router";
import queryString from "query-string";
function FormWithQuery() {
const [show, setShow] = React.useState(false);
const router = useRouter();
const parsed = queryString.parse(router.query);
React.useEffect(() => {
// your logic here
setShow(true);
}, [parsed]);
return (
<div>
{show ? (
<FormComponent />
) : (
<input onFocus={() => router.push("/?searchForm=true")} />
)}
</div>
);
}
export default FormWithQuery;
I'm trying to display text when I hover over an icon, but if you hover the icons quickly it'll get stuck on displaying the text instead of displaying the icon (default state)
ex: https://giphy.com/gifs/UsS4JcRJGV5qfCI5VI
Skills Component:
import React, { useState } from 'react';
import { UserIcon } from './AboutBtnStyling';
import IconText from '../../../IconText';
const AboutBtn = () => {
const [hover, setHover] = useState(false);
const onHover = () => {
setHover(true)
}
const onLeave = () => {
setHover(false)
}
return (
<div onMouseEnter={onHover} onMouseLeave={onLeave} role="button">
{hover ? <IconText text="ABOUT" /> : <UserIcon /> }
</div>
)
}
export default AboutBtn;
Then I hoped converting it to a class component would help, bc of stale closure problem associate with useState hook
import React, { Component } from 'react';
import { SkillIcon } from './SkillsBtnStyling';
import IconText from '../../../IconText';
class SkillsBtn extends Component {
constructor(props) {
super(props);
this. state = { hover: false }
}
onHover = () => {
this.setState({ hover: true })
}
onLeave = () => {
this.setState({ hover: false })
}
render() {
return (
<div onMouseEnter={this.onHover} onMouseLeave={this.onLeave} role="button">
{this.state.hover ? <IconText text="SKILLS" /> : <SkillIcon /> }
</div>
)
}
}
export default SkillsBtn;
Would greatly appreciate any insight! I really want to solve this problem, instead of resorting to achieving this effect using CSS
An important aspect of useState is that it is asynchronous. I believe this is causing your code to act a bit buggy. I would add more decisiveness to your setState calls and set it (true/false) based on mouse position rather than toggle.
import React, { useState } from 'react';
import { SkillsButton } from './SkillsBtnElements'
const SkillsBtn = () => {
const [hover, setHover] = useState(false);
const onHover = () => {
setHover(!hover)
}
return (
<div onMouseEnter={() => setHover(true)} onMouseLeave={() =>
setHover(false)} role="button" tabIndex='-3' >
{ hover ? "SKILLS" : <SkillsButton /> }
</div>
)
}
export default SkillsBtn;
So I have an array of data in and I am generating a list of components with that data. I'd like to have a ref on each generated element to calculate the height.
I know how to do it with a Class component, but I would like to do it with React Hooks.
Here is an example explaining what I want to do:
import React, {useState, useCallback} from 'react'
const data = [
{
text: 'test1'
},
{
text: 'test2'
}
]
const Component = () => {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<div>
{
data.map((item, index) =>
<div ref={measuredRef} key={index}>
{item.text}
</div>
)
}
</div>
)
}
Not sure i fully understand your intent, but i think you want something like this:
const {
useState,
useRef,
createRef,
useEffect
} = React;
const data = [
{
text: "test1"
},
{
text: "test2"
}
];
const Component = () => {
const [heights, setHeights] = useState([]);
const elementsRef = useRef(data.map(() => createRef()));
useEffect(() => {
const nextHeights = elementsRef.current.map(
ref => ref.current.getBoundingClientRect().height
);
setHeights(nextHeights);
}, []);
return (
<div>
{data.map((item, index) => (
<div ref={elementsRef.current[index]} key={index} className={`item item-${index}`}>
{`${item.text} - height(${heights[index]})`}
</div>
))}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Component />, rootElement);
.item {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ccc;
}
.item-0 {
height: 25px;
}
.item-1 {
height: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"/>
I created a tiny npm package that exposes a React Hook to handle setting and getting refs dynamically as I often run into the same problem.
npm i use-dynamic-refs
Here's a simple example.
import React, { useEffect } from 'react';
import useDynamicRefs from 'use-dynamic-refs';
const Example = () => {
const foo = ['random_id_1', 'random_id_2'];
const [getRef, setRef] = useDynamicRefs();
useEffect(() => {
// Get ref for specific ID
const id = getRef('random_id_1');
console.log(id)
}, [])
return (
<>
{/* Simple set ref. */}
<span ref={setRef('random_id_3')}></span>
{/* Set refs dynamically in Array.map() */}
{ foo.map( eachId => (
<div key={eachId} ref={setRef(eachId)}>Hello {eachId}</div>))}
</>
)
}
export default Example;
You have to use a separate set of hooks for each item, and this means you have to define a component for the items (or else you’re using hooks inside a loop, which isn’t allowed).
const Item = ({ text }) => {
const ref = useRef()
const [ height, setHeight ] = useState()
useLayoutEffect(() => {
setHeight( ref.current.getBoundingClientRect().height )
}, [])
return <div ref={ref}>{text}</div>
}