So im working on a React app, it's basically a CRUD app pretty much (serving right now as a learning practice project).
Anyways, I have a modal that appears when the user needs to edit some basic information on a resource (right now it's just a "name" that's being edited). The modal is a presentational/dumb component that takes in props from it's parent (Which is essentially just a table of the resources/items).
If im literally only going to be submitting the form to change 1 field I would only need a state with 1 item (for that input field to edit).......does it make sense to STILL make it a class just because it has a state? From what i've read if you have a state you should automatically make it a class?
Is this always true?
Are you talking like this?
When you click on any list item it goes to child(functional) component as you were asking.
You can check working live example Live demo
export default function App() {
const [arr, setArr] = useState(["One", "Two", "Three"]);
const [name, setName] = useState("");
const handleChange = e => {
setName(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
setArr([...arr, name]);
setName("");
};
return (
<div>
{arr.map(a => (
<div style={myStyle} key={a}>
{a} <button onClick={() => setName(a)}>Edit</button>
</div>
))}
<Child
handleChange={handleChange}
handleSubmit={handleSubmit}
name={name}
/>
</div>
);
}
const Child = ({ handleChange, name, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<input type="text" value={name} onChange={handleChange} />
<input type="submit" value="Save" />
</form>
);
};
const myStyle = {
margin: 5,
cursor: "pointer",
border: "1px solid lightgrey",
padding: "5px",
display: "flex",
justifyContent: "space-between"
};
Yes whenever you use a state you should make it a class (way back then) but now we have this thing called HOOKS that allows you to use states within functional components.
Simple sample implementation below.
import React, { useState } from 'react';
const Example = () => {
//creates a state name and a setter for it. Then initialize it with empty string
const [name, setName] = useState("");
return (
<div>
//suppose you have input here
<button onClick={() => setName("Your name value")}>
Click me
</button>
</div>
);
}
Related
I'm trying to create a reusable "Modal" component in React. The Modal component will have some input fields and a submit button and when user clicks the Submit button in the Modal. the modal should be closed and the data user entered in the input fields should be passed up to the parent component through a callback function declared in parent component. The code is working fine but i think(not sure) it's not the right solution as i had to create "React state" in both the Modal(child) component and the parent component where the Modal component is used. As you can see(sandbox code) the state is repetitive in both components and i was wondering how can i keep the state in only one place. My understanding is I'd definitely need to create a state in Modal component for the input fields to keep track of changes but i am not sure how can i use that data in parent component without creating same state
Here is a Sandbox that i created to show what i've done so far:
https://codesandbox.io/s/jovial-matsumoto-1zl9b?file=/src/Modal.js
In order to not duplicate state I would enclose the inputs in the modal in a form element and convert the inputs to uncontrolled inputs. When the form is submitted grab the form field values and pass in the formData callback and reset the form. Explicitly declare the button to be type="submit".
const Modal = (props) => {
const onFormSubmit = (e) => {
e.preventDefault();
const firstName = e.target.firstName.value;
const lastName = e.target.lastName.value;
props.formData({ firstName, lastName });
e.target.reset();
};
if (!props.show) {
return null;
} else {
return (
<div className="modal" id="modal">
<form onSubmit={onFormSubmit}>
<input type="text" name="firstName" />
<input type="text" name="lastName" />
<button className="toggle-button" type="submit">
Submit
</button>
</form>
</div>
);
}
};
It seems the crux of your question is about the code duplication between your parent component and a modal. What I would really suggest here is to decouple the modal from any specific use case and allow any consuming parent components to pass a close handler and children components.
This keeps the state in the parent, and thus, control.
Example modal component:
const Modal = ({ onClose, show, children }) =>
show ? (
<div className="modal" id="modal">
<button type="button" onClick={onClose}>
X
</button>
{children}
</div>
) : null;
Parent:
function App() {
const [isShowModal, setIsShowModal] = useState(false);
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
const showModal = (e) => {
setIsShowModal((show) => !show);
};
const closeModal = () => setIsShowModal(false);
const onFormSubmit = (e) => {
e.preventDefault();
const firstName = e.target.firstName.value;
const lastName = e.target.lastName.value;
setFirstName(firstName);
setLastName(lastName);
e.target.reset();
closeModal();
};
return (
<div className="App">
<h2>First Name is : {firstName}</h2>
<h2>Last Name is : {lastName}</h2>
<button className="toggle-button" onClick={showModal}>
Show Modal
</button>
<Modal show={isShowModal} onClose={closeModal}>
<form onSubmit={onFormSubmit}>
<input type="text" name="firstName" />
<input type="text" name="lastName" />
<button className="toggle-button" type="submit">
Submit
</button>
</form>
</Modal>
</div>
);
}
Here it's really up to you how you want to manage the interaction between parent and modal content. I've shown using a form and form actions so your state isn't updated until a user submits the form. You can use form utilities (redux-form, formix, etc...) or roll your own management. Life's a garden, dig it.
You can define these states in your App component :
const [showModal, setShowModal] = useState(false);
const [user, setUser] = useReducer(reducer, {
firstName: "",
lastName: ""
});
const toggleModal = () => {
setShowModal(!showModal);
};
And pass them through props when you render your Modal component :
<div className="App">
<h2>First Name is : {firstName}</h2>
<h2>Last Name is : {lastName}</h2>
<button className="toggle-button" onClick={toggleModal}>
Show Modal
</button>
<Modal
show={showModal}
hide={() => toggleModal(false)}
user={user}
updateUser={setUser}
/>
</div>
Then, in your Modal component, define states for firstName and lastName :
const Modal = (props) => {
const [firstName, setFirstName] = useState(props.user.firstName);
const [lastName, setLastName] = useState(props.user.lastName);
const onFirstNameChange = ({ target }) => {
setFirstName(target.value);
};
const onLastNameChange = ({ target }) => {
setLastName(target.value);
};
// ...
}
And now you can submit changes like this from your Modal component :
const onFormSubmit = (e) => {
props.updateUser({
firstName,
lastName
});
props.hide();
};
Im using react hooks to create a multi-select component, that generates sliders for each selection (using material-ui). Once a user makes their selections I'm generating an individual slider for each selection using map. Each slider component has its own instance of useState to keep track of the value of the slider. Im passing a nodeid as a key to the component and would like to have an array of [nodeid, valueFromSlider] for each slider returned. For example: if a user makes 3 selections and then calls 'update feedback scores' button, returned values should be [[nodeid, valueFromSlider], [nodeid, valueFromSlider], [nodeid, valueFromSlider]].
What is the best way for my parent component to know what each individual sliders state is? is there a way to call component by id? Should i be usings forms or something? first time really using hooks since switching from redux.
Here is my code, thanks in advance.
const NodeSlider = (props) => {
const { node, nodeId } = props;
const [value, setValue] = useState(5);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<div>
<Typography id="discrete-slider" gutterBottom>
{node.display_name}
</Typography>
<Slider
step={1}
marks
min={-10}
max={10}
valueLabelDisplay="on"
defaultValue={5}
value={value}
onChange={handleChange}
/>
</div>
);
}
const TunerSlider = (props) => {
const { sliders } = props;
const [feedbackArray, setFeedbackArray] = useState(null);
console.log('sliders', sliders);
const createFeedbackArray = () => {
// FIGURE THIS OUT? use this to call setfeedback array with value from node slider?
};
return (
<div>
{sliders.map((slider) => (
<NodeSlider
key={slider.node_id}
node={slider}
nodeId={slider.node_id}
/>
))}
<Button
variant="contained"
color="primary"
onClick={() => createFeedbackArray()}
>
Update Feedback Scores
</Button>
</div>
);
};
const TunerSearch = ({ nodeData }) => {
const [searchValues, setSearchValues] = useState(null);
const [sliderValues, setSliderValues] = useState(null);
const generateSlider = () => {
setSliderValues(searchValues);
};
return (
<div>
<div>Tuner Search</div>
<Autocomplete
multiple
id="tags-outlined"
options={nodeData}
getOptionLabel={(option) => option.display_name}
filterSelectedOptions
onChange={(event, value) => setSearchValues(value)}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="select nodes"
placeholder="add more nodes"
/>
)}
/>
<Button
variant="contained"
color="primary"
onClick={() => generateSlider()}
>
Generate Node Sliders
</Button>
{sliderValues ? <TunerSlider sliders={sliderValues} /> : null}
</div>
);
};
export default TunerSearch;
The best approach would be to move the states to the highest common parent.
Then you can define your function that will track your feedback in the parent component and then share them with the children component(s).
Here is an example of how I would approach this:
There is array with words. const = ['cat','dog']. Also textarea or input, or editable div. I need to write words in this field and highlight words if they match.
Now I have a bad variant because I use the highlight npm package and contain it over the input field and hide input text. Caret runs ahead and also I have a lot of problems besides all this.
The default value from this form goes to useState.
Also, I should be able to call the onKeyPress function or something similar, because I add a new tag if e.keyCode === 32 and contain #.
I need to do it use react function component.
Are you looking for something like this(code mentioned below)?
Also, I didn't understand the part of using a key, what do you exactly want to achieve with it.
And keyCode is deprecated, I'll suggest you use key key==='#'
Plus, if you can add a code snippet or elaborate more would help in helping you better
import { useState, useEffect, useMemo } from "react";
const Input = () => {
const [inputValue, setInputValue] = useState("");
const tagsArr = useMemo(() => ({ cats: false, dogs: false }), []);
useEffect(() => {
Object.keys(tagsArr).forEach((el, i) => {
if (inputValue.includes(el)) {
tagsArr[el] = true;
} else {
tagsArr[el] = false;
}
});
}, [inputValue, tagsArr]);
const handleChange = ({ target }) => {
setInputValue(target.value);
};
return (
<div>
<input type="text" value={inputValue} onChange={handleChange} />
<div>
<ul>
{Object.keys(tagsArr).map((el, i) =>
tagsArr[el] ? (
<li
key={el}
style={{
listStyle: "none",
backgroundColor: "springgreen",
width: "max-content",
display: "inline",
padding: "10px",
marginRight: "10px",
}}
>
{"#" + el}
<button>X</button>
</li>
) : null
)}
</ul>
</div>
</div>
);
};
export default Input;
I've put together a little demo here: https://codesandbox.io/s/bold-meitner-r3wzq?file=/src/App.js
When I click the button to display and focus on my first radio button - My radio styling isn't applied. Although, if you press space immediately after, it focuses the right element.
The strange thing is, if I focus after a 1ms timeout, it works as expected. Obviously this is more of a hack than a solution...
I feel like I'm missing something really simple here, any help would be great!
Thanks!!
Alternatively - This is all of the code required to replicate:
import React from "react";
import styled from "styled-components";
import "./styles.css";
const { useRef, forwardRef, useState } = React;
const Button = ({ onClick }) => (
<button onClick={onClick}>Click me to show and focus radio buttons</button>
);
const Fieldset = styled.fieldset`
display: ${props => (props.isVisible ? "block" : "none")};
input {
:focus {
outline: 5px solid red;
}
}
`;
const RadioButtons = forwardRef(({ isVisible, legend }, ref) => (
<Fieldset isVisible={isVisible}>
<legend>{legend}</legend>
<label for="one">One</label>
<input ref={ref} type="radio" id="one" name="radios" />
<label for="two">Two</label>
<input type="radio" id="two" name="radios" />
</Fieldset>
));
export default function App() {
const [isVisible, setIsVisible] = useState(false);
const inputRef = useRef();
const focusInput = () => {
setIsVisible(true);
// This doesn't work as expected
inputRef.current.focus();
// This does - But it definitely doesn't feel like a "solution"
// setTimeout(() => {
// inputRef.current.focus();
// }, 1);
};
return (
<>
<Button onClick={focusInput} />
<RadioButtons
isVisible={isVisible}
ref={inputRef}
legend="Radio Legend"
/>
</>
);
}
You have to place the focusing login inside the useEffect hook like this
export default function App() {
const [isVisible, setIsVisible] = useState(false);
const inputRef = useRef();
const focusInput = () => {
setIsVisible(true);
};
React.useEffect(() => {
if (isVisible) {
inputRef.current.focus();
}
}, [isVisible]);
return (
<>
<Button onClick={focusInput} />
<RadioButtons
isVisible={isVisible}
ref={inputRef}
legend="Radio Legend"
/>
</>
);
}
Here is the link
As it always seems to go... You feel like you exhaust every option - Then you write a question, and then you immediately find the answer!
In this instance - Moving the refs focus into a useEffect that watches isVisible did the trick
useEffect(() => {
// I check here because my actual code toggles isVisible
if (isVisible) inputRef.current.focus();
}, [isVisible]);
Still interested to see if this can be done in a better way though
The useState's value will change when the current button is clicked, which will call the child component.
Child component is Modal.
However, when the parent's button is repressed, the changed value is not changed again and will not be recalled.
https://codesandbox.io/s/patient-snowflake-wdm78
You can send the callback function to the Child Component like this.
StepComp.js
const StepComp = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
const btnStyled = {
margin: "10rem"
};
const setVisible = visible => {
setIsModalVisible(visible);
};
return (
<div>
<Button onClick={() => setIsModalVisible(true)} style={btnStyled}>
jump
</Button>
<LoginModal open={isModalVisible} setVisible={setVisible} />
</div>
);
};
LoginModal.js
const LoginModal = ({ open, setVisible }) => {
const inputStyled = {
borderColor: "none",
borderBottom: "1px solid #EBEBEB",
marginBottom: "1rem"
};
return (
<div>
<Modal
title="login"
centered
visible={open}
onOk={() => setVisible(false)}
onCancel={() => setVisible(false)}
>
<div>
<input type="text" style={inputStyled} /> <br />
<input type="text" style={inputStyled} />
</div>
</Modal>
</div>
);
};
You're using isModelVisible and visible state properties, and initializing them a a boolean, then setting them to the opposite. This works, but only the first time.
What you need to do is setVisible(!visible) and setIsModalVisible(!isModalVisible) respectively.
While this will make it work, what I suggest doing instead is actually only having one state property, in the parent (StepComp), which then is passed as a callback to the child (Modal) along with the property whether it is open, then you render the child, if it's true, and use the same setState function in the child to close it.
This approach keeps all the logic for opening the Modal in one component.