How to keep track of selected component? - javascript

I'm working on a coding challenge and I'm having problems to navigate back and forth between my components. Here's what a very simplified version of my parent component looks like:
import React, { useState } from 'react';
function App() {
const [component, setComponent] = useState({
shoppingBasket: true,
contactDetails: false,
paymentOptions: false,
checkout: false,
summary: false
});
const switchState = input => {
const { name, value } = input.target;
setComponent({
...component,
[name]: value
});
};
return (
<>
{
component.shoppingBasket &&
<ShoppingBasket
shoppingBasket={component.shoppingBasket}
switchState={switchState}
/>
}
{
component.contactDetails &&
<ContactDetails
contactDetails={component.contactDetails}
switchState={switchState}
/>
}
{
component.paymentOptions &&
<PaymentOptions
paymentOptions={component.paymentOptions}
switchState={switchState}
/>
}
{
component.checkout &&
<Checkout
checkout={component.checkout}
switchState={switchState}
/>
}
{
component.summary &&
<Summary
summary={component.summary}
switchState={switchState}
/>
}
</>
);
}
export default App;
It's supposed to be the checkout screen of an e-commerce page and it starts with the <ShoppingBasket /> component. When clicking "Continue", it shows the next component, when clicking "Back" it goes back to the previous one. They should appear in the order shown in the code.
My first attempt was to show the next component only when the previous one(s) evaluate true but at the end it was showing all of the components, so that didn't work. Note: I passed the switchState function and the respective state as prop to the child component.
I guess the smartest way is to show only the component that is currently selected but how do I do that? I assume working with IDs?
And would it be still necessary to keep track of the previous components evaluating to true, when it's only showing the selected one anyway?
SOLUTION:
Parent component (simplified but working):
import React, { useState } from 'react';
// COMPONENTS
import ShoppingBasket from './components/ShoppingBasket';
import PaymentOptions from './components/PaymentOptions';
import ContactDetails from './components/ContactDetails';
import Checkout from './components/Checkout';
import Summary from './components/Summary';
export default function App() {
const [component, setComponent] = useState(0);
const switchComponent = (index) => {
setComponent(index);
};
return (
<>
{
{
0: <ShoppingBasket switchComponent={switchComponent} />,
1: <ContactDetails switchComponent={switchComponent} />,
2: <PaymentOptions switchComponent={switchComponent} />,
3: <Checkout switchComponent={switchComponent} />,
4: <Summary />,
}[component]
}
</>
);
}
Child component with index 3 (also simplified):
import React from 'react';
export default function Checkout({ switchComponent }) {
return (
<>
<button onClick={() => switchComponent(2)}>BACK</button>
<button onClick={() => switchComponent(4)}>CONTINUE</button>
</>
);
}

Update:
import React, { useState } from 'react';
function App() {
const [component, setComponent] = useState(0);
const switchComponent = index => {
setComponent(index);
};
return (
<>
{
// this act like switch case js function
{
0:
(<ShoppingBasket
//shoppingBasket={component.shoppingBasket} // no need for this
componentIndex={component}
switchState={switchComponent}
/>),
1:
(<ContactDetails
// contactDetails={component.contactDetails}
componentIndex={component}
switchState={switchComponent}
/>),
2:
(<PaymentOptions
// paymentOptions={component.paymentOptions}
componentIndex={component}
switchState={switchComponent}
/>),
3:
(<Checkout
// checkout={component.checkout}
componentIndex={component}
switchState={switchComponent}
/>),
4:
(<Summary
// summary={component.summary}
componentIndex={component}
switchState={switchComponent}
/>)
}[component]
}
</>
);
}
export default App;

