Svelte store function update - javascript

Svelte store documentation shows String or Integer being updated, but I did not find any dynamic function in store.
I don't understand how to make the getData function as a writable in order to notify the html of the change.
In the following sample, I would like b to be shown after the updateKey function is called.
You will find a minimal code in REPL here: https://svelte.dev/repl/3c86bd48d5b5428daee514765c926e58?version=3.29.7
And the same code here in case REPL would be down:
App.svelte:
<script>
import { getData } from './store.js';
import { updateKey } from './store.js';
setTimeout(updateKey, 1000);
</script>
<h1>{getData()}!</h1>
store.js
import {setContext} from 'svelte';
import {writable} from 'svelte/store';
var data = {
'a': 'a',
'b': 'b'
};
var key = 'a';
export const getData = function() {
return data[key];
}
export const updateKey = () => {
key = 'b';
}
The goal is to work with a dynamic function in the store.

Well, I think you still have a bit of confusion about how things work in Svelte... Not sure how to best answer your question, so here's some code for what's you're trying to achieve, along with some comments. I hope it will help you better understand how things come together in regards to stores.
App.svelte
<script>
import { onMount } from 'svelte'
import { key, data, updateKey } from './store.js'
onMount(() => {
// it's not safe to have an unchecked timer running -- problems would
// occur if the component is destroyed before the timeout has ellapsed,
// that's why we're using the `onMount` lifecycle function and its
// cleanup function here
const timeout = setTimeout(updateKey, 1000);
// this cleanup function is called when the component is destroyed
return () => {
clearTimeout(timeout)
}
})
// this will log the value of the `key` store each time it changes, using
// a reactive expression (a Sveltism)
$: console.log($key)
</script>
<!--
NOTE: we're using the $ prefix notation to access _the value_ of the store,
and not `data`, which would be _the store itself_ (an object with
subscribe, set, etc.)
-->
<h1>{$data}</h1>
store.js
import { writable, derived } from 'svelte/store'
const db = {
'a': 'a',
'b': 'b'
}
// a writable store with initial value 'a'
export const key = writable('a')
export const updateKey = () => {
// a writable store has a `set` method to change its value
key.set('b')
}
// you can use a derived store to compute derived values from
// the current value of other stores
//
// here, we're getting the value from the db when the value of
// the `key` store changes
export const data = derived([key], ([$key]) => db[$key])

if I understood your question correctly, you want to be able to change the function (logic) that is executed by getData() and you want on each function change the html to be updated
for this use case you'll need to create your own custom store
as follows in store.js
import { writable } from 'svelte/store';
// an object to hold our functions
const functions = {
"funcA": () => {
// do something
return "whatevedata for a"
},
"funcB": () => {
// do something
return "the data of b"
}
}
// this how to create a custom store, taken from svelte documentation
function createCustomStore(defaultValue) {
const { subscribe, set, update } = writable(defaultValue);
return {
subscribe,
//custom function change func where suppliedValue the value you input to the store
// set() is a default function for a store to change it's value
changeFunc: (suppliedValue) => set(functions[suppliedValue]),
reset: () => set(defaultValue)
};
}
export const getData = createCustomStore(() => "default");
export const updateKey = () => {
// this to update which function the store uses
getData.changeFunc("funcB")
}
in App.svelte
<script>
import { getData } from './store.js';
import { updateKey } from './store.js';
setTimeout(function() {
updateKey()
}, 1000);
</script>
<h1>{$getData()}</h1>
we added the $ to getData because it's a store that holds reference to functions and the () is there to execute any function referenced by getData store. since it is a store on each value change (function change) of getData, the html will be updated
here is a repl of the implementation

Related

How can we use a mobx store in utility function?

How can we use a mobx store in utility function?
I have a mobx store and a utility function to make a axio call, I want to use stote value in the utility, how can I do this?
// Store example
export default class SampleStore {
#observable title = "Coding is Love";
#observable user = {
userId: 1,
};
#action
setUser(user) {
this.user = user;
}
#action
updateUser(data) {
this.user = { ...this.user, ...data };
}
#action
clearUser() {
this.user = undefined;
}
#action
setTitle(title) {
this.title = title;
}
}
// Utility function in different file
export function makeApiCall () {
// Use SampleStore here
}
Depends on how you actually initialize your store, how your app is organized and many other factors.
Most simple way is to have singleton store and then you just import it and use directly:
// export an instance instead
export const sampleStore = new SampleStore()
// .. util fil
import { sampleStore } from './SampleStore.js'
export function makeApiCall () {
sampleStore.setUser()
}
Another way is just to pass store to the function, for example if you want to make this call inside useEffect or something:
// Make function accept store as an argument
export function makeApiCall (sampleStore) {
sampleStore.setUser()
}
// ... inside of some React component
// get store from the context (I guess you will have it at that point)
const { sampleStore } = useStores()
useEffect(() => {
// and just pass to the function
makeApiCall(sampleStore)
}, [])

