# use-context-member

> use context's member like using useState

Latest version **1.0.3** (published 2021-02-21) · MIT license · 0 weekly downloads

## Install

```sh
npm install use-context-member
pnpm add use-context-member
yarn add use-context-member
bun add use-context-member
```

## Health

**Score 20/100 (F)** — status: abandoned.

Positive: esm support; no vulnerabilities.

Warnings: low downloads; no types.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.0.3 |
| Published | 2021-02-21 |
| First published | 2021-02-19 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 47.8 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | yy17yy |
| Maintainers | yy17yy |
| Keywords | react, context, hooks |

## Links

- npm: https://www.npmjs.com/package/use-context-member
- npm.io page: https://npm.io/package/use-context-member

## Alternatives

- [mobx-react](https://npm.io/package/mobx-react.md) — 2.8M weekly downloads
- [rc-tree](https://npm.io/package/rc-tree.md) — 2.6M weekly downloads
- [@react-oauth/google](https://npm.io/package/@react-oauth/google.md) — 1.3M weekly downloads
- [@wagmi/connectors](https://npm.io/package/@wagmi/connectors.md) — 877.0K weekly downloads
- [vee-validate](https://npm.io/package/vee-validate.md) — 836.4K weekly downloads

## Recent versions

- 1.0.3 (latest) — 2021-02-21
- 1.0.2 — 2021-02-21
- 1.0.1 — 2021-02-21
- 1.0.0 — 2021-02-19

## README

## Introduction
###### `use-context-member` provided `useMember` hook,to use React Context more easily and reduce component render times.
###### - Official `useContext`: `const { user } = useContext(UserContext)`
###### - Our `useMember`: `const [user, setUser] = useMember(UserContext, 'user')`
###### The 2 examples are both to select user value from UserContext. Our `useMember` hook also returned a setter, you can directly use it to modify user. What's more `useMember` will reduce component's render times. Only if user is changed, the component will re-render. Well for `useContext`, the component will re-render when any member of UserContext changed.

## Install
npm i -D use-context-member

## API
### createContext
`use-context-member` Provided `createContext`, same usage with `React.createContext`.You can not use `React.createContext` directly.
```
import { createContext } from 'use-context-member';

const UserContext = createContext();

const User = () => {
  const user = {name: {firstname: 'Harry', lastname: ''}, id: '001', email: ['harry@mail.com']};
  return (
    <UserContext.Provider value={user}>
      <SomeOtherComponent />
    </UserContext.Provider>
  )
}
```
inside context, we maintained it's state, if you need to listen the change of it, you can use `onChange` property.
```
const handleChange = useCallback(data => {
  // do something
}, [])
return (
  <UserContext.Provider value={user} onChange={handleChange}>
    <SomeInnerComponents />
  </UserContext.Provider>
)
```

### useMember
```
import { useMember } from 'use-context-member';

const [name, setName] = useMember(UserContext, 'name')
```
###### [name, setName] are similar with `useState`. function type param are supported for `setName` too.
###### The first param is the `UserContext` you created.
###### The second param is an expression, used to select member from context. If you left it as `undefined`, then it means to select the whole context. expression rules are simple.
###### - use `.` to select member from object, the first `.` can be omitted: `'name.firstname'`, You can also use `[]` to select: `'[name][firstname]'`, Sure you can mix the both: `'name[firstname]'`
###### - use `[]` to select member from array: `'email[0]'`. negative number are supported too: `'email[-1]'`, same meaning as param for `Array.slice`. You can even make a simple finder: for example when context is `[{id: '001'}, {id: '002'}]`, use `'[id==001]'` to select the first element(only `==` is supported at here).
###### When the selected member is an element of an array, a deleter are provided as the third part of return value: `const [email, setEmail, deleteEmail] = useMember(UserContext, 'email[0]')`, execute `deleteEmail()` will delete this email form the email array.
###### the returned setter and deleter are both stable, so you can safely append them to some hooks' dependencies, e.g., useCallback, useEffect.

### useOperations
```
import { useOperations } from 'use-context-member';

const [setEmail, deleteEmail] = useOperations(UserContext, 'email[0]`)
```
###### Same with `useMember`, except that this hook will not get the select member itself.

### useSelector
```
import { useSelector } from 'use-context-member';

const email = useSelector(UserContext, s => s.email.find(email => email.endsWith('mail.com')))
```
###### Like Redux, use a function to select member. useMember can handle most scenario in your application. However expression is not enough for some complex cases, and then please use `useSelector` instead. On the other hand, `useSelector` don't provide a setter, you need to use `useOperations` to get one. I suggest you use [immer](https://immerjs.github.io/immer/docs/introduction) to deal with complex update logic.

### No need to care NPE in hooks
###### `useMember`, `useOperations` and `useSelector` have already catch the exceptions, and return `undefined` as the selected member's value when exception occurred. That's because Context.Provider is just a component, we should allow to leave it's value as null in initialization, and then update it in side effects.

## One Example
```
import React, { useEffect, useState, useRef, useCallback } from 'react';
import { createContext, useMember } from 'use-context-member'

const BookContext = createContext();

const Book = ({ id }) => {
  const [book, setBook, deleteBook] = useMember(BookContext, `books[id==${id}]`);
  if (!book) { // handle stale props issue(that is, if you used props in useMember's expression, the returned member can be undefined.)
    return <></>;
  }
  const { name, count } = book;
  return (
    <div>
      <div>{name}</div>
      <div>Count: {count}</div>
      <button onClick={() => setBook({ ...book, count: book.count + 1 })}>Add Count</button>
      <button onClick={() => deleteBook()}>Delete</button>
    </div >
  )
}

const BookList = () => {
  const [books = [], setBooks] = useMember(BookContext, 'books');
  const [title] = useMember(BookContext, 'title');
  const maxId = useRef(5);

  const handleAddBook = useCallback(() => {
    maxId.current += 1;
    setBooks(prev => [...prev, { name: 'Book' + maxId.current, count: 1, id: maxId.current }]);
  }, [setBooks]); // `setBooks` is stable.

  return (
    <div>
      <div>{title}</div>
      {
        books.map(book => <Book key={book.id} id={book.id} />)
      }
      <button onClick={handleAddBook}>Add Book</button>
    </div>
  )
}

export const TestPage = () => {
  const [bookContext, setBookContext] = useState();

  useEffect(() => { // set context value in side effect is ok, for useMember just return undefined when target member can't be found.
    const mockBooks = [];
    for (var i = 0; i < 1; ++i) {
      mockBooks.push({ id: i, name: 'Book' + i, count: 1 });
    }
    setBookContext({
      books: mockBooks,
      title: 'Book List',
    })
  }, [])

  return (
    <BookContext.Provider value={bookContext}>
      <BookList />
    </BookContext.Provider>
  )
}
```

## License
[MIT](https://choosealicense.com/licenses/mit/)

---
_Source: https://npm.io/package/use-context-member · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
