Skip to content

Commit 7cb891d

Browse files
authored
Update reactivity.md
Use headings with troubleshooting, document static props
1 parent 66426c5 commit 7cb891d

File tree

1 file changed

+121
-102
lines changed

1 file changed

+121
-102
lines changed

docs/reactivity.md

Lines changed: 121 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -12,139 +12,158 @@ This rule is **a warning** by default.
1212
Below are a few common patterns that cause warnings, what these warnings mean, and whether these
1313
warnings can be safely ignored.
1414

15-
- **Accessing reactive variables in the component body**
15+
### Accessing reactive variables in the component body
1616

17-
```jsx
18-
function Greeting(props) {
19-
const text = `Hello, ${props.name}!`;
20-
// ^^^^^^^^^^
21-
// The reactive variable 'props.name' should be used within JSX, a tracked scope (like
22-
// createEffect), or inside an event handler function, or else changes will be ignored.
17+
```jsx
18+
function Greeting(props) {
19+
const text = `Hello, ${props.name}!`;
20+
// ^^^^^^^^^^
21+
// The reactive variable 'props.name' should be used within JSX, a tracked scope (like
22+
// createEffect), or inside an event handler function, or else changes will be ignored.
2323

24-
return <span class="greeting">{text}</span>;
25-
}
26-
```
24+
return <span class="greeting">{text}</span>;
25+
}
26+
```
2727

28-
Using a prop (or calling a signal, etc.) directly inside of the component body will trigger a
29-
warning about a real problem and should rarely be ignored. This is because props (and signals,
30-
etc.) can change over time, but Solid components only run once, so a change to the prop will have
31-
no effect. For example, if `props.name` is initially equal to `'Alice'`, but is later changed to
32-
`'Bob'`, the `<Greeting />` component will still display "Hello, Alice!"
28+
Using a prop (or calling a signal, etc.) directly inside of the component body will trigger a
29+
warning about a real problem and should rarely be ignored. This is because props (and signals,
30+
etc.) can change over time, but Solid components only run once, so a change to the prop will have
31+
no effect. For example, if `props.name` is initially equal to `'Alice'`, but is later changed to
32+
`'Bob'`, the `<Greeting />` component will still display "Hello, Alice!"
3333

34-
Given that components run once, the way to handle this is to move `props.*` accesses and signal calls
35-
into the JSX (or certain other places called "tracked scopes"). Alternatively, you can use wrapper functions
36-
instead, and call those functions in the JSX. For example, either of the following solutions will work
37-
and eliminate the warning:
34+
Given that components run once, the way to handle this is to move `props.*` accesses and signal calls
35+
into the JSX (or certain other places called "tracked scopes"). Alternatively, you can use wrapper functions
36+
instead, and call those functions in the JSX. For example, either of the following solutions will work
37+
and eliminate the warning:
3838

39-
```jsx
40-
function Greeting(props) {
41-
return <span class="greeting">Hello, {props.name}!</span>;
42-
}
43-
// or
44-
function Greeting(props) {
45-
const text = () => `Hello, ${props.name}!`; // `text` now acts like a signal
39+
```jsx
40+
function Greeting(props) {
41+
return <span class="greeting">Hello, {props.name}!</span>;
42+
}
43+
// or
44+
function Greeting(props) {
45+
const text = () => `Hello, ${props.name}!`; // `text` now acts like a signal
4646

47-
return <span class="greeting">{text()}</span>;
48-
}
47+
return <span class="greeting">{text()}</span>;
48+
}
4949
```
5050

51-
<details>
52-
<summary>Why does this work?</summary>
51+
<details>
52+
<summary>Why does this work?</summary>
5353

54-
Solid's compiler transforms expressions in JSX; it wraps them in a function, and creates an effect
55-
to track when they've changed in order to update the UI. So `<span>{text()}</span>` becomes
56-
`<span>{() => text()}</span>`, and `<span>{props.name}</span>` becomes `<span>{() => props.name}</span>`. The `props.name` access works just like a signal call because `.name` is a
57-
getter function under the hood; a function call in both cases. It's important that the accesses
58-
happen in the JSX for the tracking to work.
59-
</details>
54+
Solid's compiler transforms expressions in JSX; it wraps them in a function, and creates an effect
55+
to track when they've changed in order to update the UI. So `<span>{text()}</span>` becomes
56+
`<span>{() => text()}</span>`, and `<span>{props.name}</span>` becomes `<span>{() => props.name}</span>`. The `props.name` access works just like a signal call because `.name` is a
57+
getter function under the hood; a function call in both cases. It's important that the accesses
58+
happen in the JSX for the tracking to work.
59+
</details>
6060

61-
- **Initializing state from props**
61+
### Initializing state from props
6262

63-
```js
64-
function Counter(props) {
65-
const [count, setCount] = createSignal(props.count);
66-
// ^^^^^^^^^^^
67-
// The reactive variable 'props.count' should be used within JSX, a tracked scope (like
68-
// createEffect), or inside an event handler function, or else changes will be ignored.
69-
}
70-
```
63+
```js
64+
function Counter(props) {
65+
const [count, setCount] = createSignal(props.count);
66+
// ^^^^^^^^^^^
67+
// The reactive variable 'props.count' should be used within JSX, a tracked scope (like
68+
// createEffect), or inside an event handler function, or else changes will be ignored.
69+
}
70+
```
7171

72-
Even when using a prop to initialize a signal, you'll still get the same warning as described
73-
above. This code ignores any changes to `props.count`, instead of reacting to those changes.
72+
Even when using a prop to initialize a signal, you'll still get the same warning as described
73+
above. This code ignores any changes to `props.count`, instead of reacting to those changes.
7474

75-
> React developers: this is equivalent to `const [count, setCount] = useState(props.count)`.
75+
> React developers: this is equivalent to `const [count, setCount] = useState(props.count)`.
7676
77-
Though it's often better to use props directly instead of creating new state from props, there are
78-
cases where this pattern is what you want. You can safely ignore the rule when you are providing
79-
an "initial" or "default" value to a component ("uncontrolled components" in React).
77+
Though it's often better to use props directly instead of creating new state from props, there are
78+
cases where this pattern is what you want. You can safely ignore the rule when you are providing
79+
an "initial" or "default" value to a component ("uncontrolled components" in React).
8080

81-
That's why there's an escape hatch for this case; any props beginning with `initial` or `default`
82-
won't trigger this warning. By using the `initial` or `default` prefix, you've shown that you
83-
intend to ignore updates to that prop. If you can't or don't want to use the prefix, adding an `// eslint-disable-next-line` comment to disable the warning accomplishes the same thing.
81+
That's why there's an escape hatch for this case; any props beginning with `initial` or `default`
82+
won't trigger this warning. By using the `initial` or `default` prefix, you've shown that you
83+
intend to ignore updates to that prop. If you can't or don't want to use the prefix, adding an `// eslint-disable-next-line` comment to disable the warning accomplishes the same thing.
8484

85-
```js
86-
const [count, setCount] = createSignal(props.initialCount); // fixed!
87-
```
85+
```js
86+
const [count, setCount] = createSignal(props.initialCount); // fixed!
87+
```
8888

89-
- **Using reactivity with a context provider**
89+
### Using reactivity with a context provider
9090

91-
```jsx
92-
const CountContext = createContext();
93-
function CountProvider(props) {
94-
const [count, setCount] = createSignal(0);
91+
```jsx
92+
const CountContext = createContext();
93+
function CountProvider(props) {
94+
const [count, setCount] = createSignal(0);
9595

96-
return <CountContext.Provider value={count()}>{props.children}</CountContext.Provider>;
97-
// ^^^^^^^
98-
// The reactive variable 'count' should be used within JSX, a tracked scope (like
99-
// createEffect), or inside an event handler function, or else changes will be ignored.
100-
}
101-
```
96+
return <CountContext.Provider value={count()}>{props.children}</CountContext.Provider>;
97+
// ^^^^^^^
98+
// The reactive variable 'count' should be used within JSX, a tracked scope (like
99+
// createEffect), or inside an event handler function, or else changes will be ignored.
100+
}
101+
```
102102