ShoppingBasket.js
const ShoppingBasket = props => {
return (
// your ShoppingBasket component .....
)
}
** ContactDetails.js**
const ContactDetails = props => {
return (
// your ContactDetails component .....
)
}
....... the same for other components
App.js
import React, { useState } from 'react';
function App() {
const [component, setComponent] = useState(0);
const switchComponent = index => {
setComponent(index);
};
return (
<>
{
// this act like switch case js function
//
{
0:
(<ShoppingBasket/>),
1:
(<ContactDetails/>),
2:
(<PaymentOptions/>),
3:
(<Checkout/>),
4:
(<Summary/>)
}[component]
}
// navigation buttons .... always visible
<NavigateButtons
componentIndex={component}
switchState={switchComponent}
/>
</>
);
}
export default App;
----------------------------------***----------------
Note : make sure the buttons next and previous are just one splited component so you can use it in diffrent other components
const NavigateButtons = (props) => (
<div>
<Button
disabled={componentIndex == 4}
onClick={()=> props.switchState(componentIndex + 1)}>next
</Button>
<Button
disabled={componentIndex == 0}
onClick={()=> props.switchState(componentIndex - 1)}>previous
</Button>
</div>
)

Related

React 17: Trouble with code executed twice

I have a "presentational" Component:
import { Fragment, useEffect } from 'react'
import { Card, Col, Row } from 'react-bootstrap'
import { connect } from "react-redux"
import Filter from '../common/Filter'
import { actions as tagsActions } from "../../actions/TagsActions"
export const DocumentsPresentational = props => {
const { fetchTags } = props
useEffect(() => {
fetchTags()
}, [fetchTags])
const formatTags = () => {
let tags = null
if (props.tags && props.tags.length > 0) {
tags = props.tags.map(function(item) {
return {
label: item.key,
value: item.key
}
})
}
return tags
}
return (
<Fragment>
<Row>
<Col><h1 className="h3 mb-4 text-gray-800">Documenti</h1></Col>
</Row>
<Card>
<Card.Body>
<Row>
<Col md={4}>
<p>Tags</p>
<Filter
options={formatTags()}
/>
</Col>
</Row>
</Card.Body>
</Card>
</Fragment>
)
}
const mapStateToProps = state => {
return {
isLoading: state.documents.isLoading,
items: state.documents.items,
item: state.documents.item,
total: state.documents.total,
idItem: state.documents.idItem,
feedbackSuccess: state.documents.feedbackSuccess,
feedbackError: state.documents.feedbackError,
showModalDetail: state.documents.showModalDetail,
closeModalDetail: state.documents.closeModalDetail,
tags: state.tags.items,
}
}
const mapDispatchToProps = dispatch => {
return {
fetchTags: (orderBy, orderWay, page, perPage) => dispatch(tagsActions.fetchItems(orderBy, orderWay, page, perPage)),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(DocumentsPresentational)
Filter is a simple
import Select from 'react-select'
const Filter = (props) => {
return (
<Select
isMulti
options={props.options}
/>
)
}
export default Filter
My trouble is that I have twice printed in console the tags object, got from formatTags function.
Why it is executed twice?
Thank you
In DocumentsPresentational component, you have called fetchTags twice. useEffect runs fetchTags at the initial rendering. And then, you have called fetchTags in Select component. If fetchTags is a network or cpu intensive function, then you can just call it once in the useEffect and sign it's returned data to a state variable and pass to Select component.

Reducer/Context Api

So I have a Context created with reducer. In reducer I have some logic, that in theory should work. I have Show Component that is iterating the data from data.js and has a button.I also have a windows Component that is iterating the data. Anyway the problem is that when I click on button in Show Component it should remove the item/id of data.js in Windows Component and in Show Component, but when I click on it nothing happens. I would be very grateful if someone could help me. Kind regards
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
```Context```
import React, { useState, useContext, useReducer, useEffect } from 'react'
import {windowsIcons} from './data'
import reducer from './reducer'
const AppContext = React.createContext()
const initialState = {
icons: windowsIcons
}
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
const remove = (id) => {
dispatch({ type: 'REMOVE', payload: id })
}
return (
<AppContext.Provider
value={{
...state,
remove,
}}
>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
reducer.js
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
return {
...state,
icons: state.icons.filter((windowsIcons) => windowsIcons.id !== action.payload),
}
}
}
export default reducer
``data.js```
export const windowsIcons =[
{
id:15,
url:"something/",
name:"yes",
img:"/images/icons/crud.png",
},
{
id:16,
url:"something/",
name:"nine",
img:"/images/icons/stermm.png",
},
{
id:17,
url:"domething/",
name:"ten",
img:"/images/icons/ll.png",
},
{
id:18,
url:"whatever",
name:"twenty",
img:"/images/icons/icons848.png",
},
{
id:19,
url:"hello",
name:"yeaa",
img:"/images/icons/icons8-96.png",
},
]
``` Show Component```
import React from 'react'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
const Show = () => {
const { remove, } = useGlobalContext()
return (
<div className='control'>
{windowsIcons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
</div>
)
}
export default Show
import React from 'react'
import {windowsIcons} from "../data"
import './WindowsIcons.css'
const WindowsIcons = ({id, url, img, name}) => {
return (
<>
{windowsIcons.map((icons)=>{
const {id, name , img ,url} =icons
return(
<div className='windows__icon' >
<li className='windows__list' key={id}>
<a href={url}>
<img className='windows__image' src={img}/>
<h4 className='windows__text'>{name}</h4>
</a>
</li>
</div>
)
})}
</>
)
}
Issue
In the reducer you are setting the initial state to your data list.
This is all correct.
However, then in your Show component you are directly importing windowsIcons and looping over it to render. So you are no longer looping over the state the reducer is handling. If the state changes, you won't see it.
Solution
In your Show component instead loop over the state that you have in the reducer:
const { remove, icons } = useGlobalContext()
{icons.map((unin) => {
// Render stuff
}
Now if you click remove it will modify the internal state and the icons variable will get updated.
Codesandbox working example

Toggle visibility of two components from buttons inside of them in React

I have this structure
component 1
import React, { useState } from 'react'
export default function Component1() {
return (
<div>
<button onClick={handleChange}></button>
</div>
)
}
component 2
import React, { useState } from 'react'
export default function Component2() {
return (
<div>
<button onClick={handleChange}></button>
</div>
)
}
and the parent
import React from 'react'
export default function Parent() {
return (
<div>
<Component1 />
<Component2 />
</div>
)
}
The question is, how can I toggle visibility between the two, without having a button in the parent. Just the buttons inside each component. - The Component1 should be visible by default and when you press the button in Component1 it will hide it and show Component2 and vice-versa.
I've tried using useState hook on the Component1 button, but I'm not sure how to export the state and add it to the parent component.
const [showMini, setShowMini] = useState(false);
const handleChange = () => {
setShowMini(true);
}
Is this possible? or it's possible just with a button in the parent that control the two?
Thanks
Try this:
import React from 'react'
export default function Parent() {
const[show,setShow]=useState(false);
const handleChange=()=>{
setShow(!show);
}
return (
<div>
{show ? <Component2 handleChange={handleChange}/> : <Component1 handleChange={handleChange}/>}
</div>
)
}
and inside Component1 have this:
import React, { useState } from 'react'
export default function Component1({handleChange}) {
return (
<div>
<button onClick={handleChange}></button>
</div>
)
}
Similarly do it for Component2
You can do with state value and pass handleChange function ad props in the child component and in click on the button in child component call handleChange method under parent component and show hide based on state value.
import React from 'react'
const [showChild, setshowChild] = useState(false);
const handleChange = () => {
setshowChild(!showChild);
}
export default function Parent() {
return (
<div>
{showChild ? <Component2 handleChange = {handleChange}/> : <Component1 handleChange= {handleChange} />}
</div>
)
}
You can manage the state in the parent and pass down a handler to the children
import React, { useState } from 'react'
const [currentView, setCurrentView] = useState('component1')
const changeCurrentView = (view) => setCurrentView(view)
const renderViews = () => {
switch(currentView) {
case 'component1':
return <Component1 changeCurrentView={changeCurrentView} />
case 'component2':
return <Component2 changeCurrentView={changeCurrentView} />
default:
return <Component1 changeCurrentView={changeCurrentView} />
}
}
export default function Parent() {
return (
<div>
{renderViews()}
</div>
)
}
Other components
import React from 'react'
export default function Component1({ changeCurrentView }) {
return (
<div>
<button onClick={() => changeCurrentView('component1')}></button>
</div>
)
}
export default function Component2({ changeCurrentView }) {
return (
<div>
<button onClick={() => changeCurrentView('component2')}></button>
</div>
)
}
Your parent component should keep track of the state:
import React, {useState} from 'react'
export default function Parent() {
const [showChild, setShowChild] = useState(1);
const showNextChild = () => {
setShowChild( showChild === 1 ? 2 : 1 ); // set to 2 if already 1, else to 1
}
return (
<div>
{ showChild === 1 && <Component1 handleChange={showNextChild} /> }
{ showChild === 2 && <Component2 handleChange={showNextChild} /> }
</div>
)
}
A few notes:
Your components are identical, so the duplication is unnecessary, but I assume the example is just contrived.
This assumes toggling 2 components back and forth. If you have more than 2 components you are "looping" through, you can instead increment the previous showChild state and then reset it to 0 if higher than the # of components you have.
The syntax you see, showChild === 1 && <Component1 ... uses the behavior of the && operator which actually returns the 2nd item it is evaluating if both are true. In other words, const isTrue = foo && bar; sets isTrue to bar, not true as you might expect. (You know, however, that bar is "truthy" in this case, so isTrue still works in future if statements and such.) The component is always truthy, so the effect is that the component is returned if the first part is true, otherwise it is not. It's a good trick for conditionally showing components.
Try this. You can send information from child to parent with functions passed as a prop.
Parent Component:
const Parent = () => {
const [show, setShow] = useState(true);
const toggleVisibility = () => {
setShow(!show);
};
return (
<div>
{show ? (
<Child1 toggle={toggleVisibility}></Child1>
) : (
<Child2 toggle={toggleVisibility}></Child2>
)}
</div>
);
};
Child 1
const Child1 = (props) => {
const { toggle } = props;
return (
<div style={{ width: '100px', height: '100px' }}>
<button onClick={toggle}>Child 1's button</button>
</div>
);
};
Child 2
const Child2 = (props) => {
const { toggle } = props;
return (
<div style={{ width: '100px', height: '100px' }}>
<button onClick={toggle}>Child 2's button</button>
</div>
);
};

Render the component lazily, on the select of a value change - React

How to render the component lazily, on the select of a value change.
Here is my below snippet, i am not able to see the title, nothing is rendering
App.js
import React, { useState } from "react";
import "./styles.css";
import { SectionOne, SectionTwo } from "./ChildComponents";
export default function App() {
const [state, setState] = useState(null);
const sectionToBeDisplayed = {
section_one: (title = "section one") => <SectionOne title={title} />,
section_two: (title = "section two") => <SectionTwo title={title} />
};
const handleSelectChange = (e) => {
setState(e.target.value);
};
return (
<div className="App">
<select onChange={handleSelectChange}>
<option disabled selected value>
{" "}
-- select an option --{" "}
</option>
<option value="section_one">SectionOne</option>
<option value="section_two">SectionTwo</option>
</select>
{sectionToBeDisplayed[state]}
</div>
);
}
ChildComponents.js
import React from "react";
export const SectionOne = ({ title }) => {
return <h1>{title}</h1>;
};
export const SectionTwo = ({ title }) => {
return <h2>{title}</h2>;
};
So based on the selection only i need to load this component, I am newbie to react, read that we can use React.lazy but i don't see any use case like this. Also whenever i change it should not build the dom again. Should we use useMemo, I am not clear to use React.memo or useMemo, Which one is better.
You need to invoke the function component:
// Component is a function component
const Component = (title = "section one") => <SectionOne title={title} />;
// Invoke it
<Component/>
const sectionToBeDisplayed = {
section_one: ...
};
export default function App() {
...
const SectionComponent = sectionToBeDisplayed[state];
return (
<div className="App">
...
<SectionComponent />
</div>
);
}

selected options from radio and checkbox are not passing data

I am trying to make a set of questions through multiple pages, so I need the selected answer from page 1 and page 2 to be passed to page 3 because page 3 is like the confirmation page which will show all the selected answer from the past 2 pages.
The interface is successfully shown, well, easy but it seems like there is no data passed at all, oh, and the types of questions are radio and checkbox, so it's kinda hard for me because these 2 types are something new for me (if textarea or normal input, is easy).
This is the mainpage.jsx
// MainForm.jsx
import React, { Component } from 'react';
import AircondQuantity from './AircondQuantity';
import ServicePackage from './ServicePackage';
import Confirmation from './Confirmation';
import Success from './Success';
class MainForm extends Component {
state = {
step: 1,
oneAircond: ''
}
nextStep = () => {
const { step } = this.state
this.setState({
step : step + 1
})
}
prevStep = () => {
const { step } = this.state
this.setState({
step : step - 1
})
}
handleChange = input => event => {
this.setState({ [input] : event.target.value })
}
render(){
const {step} = this.state;
const { oneAircond } = this.state;
const values = { oneAircond };
switch(step) {
case 1:
return <AircondQuantity
nextStep={this.nextStep}
handleChange = {this.handleChange}
values={values}
/>
case 2:
return <ServicePackage
nextStep={this.nextStep}
prevStep={this.prevStep}
handleChange = {this.handleChange}
values={values}
/>
case 3:
return <Confirmation
nextStep={this.nextStep}
prevStep={this.prevStep}
values={values}
/>
case 4:
return <Success />
}
}
}
export default MainForm;
this is the first page, AircondQuantity.jsx
// AircondQuantity.jsx
import React, { Component } from 'react';
import { Form, Button, FormRadio, Radio } from 'semantic-ui-react';
class AircondQuantity extends Component{
saveAndContinue = (e) => {
e.preventDefault()
this.props.nextStep()
}
render(){
const { values } = this.props;
return(
<Form >
<h1 className="ui centered"> How many aircond units do you wish to service? </h1>
<Form.Field>
<Radio
label='1'
name='oneAircond'
value='oneAircond'
//checked={this.state.value === this.state.value}
onChange={this.props.handleChange('oneAircond')}
defaultValue={values.oneAircond}
/>
</Form.Field>
<Button onClick={this.saveAndContinue}> Next </Button>
</Form>
)
}
}
export default AircondQuantity;
this is the next page, ServicePackage.jsx
// ServicePackage.jsx
import React, { Component } from 'react';
import { Form, Button, Checkbox } from 'semantic-ui-react';
import { throws } from 'assert';
class ServicePackage extends Component{
saveAndContinue = (e) => {
e.preventDefault();
this.props.nextStep();
}
back = (e) => {
e.preventDefault();
this.props.prevStep();
}
render(){
const { values } = this.props
return(
<Form color='blue' >
<h1 className="ui centered"> Choose your package </h1>
<Form.Field>
<Checkbox
label={<label> Chemical Cleaning </label>} />
</Form.Field>
<Form.Field>
<Checkbox
label={<label> Deep Cleaning </label>} />
</Form.Field>
<Button onClick={this.back}> Previous </Button>
<Button onClick={this.saveAndContinue}> Next </Button>
</Form>
)
}
}
export default ServicePackage;
this is the confirmation.jsx page, the page that will show all the selected options
// Confirmation.jsx
import React, { Component } from 'react';
import { Button, List } from 'semantic-ui-react';
import AircondQuantity from './AircondQuantity';
class Confirmation extends Component{
saveAndContinue = (e) => {
e.preventDefault();
this.props.nextStep();
}
back = (e) => {
e.preventDefault();
this.props.prevStep();
}
render(){
const {values: { oneAircond }} = this.props;
return(
<div>
<h1 className="ui centered"> Confirm your Details </h1>
<p> Click Confirm if the following details have been correctly entered </p>
<List>
<List.Item>
<List.Content> Aircond Quantity: {oneAircond}</List.Content>
</List.Item>
</List>
<Button onClick={this.back}>Back</Button>
<Button onClick={this.saveAndContinue}>Confirm</Button>
</div>
)
}
}
export default Confirmation;
I am very new in using React and I know that I have some mistakes in transferring values or variables but I can't detect it, coz I am a newbie, so em, can you help me? thank you.
This doesn't look right to me:
onChange={this.props.handleChange('oneAircond')}
Firstly, it's going to be called instantly, before onChange is actually called, to fix that do this:
onChange={() => this.props.handleChange('oneAircond')}
However you'll also need to pass the change event from the radio button, try this:
onChange={(event) => this.props.handleChange('oneAircond')(event)}
The handleChange function below is a function that returns another function, the first one taking the 'oneAircond' string (input) and returning a function that is expecting the event from the radio button to be passed which is what you're missing
handleChange = input => event => {
this.setState({ [input] : event.target.value })
}
Ok, first i convert solution into hooks:
// MainForm.jsx
import React, { Component, useState, useEffect } from 'react';
import AircondQuantity from './AircondQuantity';
import ServicePackage from './ServicePackage';
import Confirmation from './Confirmation';
// import Success from './Success';
let MainForm = (props) => {
const [step, setStep] = useState(1)
const [input, setInput] = useState([])
const [oneAircond, setoneAircond] = useState('')
const nextStep = () => {
setStep(step + 1)
}
const prevStep = () => {
setStep(step - 1)
}
const handleChange = input => event => {
setInput({ [input] : event.target.value})
console.log(input)
}
const values = { oneAircond };
return(
<React.Fragment>
{(() => {
switch(step) {
case 1:
return <AircondQuantity
nextStep={nextStep}
handleChange = {handleChange}
values={values}
/>
case 2:
return <ServicePackage
nextStep={nextStep}
prevStep={prevStep}
handleChange = {handleChange}
values={values}
/>
case 3:
return <Confirmation
nextStep={nextStep}
prevStep={prevStep}
values={values}
/>
case 4:
// return <Success />
}
})()}
</React.Fragment>
)
}
export default MainForm;
Next I transform AirCondQuantity.jsx, and create new component Radio.jsx, which holds Radio structure (it is a component into which we inject data directly)
So here is:
// AircondQuantity.jsx
import React, { Component, useState } from 'react';
import { Form, Button, FormRadio, } from 'semantic-ui-react';
import Radio from './Radio';
let AircondQuantity = (props) => {
const [radio, setRadio] = useState();
const radioSelect = (name, e) => {
localStorage.setItem('FirstStep', name);
setRadio({ selectedRadio: name })
console.log(radio)
}
const saveAndContinue = (e) =>{
e.preventDefault()
props.nextStep()
}
return(
<Form >
<h1 className="ui centered"> How many aircond units do you wish to service? </h1>
<Form.Field>
<Radio
handleRadioSelect={radioSelect}
selectedRadio={radio}
name ="oneAircond"/>
<Radio
handleRadioSelect={radioSelect}
selectedRadio={radio}
name ="twoAircond"/>
</Form.Field>
<Button onClick={saveAndContinue}> Next </Button>
</Form>
)
}
export default AircondQuantity;
I added function, which setting the item to LocalStorage + send it in radio state
Additional here is a Radio component:
import React, { Component, useState } from 'react';
const Radio = (props) => {
return (
<div className = "RadioButton"
onClick={() => props.handleRadioSelect(props.name)} >
<div>
<input id={props.id} value={props.value} type="radio" checked={props.selectedRadio === props.name} />
{props.name}
</div>
</div>
);
}
export default Radio;
The solution works, despite one thing - it doesn't check the box, I have no idea how to fix it.

Categories