How to navigate between pages in react with hooks - reactjs

I'm a beginner at react hooks and I am trying to assign a state to every page of my module and set the initial state to an intro page and change state (page) on clicking next button.
This is how my main payment page looks
const [portalStage, setPortalStage] = useState("INTRO");
const Screens = {
INTRO: <IntroScreen />,
Post_INTRO: <Payment />, }
useEffect(() => {
setScreen();
}, [context]);
const setScreen = async () => {
setPortalStage("Post_INTRO");
}
{Screens[portalStage]}
<IntroScreen onClick = {setScreen} />
Now I am trying to add an Intro page to it which should always open first and then on clicking the next button on the introscreen it should redirect to the main component.
const IntroScreen = () => {
return (
<Wrapper>
<Content>
<h1>Coupons only!</h1>
<p>Increase your eligibility limit today!</p>
<Button>
Next
</Button>
</Content>
</Wrapper>
);
};
export default IntroScreen;
With this approach I can only see the main page and not the introscreen. What am I doing wrong in assigning state to both screens

A clean way to do this if you dont want use different routes would be to re-render when the next button is clicked. Some thing like below:
MainPaymentPage.js
const MainPaymentPage =(props)=>{
const [isNextClicked,setNextClicked]=useState(false);
let componentCode=<IntroScreen click={()=>setNextClicked(true)}/>
if(isNextClicked){
componentCode=<Payment/>
}
return(
{componentCode}
)
}
export default MainPaymentPage;
and add a click listener in your IntroScreen component like below:
const IntroScreen = (props) => {
return (
<Wrapper>
<Content>
<h1>Coupons only!</h1>
<p>Increase your eligibility limit today!</p>
<Button onClick={props.click}>
Next
</Button>
</Content>
</Wrapper>
);
};
export default IntroScreen;
you will have to make a similar change in your button component so that it can handle the click event. If the Button component comes from a framework like MaterialUI or Bootstrap, it should be able to handle it on its own, but you might have to rename the listener from onClick to whatever your framework wants.
The way the above code works is that there is now a parent component which has a state deciding which component to display based on the state value(isNextClicked, in this case). Initially, it will be set to false, causing the componentCode variable to be set to the code for IntroScreen. When the next button is clicked in the intro screen, it will change the state of the parent component(MainPaymentPage, in this case) to true, causing a re-render. This time, since isNextClicked is true, componentCode will be set to the code for your Payment component.

Related

Update state for multiple components sharing a state

I have these multiple button components and one state isYes. I want to handle the states such that clicking on one button component will trigger applyMethod and the state for all buttons will be toggled. (i.e for button which was clicked + other buttons whose state was previously set to true)
That is: only one button can have isYes as true at a time. Others should be set back to false. Only one button can have "Hello" text, others should be to default "Bye"
Right now, it only toggles state for the button that was clicked.
const [isYes, setIsYes] = useState(false);
const applyMethod = () => {
setYes(!isYes);
};
<Button onClick={applyMethod}>
<div>
isYes
? 'Hello'
: 'Bye'}
</div>
</Button>
You can use different state for each component, I don't think it is a good idea to use same state for multiple components (buttons). You can use an array to store states for different components and depending on each button click, you can update that and other button states accordingly.
I've taken 10 button as example in this case:
import React from "react";
export default function App() {
const btnCount = 10;
const btnsIntialState = new Array(btnCount);
btnsIntialState.fill(false);
btnsIntialState[0] = true;
const [btn, setBtn] = React.useState(btnsIntialState);
console.log(btnsIntialState);
const applyMethod = (index, value) => {
const btnCopy = [...btn];
btnCopy.fill(value);
btnCopy[index] = !value;
setBtn(btnCopy);
};
return (
<div className="App">
{btn.map((value, i) => (
<button onClick={() => applyMethod(i, value)} key={i}>
{value ? "Hello" : "Bye"}
</button>
))}
</div>
);
}
You can do a couple things, first thing you need to look at is your statrmanagemen pattern. Redux is a great way to pass data to multiple components.Theres a little setup but the rest of your code will be lighter and with react-redux hooks any component can access the data..
Now if your looking for an easier fix, you can setup views in your app. This is a component that basically handles all the state for this part a the app or feature. Then you can pass all props or state you like to your buttons

