Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ node_modules
*.js
*.cjs
tsup.config.ts
tsup-docs.config.ts
theme.config.jsx
documentation/dist
documentation/.next
Expand Down
4 changes: 2 additions & 2 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const key = sliceKey([sliceA], {
},
});

const sel0 = key.selector(
const sel0 = key.derive(
// will have sliceA
(state) => {
return key.get(state).z;
Expand All @@ -31,7 +31,7 @@ const sel0 = key.selector(
},
);

const sel1 = key.selector(
const sel1 = key.derive(
// will have sliceA
(state) => {
const otherSel = sel0(state);
Expand Down
24 changes: 12 additions & 12 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting

## Enforcement Responsibilities
Expand All @@ -59,7 +59,7 @@ representative at an online or offline event.
## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
reported to the community leaders responsible for enforcement at
cubist.balsa_0p@icloud.com (address to Kushan).
All complaints will be reviewed and investigated promptly and fairly.

Expand Down Expand Up @@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
Expand Down
71 changes: 61 additions & 10 deletions documentation/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,66 @@
import {
Sandpack,
SandpackCodeEditor,
SandpackLayout,
SandpackPreview,
SandpackProvider,
useSandpack,
} from '@codesandbox/sandpack-react';
import { Sandpack } from '@codesandbox/sandpack-react';
import { sandpackDark } from '@codesandbox/sandpack-themes';
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
// @ts-expect-error - no types
import rawNsmCode from '../dist/nsm-docs-bundle/index.mjs?raw';
import prettier from 'prettier';

export function CodeBlock() {
return <Sandpack />;
export function CodeBlockVanilla({
height,
children,
}: {
height?: number;
children: string;
}) {
const { theme } = useTheme();
const code = `
${children.trim()}
`.trim();

return (
<Sandpack
options={{
layout: 'console',
editorHeight: height,
showRefreshButton: true,
editorWidthPercentage: 70, // default - 50
}}
files={{
'/index.ts': {
code: `${code}`.trim(),
},
'/node_modules/nalanda/package.json': {
hidden: true,
code: JSON.stringify({
name: 'nalanda',
main: './index.mjs',
}),
},
'/node_modules/nalanda/index.mjs': {
hidden: true,
code: rawNsmCode,
},
}}
theme={theme === 'light' ? 'light' : 'dark'}
template="vanilla-ts"
/>
);
}
export function CodeBlock({ children }: { children: string }) {
const { theme } = useTheme();

return (
<Sandpack
options={{}}
files={{
'/App.tsx': {
// not doing trim causes weird errors
code: children.trim(),
},
}}
theme={theme === 'light' ? 'light' : 'dark'}
template={'react-ts'}
/>
);
}
13 changes: 12 additions & 1 deletion documentation/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,24 @@ const withNextra = require('nextra')({
});

module.exports = {
...withNextra(),
...withNextra({
webpack: (config) => {
// console.log(config.module?.rules);
config.module?.rules?.unshift({
resourceQuery: /raw/,
type: 'asset/source',
});

return config;
},
}),
reactStrictMode: true,
swcMinify: true,
trailingSlash: true,
images: {
unoptimized: true,
},

async redirects() {
return [
{
Expand Down
3 changes: 2 additions & 1 deletion documentation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"nextra": "^2.13.1",
"nextra-theme-docs": "^2.13.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"prettier": "^3.0.3"
},
"devDependencies": {
"@types/node": "18.14.6",
Expand Down
86 changes: 86 additions & 0 deletions documentation/pages/docs/actions.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Actions

Actions help you update your application state. They are the only way to update the state of your application.

> Always keep your actions in the same file as the slice and key. See more about [structuring your code](./structuring-your-code).

## Simple field update

To update a field of your slice, you can use the `update` method on the field.

```ts
const key = createKey('counterSlice');
const counter = key.field(0);

function increment() {
return counter.update((val) => val + 1);
}

// store.js
store.dispatch(increment());
// Get the updated state
counterSlice.get(store.state).counter; // 1
```

> Note: just calling `increment()` is not enough to update the state. You need to dispatch the action to the store.

## Updating many fields

```ts
// userDataSlice.ts
const key = createKey('userDataSlice');

const userData = key.field(undefined);
const isLoading = key.field(false);

// Update multiple fields of a slice in a single action
function setUserData(data) {
const txn = key.transaction();

return txn.step((state) => {
state = state.apply(userData.update(data));
state = state.apply(isLoading.update(false));
return state;
});
}

export const userDataSlice = key.slice({
actions: {
setUserData,
},
fields: {
// ...
},
});
```

## Merging transactions

You can merge transaction from external actions into your slice's action. This is useful when you want to also change the state of other slices in one single action.

In the example below we merge the transaction from an external `setUserData` action (see section above):

```ts
import { userDataSlice } from './userDataSlice';

// set the userDataSlice as the dependency
const key = createKey('loginSlice', [userDataSlice]);

const loggedIn = key.field(false);

function loginUser(data) {
const txn = key.transaction();

return txn.step((state) => {
// get the transaction from the external action
const userDataTxn = userDataSlice.setUserData(data);

state = state.apply(userDataTxn);
state = state.apply(loggedIn.update(true));

// the new state contains both the changes from the external action
// and the changes from this action
return state;
});
}
```
1 change: 1 addition & 0 deletions documentation/pages/docs/advanced-topics/operations.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Operations
47 changes: 47 additions & 0 deletions documentation/pages/docs/advanced-topics/powerful-effects.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## Custom isEqual camparison

By default `track` uses `Object.is` to compare values. But this might not be useful in all cases.
Nalanda allows you to pass a custom `isEqual` function to `track.<field>` to decide when a slice field has changed and the effect should be re-run.

```typescript
const userName = userSlice.track.userName(store, {
// prevent re-running of effect if 'userName' differs only in case
isEqual: (a, b) => a.toLocaleLowerCase() === b.toLocaleLowerCase(),
});
```

## Not tracking changes

While re-running effect whenever a slice field changes is helpful, sometimes you just want to read the state without tracking it.
You can do this by using the `get` method instead of `track`.

```typescript
effect((store) => {
// effect will rerun only when 'isLoggedIn' changes
const { isLoggedIn } = loginSlice.track(store);
// effect will not rerun when 'userName' changes
const { userName } = userSlice.get(store.state);

if (isLoggedIn) {
console.log(`The user ${userName} is logged in`);
} else {
console.log(`Please login ${userName}`);
}
});
```

## Conditional tracking

If a condition is false, any tracking inside the condition will not be tracked (effects re-run will not depend on it) until the condition becomes true.

In the following case when `isLoggedIn` becomes `true`, the effect will start tracking `userName` i.e. it will re-run whenever `userName` changes.

```typescript
effect((store) => {
const { isLoggedIn } = loginSlice.track(store);

if (isLoggedIn) {
const { userName } = userSlice.track(store);
}
});
```
62 changes: 62 additions & 0 deletions documentation/pages/docs/advanced-topics/refs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Refs

Ref (short for reference) is a special helper wrapper that allows you save value in a mutable fashion.
This allows you to persist data across multiple runs of an effects.

```typescript
import { ref } from 'nalanda';

const getValueRef = ref(initialValue);

effect((store) => {
const valueRef = getValueRef(store);
valueRef.current; // to get the value
valueRef.current = 'newValue'; // to set the value
});
```

### Basic Usage

Here is a basic example where ref is used to manage the abort a request across multiple effects.

```ts
import { ref } from 'nalanda';

const getAbortRef = ref(new AbortController());

effect(async (store) => {
const abortRef = getAbortRef(store);

if (!abortRef.current.signal.aborted) {
const data = await fetchUserData();
// handle fetched data

// reset the abort controller
abortRef.current = new AbortController();
}
});

// an effect that can cancel the fetch
effect(async (store) => {
const abortRef = getAbortRef(store);
const { isUserLoggedIn } = useSlice.track(store);

// if user logs out, cancel any ongoing fetch
if (!isUserLoggedIn) {
abortRef.current.abort();
}
});
```

### When to use them?

Refs share a lot of similarities with state. However, they are not the same. By default its a **good** idea to use slice state whever possible.

1. When you don't need reactivity.
Setting a ref value will not trigger a re-render. This is useful when you want to store a value that is not related to the UI.

1. When you need to share imperative data across multiple effects.
Things like `AbortController`, `setTimeout` and `setInterval` are good examples of things you want to share across multiple effects without the need to re-render.

1. When the data is local to the effect.
If you need to store a value that is local to the effect and does not need to be widenly available across your application. For example, storing a `setInterval` id to clear it later.
Empty file.
Empty file.
Loading