Unexpected behavior of React render props - reactjs

I've come up with the following code using React Transition Group:
const Transition = ({ elements, selectKey, render: Element, ...props }) => (
<TransitionGroup {...props}>
{elements.map((element) => (
<CSSTransition
key={selectKey(element)}
timeout={1000}
className="transition-slide"
>
<Element {...element} />
</CSSTransition>
))}
</TransitionGroup>
)
The key part here is that Transition component receives the render prop and renders it applying some transitions.
The way I expected for this to work:
<Transition render={(props) => <Toast {...props} />} />
But this code does not work as I expected: the transition of the next element interrupts the transition of the previous one.
However, this code works just fine:
const Element = (props) => <Toast {...props} />
// ...
<Transition render={Element} />
How can I fix this issue without putting the desired render-prop into a separate component?
Codesandbox: Example sandbox. The sandbox presents a non-working option with animation interruption. To get a working version, you need to uncomment lines 16 and 30 in the /Toasts/index.js file
P.S. I can't just use render={Toast} because I need to do ({id}) => <Toast dismiss={() => {deleteToast(id)}} />. I omitted this detail in order to simplify the understanding of the problem.

If you don't want to put the render function into another component, putting it into a useCallback() solved it for me.
const Toasts = () => {
const [toasts, addToast] = useToasts();
const Element = useCallback((props) => <Toast {...props} />, []);
return (
<div>
<button onClick={addToast}>Add toast</button>
<List>
<Transition
elements={toasts}
selectKey={({ id }) => id}
render={Element}
/>
</List>
</div>
);
}
(I don't quite understand the origin of the issue, but it has to do something with the function references.)

Related

CSS transition , react-transition-group and React.findDOMnode deprecation

I encountered a Warning relative to the findDOMnode deprecation when trying to use react routing in combination with react-transition-group, the warning stated:
index.js:1 Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Transition which is inside StrictMode. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-find-node
The above warning refers to the following code:
<Route key={path} path={path} exact>
{({match})=>(
<CSSTransition
in={match!=null}
timeout={300}
classNames="page"
unmountOnExit
mountOnEnter
>
<div className="page">
<Component />
</div>
</CSSTransition>
)}
</Route>)
My first attempt of getting rid of that warning was to make use of useRef as suggested:
const nodeRef = useRef(null);
passing nodeRef as ref prop of the CSStransation element but the warning was still showing up.
For some reason I could only get rid of the warning by passing the triggering event that I was also using in the 'in' prop of the CSStransition element, like showed here below:
<Route key={path} path={path} exact>
{({match})=>(
<CSSTransition
ref={nodeRef}
in={match!=null}
timeout={300}
classNames="page"
unmountOnExit
mountOnEnter
key={match!=null} <------------ Adding this removed the warning
>
<div className="page">
<Component />
</div>
</CSSTransition>
)}
</Route>)
Everything work smoothly now but I cant really undestand why, and even if I remove the the ref from the CSStransition element I dont get any warning anymore.
Does anybody undestand why this is actually happening?
I spent a while trying to figure this out as well, and I finally got it! You need to use the nodeRef prop in the CSSTransition for each route individually. Each route gets its own ref, and that ref needs to be assigned to the nodeRef accordingly. I was able to get this working by using an array of refs, mapping each route and assigning the refs to the current index.
Take a look at this working example I made:
https://codesandbox.io/s/react-transition-routes-with-noderef-k9q47
And here's the block of code that is going to be the most helpful:
////// CONTENT TRANSITION ROUTER
const PageContent = withRouter(({ location }) => {
let routeRefs: any[] = [];
const isMatch = useCallback(
(path: string): boolean => {
return location.pathname === path ? true : false;
},
[location]
);
return (
<>
{appRoutes.map(({ path, Component }, index) => {
routeRefs[index] = React.useRef(null);
return (
<Route key={index} exact path={path}>
{() => {
// Route callback ensures the transitions are loaded correctly
return (
<CSSTransition
nodeRef={routeRefs[index]}
in={isMatch(path)}
timeout={300}
classNames="fade"
unmountOnExit
appear
>
<div ref={routeRefs[index]} className="fade">
<Component />
</div>
</CSSTransition>
);
}}
</Route>
);
})}
</>
);
});

