Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass multiple states through react context api

I'm creating a react game app and I want to pass state across multiple components. For that purpose I'm trying the react context api for the first time. So this is my GameContext.js

import React, { useState, createContext } from 'react';

const GameContext = createContext();

const GameProvider = ({ children }) => {
  const [name, setName] = useState('');
  const [color, setColor] = useState('');
  const [startgame, setStartgame] = useState(false);


  return (
    <GameContext.Provider value={[name, setName]}>
      {children}
    </GameContext.Provider>
  );
};

export { GameContext, GameProvider };

And I'm able to access name in the child component using

import { GameContext } from '../../context/GameContext';    
const [name, setName] = useContext(GameContext);
console.log(name);

But now I want to get the other state values into the same child component, like [color, setColor] and [startgame, setStartgame], from the GameContext.js.
How do I get those values into a child component?

I have another question, Somehow I feel this is a really stupid question, but why can't I do something like this in the GameContext.js?...

<GameContext.Provider value={[name, setName,color, setColor, startgame, setStartgame]}>

and get the values in the child component like this...

const [name, setName,color, setColor, startgame, setStartgame] = useContext(GameContext);

I tried this, but the browser is complaining that I'm breaking rules of react hooks.

like image 859
anoop chandran Avatar asked Mar 26 '20 12:03

anoop chandran


People also ask

How to pass multiple values in react context?

We can easily pass a single value in the React context. But the question remains on how to pass multiple values in React Context? To pass multiple values in React Context, we can use the Provider API.

What would happen if we didn’t use the react Context API?

If we didn’t use the React Context API, we would have needed to pass the state down to every component as props. In our example, it would have only been a slight annoyance to pass cities and addCity to the right components.

How to pass the initial state of a component to react?

Passing the initial state to React. createContext. This function then returns an object with a Provider and a Consumer. Using the Provider component at the top of the tree and making it accept a prop called value. This value can be anything! Using the Consumer component anywhere below the Provider in the component tree to get a subset of the state.

What is the best way to store state in react context?

But the rule of good software development is to always try the simplest solution first. React Context API: Store the state in a Context value in the common ancestor component (called the Provider Component), and access it from as many components as needed (called Consumer Components), which can be nested at any depth under this ancestor.


Video Answer


4 Answers

Provider accepts passing any value so you can paas object here and your values as properties.

<GameContext.Provider
 value={{ name: [name, setName], color: [color, setColor] }}
   >
  {props.children}
</GameContext.Provider>;

and where you are accessing in Child

 const { name, color } = React.useContext(GameContext);
 const [nameValue, setnameValue] = name;
 const [colorValue, setcolorValue] = color;
like image 115
Zohaib Avatar answered Oct 24 '22 06:10

Zohaib


useReducer is better suited to your case:

import React, { useState, useReducer, createContext } from 'react';

const initialState = {
  name: '',
  color: '',
  startgame: false
}

function reducer(state, action) {
  return { ...state, ...action };
}

const GameContext = createContext();

const GameProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <GameContext.Provider value={{ state, dispatch }}>
      {children}
    </GameContext.Provider>
  );
};

export { GameContext, GameProvider };

The child component:

import { GameContext } from '../../context/GameContext'; 

...

const { state: { name, color }, dispatch } = useContext(GameContext);
console.log(name);
console.log(color);

// event handler
const handleChangeName = (event) => {
  dispatch({ name: event.target.value });
}

const handleChangeColor = (event) => {
  dispatch({ color: event.target.value });
}
like image 23
Fraction Avatar answered Oct 24 '22 08:10

Fraction


import React, { useState, createContext } from 'react';

const GameContext = createContext();

const GameProvider = ({ children }) => {
  const [state, setState] = useState({
       name: '',
       color: '',
       startgame: false
  });


  return (
    <GameContext.Provider value={{
         ...state, 
         setState: (data) => setState({...state, ...data})
       }}
    >
      {children}
    </GameContext.Provider>
  );
};

export { GameContext, GameProvider };
import { GameContext } from '../../context/GameContext';    
const {name, color, startGame, setState} = useContext(GameContext);
console.log(name);
// Updating name
setState({ name: 'test' });
like image 22
Niyas Nazar Avatar answered Oct 24 '22 08:10

Niyas Nazar


Pass them as an Object like Niyas Nazar commented.

Example:

CommandBarContext.tsx

import React, { createContext, useContext, useState } from 'react'

interface ComandBarState {
  newEnabled: boolean
  setNewEnabled: (state: boolean) => void
  copyEnabled: boolean
  setCopyEnabled: (state: boolean) => void
  deleteEnabled: boolean
  setDeleteEnabled: (state: boolean) => void
}