Why React is rendering parent element, even if changed state isn't used in jsx? (Using React Hooks)

Im doing a React small training app using Hooks. Here's the example:
There is a MainPage.js and it has 3 similar child components Card.js. I have global state in MainPage and each Card has its own local state. Every Card has prop "id" from MainPage and clickButton func.
When I click button in any Card there are 2 operations:
Local variable 'clicked' becomes true.
The function from parent component is invoked and sets value to global state variable 'firstCard'.
Each file contains console.log() for testing. And when I click the button it shows actual global variable "firstCard", and 3x times false(default value of variable "clicked" in Card).
It means that component MainPage is rendered after clicking button ? And every Card is rendered too with default value of "clicked".
Why MainPage componenet is rendered, after all we dont use variable "firsCard", except console.log()?
How to make that after clicking any button, there will be changes in exactly component local state, and in the same time make global state variable "firstCard" changed too, but without render parent component(we dont use in jsx variable "firstCard")
Thanks for your help !
import Card from "../Card/Card";
const Main = () => {
const [cards, setCards] = useState([]);
const [firstCard, setFirstCard] = useState(null);
useEffect(() => {
setCards([1, 2, 3]);
}, []);
const onClickHandler = (id) => {
setFirstCard(id);
};
console.log(firstCard); // Showing corrrect result
return (
<div>
{cards.map((card, i) => {
return (
<Card
key={Date.now() + i}
id={card}
clickButton={(id) => onClickHandler(id)}
></Card>
);
})}
</div>
);
};
import React, { useState } from "react";
const Card = ({ id, clickButton }) => {
const [clicked, setClicked] = useState(false);
const onClickHandler = () => {
setClicked(true);
clickButton(id);
};
console.log(clicked); // 3x false
return (
<div>
<h1>Card number {id}</h1>
<button onClick={() => onClickHandler()}> Set ID</button>
</div>
);
};
export default Card;
You have wrong idea how react works.
When you change something in state that component will re render, regardless if you use that state variable in render or not.
Moreover, react will also re render all children of this component recursively.
Now you can prevent the children from re rendering (not the actual component where state update happened though) in some cases, for that you can look into React.memo.
That said prior to React hooks there was a method shouldComponentUpdate which you could have used to skip render depending on change in state or props.

Can I call React Hook inside a function?

I want to change a state using React Hook and also redirect to a new page with passing the changed state to other component after clicking a Button. So I did:
const MovieCard = (props) => {
const [movie, setMovie] = useState({})
function handleClick(movie) {
setMovie(movie)
props.history.push({
pathname: '/otherPage',
data: movie
})
}
return(
...
<Button onClick={() => handleClick(props.movie)} />
...
)
}
But Hook didn't update the state. Because based on React Hook docs, it would works like this:
<Button onClick={() => setMovie(props.movie)} />
Is it possible to call a hook inside a function or there is any way to do it?
It is possible to do what you are doing, however by changing to another page you are clearing the state that was stored on that component. You either need to not redirect (which I'm assuming you don't want to do) or you need to pass on that state to the other component you are redirecting to.

React Router <Link> to dynamic page and go back to previous state in useEffect