React hooks : 'Cannot read property 'push' of undefined'

I'm trying to redirect my homepage to "/call" page based on a redux state. I can go to that component by typing the url manually but cant do it with a function. I tried "Redirect to", "history.push" but none of them worked for me. I cant solve the problem. Here is my code;
const Phone = ({ hidden, photoOpened, inCall }) => {
const dispatch = useDispatch(getContacts());
let history = useHistory();
useEffect(() => {
if (inCall.inCall) {
history.push('/call')
}
}, [inCall]);
useEffect(() => {
dispatch(getContacts());
}, [])
return (
<div hidden={process.env.NODE_ENV === 'development' ? !hidden : hidden} className={photoOpened ? "phone-container-rotate" : "phone-container"}>
<div className="coque" />
<Suspense fallback={<div className="animated fadeIn pt-1 text-center">Loading...</div>}>
<HashRouter basename="/phone">
<div
className="phone-content"
style={{ backgroundImage: `url(${background})` }}
>
<HeaderBar />
<BannerNotifications />
<Switch>
{routes.map((route, idx) => {
return route.component ? (
<Route
key={idx}
path={route.path}
exact={route.exact}
render={props => <route.component {...props} />}
/>
) : null;
})}
</Switch>
</div>
<Route component={BottomPhoneNavigator} />
</HashRouter>
</Suspense>
</div>
);
};
You could try and test for history existence of the history in your effect, also add it to dependency list
useEffect(() => {
if (history && inCall.inCall) {
history.push('/call')
}
}, [inCall, history]);
And important thing, your component using this hook must be within the Router, I see you'\re using HashRouter but as child of component using the hook.
Also if you're stuck to this structure, why wont you try to use Redirect within the Switch? This could work with some smart test so you wont end up in a loop:)
To use history your Phone component should be inside router component

Handling events across different exports with React

