whenever i try. to change an input it's show the 2 component changes of input at the same time how i show only the changed one
input.tsx
export const Input: React.FC<InputProps> = ({ type, placeholder, classNameProp, value, onChange, forwardRef, isReadOnly = false, isRequired = true, errorText }) => {
console.log(forwardRef?.current);
return (
<>
<input type={type} placeholder={placeholder} className={`p-2 mb-4 rounded-sm border-b-2 border-gray-300 focus:outline-none focus:border-orange-500 ${classNameProp}`} value={value} onChange={onChange} disabled={isReadOnly} required={isRequired} ref={forwardRef} />
</>
);
};
formSurvey.tsx
export const SurveyForm: React.FC<SurveyFormProps> = () => {
const titleRef = React.useRef<HTMLInputElement>(null);
const subjectRef = React.useRef<HTMLInputElement>(null);
const [surveyTitle, setSurveyTitle] = React.useState("");
const [surveySubject, setSurveySubject] = React.useState("");
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(surveyTitle);
let state: any = {
title: surveyTitle,
subject: surveySubject,
};
};
return (
<div className="relative">
<form className="max-w-3xl mx-auto bg-white rounded-xl p-5 mb-6" onSubmit={handleSubmit}>
<Input type="text" placeholder="Survey Title" classNameProp={`w-full ${titleRef.current?.value.length === 0 ? "border-red-500" : ""}`} value={surveyTitle} onChange={React.useCallback(setter(setSurveyTitle), [])} forwardRef={titleRef} errorText="Please fill the title field" />
<Input type="text" placeholder="Subject Line" classNameProp={`w-full ${subjectRef.current?.value.length === 0 ? "border-red-500" : ""}`} value={surveySubject} onChange={React.useCallback(setter(setSurveySubject), [])} forwardRef={subjectRef} errorText="Please fill the subject field" />
<div className="form-footer flex justify-center space-x-10">
<button className="bg-orange-500 text-white px-4 py-2 rounded-3xl font-semibold" type="submit">
Submit
</button>
</div>
</form>
</div>
);
};
setter.ts
type Set<T> = React.Dispatch<React.SetStateAction<T>>;
type ChangeEvent<E> = React.ChangeEvent<E>;
type Input = HTMLInputElement | HTMLTextAreaElement;
export function setter<T extends number | string | Date, E extends Input = HTMLInputElement>(setX: Set<T>) {
return (e: ChangeEvent<E>) => {
setX(e.target.value as T);
};
}
try to find the best way to implement reusable component in react js
you can see the code here also here
Since you use state, whenever you call setState, each children re-renders. If the components are not so big, most of the time re-renders don't matter and everything is fast enough (optimized)
But if you want to optimize this, there are techniques you can use to avoid unnecessary renders.
The quickest solution for you would be to use React.memo to wrap your inputs.
const Input = React.memo(...your component body)
Or you can use uncontrolled inputs.
Or some library that does this for you, like react-hook-form
Related
I have a deck of cards with assigned numbers using dataset-keyMatch.
When I click on the first card I want it to assign the keyMatch value to the first state (choiceOne) and then when I click a second card I want to check that choiceOne has a value and if that is true I then assign choiceTwo the keyMatch value of the second card.
The card elements are imported from a MatchingCard Component.
Then the onClick event is assigned in the [classId] Component using the function handleChoice.
For choiceOne & choiceTwo
I tried with a default state of null
Then I tried with a default state of 0
// -----------------Matching Card Game Functionality --------------------
// --------------------------------------------------------------------------
// States
const [doubledDeck, setDoubledDeck] = useState([]);
const [shuffledDeck, setShuffledDeck] = useState([]);
const [deckReady, setDeckReady] = useState([]);
const [matchingGameActive, setMatchingGameActive] = useState(false);
const [turns, setTurns] = useState(0);
const [choiceOne, setChoiceOne] = useState(0);
const [choiceTwo, setChoiceTwo] = useState(0);
// test the logic that I want for the onClick event on the MatchingCard
// This works....so I don't know why it won't work when I click the cards
const random = Math.floor(Math.random() * 10);
const check = () => {
choiceOne != 0 ? setChoiceTwo(random) : setChoiceOne(random);
if (turns === 0) {
setChoiceOne(random);
setTurns((prevTurn) => prevTurn + 1);
}
if (turns === 1) {
setChoiceTwo(random);
setTurns(0);
}
};
// Take the original double sided deck - split it - and combine into big deck for game
const doubleTheDeck = (deck: any) => {
const FlashcardsEnglish = deck.map((card: { props: { children: { props: any }[] } }) => {
return card.props.children[0].props;
});
const FlashcardsJapanese = deck.map((card: { props: { children: { props: any }[] } }) => {
return card.props.children[1].props;
});
const joinedDeck = FlashcardsEnglish.concat(FlashcardsJapanese);
setDoubledDeck(joinedDeck);
};
// shuffle deck -----
const shuffle = (deck: any[]) => {
const shuffledCards = deck
.sort(() => Math.random() - 0.5)
.map((card) => ({
...card,
}));
setShuffledDeck(shuffledCards);
};
// choice functionality
const handleChoice = (e: { target: { dataset: { keyMatch: any } } }) => {
const parsed = parseInt(e.target.dataset.keyMatch);
// const str = e.target.dataset.keyMatch;
// attempt #1 using "null" as the default state for choiceOne and Two
// choiceOne ? setChoiceTwo(parsed) : setChoiceOne(parsed);
// attempt #2 using 0 as the default state for choiceOne and Two
if (turns === 0) {
setChoiceOne(parsed);
setTurns((prevTurn) => prevTurn + 1);
}
if (turns === 1) {
setChoiceTwo(parsed);
setTurns(0);
}
};
console.log(choiceOne, choiceTwo);
// create JSX elements------
const finalDeck = shuffledDeck.map((card: { matchId: any; word: any }) => {
const { matchId, word } = card;
return (
<MatchingCards key={matchId + word[0]} matchId={matchId} word={word} handleChoice={handleChoice}></MatchingCards>
);
});
// prepare deck for game start -----
const handleMatchingGameClick = () => {
// take flahscards and double split them into two - doubling size
doubleTheDeck(cardsForMatchingGame);
// shuffle the deck & sets the shuffledCards
shuffle(doubledDeck);
// create JSX elements from the new cards
setDeckReady(finalDeck);
// set game to active
setMatchingGameActive((prevState) => !prevState);
};
// useEffect(() => {}, []);
return (
<div>
<div>
<Header pageHeader={className} />
<div className="bg-white">choice 1: {choiceOne}</div>
<div className="bg-white">choice 2: {choiceTwo}</div>
<button onClick={check}>check</button>
</div>
{/* <ToggleButton /> */}
<div className="flex items-center justify-between bg-slate-200 dark:bg-bd-1 p-4 ">
<HomeButton />
{/* <button onClick={handleMatchingGameClick}>start game</button> */}
{/* <ShuffleButton onClick={shuffle(doubledDeck)} /> */}
<MatchingGameButton
content={matchingGameActive ? "Back to Regular Deck" : "Go to Matching Game"}
onClick={handleMatchingGameClick}
/>
<ToggleButton />
</div>
<div
className="
dark:bg-bd-1
p-10
bg-slate-200 gap-5 flex flex-col items-center justify-center
sm:items-center sm:justify-center
sm:grid
sm:grid-cols-2
md:grid
md:grid-cols-3
lg:grid-cols-4
"
>
{matchingGameActive ? deckReady : cards};
</div>
</div>
);
What I return from the MatchingCards Component
return (
<div>
<div>
<Header pageHeader={className} />
<div className="bg-white">choice 1: {choiceOne}</div>
<div className="bg-white">choice 2: {choiceTwo}</div>
<button onClick={check}>check</button>
</div>
{/* <ToggleButton /> */}
<div className="flex items-center justify-between bg-slate-200 dark:bg-bd-1 p-4 ">
<HomeButton />
{/* <button onClick={handleMatchingGameClick}>start game</button> */}
{/* <ShuffleButton onClick={shuffle(doubledDeck)} /> */}
<MatchingGameButton
content={matchingGameActive ? "Back to Regular Deck" : "Go to Matching Game"}
onClick={handleMatchingGameClick}
/>
<ToggleButton />
</div>
<div
className="
dark:bg-bd-1
p-10
bg-slate-200 gap-5 flex flex-col items-center justify-center
sm:items-center sm:justify-center
sm:grid
sm:grid-cols-2
md:grid
md:grid-cols-3
lg:grid-cols-4
"
>
{matchingGameActive ? deckReady : cards};
</div>
</div>
);
When I click on a card my function only assigns choiceOne and then continues to reassign choiceOne. ChoiceTwo never gets given a value despite ChoiceOne already having a value.
I made a test function called "check" and the logic works there so I have no idea why it is not working on my card element.
When a component renders MatchingCards Component is called leads to new state creation.
I want to update the text whatever users types in the input field and then join that text with another text (i.e ".com" in this example). So somehow I managed to join the extension with the user's input text but when the input field is empty the extension is still showing. Can someone help me to remove that extension text when the input field is empty?
Here's my code
import React, { useState } from "react";
import Check from "./Check";
export default function App() {
const [inputValue, setInputValue] = useState("");
const [inputExtension, setInputExtension] = useState("");
const handleChange = (e) => {
setInputValue(e.target.value);
if (setInputValue == inputValue) {
setInputExtension(inputExtension);
}
if (inputValue == inputValue) {
setInputExtension(".com");
}
};
return (
<>
<h1 className="text-[2.1rem] font-semibold text-black">
Find Your Perfect Domain
</h1>
<form>
<label
for="default-search"
class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-gray-300"
>
Search
</label>
<div class="relative">
<input
type="search"
id="in"
value={inputValue}
onChange={handleChange}
class="block p-4 pl-10 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500"
placeholder="Search for domains..."
/>
</div>
</form>
<Check domain={inputValue} extension={inputExtension} />
<Check domain={inputValue} extension={inputExtension} />
</>
);
}
Here you have the code
if (inputValue == inputValue) {
setInputExtension(".com");
}
Change this to
if(inputValue.length > 0) {
setInputExtension(".com");
}
If you are passing both inputValue and inputExtension as props to the Check component then it should be trivial to just check the inputValue length, i.e. use the fact that empty strings are falsey, and conditionally append the extension value.
Example:
const value = ({
inputValue,
inputExtension = ".com"
}) => inputValue + (inputValue ? inputExtension : "");
console.log({ value: value({ inputValue: "" }) }); // ""
console.log({ value: value({ inputValue: "someValue" })}); // "someValue.com"
Code:
const Check = ({ inputValue = "", inputExtension = "" }) => {
const value = inputValue + (inputValue ? inputExtension : "");
...
};
So I am trying to pass in data previously selected in a form to an update version of the form. The update version of the form needs to display all previously selected data to the user and allow them to make any necessary changes.
Here is the problem I'm having. I am using a multi-select check box component. I am passing the previously selected data in to the component. When I set the selected property for component to the previously selected data using useEffect, It will let me submit the initial data or add new selections and everything functions correctly. It will not let me uncheck/remove selections. They get submitted even though they are unchecked.
If I don't use useEffect to set selected to previousData, I can not submit with the initial data but I can add and remove selections as intended. This is not acceptable though because users will most likely not make changes every time to the check boxes.
So to clarify, I can submit without making changes and add selections the first way and I can add and remove selections the second way but not submit without making changes the second way.
import { useState, useEffect } from 'react';
export default function UpdateMultiCheckBox({
title,
hint,
data,
previousData,
setSelectedData,
dotName,
}) {
const [selected, setSelected] = useState([]);
const handleChange = (event) => {
const { checked, value } = event.currentTarget;
setSelected((prev) =>
checked ? [...prev, value] : prev.filter((val) => val !== value),
);
};
{/* This is what is causing all the problems but it will not work without this without making a change to the selections */}
useEffect(() => {
let tempData = previousData?.map((a) => a.id);
setSelected(tempData);
}, [previousData]);
useEffect(() => {
setSelectedData(selected);
}, [selected]);
const difference = data?.filter(
(item) =>
!previousData?.some((itemToBeRemoved) => itemToBeRemoved.id === item.id),
);
return (
<fieldset className='space-y-5'>
<div>
<legend className='sr-only'>{title}</legend>
<label className='text-base font-medium text-brandText'>{title}</label>
<p className='text-sm leading-5 text-brandText'>
Please select all that apply.
</p>
</div>
{previousData?.map((item) => (
<div key={item.id} className='relative flex items-start'>
<div className='flex h-5 items-center'>
<input
id={item.id}
value={item.id}
defaultChecked={true}
type='checkbox'
onChange={handleChange}
className='h-4 w-4 rounded border-gray-300 text-brandPrimary focus:ring-brandPrimary'
/>
</div>
<div className='ml-3 text-sm'>
<label htmlFor={item.id} className='font-medium text-brandText'>
{item[dotName]}
</label>
<span id='comments-description' className='text-brandText'>
{hint}
</span>
</div>
</div>
))}
{difference?.map((item) => (
<div key={item.id} className='relative flex items-start'>
<div className='flex h-5 items-center'>
<input
id={item.id}
value={item.id}
type='checkbox'
onChange={handleChange}
className='h-4 w-4 rounded border-gray-300 text-brandPrimary focus:ring-brandPrimary'
/>
</div>
<div className='ml-3 text-sm'>
<label htmlFor={item.id} className='font-medium text-brandText'>
{item[dotName]}
</label>
<span id='comments-description' className='text-brandText'>
{hint}
</span>
</div>
</div>
))}
</fieldset>
);
}
If I understood correctly, the useEffect that you say is causing the problem, is setting the state with the previousData and loosing the selected state data.
If that is the case you could try the following:
useEffect(() => {
const tempData = previousData?.map((a) => a.id);
setSelected([...selected, ...tempData]);
}, [previousData]);
So now we are adding to the selected state the previousData, another way of doing it as seen in the wild:
useEffect(() => {
const tempData = previousData?.map((a) => a.id);
setSelected(prevState => [...prevState, ...tempData]);
}, [previousData]);
Which is effectively the same but in certain scenarios could be be useful when you only pass the state setter as a prop.
I'm working on a project ( Airbnb clone - for personal learning), and on this project I am trying to understand concepts and conventions thoroughly.
Here's the problem:
On this page I use a date input custom component like this:
-- parent component -
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { testData } from "../utils/mockData";
import Header from "../components/header/Header";
import DateSearchBar from "../components/header/DateSearchBar";
import Guests from "../components/cards/Guests";
const Property = () => {
const propertyId = useParams();
const convertedId = parseInt(propertyId.id);
const [noOfGuests, setNoOfGuests] = useState(0);
const [fromDate, setFromDate] = useState();
const [toDate, setToDate] = useState();
const selectedProperty = testData.filter(item => item.id === propertyId.id)
const handleGuests = (val) => {
if (noOfGuests === 0) {
setNoOfGuests(0);
}
setNoOfGuests(noOfGuests + val)
}
const handleDate = (val) => {
setFromDate(val);
}
return (
<div>
<Header />
<div className="flex justify-center space-x-24 mt-6">
<div className=" max-w-sm rounded-xl overflow-hidden shadow-sm w-9/12">
<img
className=" text-centerw w-96 rounded-md h-64"
src={selectedProperty[0].image}
/>
<p className="h-16">{selectedProperty[0].title}</p>
</div>
<div className="ml-96">
<h4 className="text-center italic font-extrabold">From</h4>
<DateSearchBar name="fromDate" value={fromDate} handleDate={handleDate } />
<h4 className="text-center italic font-extrabold">To</h4>
<DateSearchBar name="toDate" value={fromDate} handleDate={handleDate } />
<Guests handleGuests={handleGuests} noOfGuests={noOfGuests} />
</div>
</div>
</div>
);
};
export default Property;
--- Child Component ---
import React from "react";
const DateSearchBar = ({ handleDate }) => {
return (
<div>
{/* fromDate */}
<div className="text-center">
<input
className="bg-slate-50 hover:bg-red-200 rounded-md h-12 w-80 text-center mb-16 "
type="date"
onChange={(e) => handleDate(e.target.value)}
/>
</div>
</div>
);
};
export default DateSearchBar;
The Property.js component owns the local state, I am using a callback function to set a local state in the parent component.
The problem is that I need to differ between the fromDate state and the toDate state in the parent component, but I'm not sure how to write this logic.
Obviously I can set up another date component and target it, however it beats the purpose of creating and using reusable components and keeping your code DRY.
Also, Redux/Context seem too much for this issue ( but I might be wrong)
Any ideas on how I can solve this ?
First thing is, that you are passing fromDate in both of the components. I believe you should have a value of fromDate in the first and toDate in the second.
Next, to be able to reuse handleDate for both inputs, you need to pass the element's name back to the parent along with the value and then use that name argument to differentiate between the two components.
In parent:
const handleDate = (name, value) => {
if (name === "fromDate") {
setFromDate(value)
} else if (name === "toDate") {
setToDate(value)
}
}
In child:
onChange={e => handleDate(props.name, e.target.value)}
Another approach would be to return a method from handleDate():
In parent:
const handleDate = (name) => {
if (name === "fromDate") {
return (value) => setFromDate(value)
} else if (name === "toDate") {
return (value) => setToDate(value)
}
}
...
<DateSearchBar name="fromDate" value={fromDate} handleDate={handleDate("fromDate")} />
<DateSearchBar name="toDate" value={toDate} handleDate={handleDate("toDate")} />
In this case, you don't have to change child component.
However IMO this still isn't the simplest approach. Yes, we should try to follow these clean code recommendations but only to the point where they don't lead to further complexity. For example, in the above case we are over-complicating handleDate(), it would be a lot simpler to have separate inline change handlers for each component:
<DateSearchBar name="fromDate" value={fromDate} handleDate={val => setFromDate(val)} />
<DateSearchBar name="toDate" value={toDate} handleDate={val => setToDate(val)} />
If our form grows bigger, we can use dedicated form handling React libraries such as Formik and React-hook-form to keep our component logic simpler.
When i'm trying to type in the input element the state dosen't update. So i can't type anything in the input. I get no error code either. In the handleChange function the text variable is undefined when i log it to the console. But the value variable updates every single letter i type. But not as a whole sentence, the letters just overwrites them self.
import React, { useState } from "react";
function AddTodo(props) {
const initialFromState = { text: "", isComplete: null, id: null };
const [todo, setTodo] = useState(initialFromState);
const handleSubmit = e => {
e.preventDefault();
if (!todo.text) return;
props.AddTodo(todo);
setTodo(initialFromState);
console.log(todo);
};
const handleChange = event => {
const { text, value } = event.target;
setTodo({ ...todo, [text]: value });
};
return (
<div className="container mx-auto text-center">
<form onSubmit={handleSubmit} className="rounded flex">
<input
className="shadow appearance-none border rounded w-full py-2 mr-4 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
type="text"
name="text"
value={todo.text}
onChange={handleChange}
autoComplete="off"
autoFocus={true}
placeholder="Eg. Buy apples"
/>
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold px-4 rounded focus:outline-none focus:shadow-outline"
>
Add
</button>
</form>
</div>
);
}
export default AddTodo;
I expect that what i'm typing to the input is stored in the state and that i can see what i'm typing. Next challenge is to see if the todo actually is stored and showed among the other todos. :) One step at the time.
I think you have wrong prop names in your handleChange, text should be name:
const handleChange = event => {
const { name, value } = event.target;
setTodo({ ...todo, [name]: value });
};
Its supposed to be name, you do not have text attribute to target it.
name='text' attribute given use that.
const { name, value } = event.target;
setTodo({ ...todo, [name]: value });