Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Selectively Retrieve Values from Context and update components accordingly #17777

Closed
GasimGasimzada opened this issue Jan 4, 2020 · 1 comment

Comments

@GasimGasimzada
Copy link
Contributor

GasimGasimzada commented Jan 4, 2020

Do you want to request a feature or report a bug?

Feature

What is the current behavior?

Currently, any changes in the context will update all components.

What is the expected behavior?

Currently, when subscribing to context, if any value in the context changes, all consumers will be updated:

const value = useContext(MyContext);

My suggestion is the following: optionally, allow receiving specific values from context values and only update the components if the returned values change. Here is an example:

const data = useContext(MyContext, value => value.data); // value = context value

When the second function argument is defined, only the returned value from the function will be compared and accessed. This will simplify a lot of workflows where multiple contexts are used for multiple values in order to reduce the number of context updates. Here is an example from Hooks FAQ (modified a little bit):

function TodosApp() {
  // Note: `dispatch` won't change between re-renders
  const [todos, dispatch] = useReducer(todosReducer);

  return (
    <TodosData.Provider value={todos}>
      <TodosDispatch.Provider value={dispatch}>
        <DeepTree todos={todos} />
      </TodosDispatch.Provider>
    </TodosData.Provider>
  );
}

function DeepChild(props) {
  // If we want to perform an action, we can get dispatch from context.
  const dispatch = useContext(TodosDispatch);

  function handleClick() {
    dispatch({ type: 'add', text: 'hello' });
  }

  return (
    <button onClick={handleClick}>Add todo</button>
  );
}

function AnotherDeepChild(props) {
  const data = useContext(TodosData);
  ...
}

With the proposed API addition, we can just use the same context to do retrieve two different values:

function TodosApp() {
  // Note: `dispatch` won't change between re-renders
  const [todos, dispatch] = useReducer(todosReducer);

  return (
    <TodosContext.Provider value={{ todos, dispatch }}>
        <DeepTree todos={todos} />
    </TodosContext.Provider>
  );
}

function DeepChild(props) {
  const dispatch = useContext(TodosContext, value => value.dispatch);
}

function AnotherDeepChild(props) {
  const todos = useContext(TodosContext, value => value.todos);
}

Another useful scenario for this addition is dynamically selecting items from a centralized store based on context. If a developer needs to selectively retrieve specific values from an object, they can do it very easily. Something similar to Redux' useSelector but is part of a normal Context Flow:

function UserInfoApp() {
  // Note: `dispatch` won't change between re-renders
  const [user, dispatch] = useReducer(userReducer, { name: ..., dob: ..., active: ... });

  return (
    <UserInfoContext.Provider value={user}>
        <DeepTree todos={todos} />
    </UserInfoContext.Provider>
  );
}

function UserInfoTable(props) {
  const name = useContext(UserInfoContext, value => value.name);
  const dob = useContext(UserInfoContext, value => value.dob);
}

function UserActiveTracker(props) {
  const todos = useContext(UserInfoContext, value => value.active);
}

Now, if active value is changed, only UserActiveTracker will be updated. If name or dob is changed, only UserInfoTable will be activated.

The API can also be implemented for Context.Consumer component:

<MyContext.Consumer selector={value => value.active}> ... </MyContext.Consumer>

and static contextType static class variable:

class MyComponent extends React.Component {
  static contextType = MyContext;
  static contextSelector = value => value.active;

  render() {
    const val = this.context.active;
    const wrongVal = this.context.name; // = undefined

    return ...
  }
}
@bvaughn
Copy link
Contributor

bvaughn commented Jan 4, 2020

This sort of request should follow our RFC process:
https://github.com/reactjs/rfcs/

In this case, there already are some related RFCs:

I suggest you either create a new alternative RFC or collaborate with the existing open one. 😄 (I'm going to close this issue out in the meanwhile)

For what it's worth, Dan brought up this issue a while ago (#14110) and there are some current mitigation strategies we recommend. He's outlined them here: #15156 (comment)

@bvaughn bvaughn closed this as completed Jan 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants