I have a writable store in Svelte with an object. I'd like to display the properties and be able to change the property values as a user.
I'm able to get the properties and values to display in input tags with an on:input event to update the value in the store, but I get an error saying the sampleStore.set isn't a function. I tried to add a custom update function to the store, but I'm getting the same error.
What's the best way to do this?
Sample code below
Store.js
import { writable } from 'svelte/store';
function createSampleStore() {
const sampleStore = writable({
property1a: 'value1a',
property1b: 'value1b',
etc...
}
return {
subscribe: sampleStore.subscribe,
updateValue: (propertyName, propertyValue) =>
sampleStore.update((o) => {
o[propertyName] = propertyValue;
return o;
}),
};
}
export const sampleStore = createSampleStore();
InfoDisplay.svelte
<script>
import {sampleStore} from './sampleStore.js';
const propertyNames = Object.keys($sampleStore);
$: console.log($sampleStore);
</script>
<ul>
{#each propertyNames as propertyName}
<li>
{propertyName}:
<input
on:input={(value) =>
sampleStore.updateValue(propertyName, propertyValue)}
bind:value={$sampleStore[propertyName]}
/>
</li>
{/each}
</ul>
I tried to adjust the values shown in the input tag and have the values in the store be updated, but I kept getting the .set is not a function.
The binding on the input value creates a get/set relationship, you should only pass the value in and use the event to get it out:
<input
on:input={e => sampleStore.updateValue(propertyName, e.target.value)}
value={$sampleStore[propertyName]} /> <!-- no bind here -->
You also do not need a custom store to do this and could just use a writable directly:
<input bind:value={$store[propertyName]} />
REPL
Related
I'm sure this is a super easy fix, but I'm having an issue where I setup a writable store, and it's mostly reactive, except when a component changes the data, the reactivity in the App file doesn't fire and vice versa. Here's the code:
App.svelte:
<script>
import { data } from './store.js'
import Component from './Component.svelte'
let localData
data.subscribe((value) => {
localData = value;
});
</script>
<h2>In App.svelte</h2>
<p>Hello {localData.name}!</p>
<input name="name" type="text" bind:value={localData.name}>
<p>
{localData.details.number}
</p>
<h2>In Component.svelte</h2>
<Component />
Component.svelte:
<script>
import { data } from './store.js'
let localData
data.subscribe((value) => {
localData = value;
});
</script>
<input type="number" bind:value={localData.details.number}>
<p>Hello {localData.name}!</p>
<p>{localData.details.number}</p>
store.js:
import { writable } from 'svelte/store'
export let data = writable({
name: 'Bob Smith',
details: {
dob: '1982/03/12',
favoriteFoods: ['apples', 'pears', 'bourbon'],
number: 1
},
})
And, if you want to use it in the Svelte REP: https://svelte.dev/repl/164227336d6c4cc29f7ea0a15e89c584?version=3.44.3
You are subscribing to the data and putting it into a local variable and then bind to that. This means the store does not know that anything changed and updates won't be propagated. Two options:
First option: You get rid of the two way binding and update the store explicitely like this:
<script>
import { data } from './store.js'
import Component from './Component.svelte'
let localData
data.subscribe((value) => {
localData = value;
});
function updateName(evt) {
const newName = evt.target.value;
data.update(value => ({...value, name: newName }));
}
</script>
<h2>In App.svelte</h2>
<p>Hello {localData.name}!</p>
<input name="name" type="text" value={localData.name} on:input={updateName}>
<p>
{localData.details.number}
</p>
<h2>In Component.svelte</h2>
<Component />
This is very explicit but also a bit boilerplate-y. We have Svelte's handy auto subscription feature, so let's use that instead. Second and prefered option:
<script>
import { data } from './store.js'
import Component from './Component.svelte'
</script>
<h2>In App.svelte</h2>
<p>Hello {$data.name}!</p>
<input name="name" type="text" bind:value={$data.name}>
<p>
{$data.details.number}
</p>
<h2>In Component.svelte</h2>
<Component />
Notice how we got rid of all the subscription boilerplate. $data accesses the current state of the store and since it's a writable store you can also write back to it that way. You can read more about stores in the docs: https://svelte.dev/docs#component-format-script-4-prefix-stores-with-$-to-access-their-values
I'm working in a project already began that's using react class version. I plan to work with react hooks, so to don't refactor all the classes, as I write new codes, I'm trying to mix those react versions (idk if it's a good idea and I should refactor all).
I'm creating a list with pagination and search. The pagination and search are in an unique component.
To this component a need pass the search character value input by user, and here is where I'm facing problem. In other words, I need pass a value to the parent component.
Code is below:
useState hook:
const [search, setSearch] = useState('');
Filter component, that change the search value:
const Filter = () => {
return (
<Card>
<Form.Group label="Filtro">
<Grid.Row gutters="xs">
<Grid.Col>
<Form.Input
name='search'
placeholder='Filtro'
autoFocus
value={search}
onChange={e => setSearch(e.target.value)}
/>
</Grid.Col>
<Grid.Col auto>
<Button
color="success"
icon="search"
onClick={filtrar}
>
</Button>
</Grid.Col>
</Grid.Row>
</Form.Group>
</Card>
);
}
function getSearchDB() {
setSearch((search) => {
return search;
})
}
Pagination component, that receive the props:
<Pagination
baseUrl={'vehicles/toUse'}
updateState={setStateDB}
getSearch={getSearchDB}
fields={'license_plate'}
/>
Printing search value pass through Pagination component:
console.log(this.props.getSearch()) //print undefined
OBS: updateState={setStateDB} is working fine.
Things done to make this work (no success):
In getSearch={getSearchDB} directly pass search value. Result: this.props.getSearch() print undefined
Defined getSearchDB() to be like:
function getSearchDB() {
return search;
}
Result: this.props.getSearch() print undefined.
Is there a way to put it to work?
Guys, let me know if the post is confusion or the English is poorly written.
Instead of passing down a function that returns search, why not just pass down search itself as a prop?
<Pagination
search={search}
const Pagination = (props) => {
console.log(props.search);
add :
<Pagination
search={search}
/>
In component Pagination :
const Pagination = ({search}) => {
console.log(search);
return {
//...
}
}
I used to compile and insert JSX components via
<div key={ ID } dangerouslySetInnerHTML={ { __html: HTML } } />
which wrapped my HTML into a <div>:
<div>my html from the HTML object</div>
Now react > 16.2.0 has support for Fragments and I wonder if I can use that somehow to avoid wrapping my HTML in a <div> each time I get data from the back end.
Running
<Fragment key={ ID } dangerouslySetInnerHTML={ { __html: HTML } } />
will throw a warning
Warning: Invalid prop `dangerouslySetInnerHTML` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.
in React.Fragment
Is this supported yet at all? Is there another way to solve this?
Update
Created an issue in the react repo for it if you want to upvote it.
Short Answer
Not possible:
key is the only attribute that can be passed to Fragment. In the
future, we may add support for additional attributes, such as event
handlers.
https://reactjs.org/docs/fragments.html
You may want to chime in and suggest this as a future addition.
https://github.com/facebook/react/issues
In the Meantime
You may want to consider using an HTML parsing library like:
https://github.com/remarkablemark/html-react-parser
Check out this example to see how it will accomplish your goal:
http://remarkablemark.org/blog/2016/10/07/dangerously-set-innerhtml-alternative/
In Short
You'll be able to do this:
<>
{require('html-react-parser')(
'<em>foo</em>'
)}
</>
Update December 2020
This issue (also mentioned by OP) was closed on Oct 2, 2019. - However, stemming from the original issue, it seems a RawHTML component has entered the RFC process but has not reached production, and has no set timeline for when a working solution may be available.
That being said, I would now like to allude to a solution I currently use to get around this issue.
In my case, dangerouslySetInnerHTML was utilized to render plain HTML for a user to download; it was not ideal to have additional wrapper tags included in the output.
After reading around the web and StackOverflow, it seemed most solutions mentioned using an external library like html-react-parser.
For this use-case, html-react-parser would not suffice because it converts HTML strings to React element(s). Meaning, it would strip all HTML that wasn't standard JSX.
Solution:
The code below is the no library solution I opted to use:
//HTML that will be set using dangerouslySetInnerHTML
const html = `<div>This is a div</div>`
The wrapper div within the RawHtml component is purposely named "unwanteddiv".
//Component that will return our dangerouslySetInnerHTML
//Note that we are using "unwanteddiv" as a wrapper
const RawHtml = () => {
return (
<unwanteddiv key={[]}
dangerouslySetInnerHTML={{
__html: html,
}}
/>
);
};
For the purpose of this example, we will use renderToStaticMarkup.
const staticHtml = ReactDomServer.renderToStaticMarkup(
<RawHtml/>
);
The ParseStaticHtml function is where the magic happens, here you will see why we named the wrapper div "unwanteddiv".
//The ParseStaticHtml function will check the staticHtml
//If the staticHtml type is 'string'
//We will remove "<unwanteddiv/>" leaving us with only the desired output
const ParseStaticHtml = (html) => {
if (typeof html === 'string') {
return html.replace(/<unwanteddiv>/g, '').replace(/<\/unwanteddiv>/g, '');
} else {
return html;
}
};
Now, if we pass the staticHtml through the ParseStaticHtml function you will see the desired output without the additional wrapper div:
console.log(ParseStaticHtml(staticHtml));
Additionally, I have created a codesandbox example that shows this in action.
Notice, the console log will throw a warning: "The tag <unwanteddiv> is unrecognized in this browser..." - However, this is fine because we intentionally gave it a unique name so we can easily differentiate and target the wrapper with our replace method and essentially remove it before output.
Besides, receiving a mild scolding from a code linter is not as bad as adding more dependencies for something that should be more simply implemented.
i found a workaround
by using react's ref
import React, { FC, useEffect, useRef } from 'react'
interface RawHtmlProps {
html: string
}
const RawHtml: FC<RawHtmlProps> = ({ html }) => {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
if (!ref.current) return
// make a js fragment element
const fragment = document.createDocumentFragment()
// move every child from our div to new fragment
while (ref.current.childNodes[0]) {
fragment.appendChild(ref.current.childNodes[0])
}
// and after all replace the div with fragment
ref.current.replaceWith(fragment)
}, [ref])
return <div ref={ref} dangerouslySetInnerHTML={{ __html: html }}></div>
}
export { RawHtml }
Here's a solution that works for <td> elements only:
type DangerousHtml = {__html:string}
function isHtml(x: any): x is DangerousHtml {
if(!x) return false;
if(typeof x !== 'object') return false;
const keys = Object.keys(x)
if(keys.length !== 1) return false;
return keys[0] === '__html'
}
const DangerousTD = forwardRef<HTMLTableCellElement,Override<React.ComponentPropsWithoutRef<'td'>,{children: ReactNode|DangerousHtml}>>(({children,...props}, ref) => {
if(isHtml(children)) {
return <td dangerouslySetInnerHTML={children} {...props} ref={ref}/>
}
return <td {...props} ref={ref}>{children}</td>
})
With a bit of work you can make this more generic, but that should give the general idea.
Usage:
<DangerousTD>{{__html: "<span>foo</span>"}}</DangerousTD>
I have a React component that is passing an object to a child element containing an input field to be modified via a passed function. The input field is changing the state of the child element but somehow, without invoking the passed function, the props object is being modified.
this is the render function of the parent class:
render: function() {
const newTriggerClass = this.state.item.triggerID === ""
? " new-trigger"
: "";
return (
<div className={newTriggerClass}>
<div className="triggers-detail">
<section className="trigger-info group">
<TriggerInfoEntry
item={this.state.item}
triggerState={this.state.editTriggerState}
columnHeaders={this.props.columnHeaders}
hideDetail={this.hideDetail}
editAction={this.props.editAction}
editToggle={this.editToggle}
_onChange={this._onInfoChange}
createAction={this.props.createAction}
deleteAction={this.props.deleteAction}/>
</section>
<section className="trigger-actions group">
<TriggerActionEntry
item={this.state.item}
actionState={this.state.editActionState}
editToggle={this.editToggle}
editAction={this.editTriggerAction}
actionIndex={this.state.selectedActionIndex}/>
</section>
</div>
<div className={"modal-overlay" + newTriggerClass}>
</div>
</div>
);
},
and the child TriggerActionEntry is setting it's own state.item to a clone of props.item.
propTypes: {
item: React.PropTypes.object.isRequired,
editAction: React.PropTypes.func.isRequired,
editToggle: React.PropTypes.func.isRequired,
actionState: React.PropTypes.bool
},
getInitialState: function() {
return {
item: assign({testy: true}, this.props.item),
actionIndex: 0,
deleteState: false,
editState: false
};
},
when I change the state of the child component (state.item) through an input, the parent state gets changed! I am not sure of the cause. Nothing that I have read online alludes to this behavior. Any help is appreciated.
UPDATE:
#garrettmaring The code I use to change state is only designed to work on the child state which is set in the getInitialState as a clone of this.props.item through assign which is my alias for object-assign which is an Object.assign ponyfill. The form to change item is actually one more child down. Here is the render of TriggerActionEntry
<div>
<TriggerActionForm
action={this.state.item.actions[this.state.actionIndex]}
index={this.state.actionIndex}
addNewAction={this.addNewAction}
editTriggerAction={this.editTriggerAction}
deleteTriggerAction={this.deleteTriggerAction}
editState={this.state.editState}
deleteState={this.props.actionState}
toggleEdit={this.toggleEdit}
activateEdit={this.activateEdit}/>
</div>
and here is the _onChange function of the grandchild TriggerActionForm
_onChange: function (e) {
e.preventDefault();
let newAction = assign({}, this.state.action);
const keyname = e.currentTarget.dataset.keyname;
if (keyname === "minDelay") {
newAction[keyname] = e.currentTarget.value;
} else {
newAction.args[keyname] = e.currentTarget.value;
}
this.setState({action: newAction});
if (!this.props.editState) {
this.props.activateEdit();
}
},
even when I unmount that component, the TriggerActionEntry state.item is modified, and shows up in the modified state when the component is mounted again...
UPDATE 2: OK, the problem is that neither Object.assign nor object-assign deeply clones objects. The classic! The actions were objects that were nested within item and rather than being cloned, another reference to the nested object was being created. Thanks for the help folks.
Im currently working on the same thing . The parent pass the data as a props to the children function. (im using hook instead of class). The purpose of the child component to process the data given from the parent and export as pdf or excel. Imagine the data pass in as an object that consists multiple array of objects . Each child object have an element of proj id which i would like to remove. The moment the data pass in , i have this
enter image description here
i set state with the data pass in from parent. When i gone throught some process and remove the the projid. When i try to access with any elements in the parent function. the whole thing gone broken.
I've built my module and made an AdminController that list items from my table, with creation/update/delete/view.
In the listing page, I'd like to add a message after the breadcrumb, but before the table.
I saw there is a hook available : "displayAdminListBefore" and a block to extend "override_header", but I don't know how to make it work!
Can someone could point me in the right direction please?
You can simply add your module to the displayAdminListBefore hook.
First hook the module to this hook with the install function:
public function install()
{
if (!parent::install() || !$this->registerHook('displayAdminListBefore'))
return false;
return true;
}
Then create the hook function like that:
public function hookDisplayAdminListBefore($params)
{
return '
<div class="bootstrap">
<div class="alert alert-success">
<button data-dismiss="alert" class="close" type="button">×</button>
Add your text here
</div>
</div>
';
}
Or, you can also use a .tpl:
public function hookDisplayAdminListBefore($params)
{
$this->smarty->assign(array(
'first_var' => $first_var,
'second_var' => $second_var',
));
return $this->display(__FILE__, 'views/templates/admin/listbefore.tpl');
}
The best way for you will be to override list_header.tpl and use the override_header hook.
To do so, create a new file list_header.tpl in modules/your_module/views/templates/admin/your_module/helpers/list/list_header.tpl
In this file copy the following code:
{extends file="helpers/list/list_header.tpl"}
{block name="override_header"}
Your text
{$your_var}
{/block}
$your_var must be defined in your controller in the function renderList():
$this->context->smarty->assign(
array(
'your_var' => 'your_var_value'
)
);