So I wanted to create a popup div that would slide from the side when an object has been selected and then exit when the object is re selected. I also want to create an exit button that would also close the div. I can pretty much understand how to do this except that I want to reuse this div component which is why I have kept it as an export in a different javascript file. This is where the issue is as I am having trouble handling the events across the files.
Here is my code:
/*Popup div export*/
export default () => {
const [toggle, set] = useState(true);
const { xyz } = useSpring({
from: { xyz: [-1000, 0, 0] },
xyz: toggle ? [0, 0, 0] : [-1000, 0, 0]
});
return (
<a.div
style={{
transform: xyz.interpolate(
(x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`
)
}}
className="expand"
>
<Link to={link}>
<a.button>Next</a.button>
</Link>
<button onClick={() => set(!toggle)}>Exit</button>
</a.div>
);
};
/*This is where I am implementing the export*/
<Canvas>
<Suspense fallback={<Fallback />}>
<Cube position={[-1.2, 0, 0]} onClick={e => <Toggle />} /> <---/*Here is the click event where I call the div*/-->
<Cube position={[1.2, 0, 0]} />
</Suspense>
</Canvas>
);
}
I have tried changing the styling to make the display 'hidden' and 'block' but this doesn't work as it doesn't show the slide in animation it just pops up. Furthermore, If I try to manipulate this separately, for example, create a click event within the canvas to make the div appear with react-spring, if I try to use the exit button, the click event doesn't work anymore.
Here is my sandbox to show what is happening. : (p.s sorry if this all seems confusing)
The codes are within Page1.js and toggle.js
https://codesandbox.io/s/sad-goldberg-pmb2y?file=/src/toggle.js:250-326
Edit:
simpler sandbox visual:
https://codesandbox.io/s/happy-chatelet-vkzjq?file=/src/page2.js
Your example is a bit confusing to follow, a simpler reproduction would be nice. That said, if I understand the overall goal, I think you want to store some global state (perhaps in your App.js component) that has some sort of state about the sidebar being visible.
For example:
function App() {
const [sidebarVisible, setSidebarVisible] = React.useState(false)
const toggleSidebar = () => setSidebarVisible(!sidebarVisible)
return (
<Router>
<Switch>
<Route path="/page1">
<Page1 toggleSidebar={toggleSidebar} />
</Route>
<Route path="/page2">
<Page2 toggleSidebar={toggleSidebar} />
</Route>
<Route path="/">
<Start toggleSidebar={toggleSidebar} />
</Route>
</Switch>
</Router>
)
}
function Page1({ toggleSidebar }) {
return <Toggle toggleSidebar={toggleSidebar} />
}
function Toggle({ toggleSidebar }) {
return <button onClick={toggleSidebar}>Toggle</button>
}
This is just to give you ideas, you could of course pass the setSidebarVisible function or make another function that stores some sort of state about what should show on the sidebar.
You could also use something like Redux or React Context to pass down state/actions into your components.
Hope this helps somewhat 👍🏻

React Navigation Global FAB

I am using react-navigation v5 and ended up with App.js like this
const MainStack = createStackNavigator();
export default () => (
<NavigationNativeContainer>
<MainStack.Navigator
screenOptions={{
header: () => <AppbarHeader />,
}}
>
<MainStack.Screen name="index" component={index} />
<MainStack.Screen name="alternate" component={alternate} />
</MainStack.Navigator>
</NavigationNativeContainer>
);
I would like to add a Floating Action(FAB) Button that would be visible on the bottom of both the index and alternate page that would allow the user to show a Modal that is currently omitted. Right now I feel like the only solution is to put my FAB component inside both the index and alternate components but this doesn't seem right because it shouldn't re-render and transition in with the page. It should float above the page transition. I feel like it should be more of a global navigator of some sort but I am not sure how that should work with the existing StackNavigator shown above.
I am Looking forward to any solutions provided.
As #satya164 noted, you can put FAB in your root component. In addition, to access to navigation actions, you can use a ref to your navigation container.
const App = () =>{
const ref = React.useRef(null);
return (
<>
<NavigationNativeContainer ref={ref}>
// ...
</NavigationNativeContainer>
<FAB onPress={() => ref.current && ref.current.navigate('A SCREEN')}/>
</>
);
If you really want it to be global, put it in your root component and not inside a screen.
const App = () => (
<>
<NavigationNativeContainer>
// ...
</NavigationNativeContainer>
<FAB />
</>
);

Can't redirect to search result page

I'm not good at English, so it might be hard to explain my intention.
I'm using React, React router, Apollo client,
In production build, When I click the search button, I can't redirect to render a result component because of error with error message
error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
First, I tried without useEffect. It occur error as described above.
Second, I tried with useEffect hook, that change redirect state to false. and it changes url. but doesn't render result component.
useEffect(() => {setRedirect(false)}, [redirect])
Finally I tried to make another react app to test this situation without apollo, and some custom components that are for clean code in the production build. I tried in development build. And it perfectly works without error
// search-bar.js
function SearchBar(props) {
// keywords for searching books
const [keywords, setKeywords] = useState('');
// search option
const [option, setOption] = useState('All');
// determine to redirect or not
const [redirect, setRedirect] = useState(false);
return (
<>
<div className="nav-search">
<form
role="search"
onSubmit={ e => {
e.preventDefault();
setRedirect(true);
}}
className="form-search"
>
<NotRequiredInput
type="search"
label="Search keywords: "
id="keywords"
value={keywords}
onChange={e => {
setKeywords(e.target.value);
}}
/>
// it map the list's items to option
<SelectBoxWithoutNone
label="Search option: "
id="search-option"
value={option}
onChange={e => {
setOption(e.target.value);
}}
list={['All', 'Title', 'Author']}
/>
<SubmitButton label="Search" />
</form>
</div>
{ redirect && <Redirect to={`/search?o=${option}&k=${keywords}`} /> }
</>
);
}
// app.js
<Query>
{({loading, data}) => {
if (loading)
return (
<Header>
<NavBar>
<main>
<Suspense>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/search" render={props => <SearchResult {...props}/>}
)
}}
// app.js looks like this with many route component.
// also using React.lazy for code splitting
// search-result.js
function SearchResult (props) {
const parsed = queryString.parse(props.location.search);
const option = parsed.o;
const keywords = parsed.k;
return (
<div className="div-search-result">
<p>{option}</p>
<p>{keywords}</p>
</div>
);
}
I expected that it renders result component (with or without hooks)
but as I described above, It occurred error
Update: When I tried to type some query parameter on url path directly, it works.

Resources