Can I use Redux without without a server-sided implementation (server.js) with rendering on client-side only? The challenge is to rewrite a part of the web application without changing my current server settings (Apache + PHP).
server.js in the Redux examples is just for running a Webpack dev server for development convenience. It has nothing to do with Redux, just like Webpack, or Grunt, or Gulp, or npm have nothing to do with Redux. These are completely unrelated technologies.
You might find the counter-vanilla example useful. It shows how Redux can be used without any build system at all:
<!DOCTYPE html>
<html>
<head>
<title>Redux basic example</title>
<script src="https://npmcdn.com/redux#latest/dist/redux.min.js"></script>
</head>
<body>
<div>
<p>
Clicked: <span id="value">0</span> times
<button id="increment">+</button>
<button id="decrement">-</button>
<button id="incrementIfOdd">Increment if odd</button>
<button id="incrementAsync">Increment async</button>
</p>
</div>
<script>
function counter(state, action) {
if (typeof state === 'undefined') {
return 0
}
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
var store = Redux.createStore(counter)
var valueEl = document.getElementById('value')
function render() {
valueEl.innerHTML = store.getState().toString()
}
render()
store.subscribe(render)
document.getElementById('increment')
.addEventListener('click', function () {
store.dispatch({ type: 'INCREMENT' })
})
document.getElementById('decrement')
.addEventListener('click', function () {
store.dispatch({ type: 'DECREMENT' })
})
document.getElementById('incrementIfOdd')
.addEventListener('click', function () {
if (store.getState() % 2 !== 0) {
store.dispatch({ type: 'INCREMENT' })
}
})
document.getElementById('incrementAsync')
.addEventListener('click', function () {
setTimeout(function () {
store.dispatch({ type: 'INCREMENT' })
}, 1000)
})
</script>
</body>
</html>
Related
Accordingly to this Issue it should work with the current version v3.2.x.
But it doesn't.
Here is the playground:
const { createApp } = Vue;
const myComponent = {
template: '#my-component',
setup(props, { slots }) {
console.log(slots)
}
}
const App = {
components: {
myComponent
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
<my-component>Default
<template #footer>Footer</template>
</my-component>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script type="text/x-template" id="my-component">
<div>
<slot></slot>
<hr/>
<slot name="footer"></slot>
</div>
</script>
The solution was provided by Duannx.
With console.log(slots) they are listed correctly.
{
"footer": (...n)=>{o._d&&Tr(-1);const r=hn(t);let s;try{s=e(...n)}finally{hn(r),o._d&&Tr(1)}return s},
"default": (...n)=>{o._d&&Tr(-1);const r=hn(t);let s;try{s=e(...n)}finally{hn(r),o._d&&Tr(1)}return s}
}
Explanation
JSON.stringify doesn't show the slots since they are functions.
Here is the explanation from the MDN Docs JSON.stringify():
undefined, Function, and Symbol values are not valid JSON values. If any such values are encountered during conversion, they are either omitted (when found in an object) or changed to null (when found in an array). JSON.stringify() can return undefined when passing in "pure" values like JSON.stringify(() => {}) or JSON.stringify(undefined).
Example
console.log("JSON.stringify(() => {}): " + JSON.stringify(() => {}));
console.log(JSON.stringify({ "func": function () {}, "lmbd": () => {} }))
I have the following working code for a search input using options API for component data, watch and methods, I am trying to convert that to the composition api.
I am defining props in <script setup> and also a onMounted function.
<template>
<label for="search" class="hidden">Search</label>
<input
id="search"
ref="search"
v-model="search"
class="border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm h-9 w-1/2"
:class="{ 'transition-border': search }"
autocomplete="off"
name="search"
placeholder="Search"
type="search"
#keyup.esc="search = null"
/>
</template>
<script setup>
import {onMounted} from "vue";
const props = defineProps({
routeName: String
});
onMounted(() => {
document.getElementById('search').focus()
});
</script>
<!--TODO convert to composition api-->
<script>
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {
// page.props.search will come from the backend after search has returned.
search: this.$inertia.page.props.search || null,
};
},
watch: {
search() {
if (this.search) {
// if you type something in the search input
this.searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
this.$inertia.get(route(this.routeName));
}
},
},
methods: {
searchMethod: _.debounce(function () {
this.$inertia.get(
route(this.routeName),
{ search: this.search }
);
}, 500),
},
});
</script>
What I am trying to do is convert it to the composition api. I have tried the following but I can't get it to work at all.
let search = ref(usePage().props.value.search || null);
watch(search, () => {
if (search.value) {
// if you type something in the search input
searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
Inertia.get(route(props.routeName));
}
});
function searchMethod() {
_.debounce(function () {
Inertia.get(
route(props.routeName),
{search: search}
);
}, 500)
}
Any help or pointers in how to convert what is currently in <script> into <script setup> would be greatly appreciated thanks.
I managed to get this working with the below!
<script setup>
import {onMounted, ref} from "vue";
import {Inertia} from "#inertiajs/inertia";
const props = defineProps({
route_name: {
type: String,
required: true
},
search: {
type: String,
default: null
}
});
const search = ref(props.search);
onMounted(() => {
search.value.focus();
search.value.addEventListener('input', () => {
if (search.value.value) {
searching();
} else {
Inertia.get(route(props.route_name));
}
});
});
const searching = _.debounce(function() {
Inertia.get(route(props.route_name), {search: search.value.value});
}, 500);
</script>
I'm making a fullstack app with vue3, axios using FormKit. For editing existing records I want to populate the input fields with the current data fetched from a mysql database. I stripped down the code to everything needed to display my problem, which in this code example is populating the FormKit input field with the lotnumber I fetched via the asynchronous function "getLotById". The lotnumber appears in the paragraph section but not in the input field. How can I properly delay the rendering of the FormKit element until the lotnumber has been fetched? Here's my code:
<script>
// import axios
import axios from "axios";
export default {
name: "LotEdit",
data() {
return {
lotnumber: this.lotnumber
}
},
props: {
lotid: Number
},
created: async function () {
await this.getLotById();
},
methods: {
// Get Lot By Id
async getLotById() {
try {
const response = await axios.get(`http://localhost:5000/lot/${this.$route.params.id}`);
this.lotnumber = response.data.lotnumber;
console.log(response.data);
}
catch (err) {
console.log(err);
}
},
}
};
</script>
<template>
<div>
<FormKit
type="text"
name="lotnumber"
label="lotnumber"
placeholder=""
validation="required"
:value="lotnumber"
/>
</div>
<div>
<p> Here the lotnumber appears: {{ lotnumber }}</p>
</div>
</template>
I suggest using a v-model on the FormKit input. Because it is two-way bound it means as soon as the async/await completes the data is populated on the template too. Something like...
<FormKit
v-model="lotnumber"
type="text"
name="lotnumber"
label="lotnumber"
placeholder=""
validation="required"
:value="lotnumber"
/>
Getting a little smarter I managed to solve the problem in the following way:
<script>
// import axios
import axios from "axios";
export default {
name: "LotEdit",
data() {
return {
lotnumber: this.lotnumber
}
},
props: {
lotid: Number
},
mounted: async function () {
const response = await this.getLotById();
const node = this.$formkit.get('lotnumber')
node.input(response.data.lotnumber, false)
},
methods: {
// Get Lot By Id
async getLotById() {
try {
const response = await axios.get(`http://localhost:5000/lot/${this.$route.params.id}`);
console.log(response.data);
return response;
}
catch (err) {
console.log(err);
}
},
}
};
</script>
<template>
<div>
<FormKit
type="text"
id="lotnumber"
name="lotnumber"
label="lotnumber"
placeholder=""
validation="required"
:value="lotnumber"
/>{{ lotnumber }}
</div>
</template>
Feel free to post any recommendations as I'm not a pro yet...
I'm also still figuring out how to handle controlled forms but I guess an alternative way to do it is with Form Generation
<script>
export default {
// ...
async setup() {
try {
const response = await axios.get(`http://localhost:5000/lot/${this.$route.params.id}`);
const schema = [
{
$formkit: "text",
label: "Lot Number",
value: response.data.lotnumber,
validation: "required",
},
];
} catch (err) {
console.log(err);
}
return { schema }
}
// ...
}
</script>
<template>
<FormKit type="form">
<FormKitSchema :schema="schema" />
</FormKit>
</template>
I have simple component, that wraps text area. And I've another simple component, that renders a button. I want to set focus to text area when clicking the button.
This simplified example fails:
<template>
<MyCommand #resize="testResize" />
<TextArea ref="refElement" />
</template>
<script lang="ts">
// ...
export default defineComponent({
name: 'SimpleComponent',
setup(props, context) {
const refElement = ref<HTMLElement | null>(null)
const testResize = () => {
console.log('resize test')
if (refElement.value !== null) {
refElement.value.focus()
}
}
return {
refElement,
testResize,
}
}
</script>
TextArea is very simple component, some input normalization, oversimplified:
<template>
<textarea v-model.trim="value" />
</template>
I get "resize test" in console, so testResize method is running, but refElement is null.
When referencing component, not a HTML element, component type should be DefineComponent instead of HTMLElement.
Wrapped element could be referenced through $el property:
<template>
<MyCommand #resize="testResize" />
<TextArea ref="refElement" />
</template>
<script lang="ts">
// ...
export default defineComponent({
name: 'SimpleComponent',
setup(props, context) {
const refElement = ref<DefineComponent>()
const testResize = () => {
console.log('resize test')
if (refElement.value) {
refElement.value.$el.focus()
}
}
return {
refElement,
testResize,
}
}
</script>
I'm not sure if this is the "best practice", it looks to me as a hack. If anyone knows better solution, please comment.
You're missing the refElement in your return
return {
testResize, refElement
}
Update
If you are dealing with a component it becomes a bit trickier. while you can use refElement.value.$el, I'd say it's not a good idea. This will only work if the component has the first child the textarea. This will make for a brittle implementation, where if you need to change that at some point, it will break. IMHO, you're better off passing the ref as a prop to the child component. This is also not best practice, because you're supposed to pass props down and emit events up, but that that would be quite the overhead to implement. Passing ref as a prop comes with it's own issues though. If you have a ref in the template, it gets automagicaly converted from the ref/propxy to a value. To get around that, you can pass the prop in a function refElement: () => refElement in the setup(can't do it template). Of course, YMMV, but this is the path I'd chose.
const app = Vue.createApp({
setup(props, context) {
const refElement = Vue.ref(null)
const testResize = () => {
if (refElement.value !== null) {
refElement.value.focus()
}
}
return {
testResize,
refElement: () => refElement
}
}
});
app.component("text-area", {
template: `<textarea ref="taRef"></textarea></div></div>`,
props: {
textarearef: {
type: Function
}
},
setup(props) {
const taRef = props.textarearef()
return {
taRef
}
}
})
app.mount("#app");
<script src="https://unpkg.com/vue#3.0.3/dist/vue.global.prod.js"></script>
<div id="app">
<text-area :textarearef="refElement"></text-area>
<button #click="testResize">🦄</button>
</div>
I want my meteor app to call setState in App on login and logout. How can I have one section of code (ie: Accounts.onLogon) affect inside another component (ie App{})? Also, what to do to detect logouts?
Accounts.onLogin(function(user){
console.log('hi');
//App.showPrivate();
});
class App extends Component {
constructor(props) {
super(props);
this.state = {
showPublic: false,
};
}
toggleShowPublic() {
this.setState({
showPublic: !this.state.showPublic,
});
}
showPublic() {
this.setState({
showPublic: true,
});
}
showPrivate() {
this.setState({
showPublic: false,
});
}
render() {
return (
<div className="container">
<div className="show-public" onClick={this.toggleShowPublic.bind(this)}>
{this.state.showPublic ?
<span className="private-public"> View private</span> :
<span className="private-public"> View public </span>
}
</div>
</div>
);
}
}
Instead of Accounts.onLogin you should use Meteor's in-built reactive data sources to determine the user's logged-in status:
class App extends Component {
constructor(props) {
super(props);
this.state = { showPublic: false };
}
toggleShowPublic() {
this.setState({ showPublic: !this.state.showPublic });
}
render() {
return (
<div className="container">
{this.props.isLoggedIn ?
<div className="show-public" onClick={this.toggleShowPublic.bind(this)}>
{showPrivate ?
<span className="private-public"> View private</span> :
<span className="private-public"> View public </span>
}
</div> :
Show something else if the user is not logged in here...
}
</div>
);
}
}
export default createContainer(() => {
return {
isLoggedIn: !!Meteor.user()
}
}, App);
Now Meteor will take care of reactively updating this.props.isLoggedIn for you. Note that you need to install meteor/react-meteor-data and import createContainer for this to work:
import { createContainer } from 'meteor/react-meteor-data';
If you still need to do something when the user logs in, you can place Accounts.onLogin basically anywhere you want in your app, as long as you consider whether you want it to run server-side or client-side or both. For best practices regarding application structure, check out Meteor Guide.
It turns out Accounts.onLogin is a distraction. To have the app update when the user logs in or out, we need to see when the logged in user changes, and react accordingly. Seeing when something changes in React is done using componentWillReceiveProps, as seen below:
componentWillReceiveProps(nextProps) {
// user just logged in/out
if (!this.props.currentUser && nextProps.currentUser) {
this.setState({ showPublic: false });
}
}
oh, and current users comes from:
export default createContainer(() => {
return {
currentUser: Meteor.user(),
};
}, App);