I'm trying to make a React app with dynamic pages and navigational fetch with next and previous buttons, when click on the Item it shows the page dynamic page but when I press the back button on the browser it forgets the state where count and input value and shows the initial state. What should I write to save the state so that when I go back it stays on the same count and value and not start from initial state?
const App = () => {
return (
<Router>
Route path='/item/:id' component={Item} />
Route path='/' exact component={Search} />
</Router>
)
}
const Items = (props) => {
return (
<div>
{props.data.map(image => (
/* Link to component Item */
<Link to={`/item/${image.id}`} key={image.id}>
<img src={image.urls.small} alt={image.description} />
</Link>
))}
</div>
);
}
const Search = () => {
useEffect(() => {
getData();
},[count]);
const nextPage = (event) => {
setCount(count + 1);
getData();
event.preventDefault();
}
const prevPage = event => {
if (count > 1) {
setCount(count - 1);
getData();
}
event.preventDefault();
}
return (
<div>
<Items data={images} />
<button onClick={prevPage}>PREV</button>
<button onClick={nextPage}>NEXT</button>
</div>
);
}
It looks like you may need to look into state management systems/patterns. What you are looking for is called persistence, where you can revisit a page and maintain the same values in the same session.
Your form state is being cleared because you're holding it in a "local" component state - a component that is being re-rendered every time you switch pages. The "easy" way to solve this is to manage the Search component's state inside of App and add all the handlers to App as well, this is also called "raising" the state. While this is the "easy" way to do things, it is also quick and dirty (you will probably need to refactor in the future) because it will eventually overcomplicate your App component if you add other page/form states, etc.
The other way (also quick and easy and what I would recommend) is to store your search values in localStorage. You can save them in a JSON format to be read and used to update the form as soon as the component mounts. I think if this is a small app, this is probably the best way.
The approach for larger applications (3+ pages) is to use a global state management system, Flux, Redux, Mobx etc. These systems make it much easier to maintain a global app state and persist information even if you navigate through different pages in the app (as long as you maintain the session). This approach, while I recommend you look into for practice is usually left fort larger applications and can add too much overhead for what its worth on smaller apps.
Another 'quick win' approach is to utilize ReactRouter's state. Basically you can set state properties to a particular point in the router's history. I've used this before to remember scroll position of a lazy-loading grid.
What this does is applies your state to the current browser history position, so when you navigate elsewhere and then hit back on your browser navigation bar, the previous state is also restored (not just the URL).
In essence, create your own Link component wrapper:
import React from 'react';
import { withRouter } from 'react-router';
import { Link } from 'react-router-dom';
class CustomLink extends React.Component {
handleClick(e) {
const { count, input, history } = this.props;
state = { count, input }
history.replace({ state });
}
render() {
const { to, children } = this.props;
return (
<Link
onClick={this.handleClick}
to={to}
>
{children}
</Link>
);
}
}
export default withRouter(CustomLink);

React Ant Design Modal Method update on state change

I'm currently migrating to antd, and have a modal appear on a certain route (ie /userid/info). I'm able to achieve this if I use the antd Modal react component, but I'd like to be able to use the modal methods provided such as Modal.confirm,Modal.info and Modal.error as they offer nicer ui straight out of the box.
I'm running to multiple issues such as having the modal rendered multiple times (both initially and after pressing delete in the delete user case), and unable to make it change due to state (ie display loading bar until data arrives). This is what i've tried but it constantly renders new modals, ive tried something else but that never changed out of displaying <Loader /> even though isFetching was false. I'm not sure what else to try.
const UserInfoFC: React.FC<Props> = (props) => {
const user = props.user.id;
const [isFetching, setIsFetching] = React.useState<boolean>(true);
const [userInfo, setUserInfo] = React.useState<string>('');
const modal = Modal.info({
content: <Loader />,
title: 'User Info',
});
const displayModal = () => {
const renderInfo = (
<React.Fragment>
<p>display user.info</p>
</React.Fragment>
);
const fetchInfo = async () => {
try {
user = // some api calls
setUserInfo(user.info);
modal.update({ content: renderInfo })
} catch (error) {
// todo
}
setIsFetching(false);
};
fetchInfo();
};
displayModal();
return(<div />);
};
reference: https://ant.design/components/modal/#components-modal-demo-confirm
edit: here is a replication of one of the issues I face: https://codesandbox.io/embed/antd-reproduction-template-1jsy8
As mentioned in my comment, you can use a useEffect hook with an empty dependency array to run a function once when the component mounts. You can initiate an async call, wait for it to resolve and store the data in your state, and launch a modal with a second hook once the data arrives.
I made a sandbox here
Instead of going to /:id/info and routing to a component which would have returned an empty div but displayed a modal, I created a displayInfo component that displays a button and that controls the modal. I got rid of attempting to use routes for this.
What I have now is similar to the docs

Resources