Getting props from children — default values - javascript

EDIT: CodePen link here.
I'm (ab)using JSX to make a form-building 'DSL' for some of my non-technical colleagues. There's a SingleChoice component that can be used like this:
<SingleChoice>
<Option value="A">
// ... what to show when A is chosen
</Option>
<Option value="B">
// ... what to show when B is chosen
</Option>
</SingleChoice>
The result is a <div> full of radio button inputs, and under the block of inputs, there are the conditional elements (based on what option is chosen).
In other words, the Option elements don't render anything by themselves, they are there just to signal to the parent how many radio buttons there are, what are their labels and what should be shown when X is chosen. They are literally empty shells, made just to carry their props, like this:
function Option({label, value, children}) { return <></> }
Yes, I could instead pass those as an array of objects { value: string, show: ReactNode }, but that's not a friendly syntax for my non-dev colleagues.
Now, to the question. In the parent I go through all his children and render the input based on their value:
...
{children.map(ch => <> <input ... /> {ch.props.value} </>)}
...
The problem is, this only works when I manually pass a value prop to the Option component. E.g. when I have
function Yes({..., value = "Yes"}) { ... }
and I do
<SingleChoice>
<Yes />
</SingleChoice>
the label is empty, as if it didn't see the default prop value. Why is that happening? Is it a bug? And how do I implement this properly? Remember, I don't really want to expose any of the implementation details to the user who writes the form (so no explicit callback passing).
The only "proper" way I could think of would be creating a context with a callback in the parent, which all the children would look up and call with their values. The problem is, there would be a lot of contexts made and updated this way, and I fear the performance implications.

I'm a bit confused as to what you want to have happen. As far as I can tell from your code on Codepen, a default prop value is never defined so when you call
<Yes/>
without passing it a prop it doesn't get rendered by your parent component. Also, a react function should just accept props as a parameter, you don't need to define value and children.

I'm answering in another answer to get the formatting
The first error you have is you run ReactDOM.render in the beginning of your code. You should add it at the end of the code, or else default values won't be used(I don't know why this is).
The second is that your functions aren't really react. In react functions take one parameter, props. You can reference value or children as props.value or props.children.
not react: function Yes({value = "Yes1", children}) { return <span></span> }
react: function Yes(props){ return <span>?+{props.value}</span> }
The third is that declaring defaultProps is done through the defaultProps command in react. So instead of setting a parameter to a value(because again, no parameters) you should write:
Yes.defaultProps={ value:"Yes1" }
Finally, your function singleChoice isn't really react as well. Theres definitely a cleaner way to refactor your code then scrolling through the yes components and checking their value. However, you can do what you want. If you fix the other problems it should work as you intended.

Related

Is it possible to rerender/update an each block in Svelte when a variable changes?