const commandBarContext = createContext<ComandBarState>({
  newEnabled: true,
  setNewEnabled: (state: boolean) => { },
  copyEnabled: false,
  setCopyEnabled: (state: boolean) => { },
  deleteEnabled: false,
  setDeleteEnabled: (state: boolean) => { },
})

export const CommandBarProvider = (props: any) => {
  const { children } = props

  const [newEnabled, setNewEnabled] = useState<boolean>(true)
  const [copyEnabled, setCopyEnabled] = useState<boolean>(false)
  const [deleteEnabled, setDeleteEnabled] = useState<boolean>(false)

  return (
    <commandBarContext.Provider value={{
      newEnabled, setNewEnabled,
      copyEnabled, setCopyEnabled,
      deleteEnabled, setDeleteEnabled
    }}>
      {children}
    </commandBarContext.Provider>)
}

export const useCommandNew = () => {
  const { newEnabled, setNewEnabled } = useContext(commandBarContext)
  return { newEnabled, setNewEnabled }
}

export const useCommandCopy = () => {
  const { copyEnabled, setCopyEnabled } = useContext(commandBarContext)
  return { copyEnabled, setCopyEnabled }
}

export const useCommandDelete = () => {
  const { deleteEnabled, setDeleteEnabled } = useContext(commandBarContext)
  return { deleteEnabled, setDeleteEnabled }
}

CommandBar.tsx

import React, { FunctionComponent } from 'react'

import {
  CommandBar as FluentCommandBar,
  ICommandBarItemProps,
  ICommandBarStyles
} from '@fluentui/react/lib/CommandBar'
import { Text } from '@fluentui/react/lib/Text'
import { Stack, StackItem } from '@fluentui/react/lib/Stack'

import {
  useCommandNew,
  useCommandCopy,
  useCommandDelete,
} from './CommandBarContext'
import { useTranslation } from 'react-i18next'

export interface CommandBarProps {
  title: string
  onCommandNew?: () => void,
  onCommandCopy?: () => void,
  onCommandDelete?: () => void,
}

export const CommandBar: FunctionComponent<CommandBarProps> = props => {
  const { title } = props

  const {
    onCommandNew = () => { },
    onCommandCopy = () => { },
    onCommandDelete = () => { },
  } = props

  const { newEnabled } = useCommandNew()
  const { copyEnabled } = useCommandCopy()
  const { deleteEnabled } = useCommandDelete()

  const { t } = useTranslation()

  const items: ICommandBarItemProps[] = [
    {
      key: 'commandNew',
      text: t('New'),
      disabled: !newEnabled,
      iconProps: { iconName: 'Add' },
      onClick: onCommandNew,
    },
    {
      key: 'commandCopy',
      text: t('Copy'),
      iconProps: { iconName: 'Copy' },
      disabled: !copyEnabled,
      onClick: onCommandCopy,
    },
    {
      key: 'commandDelete',
      text: t('Delete'),
      iconProps: { iconName: 'Delete' },
      disabled: !deleteEnabled,
      onClick: onCommandDelete,
    },
  ]

  return (
    <Stack horizontal tokens={{ childrenGap: 30 }}>
      <Text variant="xLarge" styles={{ root: { marginTop: 7 } }}>{title}</Text>
      <StackItem grow>
        <FluentCommandBar
          items={items}
          styles={commandBarStyles}
        />
      </StackItem>
    </Stack>
  )
}

const commandBarStyles: ICommandBarStyles = {
  root: {
    marginTop: 0,
    paddingLeft: 0,
    paddingRight: 0
  }
}

Use in parent component:

...

import {
  useCommandNew,
  useCommandCopy,
  useCommandDelete,
} from './CommandBarContext'

...

  const { setNewEnabled } = useCommandNew()
  const { setCopyEnabled } = useCommandCopy()
  const { setDeleteEnabled } = useCommandDelete()

...

  return (
    <CommandBarProvider>
      <Stack tokens={{ childrenGap: 5 }}>
        <CommandBar title="Locations" />
        <Table
          columns={columns}
          items={items}
          onSelection={onSelection}
          onItemInvoked={onItemInvoked}
        />
      </Stack>
      <Panel
        header="Location"
        columns={columns}
        items={selection}
        isPanelOpen={isPanelOpen}
        onCancel={onCancel}
        onSave={onSave}
      />
    </CommandBarProvider>
  )
}

...
like image 35
Phierru Avatar answered Oct 24 '22 07:10

Phierru