I created an application with 2 steps. On each step user should add some data in inputs.
After clicking on button 2, the user changes the step. I want to create a scenario:
User fill form from first step and after that click on button 2
When user clicks on button 1, the first form should appear with the data that was added previously.
const [state, seState] = useState(1);
const one = () => {
seState(1);
};
const two = () => {
seState(2);
};
return (
<div>
{state === 1 ? <Form1 /> : <Form2 />}
<button onClick={one}>1</button>
<button onClick={two}>2</button>
</div>
);
};
Demo: https://codesandbox.io/s/basic-usage-antd480-forked-kdyyn?file=/index.js:252-615
Question: Has ant design this build in functionality? How to keep data from each step, and when user will switch the steps the data should be inside inputs?
To keep the data, no matter ant or not, you would need to make a choice. Firstly you can keep all the forms in the DOM and use CSS to hide them (sandbox):
const [state, seState] = useState(1);
const one = () => {
seState(1);
};
const two = () => {
seState(2);
};
return (
<div>
<div style={state === 1 ? { display: "block" } : { display: "none" }}>
<Form1 />
</div>
<div style={state === 2 ? { display: "block" } : { display: "none" }}>
<Form2 />
</div>
<button onClick={one}>1</button>
<button onClick={two}>2</button>
</div>
);
Secondly you can choose to re-fill the form values every time you do setState by keeping the data in a shared store such as redux or userReducer. I choose the first approach for simple forms that do not require too much logic and don't have large DOMs. Second approach can be a better choice in dynamic and complex data scenarios.
Related
I'm building a page that will render a dynamic number of expandable rows based on data from a query.
Each expandable row contains a grid as well as a button which should add a new row to said grid.
The button needs to access and update the state of the grid.
My problem is that I don't see any way to do this from the onClick handler of a button.
Additionally, you'll see the ExpandableRow component is cloning the children (button and grid) defined in SomePage, which further complicates my issue.
Can anyone suggest a workaround that might help me accomplish my goal?
const SomePage = (props) => {
return (
<>
<MyPageComponent>
<ExpandableRowsComponent>
<button onClick={(e) => { /* Need to access MyGrid state */ }} />
Add Row
</button>
<MyGrid>
<GridColumn field="somefield" />
</MyGrid>
</ExpandableRowsComponent>
</MyPageComponent>
</>
);
};
const ExpandableRowsComponent = (props) => {
const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
return (
<>
{data.map((dataItem) => (
<ExpandableRow id={dataItem.id} />
))}
</>
);
};
const ExpandableRow = (props) => {
const [expanded, setExpanded] = useState(false);
return (
<div className="row-item">
<div className="row-item-header">
<img
className="collapse-icon"
onClick={() => setExpanded(!expanded)}
/>
</div>
{expanded && (
<div className="row-item-content">
{React.Children.map(props.children, (child => cloneElement(child, { id: props.id })))}
</div>
)}
</div>
);
};
There are two main ways to achieve this
Hoist the state to common ancestors
Using ref (sibling communication based on this tweet)
const SomePage = (props) => {
const ref = useRef({})
return (
<>
<MyPageComponent>
<ExpandableRowsComponent>
<button onClick={(e) => { console.log(ref.current.state) }} />
Add Row
</button>
<MyGrid ref={ref}>
<GridColumn field="somefield" />
</MyGrid>
</ExpandableRowsComponent>
</MyPageComponent>
</>
);
};
Steps required for seconds step if you want to not only access state but also update state
You must define a forwardRef component
Update ref in useEffect or pass your API object via useImerativeHandle
You can also use or get inspired by react-aptor.
⭐ If you are only concerned about the UI part (the placement of button element)
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
(Mentioned point by #Sanira Nimantha)
I have made autocomplete features using Downshift using react js. But the problem is when I am searching for something its input field value is disappearing when I click on the outside. Here is the sample code.
import logo from './logo.svg';
import './App.css';
import React, { useState } from "react";
import Highlighter from "react-highlight-words";
import Downshift from "downshift";
import axios from 'axios';
function App() {
const [names, setnames] = useState([{
const [searchTerm, setSearchTerm] = useState('')
const [movie, setmovie] = useState([])
fetchMovies = fetchMovies.bind(this);
inputOnChange = inputOnChange.bind(this);
function inputOnChange(event) {
if (!event.target.value) {
return;
}
fetchMovies(event.target.value);
}
function downshiftOnChange(selectedMovie) {
alert(`your favourite movie is ${selectedMovie.title}`);
}
function fetchMovies(movie) {
const moviesURL = `https://api.themoviedb.org/3/search/movie?api_key=1b5adf76a72a13bad99b8fc0c68cb085&query=${movie}`;
axios.get(moviesURL).then(response => {
setmovie(response.data.results);
// this.setState({ movies: response.data.results });
});
}
return (
<Downshift
onChange={downshiftOnChange}
itemToString={item => (item ? item.title : "")}
>
{({
selectedItem,
getInputProps,
getItemProps,
highlightedIndex,
isOpen,
inputValue,
getLabelProps
}) => (
<div>
<label
style={{ marginTop: "1rem", display: "block" }}
{...getLabelProps()}
>
Choose your favourite movie
</label>{" "}
<br />
<input
{...getInputProps({
placeholder: "Search movies",
onChange: inputOnChange
})}
/>
{isOpen ? (
<div className="downshift-dropdown">
{movie
.filter(
item =>
!inputValue ||
item.title
.toLowerCase()
.includes(inputValue.toLowerCase())
)
.slice(0, 10)
.map((item, index) => (
<div
className="dropdown-item"
{...getItemProps({ key: index, index, item })}
style={{
backgroundColor:
highlightedIndex === index ? "lightgray" : "white",
fontWeight: selectedItem === item ? "bold" : "normal"
}}
>
{item.title}
</div>
))}
</div>
) : null}
</div>
)}
</Downshift>
);
}
export default App;
This is the sample code I have written. Also, when I click shift+home, it is also not working.
Problem 1: when the user clicked the outside text field value whatever I searched this is disappearing.
Problem 2: shift + home is not working also.
Anyone has any idea how to solve this problem?
when the user clicked the outside text field value whatever I searched this is disappearing.
One way you could do it is to set the stateReducer on the Downshift component:
This function will be called each time downshift sets its internal state (or calls your onStateChange handler for control props). It allows you to modify the state change that will take place which can give you fine grain control over how the component interacts with user updates without having to use Control Props. It gives you the current state and the state that will be set, and you return the state that you want to set.
state: The full current state of downshift.
changes: These are the properties that are about to change. This also has a type property which you can learn more about in the stateChangeTypes section.
function stateReducer(state, changes) {
switch (changes.type) {
case Downshift.stateChangeTypes.mouseUp:
return {
...changes,
isOpen: true,
inputValue: state.inputValue,
};
default:
return changes;
}
}
This way if you click outside the text field the dropdown will stay open and the input value won't be reset.
For a list of all state change types see the documentation here
You might also be able to get something working using the onBlur prop on the input, but I didn't get that working.
I am trying to implement a local storage in REACT in order to save in a database the data(numbers) that I type in the input. The code works when I check it in the console.log but it does not work as I wish. Everytime I add a value it deletes the previous one. So I can just read the last input that I put. It does not shows me all the inputs that I put before. I would like to mention that this component is rendered dinamycally so in the parent component I get four different buttons where I can type the number that I want to. Thanks in advance
import React, { useState, useEffect } from 'react';
function Stake() {
const [stakes, setStakes] = useState([]);
const addStake = (e) => {
e.preventDefault();
const newStake = {
input: e.target.stake.value,
};
setStakes([...stakes, newStake]);
};
useEffect(() => {
const json = JSON.stringify(stakes);
localStorage.setItem("stakes", json);
}, [stakes]);
console.log(stakes)
return (
<div>
<form onSubmit={addStake}>
<input style={{ marginLeft: "40px", width: "50px" }} type="text" name="stake" required />
{/* <button style={{ marginLeft: "40px" }} type="submit">OK</button> */}
</form>
</div>
);
}
export default Stake;
Right now your component always starts with an empty array: useState([]).
When the component renders for the first time, you need to retrieve existing values from locaStorage and set it as the local state of component:
useEffect(() => {
const stakes= JSON.parse(localStorage.getItem("stakes")) || [];
setStakes(stakes);
}, []);
I want to build form in Which there are some text fields as shown in picture
in this form there is a button and after clicking the Add Units button a new form will appear
how can i render this sub form by using button onClick Event-Handler and i also want to render it as many times as i click the button , if i click button one time then it shows the the sub form only one time if i click the button two times then it will show two times
-The main issue is I want the sub form to appear as many time i click the button
(optional if i want to remove the rendered sub form using a button click then please mention the code )
use of react hooks is preferable
you can ask me anything related to question
Here is an example of how to solve your problem. You don't need useEffect, and you don't need three separate state variables. You just need one array of objects.
import React, { useState } from "react";
import ContainerSlot from "./ContainerSlot";
function ReceiptContainer() {
const [containers, setContainers] = useState([]);
const handleAddContainer = () => {
// adding an empty object; you will likely need to
// initialize this object with whatever values are stored
// in the container form
setContainers((current) => [...current, {}]);
};
const handleRemove = (index) => {
const current = [...containers];
current.splice(index, 1);
setContainers(current);
};
return (
<div className="receiptContainer container">
{/* Heading */}
<div className="receiptContainer__heading mt-3">
<h4>Container Invoice</h4>
<hr />
</div>
<button onClick={handleAddContainer}>Add container</button>
<div className="receiptContainer__container">
{/* You will likely need to pass whatever values are in
the container to your ContainerSlot component */}
{containers.map((container, index) => {
const handleRemoveClick = () => {
handleRemove(index);
};
return (
<div
key={`container-${index}`}
style={{ display: "flex", alignItems: "center" }}
>
<div>
<ContainerSlot {...container} />
</div>
<div>
<button onClick={handleRemoveClick}>Remove</button>
</div>
</div>
);
})}
</div>
</div>
);
}
export default ReceiptContainer;
EDIT: updated to show how to remove an item
I'm new to react and I'm having an issue of multiple renders and I was just wondering if I'm doing this right, so I dispatched an action to get a note list, in my list component which looks like this for now :
import React, {useState, useEffect} from 'react';
export default function NoteList (props){
const [ noteList, updateNoteList ] = useState([]);
useEffect(()=>{
updateNoteList(
props.noteList.map(note => {
return {...note, mode : 'title-mode'};
})
)
},[props.noteList])
console.log(noteList);
return (
<div>
Notes come here
</div>
)
}
this component is connected in another container class but that's irrelevant, so what happens is this component renders 4 times, two times without the useEffect hook and two more with it, what I want to achieve is I need to add an item in the object of each note (which is mode : title-mode) in a state for this component which works fine with this code, as to why I'm adding this mode in a state is that I want to change this inside the note array so I can change the view mode for each note , but this component renders 4 times as I mentioned, and in my head no way that this is the correct way to do this.
Please help if you have the time .
We could achieve the display of the notes list by making a display-mode state in the <Note /> component it self so changing the mode won't affect other notes and this way we won't have extra re-renders, also using this approach will allow also modifying the note locally without dispatching it to the store then we could update the store at the end gaining in perfs.
So basically this is the approach (codesandbox):
const Note = ({ title, content }) => {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div
style={{ border: "1px solid", margin: 5 }}
onClick={() => setIsExpanded(!isExpanded)}
>
{!isExpanded ? (
<div>
<h2>{title}</h2>
</div>
) : (
<div>
<h2>{title}</h2>
<p>{content}</p>
</div>
)}
</div>
);
};
function App() {
// this is the notes state, it could be coming from redux store so
// we could interact with it (modifying it if we need)
const [notes, setNotes] = React.useState([
{ id: 1, title: "note 1", content: "this is note 1" },
{ id: 2, title: "note 2", content: "this is note 2" }
]);
return (
<div className="App">
{notes.map((note) => (
<Note key={note.id} {...note} />
))}
</div>
);
}