Reputation: 61
Apologies if you feel like this is a repeated question, but I couldn't find the solution for my problem in any of similar questions and couldn't understand why this problem was occurring.
I have a component called Selector.tsx
which basically has an input field and a bunch of buttons that when clicked are supposed to create a card with the data from that input field.
selector.tsx
:
const profileList: Profile[] = [
{
name: 'Website',
value: 'website',
},
{
name: 'Documentation',
value: 'documentation',
},
{
name: 'Telegram',
value: 'telegram',
},
{
name: 'Twitter',
value: 'twitter',
},
{
name: 'Github',
value: 'github',
},
{
name: 'Facebook',
value: 'facebook',
},
{
name: 'Blog',
value: 'blog',
},
{
name: 'Discord',
value: 'discord',
},
{
name: 'LinkedIn',
value: 'linkedin',
},
{
name: 'Slack',
value: 'slack',
},
];
export const Selector: FC<SelectorProps> = ({ wrapperClass }) => {
const [inputVal, setInputVal] = useState('https://google.com');
const [saved, setSaved] = useState<Saved[]>([]);
const handleAdd = (val: string) => {
if (!inputVal) return;
const _saved = saved;
_saved.push({ type: val, text: inputVal });
setSaved(_saved);
};
const handleClearSaved = () => {
setSaved([]);
};
const DisplayCard: FC<DisplayCardProps> = ({ text, type }) => (
<div className="w-full text-left text-text-gray bg-red-100 px-4 py-1 border rounded-lg">
<span className="text-gray-600 text-xs">{type}:</span>
<span className="text-base text-black text-sm ml-4">{text}</span>
</div>
);
return (
<div className="mt-4 py-4 flex flex-col lg:flex-row-reverse gap-4 items-center">
<div className="mt-8 space-y-2 overflow-y-auto border border-blue-500 p-4 rounded">
{saved.length ? (
saved.map(({ type, text }, i) => (
<DisplayCard key={i} text={text} type={type} />
))
) : (
<i>Nothing to show</i>
)}
</div>
<div className="border rounded p-4">
<div className="flex items-center gap-2">
<input
className="flex-grow pl-2 text-sm leading-6 border border-transparent bg-blue-100 rounded outline-none focus:border-prim-border"
value={inputVal}
onChange={(e) => setInputVal(e.target.value)}
/>
<div className="mt-2 flex flex-col space-y-2">
{profileList.map(({ name, value }, i) => (
<button
className="border border-red-400 text-sm bg-black bg-opacity-0 hover:bg-opacity-10 transform scale-100 active:scale-90 disabled:cursor-not-allowed"
onClick={() => handleAdd(value)}
disabled={!inputVal}
>
{name}
</button>
))}
</div>
</div>
<button
className="mt-2 border border-red-500 transform scale-100 active:scale-90 px-2"
onClick={handleClearSaved}
>
Clear Fields
</button>
</div>
</div>
);
};
Whenever we click any of the profile buttons (and the input field is not empty), the handleSave
button is called & saved
array state is supposed to get updated(object is pushed into the array) & based on that, DisplayCard
s are rendered.
The problem: The saved
state doesn't get updated after any of the profile buttons are clicked. When we make changes in the input field(which updates the inputVal
state), only then the saved
state gets updated
Also weirdly enough, the handleClearSaved
function works properly when the clear button is clicked. It is able to empty the saved
array. But for some reason, pushing into the saved
array doesn't seem to update the state.
Problem repoduced in Stackblitz: https://stackblitz.com/edit/well-well-well?file=components/Selector.tsx
I can't wrap my head around, why this behavior is occurring in react. I've never encountered such issues before. If any one can help or provide any hints on this, it would be really helpful. 🙏
Thanks.
Upvotes: 2
Views: 321
Reputation: 102
the reason is you are doing variable(object) copy with reference and when reference is not changing your state is not updating
insted of
const handleAdd = (val: string) => {
if (!inputVal) return;
const _saved = saved;
_saved.push({ type: val, text: inputVal });
setSaved(_saved);
};
try this code
const handleAdd = (val: string) => {
if (!inputVal) return;
setSaved((pre) => ([...pre, { type: val, text: inputVal });
};
objects and arrays work with by reference which mean you can not copy them like this
const a = [1,2,3]
const b = a
for copying an array you should use ES6 Spread syntax to create new instance like this
const a = [1,2,3]
cosnt b = [...a]
react state check previous value and next value then do an update when reference is not changing, no render happens. for updating object state or array state you should create new instance, and you can use ES6 Spread syntax
Upvotes: 1
Reputation: 220
Use this to change the state directly:
const handleAdd = (val: string) => {
if (!inputVal) return;
setSaved(prevState => [...prevState, {type: val, text: inputVal}]);
};
Besides, you should add a key
to the buttons map, it's giving you a console error.
Upvotes: 1