I am trying to create a toggle functionality to make a search bar appear and reappear by pressing on a button. I am unsure to why this code is wrong. Does anyone have any insight?
<template>
<button
@click="toggleSearchBar"
v-if="! searchBarOpen"
>
Open Search bar
</button>
<div
v-else
>
<input />
<button
@click="toggleSearchBar"
>
</button>
</div>
<template>
<script setup>
import { ref } from "vue";
let searchBarOpen = ref(false);
const toggleSearchBar = () => {
if (searchBarOpen) {
searchBarOpen = false;
} else {
searchBarOpen = true;
}
console.log({ searchBarOpen });
};
</script>
I am trying to create a toggle functionality to make a search bar appear and reappear by pressing on a button. I am unsure to why this code is wrong. Does anyone have any insight?
<template>
<button
@click="toggleSearchBar"
v-if="! searchBarOpen"
>
Open Search bar
</button>
<div
v-else
>
<input />
<button
@click="toggleSearchBar"
>
</button>
</div>
<template>
<script setup>
import { ref } from "vue";
let searchBarOpen = ref(false);
const toggleSearchBar = () => {
if (searchBarOpen) {
searchBarOpen = false;
} else {
searchBarOpen = true;
}
console.log({ searchBarOpen });
};
</script>
Although I wrote an answer based on the typo in the question, I will specifically answer the question now.
If we have understood Vue reactivity and still want to accomplish something interesting like this, let's continue.
The React useState
hook returns two values: the readonly
current state and a function to update the reactive stored variable.
const [state, setState] = useState(initialValue);
In Vue, we can create a function called useState
, where we initialize a ref, return it as readonly
, and define a setState
function to modify the stored ref value.
import { ref, readonly } from 'vue';
function useState(initialValue) {
const state = ref(initialValue);
const setState = (newValue) => {
if (typeof newValue === "function") {
state.value = newValue(state.value);
} else {
state.value = newValue;
}
};
return [readonly(state), setState];
}
This useState
function provides a similar experience in Vue, allowing you to use it in the same way.
import { useState } from './useState.js';
const [count, setCount] = useState(0);
const increment = () => setCount((prev) => prev + 1);
Live Vue CDN Example
const { createApp, ref, readonly } = Vue;
// Custom useState function
function useState(initialValue) {
const state = ref(initialValue);
const setState = (newValue) => {
if (typeof newValue === "function") {
state.value = newValue(state.value);
} else {
state.value = newValue;
}
};
return [readonly(state), setState];
}
// Vue app setup
const app = createApp({
setup() {
const [openStatus, setOpenStatus] = useState(false);
const [count, setCount] = useState(0);
const toggleOpenStatus = () => setOpenStatus((status) => ! status)
const increment = () => setCount(prev => prev + 1);
return { openStatus, toggleOpenStatus, count, increment };
},
template: `
<div>
<button v-if="! openStatus" @click="toggleOpenStatus">Open</button>
<div v-else>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="toggleOpenStatus">Close</button>
</div>
</div>
`,
});
app.mount("#app");
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id="app"></div>
Here is a React example as well.
const { useState } = React;
function App() {
const [openStatus, setOpenStatus] = useState(false);
const [count, setCount] = useState(0);
const toggleOpenStatus = () => setOpenStatus(prevStatus => ! prevStatus);
const increment = () => setCount(prev => prev + 1);
return (
<div>
{! openStatus ? (
<button onClick={toggleOpenStatus}>Open</button>
) : (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={toggleOpenStatus}>Close</button>
</div>
)}
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You simply need to set
searchBarOpen.value = true
(or false)
The value property in Vue refs is needed for reactivity internally.
In templates however, your reference to searchBarOpen is correct without value, because the value property will be unwrapped under the hood.
Live example with React CDN
const { useState } = React;
function App() {
const [searchBarOpen, setSearchBarOpen] = useState(false);
return (
<div>
{!searchBarOpen ? (
<button onClick={() => setSearchBarOpen(true)}>Open Search Bar</button>
) : (
<div>
<input />
<button onClick={() => setSearchBarOpen(false)}>Close</button>
</div>
)}
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In Vue, you can declare reactive objects using the ref()
function. You can read or modify their data through the .value
property.
ref()
function - Vue 3 Docsreactive()
function - Vue 3 Docsref()
vs reactive()
in Vue 3 - StackOverflowref()
, toRef()
and toRefs()
? - StackOverflowYourComponent.vue (Composition API, Vue 3)
<template>
<div>
<button v-if="!searchBarOpen" @click="toggleSearchBar">
Open Search Bar
</button>
<div v-else>
<input />
<button @click="toggleSearchBar">Close</button>
</div>
</div>
</template>
<script setup>
const searchBarOpen = ref(false);
const toggleSearchBar = () => {
searchBarOpen.value = ! searchBarOpen.value;
};
</script>
Live example with Vue CDN
const { createApp, ref } = Vue;
createApp({
setup() {
const searchBarOpen = ref(false);
const toggleSearchBar = () => {
searchBarOpen.value = ! searchBarOpen.value;
};
return { searchBarOpen, toggleSearchBar };
},
template: `
<div>
<button v-if="!searchBarOpen" @click="toggleSearchBar">
Open Search Bar
</button>
<div v-else>
<input />
<button @click="toggleSearchBar">Close</button>
</div>
</div>
`
}).mount("#app");
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id="app"></div>
Declare a ref called searchBarOpen.
Then onClick on button arrow function switch boolean state.
In mustache of name of button, add a tertiary condition depending on searchBarOpen.
Show input for searching if searchBarOpen is true.
That's all
<template>
<button
@click="() => searchBarOpen = !searchBarOpen"
>
{{ searchBarOpen ? 'Close search bar' : 'Open search bar }}
</button>
<input v-if="searchBarOpen"/>
<template>
<script setup>
import { ref } from "vue";
const searchBarOpen = ref(false);
</script>