React Calling function from another file and useEffect

Hello I am trying to render the updated stuff from the database whenever the update is invoked to the database. I found this solution but right now my "add" method is not in the same file as the "fetch" method like in the linked question. I tried the below but it still isn't working:
file 1: (the return method will render the UploadedImages by mapping them)
const [UploadedImages,setUploadedImages] = useState([])
const fetchUserAllPhotos = async () => {
const res = await client.get('/get-photos')
setUploadedImages(res.data.photoList)
}
useEffect(()=>{
fetchUserAllPhotos()
},[])
file 2:
import {fetchUserAllPhotos} from './Gallery/AllPhotos';
const addNewPhoto = async () => {
if (success) {
await fetchUserAllPhotos()
}
}
However inside the file 1's return (render) method it is not giving the updated result whenever a new photos is added (I need to sign out and sign back in in order to see the change). How can I go about solving it?
what if the function is wrapped inside another one?
export default function PhotoGrid(props){
const [UploadedImages,setUploadedImages] = useState([])
const fetchUserAllPhotos = async () => {
const res = await client.get('/get-photos')
setUploadedImages(res.data.photoList)
}
useEffect(()=>{
fetchUserAllPhotos()
},[])
}
you need to add this line at the bottom of file1
export {fetchUserAllPhotos}
In general, every time you want to import function, array, object etc it should look like this:
file1:
const func = () => {
//implementaion of the function
}
export {func}
file2:
import {func} from './file1' //you need to give the path of file1
func() //now you can use the fanction everywhere in file2
note: you can export as many functions as you wants just be careful it important to use the same name in the export and in the import
export {func1, func2, func3, obj1, abj2, arr1, arr2}
if you are exporting function from react component you need to define the function outside the component
const func = () => {
}
export default function MyReactComponent(prop) {
//implementation of your component
}
export {func}
If you need to use prop of your component inside the function you can pass this prop as a function parameter.
I recomend to avoid from exporting function from react component because when your project becomes bigger it will start to be frustrate to find were you impliment each fanction. instead it is a best practice to add a new js file that you implement there all the fetches function, I usually call this file api.js
This file should look something like this (I took this code from my project there I used axios to make the fetches to the server):
import axios from "axios"
const url = 'http://localhost:8080'
const api = {
getQuestions : async () => {
return axios.get(`${url}/question/all`)
},
getQuestionById : async (id) => {
return axios.get(`${url}/question/${id}`)
}
}
export default api;

useMemo for efficient global data availability using reactJS and recoilJS

I am trying to figure out how to solve the following problem in the best way possible:
I have multiple components all requiring a global state (I am using recoil for this, since I have many different "atom" states).
Only if a component gets loaded that requires that state, it will perform an expensive computation that fetches the data. This should happen only once upon initialisation. Other components that require the same piece of data should not re-trigger the data fetching, unless they explicitly call an updateState function.
Ideally, my implementation would look something like this:
const initialState = {
uri: '',
balance: '0',
};
const fetchExpensiveState = () => {
uri: await fetchURI(),
balance: await fetchBalance(),
});
const MyExpensiveData = atom({
key: 'expensiveData',
default: initialState,
updateState: fetchExpensiveState,
});
function Component1() {
const data = useRecoilMemo(MyExpensiveData); // triggers `fetchExpensiveState` upon first call
return ...
}
function Component2() {
const data = useRecoilMemo(MyExpensiveData); // doesn't trigger `fetchExpensiveState` a second time
return ...
}
I could solve this by using useRecoilState and additional variables in the context that tell me whether this has been initialised already, like so:
export function useExpensiveState() {
const [context, setContext] = useRecoilState(MyExpensiveData);
const updateState = useCallback(async () => {
setContext({...fetchExpensiveState(), loaded: true});
}, []);
useEffect(() => {
if (!context.loaded) {
setContext({...context, loaded: true});
updateState();
}
}, []);
return { ...context, updateState };
}
It would be possible to make this solution more elegant (not mixing loaded with the state for example). Although, because this should be imo essential and basic, it seems as though I'm missing some solution that I haven't come across yet.
I solved it first by using a loaded and loading state using more useRecoilStates. However, when mounting components, that had other components as children, that all used the same state, I realized that using recoil's state would not work, since the update is only performed on the next tick. Thus, I chose to use globally scoped dictionaries instead (which might not look pretty, but works perfectly fine for this use case).
Full code, in case anyone stumbles upon a problem like this.
useContractState.js
import { useWeb3React } from '#web3-react/core';
import { useEffect, useState } from 'react';
import { atomFamily, useRecoilState } from 'recoil';
const contractState = atomFamily({
key: 'ContractState',
default: {},
});
var contractStateInitialized = {};
var contractStateLoading = {};
export function useContractState(key, fetchState, initialState, initializer) {
const [state, setState] = useRecoilState(contractState(key));
const [componentDidMount, setComponentMounting] = useState(false);
const { library } = useWeb3React();
const provider = library?.provider;
const updateState = () => {
fetchState()
.then(setState)
.then(() => {
contractStateInitialized[key] = true;
contractStateLoading[key] = false;
});
};
useEffect(() => {
// ensures that this will only be called once per init or per provider update
// doesn't re-trigger when a component re-mounts
if (provider != undefined && !contractStateLoading[key] && (componentDidMount || !contractStateInitialized[key])) {
console.log('running expensive fetch:', key);
contractStateLoading[key] = true;
if (initializer != undefined) initializer();
updateState();
setComponentMounting(true);
}
}, [provider]);
if (!contractStateInitialized[key] && initialState != undefined) return [initialState, updateState];
return [state, updateState];
}
useSerumContext.js
import { useSerumContract } from '../lib/ContractConnector';
import { useContractState } from './useContractState';
export function useSerumContext() {
const { contract } = useSerumContract();
const fetchState = async () => ({
owner: await contract.owner(),
claimActive: await contract.claimActive(),
});
return useContractState('serumContext', fetchState);
}
The reason why I have so many extra checks is that I don't want to re-fetch the state when the component re-mounts, but the state has already been initialised. The state should however subscribe to updates on provider changes and re-fetch if it has changed.