103-
Even though `count()` is used in JSX, this warning indicates a real problem and should not be
104-
ignored. Unlike most props, the `value` prop in a Provider is _not_ a tracked scope, meaning it
105-
will not react to changes in `count()`. The solution is to [pass the signal
106-
as-is](https://www.solidjs.com/docs/latest#createcontext), and call it when using `useContext()`.
103+
Even though `count()` is used in JSX, this warning indicates a real problem and should not be
104+
ignored. Unlike most props, the `value` prop in a Provider is _not_ a tracked scope, meaning it
105+
will not react to changes in `count()`. The solution is to [pass the signal
106+
as-is](https://www.solidjs.com/docs/latest#createcontext), and call it when using `useContext()`.
107107

108-
```jsx
109-
return <CountContext.Provider value={count}>{props.children}</CountContext.Provider>; // fixed!
110-
```
108+
```jsx
109+
return <CountContext.Provider value={count}>{props.children}</CountContext.Provider>; // fixed!
110+
```
111111

112-
Passing `props` or a store will work, but when passing a property access, it needs to be wrapped
113-
in a function so that the property access is done only as needed.
112+
Passing `props` or a store will work, but when passing a property access, it needs to be wrapped
113+
in a function so that the property access is done only as needed.
114114

115-
- **Passing event handler props directly to native elements**
115+
### Passing event handler props directly to native elements
116116

117-
```jsx
118-
function Button(props) {
119-
return <button onClick={props.onClick}>{props.children}</button>;
120-
// ^^^^^^^^^^^^^
121-
// The reactive variable 'props.onClick' should be wrapped in a function for
122-
// reactivity. This includes event handler bindings on native elements, which
123-
// are not reactive like other JSX props.
124-
}
125-
```
117+
```jsx
118+
function Button(props) {
119+
return <button onClick={props.onClick}>{props.children}</button>;
120+
// ^^^^^^^^^^^^^
121+
// The reactive variable 'props.onClick' should be wrapped in a function for
122+
// reactivity. This includes event handler bindings on native elements, which
123+
// are not reactive like other JSX props.
124+
}
125+
```
126126

127-
This warning indicates a real problem and should not be ignored. Unlike most props, `on*` event
128-
handlers on native elements are _not_ tracked scopes and don't react to changes in props. In this
129-
example, if `props.onClick` were to change, clicking the button would run the initial handler, not
130-
the current one.
127+
This warning indicates a real problem and should not be ignored. Unlike most props, `on*` event
128+
handlers on native elements are _not_ tracked scopes and don't react to changes in props. In this
129+
example, if `props.onClick` were to change, clicking the button would run the initial handler, not
130+
the current one.
131131

132-
This behavior is by design—instead of running `removeEventListener()` and `addEventListener()`
133-
each time the handler is changed (which is slow), Solid asks you for an event handler that
134-
accesses the _current_ prop (or signal, etc.) when called, like the following:
132+
This behavior is by design—instead of running `removeEventListener()` and `addEventListener()`
133+
each time the handler is changed (which is slow), Solid asks you for an event handler that
134+
accesses the _current_ prop (or signal, etc.) when called, like the following:
135135

136-
```jsx
137-
return <button onClick={(e) => props.onClick(e)}>{props.children}</button>; // fixed!
138-
```
136+
```jsx
137+
return <button onClick={(e) => props.onClick(e)}>{props.children}</button>; // fixed!
138+
```
139139

140-
<!-- <details><summary>Less common patterns...</summary>
140+
<details><summary>Less common patterns...</summary>
141141

142-
- **Static props**
142+
### Static props
143143

144-
Sometimes, you are _certain_ that a particular prop will _never change._ This is fragile and not
145-
recommended in most cases.
144+
Sometimes, you are _certain_ that a particular prop should _never change._ This is fragile and not
145+
recommended in most cases. But, it is possible to tell the linter that a prop should be "static,"
146+
which lets you access it anywhere, including the component body, without worrying about missing updates.
147+
148+
To do this, much like [initial/default props](#initializing-state-from-props), prefix the prop name
149+
with `static`, like
150+
151+
```jsx
152+
function Component(props) {
153+
const name = props.staticName;
154+
// ...
155+
}
156+
```
157+
158+
Though a static prop can be used directly in a component, you must not pass a reactive expression
159+
to it when using the component. For example, the following code will warn:
160+
161+
```jsx
162+
return <Component staticName={props.name} />
163+
// ^^^^^^^^^^
164+
```
146165

147-
</details> -->
166+
</details>
148167

149168
<!-- AUTO-GENERATED-CONTENT:START (OPTIONS) -->
150169

0 commit comments

Comments
 (0)