I know this is horrible convention, but I'm trying to quickly conditionally render screens in my React Native app with global variables (so no redux):
App.js:
if (global.clickStatus !== 'clicked') {
return <Screen1 />;
}
return <Screen2 />;
The app begins on Screen1, where there is a button that makes global.clickStatus = 'clicked'. When this is clicked, I want Screen2 to render. The problem is, the global.clickStatus doesn't seem to update on my App.js (even though global.clickStatus is changed, it still renders Screen1.
How can I get it to update?
I believe in <App /> component because it is a function component you can introduce a state if your button is clicked. Then with clicked state you can manipulate which component to show.
Similarly like the following - obviously this is a simplified example:
const App = () => {
const [clicked, setClicked] = useState(false);
return <>
<div onClick={() => setClicked(true)}>Click me</div>
{ clicked ? <Screen2 /> : <Screen1 /> }
</>
}
Suggested read is Using the State Hook.
The app begins on Screen1, where there is a button that makes global.clickStatus = 'clicked'
When you click the button, you did not set any state for App.js component => no re-render action is made.
I just assume the button is in Screen 1. Try code below:
import React from "react";
import "./styles.css";
export default function App() {
// Create a state
const [renderIndex, setRenderIndex] = useState(new Date().getTime())
if (global.clickStatus !== 'clicked') {
// Assume you have a button in Screen1
// Pass a callback function from this component to Screen1
// When button in Screen1 is clicked, call this callback function to update renderIndex => App component will re-render
return <Screen1 callBack={() => setRenderIndex(new Date().getTime())}/>;
}
return <Screen2 />;
}
Related
I'm beginner with React testing, learning by coding, here i have a component 'cam.tsx'
i want to test it, when i want to test Add function it goes straight like this, but when i want to test Update function it still shows Add function in my test, how to test both of them ?
Add and Update functions are forms where user can fill.
describe("Testing component ", () => {
const Camera = (): RenderResult =>
render(
<Provider store={store}>
<Cam
}}
/>{" "}
</Provider>
);
test("Cam", () => {
Camera();
const name = screen.queryByTestId(/^AddName/i);
});
});
cam.tsx:
const ADD = "ADD";
let [state, setState] = useState<State>({mode: ADD });
if (props.mode) {
state.mode = props.mode;
}
const option = state.mode;
return (
<React.Fragment>
<div data-testid="header">
{option == ADD ? Add() : <></>}
{option == UPDATE ? Update() : <></>}
</div>
</React.Fragment>
Basically cam.tsx is a component which has two forms one for updating camera and another for adding new camera.When user clicks add/update icon then cam component gets 'mode' via props ' state.mode = props.mode '
English is not my mother language, so could be mistakes
Here is how to test a component that conditionally renders components from state and can be updated via props.
import {render, screen} from '#testing-library/react';
import {Cam} from './Cam';
test('renders add by default', () => {
render(<Cam/>);
expect(screen.getByTestId('addForm'))
.toBeInTheDocument();
expect(screen.queryByTestId('updateForm'))
.not.toBeInTheDocument();
});
test('renders edit by passing props', () => {
const {rerender} = render(<Cam mode={undefined}/>);
rerender(<Cam mode={'UPDATE'} />)
expect(screen.getByTestId('updateForm'))
.toBeInTheDocument();
expect(screen.queryByTestId('addForm'))
.not.toBeInTheDocument();
});
However, it is known in the React community that updating state via props is usually an anti-pattern. This is because you now have two sources of truth for state and can be easy to have these two states conflicting. You should instead just use props to manage rendering.
If state comes from a parent component, use props.
export function Cam(props) {
const option = props.mode;
return (
<div data-testid="header">
{option === ADD ? Add() : <></>}
{option === UPDATE ? Update() : <></>}
</div>
);
}
If you really want to keep state in the child component even if props are passed in, you should update props in an useEffect hook. Additionally, you should use the setState function rather than setting state manually state.mode = props.mode
Use the useEffect hook to update state via props.
...
const [state, setState] = useState({mode: ADD});
useEffect(() => {
if (props.mode) {
setState({mode: props.mode});
}
}, [props.mode]) <-- checks this value to prevent infinite loop.
const option = state.mode;
return (
...
I'm trying to get the result of stateHook and render it properly
//import React from 'react';
import React, {useState,useEffect} from 'react';
import {DashboardLayout} from '../components/Layout';
const ProjectsPage = () => {
function GetCount() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
return (
<DashboardLayout>
<h2>Projects Page {GetCount}</h2>
</DashboardLayout>
)
}
export default ProjectsPage;
While rendering a function call in ReactJS it throws this error
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
at h2
at div
at section
at main
at div
at div
at main
at div
at BodyWrapper (http://localhost:3000/static/js/main.chunk.js:210:3)
at DashboardLayout (http://localhost:3000/static/js/main.chunk.js:338:3)
at ProjectsPage
at Route (http://localhost:3000/static/js/vendors~main.chunk.js:39794:29)
at Switch (http://localhost:3000/static/js/vendors~main.chunk.js:39996:29)
at Router (http://localhost:3000/static/js/vendors~main.chunk.js:39429:30)
at BrowserRouter (http://localhost:3000/static/js/vendors~main.chunk.js:39049:35)
at Routes
at App
And the UI remains blank,
The problem the error message is referring to is here:
<h2>Projects Page {GetCount}</h2>
GetCount is a function (specifically, a component function). You're using it (not calling it) there. You want to use the component, like so:
<h2>Projects Page <GetCount/></h2>
I'd also suggest adding the missing dependency on the useEffect hook:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
// ^^^^^^^
You don't absolutely need it if count is the only state item and the component has no properties, but it's best practice to list the effect's dependencies so it isn't called too often.
You are trying to render:
<DashboardLayout>
<h2>Projects Page {GetCount}</h2>
</DashboardLayout>
However, GetCount is a function, hence:
Functions are not valid as a React child.
What you should do is:
<DashboardLayout>
<h2>Projects Page</h2>
<GetCount />
</DashboardLayout>
I'm creating a React app and now I'm building the authentication part. Every thing is working fine but when the user logs in I have to unmount my logging component and show the home page.
I'm using Ionic React to have already made components and to show the login I'm using <IonModal /> component which has a swipeToClose prop.
So, when the user swipes to close the modal I'm listening to the onWillDismiss event to set the state variable modalOpened to false.
Every thing works great but since I'm unmounting my <Auth/> component when the user is logged in the modal onWillDismiss is triggered and it tries to update my state which doesn't exists because my component is unmounted.
I tried to create a boolean variable which is set to true when the event can update the state and false when the state can't be updated. I'm using useEffect hook to detect the component unmounting.
Strangely when my variable is set to false in my useEffect it is set back to true in my onWillDismiss event.
Here is my code:
import React, { useState, useEffect } from "react";
import {
IonPage,
IonContent,
IonModal,
IonSlides,
IonSlide,
IonButton,
} from "#ionic/react";
import Login from "./Login";
// *Stylesheet
import "./style.scss";
import { AuthSuccess } from "../../types";
const Auth: React.FC<AuthSuccess> = ({ onSuccess: successHandler }) => {
const [modalOpened, openModal] = useState(false);
let updateModal: boolean = true;
useEffect(() => {
return () => {
updateModal = false;
console.log("use effect update modal is", updateModal);
};
}, []);
return (
<IonPage>
<IonContent>
<div className="slideContainer">
<IonSlides pager className="slide">
<IonSlide>Welcome</IonSlide>
<IonSlide>First Step</IonSlide>
<IonSlide>Second Step</IonSlide>
<IonSlide>Third Step</IonSlide>
</IonSlides>
<IonButton
mode="ios"
className="login"
onClick={() => openModal(modalOpened ? false : true)}
>
Login
</IonButton>
</div>
</IonContent>
<IonModal
isOpen={modalOpened}
swipeToClose
onWillDismiss={() => {
console.log("on dismiss", updateModal);
return updateModal ? openModal(false) : null;
}}
mode="ios"
>
<Login onSuccess={successHandler} />
</IonModal>
</IonPage>
);
};
export default Auth;
and here is a screen shot of my console
Thank you in advance
Ok, I found my solution thanks to VS Code eslint extansion.
Assignments to the 'updateModal' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect.eslint(react-hooks/exhaustive-deps)
I used useRef hook and now everything works fine !
React memo isn't capturing the props neither the prevProps nor the nextProps and the component render well. The react docs say
If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost.
my problem is to stop twice rendering using react memo, but memo seems to be not working and the component renders twice with the same props.
The component renders when the Create New Event is clicked on /events
here is the live sandbox.
Child Component located at /components/Event/CreateEvent/CreateEvent.js
the parent component is located at /Pages/Event/Event.js line number 999' from where the child component is being triggered
Here is the Code:
import React from "react";
import AuthContext from "../../context/global-context";
import CreateEvent from "../../components/Event/CreateEvent/CreateEvent";
function Events({ location }) {
// Sate Managing
const [allEvents, setAllEvents] = React.useState([]);
const [creating, setCreating] = React.useState(false);
// Context As State
const { token, email } = React.useContext(AuthContext);
// Creating Event Showing
const modelBoxHandler = () => {
// works on when the ViewEvent is open
if (eventSelected) {
setEventSelected(null);
return;
}
setCreating(!creating);
};
return (
<div className="events">
{/* New Event Creating */}
{creating && (
<CreateEvent onHidder={modelBoxHandler} allEvents={allEvents} />
)}
{console.log("Event Rendered.js =>")}
</div>
);
}
export default React.memo(Events, () => true);
Child Component where the Rect memo doesn't have props:
import React from "react";
import AuthContext from "../../../context/global-context";
function CreateEvent({ onHidder, allEvents }) {
// Context
const { token } = React.useContext(AuthContext);
console.log("CreatedEvent.js REnder");
return (
... Some code here
);
}
export default React.memo(CreateEvent, (prevProps, nextProps) => {
console.log("Hello", prevProps, nextProps);
});
Thanks in advance for your valuable answer and times!
The problem is that on basis of creating variable you are actually remounting and not rendering the CreateEvent component. What it means is that if creating variable changes, the component is unmounted and re-mounted when creating is true, so its not a re-render
Also you must note that modelBoxHandler function reference also changes on each re-render so even if your CreateEvent component is in rendered state and the parent re-rendered due to some reason , the CreateEvent component too will re-render
There are 2 changes that you need to make to make it work better
Define modelBoxHandler with a useCallback hook
perform conditional rendering in createEvent based on creating prop
// Creating Event Showing
const modelBoxHandler = useCallback(() => {
// works on when the ViewEvent is open
if (eventSelected) {
setEventSelected(null);
return;
}
setCreating(prevCreating => !prevCreating);
}, [eventSelected]);
...
return (
<div className="events">
{/* New Event Creating */}
<CreateEvent creating={creating} onHidder={modelBoxHandler} allEvents={allEvents} />
{console.log("Event Rendered.js =>")}
</div>
);
and in createEvent
function CreateEvent({ onHidder, allEvents, creating }) {
// Context
const { token } = React.useContext(AuthContext);
console.log("CreatedEvent.js REnder");
if(!creating) {
return null;
}
return (
... Some code here
);
}
export default React.memo(CreateEvent);
In your example, you don't have an additional render for React.memo to work.
According to your render logic, there aren't any nextProps, you unmount the component with conditional rendering (creating).
// You toggle with `creating` value, there is only single render each time
creating && <CreateEvent onHidder={modelBoxHandler} allEvents={allEvents}/>
// Works, because there will be multiple renders (nextProps)
true && <CreateEvent onHidder={modelBoxHandler} allEvents={allEvents} />
In this case, you might not need React.memo.
I'm not understanding some ReactJs behavior and would need some help.
I have a Root Functional Component ("Index"), that contains another functional Component ("Preview").
That Preview component contains several other Functional Components ("InlineField").
The app is a simple form, where InlineField is component that renders an input and also contains a state to know if the field is "opened" or "closed" (when close it is displayed as a text, when open it is displayed as an input).
The global state is defined using hooks ad the "Index" level and moved down to the field through props (I've tried the same using Context). This state contains all form values.
The InlineField Component uses hook to maintain its local state only (is open / is closed).
When a an input is changed it updates the state (Index level) which triggers a re-render of the Index as well as its children.
This translate into the currently edited field (InlineField Component with local state = open) to refresh and lose its state value.
My question:
How can I make sure these InlineField Components retain their state even after updating global state?
I could simply move that InlineField Component state to the global state too, but I don't think it makes much sense.
I must be getting something wrong...
Thanks!
Edit: added code sample
Index Component:
import React, { useState, useEffect } from "react"
import Layout from "../components/layout"
const IndexPage = () => {
const [formValues, setFormValues] = useState({
name: 'Myname',
email: 'myemail#mail.com',
})
const onFormValueChange = (key, value) => {
setFormValues({...formValues, [key]: value})
}
return (
<Layout>
<Preview
key="previewyaknow"
formValues={formValues}
onFieldChange={setFormValues}
/>
</Layout>
)
}
export default IndexPage
Preview Component:
import React from 'react'
import { Box, TextField } from "#material-ui/core"
import { InlineField } from './inlineField'
export const Preview = ({formValues, onFieldChange}) => {
return (
<>
<Box display="flex" alignItems="center">
<InlineField
value={formValues.email}
onChange={onFormValueChange}
id="email"
field={<TextField value={formValues.email}/>>>}
/>
</>
)
}
InlineEdit Component
import React, { useState, useEffect } from "react"
export const InlineField = ({onChange, value, id, field}) => {
const [isEdit, setIsEdit] = useState(false)
const onBlur = (e) => {
setIsEdit(false)
}
let view = (<div>{value}</div>);
if (isEdit) {
view = (
<FieldContainer className={classes.fieldContainer}>
{React.cloneElement(field, {
'onBlur': onBlur,
'autoFocus': true,
'onChange': (e) => {
onChange(id, e.target.value)
}
})
}
</FieldContainer>
)
}
return (
<div onClick={()=>setIsEdit(!isEdit)}>
{view}
</div>
)
}