Make a unique store value for each instance of a function

I am trying to make a unique store value for each instance of a function that is created but for some reason each instance of the function I create is using the same store value.
I am creating a simple store function and then creating the store inside the function. But even though the stores are being created in each instance of the function they are being shared. Why does this happen.
Here is a codesandbox Sandbox
Here is the code
createStore.js
const createStore = (initialState) => {
const state = initialState;
const get = () => state;
const set = (key, value) => (state[key] = value);
return {
get,
set
};
};
export default createStore;
Main function
import createStore from "./createStore";
import initialState from "./initialState";
const controller = () => {
const store = createStore(initialState);
const setStore = (key, val) => store.set(key, val);
const getStore = () => store.get().string;
return {
setStore,
getStore
};
};
export default controller;
Then I am trying to use them by creating multiple instances of the controller like so:
import controller from "./controller";
const ctl1 = controller();
const ctl2 = controller();
ctl1.setStore('string', 'Hello')
ctl2.setStore('string', 'Welcome')
clt1.getStore() // will output the last set store value of ctl2
Looks like the issue is that your initialState that you are importing is a shared reference which you're using to start your store state. So the issue isn't so much that your store code is decoupled correctly (seems to be) but rather that you're manipulating the same underlying referenced object initialState.
Fix Approach 1
You can likely fix this by cloning/copying initialState rather than just directly using its reference.
Here's an example of what that could look like with lodash:
import { cloneDeep } from 'lodash';
/* ... */
const store = createStore(cloneDeep(initialState));
Here is a codesandbox for that approach:
https://codesandbox.io/s/modern-frog-npd2w
Fix Approach 2
Another alternative you could consider is to not have a shared singleton module of initialState and instead have it be a factory function to create that initial state, which would also solve the same issue:
import { createInitialState } from './initialState';
/* ... */
const initialState = createInitialState();
const store = createStore(initialState);
Here's a codesandbox for that approach:
https://codesandbox.io/s/charming-greider-5go5t

useSelector not updating when store has changed in Reducer. ReactJS Redux

I am changing the state in reducer. On debug I checked that the state was really changed. But the component is not updating.
Component:
function Cliente(props) {
const dispatch = useDispatch()
const cliente = useSelector(({ erpCliente }) => erpCliente.cliente)
const { form, handleChange, setForm } = useForm(null)
...
function searchCepChangeFields() {
//This call the action and change the store on reducer
dispatch(Actions.searchCep(form.Cep))
.then(() => {
// This function is only taking values ​​from the old state.
// The useSelector hook is not updating with store
setForm(form => _.setIn({...form}, 'Endereco', cliente.data.Endereco))
setForm(form => _.setIn({...form}, 'Uf', cliente.data.Uf))
setForm(form => _.setIn({...form}, 'Cidade', cliente.data.Cidade))
setForm(form => _.setIn({...form}, 'Bairro', cliente.data.Bairro))
})
}
Reducer:
case Actions.SEARCH_CEP:
{
return {
...state,
data: {
...state.data,
Endereco: action.payload.logradouro,
Bairro: action.payload.bairro,
UF: action.payload.uf,
Cidade: action.payload.cidade
}
};
}
NOTE: you better start using redux-toolkit to prevent references
in you code its a better and almost a must way for using redux
the problem your facing is very common when handling with objects,
the props do not change because you're changing an object property but the object itself does not change from the react side.
even when you're giving it a whole new object
react doesn't see the property object change because the reference stays the same.
you need to create a new reference like this:
Object.assign(state.data,data);
return {
...state,
data: {
...state.data,
Endereco: action.payload.logradouro,
Bairro: action.payload.bairro,
UF: action.payload.uf,
Cidade: action.payload.cidade
}
}
to add more you can learn about the Immer library that solves this
problem.
It's not necessary to
Object.assign(state.data, data);
always when changing data of arrays or objects
return(
object: {...state.object, a: 1, b: 2},
array: [...state.array, 1, 2, 3]
)
this 3 dots (...) ensure that you create a new object. On redux you have to always create a new object, not just update the state. This way redux won't verify that your data has changed.
When having nesting objects or arrays, is the same thing
Just have attention to:
initialState = {
object: {
...object,
anotherObject:{
...anotherObject,
a: 1,
b: 2
}
}
}
Somehow, the Object.assgin is not recognize
Update with ES6 syntax.
updatedConnectors = state.connectors
This will create a reference to the current state. In ES6, that introduce the ... to make new reference.
updatedConnectors = { ...state.connectors }
.....
return {
...state,
connectors: updatedConnectors
};
use this to extract and copy new reference. That will trigger state change too
Update Sep/27/20
I've wrote some utils function to handle this, Let try this
//Utils
export const getStateSection = ({ state, sectionName }) => {
const updatedState = { ...state }
const updatedSection = updatedState[sectionName]
return updatedSection
}
export const mergeUpdatedSection = ({ state, sectionName, updatedValue }) => {
const updatedState = { ...state }
updatedState[sectionName] = updatedValue
return updatedState
}
Then In any reducer, It should use like this:
//reducer
const initState = {
scheduleDetail: null,
timeSlots: null,
planDetail: {
timeSlots: [],
modifedTimeSlots: [],
id: 0
}
}
.....
const handlePickTimeSlot = (state, action) => {
let planDetail = getStateSection({ state, sectionName: 'planDetail' })
// do stuff update section
return mergeUpdatedSection({ state, sectionName: 'planDetail', updatedValue: planDetail })
}
Since the edit queue for elab BA is full.
The accepted answer here is what he meant by data being there
case MYCASE:
let newDataObject = Object.assign(state.data, {...action.payload});
// or
// let newDataObject = Object.assign(state.data, {key: 'value', 'key2': 'value2' ...otherPropertiesObject);
return {
...state,
...newDataObject
}
There is an interesting edge case that can happen when modifying the file where you create your Store.
If the file where you have your redux store Provider component (usually App.tsx) does not get reloaded by React's hot module reloader (HMR) but the redux store file gets modified and therefore reloaded by HMR, a new store is created and the store Provider in your App.tsx can actually end up passing an old instance of your redux store to useSelector.
I have left the following comment in my setup-store.ts file:
/**
* Note! When making changes to this file in development, perform a hard refresh. This is because
* when changes to this file are made, the react hot module reloading will re-run this file, as
* expected. But, this causes the store object to be re-initialized. HMR only reloads files that
* have changed, so the Store Provider in App.tsx *will not* be reloaded. That means useSelector
* values will be querying THE WRONG STORE.
*/
It is not a problem, you should understand how the React is working. It is expected behavior
In your case you are invoking
dispatch(Actions.searchCep(form.Cep))
.then(() => {...some logic...}
But all of this work in ONE render, and changed store you will get only in the NEXT render. And in then you are getting props from the first render, not from the next with updated values. Look for example below:
import React from 'react';
import { useSelector, useDispatch } from "react-redux";
import { selectCount, incrementAsync } from "./redux";
import "./styles.css";
export default function App() {
const value = useSelector(selectCount);
const dispatch = useDispatch();
const incrementThen = () => {
console.log("value before dispatch", value);
dispatch(incrementAsync(1)).then(() =>
console.log("value inside then", value)
);
};
console.log("render: value", value);
return (
<div className="App">
<p>{value}</p>
<button onClick={incrementThen}>incrementThen</button>
</div>
);
}
And output
value before dispatch 9
render: value 10
value inside then 9

Categories