So, I'm pretty new to svelte and very new to sveltekit, but ever since I started learning svelte I've always seemed to run into this problem one way or another.
Here, I have an each block:
{#each {length: wordLength} as _, i}
<button class={getGuessInfo(i).state}
on:click={() => changeColor(index)}>{getGuessInfo(i).letter}
</button>
{/each}
And I want to basically have this be reactive to my getGuessInfo()'s return value. I get you can make a function reactive if it doesn't have any parameters, but I'm not sure how to / if it's even possible to if the function relies on an index value.
Here is the getGuessInfo() definition:
function getGuessInfo(index) {
if (index > $Guess.length - 1) return {letter: "", state: LetterState.MISS}
return $Guess[index]
}
This basically just returns a blank letter/state if the index is too high, and the letter/state of Guess at that position if not.
This works fine when I open the website, but changing the value of Guess yields no reactivity from the page.
Honestly, I'm so new to frameworks/web development in general that I'm not really sure what I want as a solution, but maybe something like just rerendering the component? Or just the each block? Not sure.
Thanks!
Well, I tried doing:
$: $Guess && getGuessInfo()
But that obviously didn't work
I previously just had an each block that was directly tied to Guess, as in:
{#each $Guess as btn, index}
And that worked fine
Thing is I want to constantly have 5 (or wordLength) buttons showing at all times, even if they're blank. That's why I tried using the function
The reactive attempt is not too far off, you probably could define the function reactively like this:
$: getGuessInfo = index =>
index > $Guess.length - 1 ?
{ letter: "", state: LetterState.MISS } :
$Guess[index];
This should cause it to be re-evalutated when $Guess changes.
(Ternary operator just for brevity, you could use a block & return statements.)
To make the result of a function call reactive, add the variable based on which it should update as parameter - in your case
getGuessInfo(i, $Guess)
(Would be cleaner to not only add it in the function call, but also in the function definition. But if the variable is in scope, that's not essential)

model input (textfield) statemachine in xState

Would love to read your thoughts on how you would model a input (textfield) with xState.
According to an input ux article a textfield could have following states:
Input text fields can have one of the following states: default, focused, error, and disabled. All states should be clearly differentiated from one another.
This makes sense as other libs like Material UI uses more or less the same states.
Im wondering how to model this.
Let me just write down some thoughts:
I think its obvious that the value should be part of the xState context as it could have any value
the mentioned states makes sense as well
Now the part where im not so sure: lets say we have an inline validation (onChange) which says the textfields value is ok and for that we set want to set with css a class "valid" which gives the textfield a green border.
We would need either a state transition from default to default_valid (not just valid because we are still in the default state) ... the same applies for default_invalid ... and aswell for some more combinations which would possible end in a state explosion.
model it in xState as a child state and access it via default.valid or default.invalid ...
In both scenarios we would need in the textfield component another mapping which reads something like
(just pseudocode)
switch(state) {
'default.invalid': setColor(red), setDisabled(false)
'default.valid': setColor(green), setDisabled(false)
'default.valid.submitting': {
setColor(green)
setDisabled(true)
}
Im really not happy with this approach to manage the state in the component and time in xState machine. This just seems wrong to me.
I would prefer to just use the input machines states ... which works well for some default like, default, focused ... but as soon as the field is "in 2 or more states" it gets a mess.
One way would be to just keep some high level states and write the additional ones in the context and just pass it to the textfield? (sounds not that good either tbh)
So would love to hear your thoughts how you would model something like this.
You can use state.matches(...) to clean things up, and/or you can place those actions directly in the states they're relevant for, e.g., in entry or exit.

How to get the component that rendered a dom element with Vue.js

How to get the component that rendered a dom element with Vue.js ?
For example, suppose you want to retrieve which component "owns" a dom element that the user has selected, how would you do ? (it seems to be implemented in the dev tools, but I can't find a way neither in the documentation, neither on SO)
(I mean, given the DOM element, I know how to retrieve what element is selected)
DISCLAIMER : This may not be the right solution for common use cases. Always prefer handling event & co. in the root component using direct sub-component reference if you can
I do not know if this is safe or officially supported, but assuming you're trying to access the component from some external-to-Vue code, this will return the VueComponent object for a given DOM element (substitute your own DOM selector as needed):
document.getElementById('foo').__vue__
If used on the app's root element, it will instead return the Vue constructor object.
(This appears to work in both Vue 1.x and 2.x.)
This is possibly not the most elegant solution, but you can use mixins to achieve this:
var elemOwner = {
mounted: function() {
this.$el.setAttribute("isVueComponent", this.$options.name);
}
};
As long as you set the mixin to the components you need it in, when you click an element you can test the attributes to see if there's a component name in there.
See this codepen for a fuller example: https://codepen.io/huntleth/pen/EpEWjJ
Clicking the smaller blue square will return the component name of the component that rendered it.
EDIT - It should be noted though that this obviously would only work if the element is actually inside that components root element. I think that would be the case for almost all uses.
Getting this.$parent refers to the parent of a component.

React inline conditional component attribute

I've been searching everywhere and can't find an answer to my question. So I want a conditional attribute which is only displayed on certain conditions, example:
<Button {this.state.view === 'default' && 'active'}></Button>
As you can see I only want to indicate the button active if the this.state.view is equal to default. However, I get Unexpected token, error...
But when I try to put an attribute before it, for example:
<Button isActive={this.state.view === 'default' && 'active'}></Button>
It passes the syntax error and displays fine but this is not what I wanted to achieve.
How can I fix this? And what could be the reason behind it not passing?
UPDATE
So I just found out that in react-bootstrap the property active is a shorthand of active=true so I solved it using
<Button active={this.state.view === 'default'}></Button>
So In case, someone encounters this problem I'm leaving it here. However, I still want to know why the conditional attribute is failing without enclosing it inside a prop-like syntax, example:
This:
active={this.state.view === 'default'}
Versus
{this.state.view === 'default' && 'active'}
First of all, JSX is just a syntactic sugar for React.createElement. So, it may look like, but, in reality, you don't specify html attributes: in fact, you are always passing props.
For instance, the JSX code <input type="button" value="My button" /> is transpiled into React.createElement('input',{type:'button',value:'My Button'}). And, when necessary, React engine renders this React Element to the DOM as a HTML element.
That said, we have that JSX transpiles a prop without a value as true (check docs). For instance: <Button active></Button> is transpiled to React.createElement(Button, { active: true });.
But, we know that HTML5 specification does not accept attribute=true (as pointed here). For instance: <button disabled=true></button> is invalid. The correct is <button disabled></button>.
So, to render the HTML element to the DOM, React considers only props that are valid attributes (if not, the attribute is not rendered). Check all supported html attributes. And, then, finally, if it's a boolean attribute, it removes the true/false value, rendering it properly.
For instance: <button active={true}></button> is transpiled to React.createElement("button", { active: true }); and then React renders to the DOM <button></button>, because there is no active attribute in HTML specification for the <button/> tag (is not in the supported attributes list).
But <button disabled={true}></button> is transpiled to React.createElement("button", { disabled: true }); and React renders to the DOM <button disabled></button>.
I just said that to clarify your case.
You're trying to pass an active prop to the Button component (first letter uppercase means that's a React component: there is a React Component called Button handled somewhere in your code).
That means:
<Button active></Button> is transpiled to React.createElement(Button, { active: true });
and
<Button active={true}></Button> is transpiled to React.createElement(Button, { active: true });
The same thing!
So, if you want to do a conditional prop, you can simply do something like that:
<Button active={this.state.view === 'default'}></Button>
You have a condition inside brackets. Means that, if your this.state.view is equal to default (true), active prop will be passwed down to the component with the true value. If not equal, with the false value.
Button Component, then, must someway handle this active prop. It can render the button element and, for instance, change it's style, pass the disabled prop... I created a fiddle to demonstrate this: https://jsfiddle.net/mrlew/5rsx40gu/
Actually, I guess it is a duplicated question that I answered it in this link sometimes ago. for this specific post, there is no need condition prop for a boolean value, because it works well like below:
const { booleanValue } = this.props;
return (
<input checked={booleanValue} />
);
Or make your own boolean value:
const booleanValue = someThing === someOtherThing;
return (
<input checked={booleanValue} />
);
Both of them work well, because when the booleanValue is false, react doesn't see the active prop, hence it does not exist, unless you pass checked prop to a specific component that the component will receive false checked prop from this.props.
But, if you wanna a have a prop on your specific component with a condition and if the condition returns false you don't want it there are two ways, I like second:
First:
<Component someProp={condition ? yourValue : undefined} />
Second:
<Component {...(condition && { someProp: yourValue })} />
In JSX, component properties (or props) compile to a plain JavaScript object. Prop names are used as the keys of the object, and prop values are stored under those keys. The first example from the JSX docs does a great good job demonstrating this. In your case {view === 'default' && true} is a raw value without an associated prop name. Without a prop name to use as a key (specified by the syntax name=), JSX has nowhere to put that value in the final props object.
However, JSX will accept props via your original syntax if the expression inside the curly braces evaluates to an object. For example, you could do {{ active: view === "default" }}. In this case JSX can get both the key and value that it needs from the provided object, so no active= is necessary.

How to access the value of the input when it is deep in the component hierarchy?

lets say, I have below code in my react component three level deep <Foo><Bar><InputBox/><Bar></Foo> where 'InputBox' is a presentation component
<input type="text" id="inputBox" ref="inputBox" name="inputBox" value={this.state.inputVal} onChange={this.handleChange} />
I would like to get the value of the input in my container component to perform the form validation. I can get the value using either vanilla JS
document.getElementById('inputBox').value
or using React ref (But the below one doesn't work when my refs are very deep)
this.refs.inputBox.value
I would like to know which approach is better in terms of performance and why? and would like to know how to access the value of the input when it is deep in the component hierarchy using React's ref approach?
Use refs. But...
Although string refs are not deprecated, they are considered legacy,
and will likely be deprecated at some point in the future. Callback
refs are preferred.
- FB https://facebook.github.io/react/docs/more-about-refs.html#the-ref-string-attribute
So instead do something like <input ref={el => this.inputEl = el} /> and access the value as this.inputEl.value.
A few minor points:
input="text" is not needed. That's the default
Avoid IDs, they don't scale well (e.g. if another element elsewhere in the page is given the same ID, you have invalid HTML)
I would use innerHTML for this.
var input = document.GetElementByID('inputBox').innerHTML;
You can now use that input variable to do your validation.

Categories

Resources